Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30b248a0f6 | |||
| 7168455de3 | |||
| 085f63e3c7 | |||
| 015be64923 | |||
| 2c2471811d | |||
| 1025249e93 | |||
| 41ffc4bcf3 | |||
| 2739d54cc1 | |||
| c4c463cbc2 | |||
| 8cd13bd43f | |||
| e4ef279759 | |||
| cf7fecb57b | |||
| 226041dcb1 | |||
| 7548025561 | |||
| fdbee427ee | |||
| d861d6d6e4 | |||
| 0a648edcaa | |||
| 18850c1fba | |||
| f6df4cab67 | |||
| 019d29c5b7 |
+8
-38
@@ -7,53 +7,23 @@
|
||||
// !! No console.log() allowed
|
||||
// !! Do not set DEBUG
|
||||
|
||||
var supervisor = require('supervisord-eventlistener'),
|
||||
var assert = require('assert'),
|
||||
mailer = require('./src/mailer.js'),
|
||||
safe = require('safetydance'),
|
||||
assert = require('assert'),
|
||||
exec = require('child_process').exec,
|
||||
util = require('util'),
|
||||
mailer = require('./src/mailer.js');
|
||||
supervisor = require('supervisord-eventlistener'),
|
||||
path = require('path'),
|
||||
util = require('util');
|
||||
|
||||
var gLastNotifyTime = {};
|
||||
var gCooldownTime = 1000 * 60 * 5; // 5 min
|
||||
var COLLECT_LOGS_CMD = path.join(__dirname, 'src/scripts/collectlogs.sh');
|
||||
|
||||
function collectLogs(program, callback) {
|
||||
assert.strictEqual(typeof program, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var logFilePath = util.format('/var/log/supervisor/%s.log', program);
|
||||
|
||||
var boxLogData = safe.fs.readFileSync(logFilePath, 'utf-8');
|
||||
if (boxLogData === null) return callback(safe.error);
|
||||
var boxLogLines = boxLogData.split('\n').slice(-100);
|
||||
|
||||
var dockerLogPath = '/var/log/upstart/docker.log';
|
||||
|
||||
var dockerLogData = safe.fs.readFileSync(dockerLogPath, 'utf-8');
|
||||
if (dockerLogData === null) return callback(safe.error);
|
||||
var dockerLogLines = dockerLogData.split('\n').slice(-100);
|
||||
|
||||
exec('dmesg', function (error, stdout /*, stderr */) {
|
||||
if (error) console.error(error);
|
||||
|
||||
var lines = stdout.split('\n');
|
||||
var dmesgLogLines = lines.slice(-100);
|
||||
|
||||
var result = '';
|
||||
result += program + '.log\n';
|
||||
result += '-------------------------------------\n';
|
||||
result += boxLogLines.join('\n');
|
||||
result += '\n\n';
|
||||
result += 'dmesg\n';
|
||||
result += '-------------------------------------\n';
|
||||
result += dmesgLogLines.join('\n');
|
||||
result += '\n\n';
|
||||
result += 'docker\n';
|
||||
result += '-------------------------------------\n';
|
||||
result += dockerLogLines.join('\n');
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
var logs = safe.child_process.execSync('sudo ' + COLLECT_LOGS_CMD + ' ' + program, { encoding: 'utf8' });
|
||||
callback(null, logs);
|
||||
}
|
||||
|
||||
supervisor.on('PROCESS_STATE_EXITED', function (headers, data) {
|
||||
|
||||
Generated
+37
-38
@@ -105,8 +105,8 @@
|
||||
}
|
||||
},
|
||||
"cloudron-manifestformat": {
|
||||
"version": "1.4.0",
|
||||
"from": "cloudron-manifestformat@1.4.0",
|
||||
"version": "1.6.0",
|
||||
"from": "cloudron-manifestformat@1.6.0",
|
||||
"dependencies": {
|
||||
"java-packagename-regex": {
|
||||
"version": "1.0.0",
|
||||
@@ -131,18 +131,17 @@
|
||||
"resolved": "https://registry.npmjs.org/connect-ensure-login/-/connect-ensure-login-0.1.1.tgz"
|
||||
},
|
||||
"connect-lastmile": {
|
||||
"version": "0.0.12",
|
||||
"from": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-0.0.12.tgz",
|
||||
"resolved": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-0.0.12.tgz",
|
||||
"version": "0.0.13",
|
||||
"from": "connect-lastmile@0.0.13",
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.1.3",
|
||||
"from": "https://registry.npmjs.org/debug/-/debug-2.1.3.tgz",
|
||||
"from": "debug@>=2.1.0 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.1.3.tgz",
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "0.7.0",
|
||||
"from": "http://registry.npmjs.org/ms/-/ms-0.7.0.tgz",
|
||||
"from": "ms@0.7.0",
|
||||
"resolved": "http://registry.npmjs.org/ms/-/ms-0.7.0.tgz"
|
||||
}
|
||||
}
|
||||
@@ -1288,69 +1287,69 @@
|
||||
},
|
||||
"dockerode": {
|
||||
"version": "2.2.2",
|
||||
"from": "dockerode@2.2.2",
|
||||
"from": "https://registry.npmjs.org/dockerode/-/dockerode-2.2.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.2.2.tgz",
|
||||
"dependencies": {
|
||||
"docker-modem": {
|
||||
"version": "0.2.6",
|
||||
"from": "docker-modem@>=0.2.0 <0.3.0",
|
||||
"from": "https://registry.npmjs.org/docker-modem/-/docker-modem-0.2.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-0.2.6.tgz",
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "0.7.4",
|
||||
"from": "debug@>=0.7.4 <0.8.0",
|
||||
"from": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "0.0.3",
|
||||
"from": "follow-redirects@0.0.3",
|
||||
"from": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.3.tgz"
|
||||
},
|
||||
"JSONStream": {
|
||||
"version": "0.10.0",
|
||||
"from": "JSONStream@0.10.0",
|
||||
"from": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz",
|
||||
"dependencies": {
|
||||
"jsonparse": {
|
||||
"version": "0.0.5",
|
||||
"from": "jsonparse@0.0.5",
|
||||
"from": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz",
|
||||
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz"
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"from": "through@>=2.2.7 <3.0.0",
|
||||
"from": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"from": "querystring@0.2.0",
|
||||
"from": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz"
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.33",
|
||||
"from": "readable-stream@>=1.0.26-4 <1.1.0",
|
||||
"from": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.1",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0",
|
||||
"from": "http://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"from": "isarray@0.0.1",
|
||||
"from": "http://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0",
|
||||
"from": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@>=2.0.1 <2.1.0",
|
||||
"from": "http://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
}
|
||||
}
|
||||
@@ -1691,81 +1690,81 @@
|
||||
},
|
||||
"ldapjs": {
|
||||
"version": "0.7.1",
|
||||
"from": "ldapjs@*",
|
||||
"from": "https://registry.npmjs.org/ldapjs/-/ldapjs-0.7.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-0.7.1.tgz",
|
||||
"dependencies": {
|
||||
"asn1": {
|
||||
"version": "0.2.1",
|
||||
"from": "asn1@0.2.1",
|
||||
"from": "https://registry.npmjs.org/asn1/-/asn1-0.2.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.1.tgz"
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "0.1.5",
|
||||
"from": "assert-plus@0.1.5",
|
||||
"from": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
|
||||
},
|
||||
"bunyan": {
|
||||
"version": "0.22.1",
|
||||
"from": "bunyan@0.22.1",
|
||||
"from": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.1.tgz"
|
||||
},
|
||||
"nopt": {
|
||||
"version": "2.1.1",
|
||||
"from": "nopt@2.1.1",
|
||||
"from": "https://registry.npmjs.org/nopt/-/nopt-2.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.1.tgz",
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.0.7",
|
||||
"from": "abbrev@>=1.0.0 <2.0.0",
|
||||
"from": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pooling": {
|
||||
"version": "0.4.6",
|
||||
"from": "pooling@0.4.6",
|
||||
"from": "https://registry.npmjs.org/pooling/-/pooling-0.4.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/pooling/-/pooling-0.4.6.tgz",
|
||||
"dependencies": {
|
||||
"once": {
|
||||
"version": "1.3.0",
|
||||
"from": "once@1.3.0",
|
||||
"from": "https://registry.npmjs.org/once/-/once-1.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.3.0.tgz"
|
||||
},
|
||||
"vasync": {
|
||||
"version": "1.4.0",
|
||||
"from": "vasync@1.4.0",
|
||||
"from": "https://registry.npmjs.org/vasync/-/vasync-1.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/vasync/-/vasync-1.4.0.tgz",
|
||||
"dependencies": {
|
||||
"jsprim": {
|
||||
"version": "0.3.0",
|
||||
"from": "jsprim@0.3.0",
|
||||
"from": "https://registry.npmjs.org/jsprim/-/jsprim-0.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-0.3.0.tgz",
|
||||
"dependencies": {
|
||||
"extsprintf": {
|
||||
"version": "1.0.0",
|
||||
"from": "extsprintf@1.0.0",
|
||||
"from": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz"
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "0.2.2",
|
||||
"from": "json-schema@0.2.2",
|
||||
"from": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.3.3",
|
||||
"from": "verror@1.3.3",
|
||||
"from": "https://registry.npmjs.org/verror/-/verror-1.3.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.3.3.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.1.0",
|
||||
"from": "verror@1.1.0",
|
||||
"from": "https://registry.npmjs.org/verror/-/verror-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.1.0.tgz",
|
||||
"dependencies": {
|
||||
"extsprintf": {
|
||||
"version": "1.0.0",
|
||||
"from": "extsprintf@1.0.0",
|
||||
"from": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz"
|
||||
}
|
||||
}
|
||||
@@ -2261,9 +2260,9 @@
|
||||
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.13.0.tgz"
|
||||
},
|
||||
"safetydance": {
|
||||
"version": "0.0.16",
|
||||
"from": "http://registry.npmjs.org/safetydance/-/safetydance-0.0.16.tgz",
|
||||
"resolved": "http://registry.npmjs.org/safetydance/-/safetydance-0.0.16.tgz"
|
||||
"version": "0.0.19",
|
||||
"from": "safetydance@0.0.19",
|
||||
"resolved": "https://registry.npmjs.org/safetydance/-/safetydance-0.0.19.tgz"
|
||||
},
|
||||
"semver": {
|
||||
"version": "4.3.6",
|
||||
|
||||
+3
-3
@@ -18,9 +18,9 @@
|
||||
"dependencies": {
|
||||
"async": "^1.2.1",
|
||||
"body-parser": "^1.13.1",
|
||||
"cloudron-manifestformat": "^1.4.0",
|
||||
"cloudron-manifestformat": "^1.6.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"connect-lastmile": "0.0.12",
|
||||
"connect-lastmile": "0.0.13",
|
||||
"connect-timeout": "^1.5.0",
|
||||
"cookie-parser": "^1.3.5",
|
||||
"cookie-session": "^1.1.0",
|
||||
@@ -55,7 +55,7 @@
|
||||
"passport-oauth2-client-password": "^0.1.2",
|
||||
"password-generator": "^1.0.0",
|
||||
"proxy-middleware": "^0.13.0",
|
||||
"safetydance": "0.0.16",
|
||||
"safetydance": "0.0.19",
|
||||
"semver": "^4.3.6",
|
||||
"serve-favicon": "^2.2.0",
|
||||
"split": "^1.0.0",
|
||||
|
||||
@@ -24,3 +24,6 @@ yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reloadcollectd.
|
||||
|
||||
Defaults!/home/yellowtent/box/src/scripts/backupswap.sh env_keep="HOME BOX_ENV"
|
||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/backupswap.sh
|
||||
|
||||
Defaults!/home/yellowtent/box/src/scripts/collectlogs.sh env_keep="HOME BOX_ENV"
|
||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/collectlogs.sh
|
||||
|
||||
@@ -189,7 +189,6 @@ function createContainer(app, callback) {
|
||||
}
|
||||
|
||||
env.push('CLOUDRON=1');
|
||||
env.push('ADMIN_ORIGIN' + '=' + config.adminOrigin()); // ## remove
|
||||
env.push('WEBADMIN_ORIGIN' + '=' + config.adminOrigin());
|
||||
env.push('API_ORIGIN' + '=' + config.adminOrigin());
|
||||
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@ exports = module.exports = {
|
||||
|
||||
FEEDBACK_TYPE_FEEDBACK: 'feedback',
|
||||
FEEDBACK_TYPE_TICKET: 'ticket',
|
||||
FEEDBACK_TYPE_APP: 'app',
|
||||
sendFeedback: sendFeedback
|
||||
};
|
||||
|
||||
@@ -288,7 +289,7 @@ function sendFeedback(user, type, subject, description) {
|
||||
assert.strictEqual(typeof subject, 'string');
|
||||
assert.strictEqual(typeof description, 'string');
|
||||
|
||||
assert(type === exports.FEEDBACK_TYPE_TICKET || type === exports.FEEDBACK_TYPE_FEEDBACK);
|
||||
assert(type === exports.FEEDBACK_TYPE_TICKET || type === exports.FEEDBACK_TYPE_FEEDBACK || type === exports.FEEDBACK_TYPE_APP);
|
||||
|
||||
var mailOptions = {
|
||||
from: config.get('adminEmail'),
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="oauth">
|
||||
|
||||
+32
-22
@@ -1,32 +1,40 @@
|
||||
<% include header %>
|
||||
|
||||
<center>
|
||||
<h1>Login to <%= applicationName %></h1>
|
||||
</center>
|
||||
|
||||
<% if (error) { %>
|
||||
<center>
|
||||
<br/><br/>
|
||||
<h4 class="has-error"><%= error %></h4>
|
||||
</center>
|
||||
<% } %>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<form id="loginForm" action="" method="post">
|
||||
<input type="hidden" name="_csrf" value="<%= csrf %>"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputUsername">Username or Email</label>
|
||||
<input type="text" class="form-control" id="inputUsername" name="username" autofocus required>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1><img width="64" height="64" src="<%= applicationLogo %>"/> Login to <%= applicationName %> on <%= cloudronName %></h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputPassword">Password</label>
|
||||
<input type="password" class="form-control" name="password" id="inputPassword" required>
|
||||
<br/>
|
||||
<% if (error) { %>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4 class="has-error"><%= error %></h4>
|
||||
</div>
|
||||
</div>
|
||||
<input class="btn btn-primary btn-outline pull-right" type="submit" value="Sign in"/>
|
||||
</form>
|
||||
<a href="/api/v1/session/password/resetRequest.html">Reset your password</a>
|
||||
<% } %>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="loginForm" action="" method="post">
|
||||
<input type="hidden" name="_csrf" value="<%= csrf %>"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputUsername">Username or Email</label>
|
||||
<input type="text" class="form-control" id="inputUsername" name="username" autofocus required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputPassword">Password</label>
|
||||
<input type="password" class="form-control" name="password" id="inputPassword" required>
|
||||
</div>
|
||||
<input class="btn btn-primary btn-outline pull-right" type="submit" value="Sign in"/>
|
||||
</form>
|
||||
<a href="/api/v1/session/password/resetRequest.html">Reset your password</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,6 +42,8 @@
|
||||
<script>
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var search = window.location.search.slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
document.getElementById('loginForm').action = '/api/v1/session/login?returnTo=' + search.returnTo;
|
||||
|
||||
@@ -119,6 +119,9 @@ function installApp(req, res, next) {
|
||||
// allow tests to provide an appId for testing
|
||||
var appId = (process.env.BOX_ENV === 'test' && typeof data.appId === 'string') ? data.appId : uuid.v4();
|
||||
|
||||
// addons is optional
|
||||
data.manifest.addons = data.manifest.addons || {};
|
||||
|
||||
debug('Installing app id:%s storeid:%s loc:%s port:%j restrict:%s manifest:%j', appId, data.appStoreId, data.location, data.portBindings, data.accessRestriction, data.manifest);
|
||||
|
||||
apps.install(appId, data.appStoreId, data.manifest, data.location, data.portBindings || null, data.accessRestriction, data.icon || null, function (error) {
|
||||
@@ -253,6 +256,9 @@ function updateApp(req, res, next) {
|
||||
if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string'));
|
||||
if ('force' in data && typeof data.force !== 'boolean') return next(new HttpError(400, 'force must be a boolean'));
|
||||
|
||||
// addons is optional
|
||||
data.manifest.addons = data.manifest.addons || {};
|
||||
|
||||
debug('Update app id:%s to manifest:%j', req.params.id, data.manifest);
|
||||
|
||||
apps.update(req.params.id, data.force || false, data.manifest, data.portBindings, data.icon, function (error) {
|
||||
|
||||
@@ -163,7 +163,7 @@ function setCertificate(req, res, next) {
|
||||
function feedback(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
if (req.body.type !== mailer.FEEDBACK_TYPE_FEEDBACK && req.body.type !== mailer.FEEDBACK_TYPE_TICKET) return next(new HttpError(400, 'type must be either "ticket" or "feedback"'));
|
||||
if (req.body.type !== mailer.FEEDBACK_TYPE_FEEDBACK && req.body.type !== mailer.FEEDBACK_TYPE_TICKET && req.body.type !== mailer.FEEDBACK_TYPE_APP) return next(new HttpError(400, 'type must be either "ticket", "feedback" or "app"'));
|
||||
if (typeof req.body.subject !== 'string' || !req.body.subject) return next(new HttpError(400, 'subject must be string'));
|
||||
if (typeof req.body.description !== 'string' || !req.body.description) return next(new HttpError(400, 'description must be string'));
|
||||
|
||||
|
||||
+31
-20
@@ -16,6 +16,7 @@ var assert = require('assert'),
|
||||
querystring = require('querystring'),
|
||||
util = require('util'),
|
||||
session = require('connect-ensure-login'),
|
||||
settings = require('../settings.js'),
|
||||
tokendb = require('../tokendb'),
|
||||
appdb = require('../appdb'),
|
||||
url = require('url'),
|
||||
@@ -188,37 +189,47 @@ function loginForm(req, res) {
|
||||
var u = url.parse(req.session.returnTo, true);
|
||||
if (!u.query.client_id) return sendErrorPageOrRedirect(req, res, 'Invalid login request. No client_id provided.');
|
||||
|
||||
function render(applicationName) {
|
||||
var cloudronName = '';
|
||||
|
||||
function render(applicationName, applicationLogo) {
|
||||
res.render('login', {
|
||||
adminOrigin: config.adminOrigin(),
|
||||
csrf: req.csrfToken(),
|
||||
cloudronName: cloudronName,
|
||||
applicationName: applicationName,
|
||||
applicationLogo: applicationLogo,
|
||||
error: req.query.error || null
|
||||
});
|
||||
}
|
||||
|
||||
clientdb.get(u.query.client_id, function (error, result) {
|
||||
if (error) return sendError(req, res, 'Unknown OAuth client');
|
||||
settings.getCloudronName(function (error, name) {
|
||||
if (error) return sendError(req, res, 'Internal Error');
|
||||
|
||||
// Handle our different types of oauth clients
|
||||
var appId = result.appId;
|
||||
if (appId === constants.ADMIN_CLIENT_ID) {
|
||||
return render(constants.ADMIN_NAME);
|
||||
} else if (appId === constants.TEST_CLIENT_ID) {
|
||||
return render(constants.TEST_NAME);
|
||||
} else if (appId.indexOf('external-') === 0) {
|
||||
return render('External Application');
|
||||
} else if (appId.indexOf('addon-') === 0) {
|
||||
appId = appId.slice('addon-'.length);
|
||||
} else if (appId.indexOf('proxy-') === 0) {
|
||||
appId = appId.slice('proxy-'.length);
|
||||
}
|
||||
cloudronName = name;
|
||||
|
||||
appdb.get(appId, function (error, result) {
|
||||
if (error) return sendErrorPageOrRedirect(req, res, 'Unknown Application for those OAuth credentials');
|
||||
clientdb.get(u.query.client_id, function (error, result) {
|
||||
if (error) return sendError(req, res, 'Unknown OAuth client');
|
||||
|
||||
var applicationName = result.location || config.fqdn();
|
||||
render(applicationName);
|
||||
// Handle our different types of oauth clients
|
||||
var appId = result.appId;
|
||||
if (appId === constants.ADMIN_CLIENT_ID) {
|
||||
return render(constants.ADMIN_NAME, '/api/v1/cloudron/avatar');
|
||||
} else if (appId === constants.TEST_CLIENT_ID) {
|
||||
return render(constants.TEST_NAME, '/api/v1/cloudron/avatar');
|
||||
} else if (appId.indexOf('external-') === 0) {
|
||||
return render('External Application', '/api/v1/cloudron/avatar');
|
||||
} else if (appId.indexOf('addon-') === 0) {
|
||||
appId = appId.slice('addon-'.length);
|
||||
} else if (appId.indexOf('proxy-') === 0) {
|
||||
appId = appId.slice('proxy-'.length);
|
||||
}
|
||||
|
||||
appdb.get(appId, function (error, result) {
|
||||
if (error) return sendErrorPageOrRedirect(req, res, 'Unknown Application for those OAuth credentials');
|
||||
|
||||
var applicationName = result.location || config.fqdn();
|
||||
render(applicationName, '/api/v1/cloudron/avatar');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -573,7 +573,8 @@ describe('App installation', function () {
|
||||
docker.getContainer(appEntry.containerId).inspect(function (error, data) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(data.Config.ExposedPorts['7777/tcp']).to.eql({ });
|
||||
expect(data.Config.Env).to.contain('ADMIN_ORIGIN=' + config.adminOrigin());
|
||||
expect(data.Config.Env).to.contain('WEBADMIN_ORIGIN=' + config.adminOrigin());
|
||||
expect(data.Config.Env).to.contain('API_ORIGIN=' + config.adminOrigin());
|
||||
expect(data.Config.Env).to.contain('CLOUDRON=1');
|
||||
clientdb.getByAppId('addon-' + appResult.id, function (error, client) {
|
||||
expect(error).to.not.be.ok();
|
||||
|
||||
@@ -588,6 +588,17 @@ describe('Cloudron', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with app type', function (done) {
|
||||
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||
.send({ type: 'app', subject: 'some subject', description: 'some description' })
|
||||
.query({ access_token: token })
|
||||
.end(function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(result.statusCode).to.equal(201);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails without description', function (done) {
|
||||
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||
.send({ type: 'ticket', subject: 'some subject' })
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "This script should be run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $# == 1 && "$1" == "--check" ]]; then
|
||||
echo "OK"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: collectlogs.sh <program>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readonly program_name=$1
|
||||
|
||||
echo "${program_name}.log"
|
||||
echo "-------------------"
|
||||
tail --lines=100 /var/log/supervisor/${program_name}.log
|
||||
echo
|
||||
echo
|
||||
echo "dmesg"
|
||||
echo "-----"
|
||||
dmesg | tail --lines=100
|
||||
echo
|
||||
echo
|
||||
echo "docker"
|
||||
echo "------"
|
||||
tail --lines=100 /var/log/upstart/docker.log
|
||||
echo
|
||||
echo
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ function uninitialize(callback) {
|
||||
|
||||
function startNextTask() {
|
||||
if (gPendingTasks.length === 0) return;
|
||||
assert(Object.keys(gActiveTasks).length === 0); // since we allow only one task at a time
|
||||
assert.strictEqual(Object.keys(gActiveTasks).length, 0); // since we allow only one task at a time
|
||||
|
||||
startAppTask(gPendingTasks.shift());
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ scripts=("${SOURCE_DIR}/scripts/rmappdir.sh" \
|
||||
"${SOURCE_DIR}/scripts/restoreapp.sh" \
|
||||
"${SOURCE_DIR}/scripts/reboot.sh" \
|
||||
"${SOURCE_DIR}/scripts/backupswap.sh" \
|
||||
"${SOURCE_DIR}/scripts/collectlogs.sh" \
|
||||
"${SOURCE_DIR}/scripts/reloadcollectd.sh")
|
||||
|
||||
for script in "${scripts[@]}"; do
|
||||
|
||||
@@ -63,6 +63,17 @@ angular.module('Application').service('AppStore', ['$http', 'Client', function (
|
||||
});
|
||||
};
|
||||
|
||||
AppStore.prototype.getAppByIdAndVersion = function (appId, version, callback) {
|
||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||
|
||||
$http.get(Client.getConfig().apiServerOrigin + '/api/v1/apps/' + appId + '/versions/' + version).success(function (data, status) {
|
||||
if (status !== 200) return callback(new AppStoreError(status, data));
|
||||
return callback(null, data);
|
||||
}).error(function (data, status) {
|
||||
return callback(new AppStoreError(status, data));
|
||||
});
|
||||
};
|
||||
|
||||
AppStore.prototype.getManifest = function (appId, callback) {
|
||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||
|
||||
|
||||
@@ -206,6 +206,31 @@ html {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.appstore-category-missing {
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
|
||||
p {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin-bottom: 10px;
|
||||
transition: all 250ms ease-out;
|
||||
|
||||
&:focus {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.appstore-install-description {
|
||||
max-height: 250px;
|
||||
overflow-x: none;
|
||||
@@ -640,6 +665,33 @@ footer a {
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Oauth classes
|
||||
// ----------------------------
|
||||
|
||||
.oauth {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
background: #F7F7F7;
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
max-width: none;
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
margin-top: 15px;
|
||||
|
||||
@media(min-width:768px) {
|
||||
margin-top: 20%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Graphs classes
|
||||
// ----------------------------
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<option value="roleUser">Visible only to Cloudron users</option>
|
||||
</select>
|
||||
</div>
|
||||
<a ng-show="!!appConfigure.app.manifest.configurePath" ng-href="https://{{ appConfigure.app.location }}{{ !appConfigure.app.location ? '' : (config.isCustomDomain ? '.' : '-') }}{{ config.fqdn }}/{{ appConfigure.app.manifest.configurePath }}" target="_blank">Application Specific Settings</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="form-group" ng-class="{ 'has-error': (appConfigureForm.password.$dirty && appConfigureForm.password.$invalid) || (!appConfigureForm.password.$dirty && appConfigure.error.password) }">
|
||||
|
||||
@@ -64,6 +64,47 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal feedback -->
|
||||
<div class="modal fade" id="feedbackModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">App Feedback</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<fieldset>
|
||||
<form name="feedbackForm" ng-submit="submitFeedback()">
|
||||
<div ng-show="feedback.error" class="text-danger text-bold">{{feedback.error}}</div>
|
||||
<textarea class="form-control" id="feedbackDescriptionTextarea" cols="3" ng-model="feedback.description" ng-minlength="1" required placeholder="Name, Category, Links ..." autofocus></textarea>
|
||||
<input class="ng-hide" type="submit" ng-disabled="feedbackForm.$invalid || feedback.busy"/>
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-success" ng-click="submitFeedback()" ng-disabled="feedbackForm.$invalid || feedback.busy"><i class="fa fa-fw fa-paper-plane"></i> Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal app not found -->
|
||||
<div class="modal fade" id="appNotFoundModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">App not found</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
There is no such app <b>{{ appNotFound.appId }}</b><span ng-show="appNotFound.version"> with version <b>{{ appNotFound.version }}</b></span>.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="row-no-margin">
|
||||
<div class="col-md-2">
|
||||
@@ -98,6 +139,10 @@
|
||||
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'wiki' }" category="wiki">Wiki</a>
|
||||
<br/>
|
||||
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'testing' }" category="testing" ng-show="config.developerMode">Testing</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="" ng-click="showFeedbackModal()">Missing an app? Let us know.</a>
|
||||
</div>
|
||||
<div class="col-md-10" ng-show="ready && apps.length">
|
||||
<div class="row-no-margin">
|
||||
@@ -117,7 +162,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10 animateMeOpacity loading-banner" ng-show="ready && !apps.length">
|
||||
<h3 class="text-muted">No applications in this category</h3>
|
||||
<h3 class="text-muted">No applications in this category.</h3>
|
||||
<a href="" ng-click="showFeedbackModal()"><h3>Let us know if you miss something.</h3></a>
|
||||
</div>
|
||||
<div class="col-md-10 animateMeOpacity loading-banner" ng-show="!ready">
|
||||
<h2><i class="fa fa-spinner fa-pulse"></i> Loading</h2>
|
||||
|
||||
@@ -20,6 +20,47 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
||||
mediaLinks: []
|
||||
};
|
||||
|
||||
$scope.appNotFound = {
|
||||
appId: '',
|
||||
version: ''
|
||||
};
|
||||
|
||||
$scope.feedback = {
|
||||
error: null,
|
||||
success: false,
|
||||
subject: 'App feedback',
|
||||
description: '',
|
||||
type: 'app'
|
||||
};
|
||||
|
||||
function resetFeedback() {
|
||||
$scope.feedback.description = '';
|
||||
|
||||
$scope.feedbackForm.$setUntouched();
|
||||
$scope.feedbackForm.$setPristine();
|
||||
}
|
||||
|
||||
$scope.submitFeedback = function () {
|
||||
$scope.feedback.busy = true;
|
||||
$scope.feedback.success = false;
|
||||
$scope.feedback.error = null;
|
||||
|
||||
Client.feedback($scope.feedback.type, $scope.feedback.subject, $scope.feedback.description, function (error) {
|
||||
if (error) {
|
||||
$scope.feedback.error = error;
|
||||
} else {
|
||||
$scope.feedback.success = true;
|
||||
$('#feedbackModal').modal('hide');
|
||||
resetFeedback();
|
||||
}
|
||||
|
||||
$scope.feedback.busy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showFeedbackModal = function () {
|
||||
$('#feedbackModal').modal('show');
|
||||
};
|
||||
|
||||
function getAppList(callback) {
|
||||
AppStore.getApps(function (error, apps) {
|
||||
@@ -132,6 +173,13 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showAppNotFound = function (appId, version) {
|
||||
$scope.appNotFound.appId = appId;
|
||||
$scope.appNotFound.version = version;
|
||||
|
||||
$('#appNotFoundModal').modal('show');
|
||||
};
|
||||
|
||||
$scope.doInstall = function () {
|
||||
$scope.appInstall.busy = true;
|
||||
$scope.appInstall.error.other = null;
|
||||
@@ -189,11 +237,26 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
||||
|
||||
// show install app dialog immediately if an app id was passed in the query
|
||||
if ($routeParams.appId) {
|
||||
var found = apps.filter(function (app) {
|
||||
return (app.id === $routeParams.appId) && ($routeParams.version ? $routeParams.version === app.manifest.version : true);
|
||||
});
|
||||
if (found.length) {
|
||||
$scope.showInstall(found[0]);
|
||||
if ($routeParams.version) {
|
||||
AppStore.getAppByIdAndVersion($routeParams.appId, $routeParams.version, function (error, result) {
|
||||
if (error) {
|
||||
$scope.showAppNotFound($routeParams.appId, $routeParams.version);
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.showInstall(result);
|
||||
});
|
||||
} else {
|
||||
var found = apps.filter(function (app) {
|
||||
return (app.id === $routeParams.appId) && ($routeParams.version ? $routeParams.version === app.manifest.version : true);
|
||||
});
|
||||
|
||||
if (found.length) {
|
||||
$scope.showInstall(found[0]);
|
||||
} else {
|
||||
$scope.showAppNotFound($routeParams.appId, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +267,7 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
||||
refresh();
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['appInstallModal'].forEach(function (id) {
|
||||
['appInstallModal', 'feedbackModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find("[autofocus]:first").focus();
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<select class="form-control" name="type" style="width: 50%;" ng-model="feedback.type" required>
|
||||
<option value="feedback">Enhancement / Idea</option>
|
||||
<option value="ticket">Bug Report</option>
|
||||
<option value="app">Missing App</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.subject.$dirty && feedbackForm.subject.$invalid) }">
|
||||
@@ -51,6 +52,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<!-- Offset the footer -->
|
||||
<br/><br/>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<div class="cloudron-model-item-content shadow" ng-class="{ 'selected': size.slug === currentSize.slug }" style="height: {{ 120 + $index * 30 }}px">
|
||||
<!-- <img src="img/box.png" style="transform: scale({{ size.price/50.0 }});"/><br/> -->
|
||||
<h3>{{ size.name }}</h3>
|
||||
<h5>${{ size.price }}/mo</h5>
|
||||
<h5>${{ (size.price/100).toFixed() }}/mo</h5>
|
||||
<button class="btn btn-success" ng-disabled="busy" ng-hide="size.slug === currentSize.slug" ng-click="showUpgradeConfirm(size)">Upgrade</button>
|
||||
<button class="btn btn-success" ng-show="size.slug === currentSize.slug" data-toggle="tooltip" data-placement="top" title="Your Current Model"><i class="fa fa-check"></i></button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user