Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9266302c4c | |||
| 755dce7bc4 | |||
| dd3e38ae55 | |||
| 9dfaa2d20f | |||
| d6a4ff23e2 | |||
| c2ab7e2c1f | |||
| b9e4662dbb | |||
| 10df0a527f | |||
| 9aad3688e1 | |||
| e78dbcb5d4 | |||
| 5e8cd09f51 | |||
| 22f65a9364 | |||
| 81b7432044 | |||
| d49b90d9f2 | |||
| 9face9cf35 | |||
| 33ac34296e | |||
| 670ffcd489 | |||
| ec7b365c31 | |||
| 433d78c7ff | |||
| ed041fdca6 | |||
| b8e4ed2369 | |||
| d12f260d12 | |||
| ba7989b57b | |||
| 88df410f5b | |||
| 2436db3b1f | |||
| d15874df63 | |||
| 8fb90254cd | |||
| cbd712c20e | |||
| 8c004798f2 | |||
| c1b0cbe78d | |||
| 5ee72c8e98 | |||
| c125cc17dc | |||
| 18feff1bfb | |||
| f74f713bbd | |||
| 0ea14db172 | |||
| 74785a40d5 | |||
| dcfcd5be84 | |||
| 814674eac5 | |||
| 1a7fff9867 | |||
| 30b248a0f6 | |||
| 7168455de3 | |||
| 085f63e3c7 | |||
| 015be64923 | |||
| 2c2471811d | |||
| 1025249e93 | |||
| 41ffc4bcf3 | |||
| 2739d54cc1 | |||
| c4c463cbc2 | |||
| 8cd13bd43f | |||
| e4ef279759 | |||
| cf7fecb57b | |||
| 226041dcb1 | |||
| 7548025561 | |||
| fdbee427ee | |||
| d861d6d6e4 | |||
| 0a648edcaa | |||
| 18850c1fba | |||
| f6df4cab67 | |||
| 019d29c5b7 | |||
| 0b4256a992 | |||
| 7d58d69389 | |||
| 864dd5bf26 | |||
| abdde7a950 | |||
| 8bcbd860be | |||
| be61c42fe8 | |||
| 6d5afc2d75 | |||
| 88d905e8cc | |||
| d8ccc766b9 | |||
| d22e0f0483 | |||
| c8f6973312 | |||
| 3f0f0048bc | |||
| 88643f0875 | |||
| e11bb10bb8 | |||
| 7b9930c7f0 | |||
| da48e32bcc | |||
| 57e2803bd2 | |||
| 0d1ba01d65 | |||
| 95cbec19af | |||
| cc97654b23 | |||
| 5bb983f175 | |||
| 7cb6434de1 | |||
| cb1b495da2 | |||
| e134136d59 | |||
| 85a681e330 | |||
| dc5c0fd830 | |||
| e7bf8452ab | |||
| 157f972b20 | |||
| b36028dc11 | |||
| 70092ec559 | |||
| 56d740d597 | |||
| ed55e52363 | |||
| 89c36ae6a9 | |||
| 3027c119fe | |||
| 4f129102a8 | |||
| 2dd6bb0c67 | |||
| b928b08a4c | |||
| 9dcc6e68a4 | |||
| 452e67be54 | |||
| 9e0611f6d8 | |||
| ad3392ef2e | |||
| 71e8abf081 | |||
| 46172e76c6 | |||
| 7e639bd0e2 | |||
| 7a9af5373b | |||
| 3ea7a11d97 | |||
| f582ba1ba7 | |||
| b96fc2bc56 | |||
| 48c16277f0 | |||
| 4ad4ff0b10 | |||
| 25f05e5abd | |||
| 7c214a9181 | |||
| d66b1eef59 | |||
| 58f52b90f8 | |||
| edb67db4ea | |||
| 733014d8d9 | |||
| 4980f79688 | |||
| 3d8b90f5c8 | |||
| eea547411b | |||
| af682e5bb1 | |||
| 739dcfde8b | |||
| 1db58dd78d | |||
| 947137b3f9 | |||
| 509e2caa83 | |||
| a0e67daa52 | |||
| 32584f3a90 | |||
| 3513f321fb | |||
| 8aaccbba55 | |||
| 31ab86a97f | |||
| 2c0786eb37 | |||
| 3db8ebf97f | |||
| 804105ce2b | |||
| c4bb56dc95 | |||
| 87c76a3eb3 | |||
| 6bceff14ec | |||
| 6b62561706 | |||
| d558c06803 | |||
| ef9508ccc5 | |||
| ec8342c2ce |
@@ -7,48 +7,23 @@
|
|||||||
// !! No console.log() allowed
|
// !! No console.log() allowed
|
||||||
// !! Do not set DEBUG
|
// !! Do not set DEBUG
|
||||||
|
|
||||||
var supervisor = require('supervisord-eventlistener'),
|
var assert = require('assert'),
|
||||||
assert = require('assert'),
|
mailer = require('./src/mailer.js'),
|
||||||
exec = require('child_process').exec,
|
safe = require('safetydance'),
|
||||||
util = require('util'),
|
supervisor = require('supervisord-eventlistener'),
|
||||||
fs = require('fs'),
|
path = require('path'),
|
||||||
mailer = require('./src/mailer.js');
|
util = require('util');
|
||||||
|
|
||||||
var gLastNotifyTime = {};
|
var gLastNotifyTime = {};
|
||||||
var gCooldownTime = 1000 * 60 * 5; // 5 min
|
var gCooldownTime = 1000 * 60 * 5; // 5 min
|
||||||
|
var COLLECT_LOGS_CMD = path.join(__dirname, 'src/scripts/collectlogs.sh');
|
||||||
|
|
||||||
function collectLogs(program, callback) {
|
function collectLogs(program, callback) {
|
||||||
assert.strictEqual(typeof program, 'string');
|
assert.strictEqual(typeof program, 'string');
|
||||||
assert.strictEqual(typeof callback, 'function');
|
assert.strictEqual(typeof callback, 'function');
|
||||||
|
|
||||||
var logFilePath = util.format('/var/log/supervisor/%s.log', program);
|
var logs = safe.child_process.execSync('sudo ' + COLLECT_LOGS_CMD + ' ' + program, { encoding: 'utf8' });
|
||||||
|
callback(null, logs);
|
||||||
if (!fs.existsSync(logFilePath)) return callback(new Error(util.format('Log file %s does not exist.', logFilePath)));
|
|
||||||
|
|
||||||
fs.readFile(logFilePath, 'utf-8', function (error, data) {
|
|
||||||
if (error) return callback(error);
|
|
||||||
|
|
||||||
var lines = data.split('\n');
|
|
||||||
var boxLogLines = lines.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');
|
|
||||||
|
|
||||||
callback(null, result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
supervisor.on('PROCESS_STATE_EXITED', function (headers, data) {
|
supervisor.on('PROCESS_STATE_EXITED', function (headers, data) {
|
||||||
|
|||||||
@@ -105,8 +105,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cloudron-manifestformat": {
|
"cloudron-manifestformat": {
|
||||||
"version": "1.4.0",
|
"version": "1.6.0",
|
||||||
"from": "cloudron-manifestformat@1.4.0",
|
"from": "cloudron-manifestformat@1.6.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"java-packagename-regex": {
|
"java-packagename-regex": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -131,18 +131,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/connect-ensure-login/-/connect-ensure-login-0.1.1.tgz"
|
"resolved": "https://registry.npmjs.org/connect-ensure-login/-/connect-ensure-login-0.1.1.tgz"
|
||||||
},
|
},
|
||||||
"connect-lastmile": {
|
"connect-lastmile": {
|
||||||
"version": "0.0.12",
|
"version": "0.0.13",
|
||||||
"from": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-0.0.12.tgz",
|
"from": "connect-lastmile@0.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-0.0.12.tgz",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "2.1.3",
|
"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",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.1.3.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "0.7.0",
|
"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"
|
"resolved": "http://registry.npmjs.org/ms/-/ms-0.7.0.tgz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1288,69 +1287,69 @@
|
|||||||
},
|
},
|
||||||
"dockerode": {
|
"dockerode": {
|
||||||
"version": "2.2.2",
|
"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",
|
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.2.2.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"docker-modem": {
|
"docker-modem": {
|
||||||
"version": "0.2.6",
|
"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",
|
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-0.2.6.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "0.7.4",
|
"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"
|
"resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
"version": "0.0.3",
|
"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"
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.3.tgz"
|
||||||
},
|
},
|
||||||
"JSONStream": {
|
"JSONStream": {
|
||||||
"version": "0.10.0",
|
"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",
|
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsonparse": {
|
"jsonparse": {
|
||||||
"version": "0.0.5",
|
"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"
|
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz"
|
||||||
},
|
},
|
||||||
"through": {
|
"through": {
|
||||||
"version": "2.3.8",
|
"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"
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"querystring": {
|
"querystring": {
|
||||||
"version": "0.2.0",
|
"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"
|
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz"
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "1.0.33",
|
"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",
|
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.1",
|
"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"
|
"resolved": "http://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
|
||||||
},
|
},
|
||||||
"isarray": {
|
"isarray": {
|
||||||
"version": "0.0.1",
|
"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"
|
"resolved": "http://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "0.10.31",
|
"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"
|
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
|
||||||
},
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.1",
|
"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"
|
"resolved": "http://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1691,88 +1690,81 @@
|
|||||||
},
|
},
|
||||||
"ldapjs": {
|
"ldapjs": {
|
||||||
"version": "0.7.1",
|
"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",
|
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-0.7.1.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.1",
|
"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"
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.1.tgz"
|
||||||
},
|
},
|
||||||
"assert-plus": {
|
"assert-plus": {
|
||||||
"version": "0.1.5",
|
"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"
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
|
||||||
},
|
},
|
||||||
"bunyan": {
|
"bunyan": {
|
||||||
"version": "0.22.1",
|
"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",
|
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.1.tgz"
|
||||||
"dependencies": {
|
|
||||||
"mv": {
|
|
||||||
"version": "0.0.5",
|
|
||||||
"from": "mv@0.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/mv/-/mv-0.0.5.tgz"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nopt": {
|
"nopt": {
|
||||||
"version": "2.1.1",
|
"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",
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.1.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"abbrev": {
|
"abbrev": {
|
||||||
"version": "1.0.7",
|
"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"
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pooling": {
|
"pooling": {
|
||||||
"version": "0.4.6",
|
"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",
|
"resolved": "https://registry.npmjs.org/pooling/-/pooling-0.4.6.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"once": {
|
"once": {
|
||||||
"version": "1.3.0",
|
"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"
|
"resolved": "https://registry.npmjs.org/once/-/once-1.3.0.tgz"
|
||||||
},
|
},
|
||||||
"vasync": {
|
"vasync": {
|
||||||
"version": "1.4.0",
|
"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",
|
"resolved": "https://registry.npmjs.org/vasync/-/vasync-1.4.0.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsprim": {
|
"jsprim": {
|
||||||
"version": "0.3.0",
|
"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",
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-0.3.0.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"extsprintf": {
|
"extsprintf": {
|
||||||
"version": "1.0.0",
|
"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"
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz"
|
||||||
},
|
},
|
||||||
"json-schema": {
|
"json-schema": {
|
||||||
"version": "0.2.2",
|
"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"
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
|
||||||
},
|
},
|
||||||
"verror": {
|
"verror": {
|
||||||
"version": "1.3.3",
|
"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"
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.3.3.tgz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"verror": {
|
"verror": {
|
||||||
"version": "1.1.0",
|
"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",
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.1.0.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"extsprintf": {
|
"extsprintf": {
|
||||||
"version": "1.0.0",
|
"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"
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1780,11 +1772,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"dtrace-provider": {
|
|
||||||
"version": "0.2.8",
|
|
||||||
"from": "dtrace-provider@0.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1950,23 +1937,6 @@
|
|||||||
"from": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz",
|
"from": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz",
|
||||||
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
|
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
|
||||||
},
|
},
|
||||||
"nodejs-disks": {
|
|
||||||
"version": "0.2.1",
|
|
||||||
"from": "https://registry.npmjs.org/nodejs-disks/-/nodejs-disks-0.2.1.tgz",
|
|
||||||
"resolved": "https://registry.npmjs.org/nodejs-disks/-/nodejs-disks-0.2.1.tgz",
|
|
||||||
"dependencies": {
|
|
||||||
"async": {
|
|
||||||
"version": "0.2.10",
|
|
||||||
"from": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
|
|
||||||
},
|
|
||||||
"numeral": {
|
|
||||||
"version": "1.4.8",
|
|
||||||
"from": "https://registry.npmjs.org/numeral/-/numeral-1.4.8.tgz",
|
|
||||||
"resolved": "https://registry.npmjs.org/numeral/-/numeral-1.4.8.tgz"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nodemailer": {
|
"nodemailer": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
"from": "https://registry.npmjs.org/nodemailer/-/nodemailer-1.3.4.tgz",
|
"from": "https://registry.npmjs.org/nodemailer/-/nodemailer-1.3.4.tgz",
|
||||||
@@ -2273,9 +2243,9 @@
|
|||||||
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.13.0.tgz"
|
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.13.0.tgz"
|
||||||
},
|
},
|
||||||
"safetydance": {
|
"safetydance": {
|
||||||
"version": "0.0.16",
|
"version": "0.0.19",
|
||||||
"from": "http://registry.npmjs.org/safetydance/-/safetydance-0.0.16.tgz",
|
"from": "safetydance@0.0.19",
|
||||||
"resolved": "http://registry.npmjs.org/safetydance/-/safetydance-0.0.16.tgz"
|
"resolved": "https://registry.npmjs.org/safetydance/-/safetydance-0.0.19.tgz"
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^1.2.1",
|
"async": "^1.2.1",
|
||||||
"body-parser": "^1.13.1",
|
"body-parser": "^1.13.1",
|
||||||
"cloudron-manifestformat": "^1.4.0",
|
"cloudron-manifestformat": "^1.6.0",
|
||||||
"connect-ensure-login": "^0.1.1",
|
"connect-ensure-login": "^0.1.1",
|
||||||
"connect-lastmile": "0.0.12",
|
"connect-lastmile": "0.0.13",
|
||||||
"connect-timeout": "^1.5.0",
|
"connect-timeout": "^1.5.0",
|
||||||
"cookie-parser": "^1.3.5",
|
"cookie-parser": "^1.3.5",
|
||||||
"cookie-session": "^1.1.0",
|
"cookie-session": "^1.1.0",
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
"mysql": "^2.7.0",
|
"mysql": "^2.7.0",
|
||||||
"native-dns": "^0.7.0",
|
"native-dns": "^0.7.0",
|
||||||
"node-uuid": "^1.4.3",
|
"node-uuid": "^1.4.3",
|
||||||
"nodejs-disks": "^0.2.1",
|
|
||||||
"nodemailer": "^1.3.0",
|
"nodemailer": "^1.3.0",
|
||||||
"nodemailer-smtp-transport": "^1.0.3",
|
"nodemailer-smtp-transport": "^1.0.3",
|
||||||
"oauth2orize": "^1.0.1",
|
"oauth2orize": "^1.0.1",
|
||||||
@@ -55,7 +54,7 @@
|
|||||||
"passport-oauth2-client-password": "^0.1.2",
|
"passport-oauth2-client-password": "^0.1.2",
|
||||||
"password-generator": "^1.0.0",
|
"password-generator": "^1.0.0",
|
||||||
"proxy-middleware": "^0.13.0",
|
"proxy-middleware": "^0.13.0",
|
||||||
"safetydance": "0.0.16",
|
"safetydance": "0.0.19",
|
||||||
"semver": "^4.3.6",
|
"semver": "^4.3.6",
|
||||||
"serve-favicon": "^2.2.0",
|
"serve-favicon": "^2.2.0",
|
||||||
"split": "^1.0.0",
|
"split": "^1.0.0",
|
||||||
@@ -92,9 +91,9 @@
|
|||||||
"yargs": "^3.15.0"
|
"yargs": "^3.15.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"migrate_local": "NODE_ENV=local DATABASE_URL=mysql://root:@localhost/box node_modules/.bin/db-migrate up",
|
"migrate_local": "DATABASE_URL=mysql://root:@localhost/box node_modules/.bin/db-migrate up",
|
||||||
"migrate_test": "NODE_ENV=test DATABASE_URL=mysql://root:@localhost/boxtest node_modules/.bin/db-migrate up",
|
"migrate_test": "BOX_ENV=test DATABASE_URL=mysql://root:@localhost/boxtest node_modules/.bin/db-migrate up",
|
||||||
"test": "npm run migrate_test && src/test/setupTest && NODE_ENV=test ./node_modules/istanbul/lib/cli.js test $1 ./node_modules/mocha/bin/_mocha -- -R spec ./src/test ./src/routes/test",
|
"test": "npm run migrate_test && src/test/setupTest && BOX_ENV=test ./node_modules/istanbul/lib/cli.js test $1 ./node_modules/mocha/bin/_mocha -- -R spec ./src/test ./src/routes/test",
|
||||||
"postmerge": "/bin/true",
|
"postmerge": "/bin/true",
|
||||||
"precommit": "/bin/true",
|
"precommit": "/bin/true",
|
||||||
"prepush": "npm test",
|
"prepush": "npm test",
|
||||||
|
|||||||
@@ -3,4 +3,15 @@
|
|||||||
# If you change the infra version, be sure to put a warning
|
# If you change the infra version, be sure to put a warning
|
||||||
# in the change log
|
# in the change log
|
||||||
|
|
||||||
INFRA_VERSION=4
|
INFRA_VERSION=8
|
||||||
|
|
||||||
|
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||||
|
# These constants are used in the installer script as well
|
||||||
|
BASE_IMAGE=cloudron/base:0.3.1
|
||||||
|
MYSQL_IMAGE=cloudron/mysql:0.3.2
|
||||||
|
POSTGRESQL_IMAGE=cloudron/postgresql:0.3.1
|
||||||
|
MONGODB_IMAGE=cloudron/mongodb:0.3.1
|
||||||
|
REDIS_IMAGE=cloudron/redis:0.3.1 # if you change this, fix src/addons.js as well
|
||||||
|
MAIL_IMAGE=cloudron/mail:0.3.1
|
||||||
|
GRAPHITE_IMAGE=cloudron/graphite:0.3.3
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
json="${script_dir}/../node_modules/.bin/json"
|
json="${script_dir}/../node_modules/.bin/json"
|
||||||
|
|
||||||
arg_restore_url=""
|
# IMPORTANT: Fix cloudron.js:doUpdate if you add/remove any arg. keep these sorted for readability
|
||||||
arg_restore_key=""
|
arg_api_server_origin=""
|
||||||
arg_box_versions_url=""
|
arg_box_versions_url=""
|
||||||
|
arg_fqdn=""
|
||||||
|
arg_is_custom_domain="false"
|
||||||
|
arg_restore_key=""
|
||||||
|
arg_restore_url=""
|
||||||
|
arg_retire="false"
|
||||||
arg_tls_cert=""
|
arg_tls_cert=""
|
||||||
arg_tls_key=""
|
arg_tls_key=""
|
||||||
arg_api_server_origin=""
|
|
||||||
arg_web_server_origin=""
|
|
||||||
arg_fqdn=""
|
|
||||||
arg_token=""
|
arg_token=""
|
||||||
arg_version=""
|
arg_version=""
|
||||||
arg_is_custom_domain="false"
|
arg_web_server_origin=""
|
||||||
arg_retire="false"
|
|
||||||
arg_model=""
|
|
||||||
|
|
||||||
args=$(getopt -o "" -l "data:,retire" -n "$0" -- "$@")
|
args=$(getopt -o "" -l "data:,retire" -n "$0" -- "$@")
|
||||||
eval set -- "${args}"
|
eval set -- "${args}"
|
||||||
@@ -41,9 +41,6 @@ EOF
|
|||||||
arg_restore_key=$(echo "$2" | $json restoreKey)
|
arg_restore_key=$(echo "$2" | $json restoreKey)
|
||||||
[[ "${arg_restore_key}" == "null" ]] && arg_restore_key=""
|
[[ "${arg_restore_key}" == "null" ]] && arg_restore_key=""
|
||||||
|
|
||||||
arg_model=$(echo "$2" | $json model)
|
|
||||||
[[ "${arg_model}" == "null" ]] && arg_model=""
|
|
||||||
|
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--) break;;
|
--) break;;
|
||||||
@@ -52,15 +49,14 @@ EOF
|
|||||||
done
|
done
|
||||||
|
|
||||||
echo "Parsed arguments:"
|
echo "Parsed arguments:"
|
||||||
echo "restore url: ${arg_restore_url}"
|
|
||||||
echo "restore key: ${arg_restore_key}"
|
|
||||||
echo "box versions url: ${arg_box_versions_url}"
|
|
||||||
echo "api server: ${arg_api_server_origin}"
|
echo "api server: ${arg_api_server_origin}"
|
||||||
echo "web server: ${arg_web_server_origin}"
|
echo "box versions url: ${arg_box_versions_url}"
|
||||||
echo "fqdn: ${arg_fqdn}"
|
echo "fqdn: ${arg_fqdn}"
|
||||||
echo "token: ${arg_token}"
|
|
||||||
echo "version: ${arg_version}"
|
|
||||||
echo "custom domain: ${arg_is_custom_domain}"
|
echo "custom domain: ${arg_is_custom_domain}"
|
||||||
|
echo "restore key: ${arg_restore_key}"
|
||||||
|
echo "restore url: ${arg_restore_url}"
|
||||||
echo "tls cert: ${arg_tls_cert}"
|
echo "tls cert: ${arg_tls_cert}"
|
||||||
echo "tls key: ${arg_tls_key}"
|
echo "tls key: ${arg_tls_key}"
|
||||||
echo "model: ${arg_model}"
|
echo "token: ${arg_token}"
|
||||||
|
echo "version: ${arg_version}"
|
||||||
|
echo "web server: ${arg_web_server_origin}"
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ ln -sfF "${DATA_DIR}/collectd" /etc/collectd
|
|||||||
unlink /etc/nginx 2>/dev/null || rm -rf /etc/nginx
|
unlink /etc/nginx 2>/dev/null || rm -rf /etc/nginx
|
||||||
ln -s "${DATA_DIR}/nginx" /etc/nginx
|
ln -s "${DATA_DIR}/nginx" /etc/nginx
|
||||||
|
|
||||||
|
########## mysql
|
||||||
|
cp "${container_files}/mysql.cnf" /etc/mysql/mysql.cnf
|
||||||
|
|
||||||
########## Enable services
|
########## Enable services
|
||||||
update-rc.d -f collectd defaults
|
update-rc.d -f collectd defaults
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
!includedir /etc/mysql/conf.d/
|
||||||
|
!includedir /etc/mysql/mysql.conf.d/
|
||||||
|
|
||||||
|
# http://bugs.mysql.com/bug.php?id=68514
|
||||||
|
[mysqld]
|
||||||
|
performance_schema=OFF
|
||||||
|
max_connection=50
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
Defaults!/home/yellowtent/box/src/scripts/createappdir.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/createappdir.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/createappdir.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/createappdir.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/rmappdir.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/rmappdir.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmappdir.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmappdir.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/reloadnginx.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/reloadnginx.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reloadnginx.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reloadnginx.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/backupbox.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/backupbox.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/backupbox.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/backupbox.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/backupapp.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/backupapp.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/backupapp.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/backupapp.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/restoreapp.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/restoreapp.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/restoreapp.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/restoreapp.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/reboot.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/reboot.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reboot.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reboot.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/reloadcollectd.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/reloadcollectd.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reloadcollectd.sh
|
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reloadcollectd.sh
|
||||||
|
|
||||||
Defaults!/home/yellowtent/box/src/scripts/backupswap.sh env_keep="HOME NODE_ENV"
|
Defaults!/home/yellowtent/box/src/scripts/backupswap.sh env_keep="HOME BOX_ENV"
|
||||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/backupswap.sh
|
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
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ stdout_logfile=/var/log/supervisor/apphealthtask.log
|
|||||||
stdout_logfile_maxbytes=50MB
|
stdout_logfile_maxbytes=50MB
|
||||||
stdout_logfile_backups=2
|
stdout_logfile_backups=2
|
||||||
user=yellowtent
|
user=yellowtent
|
||||||
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",NODE_ENV="cloudron"
|
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",BOX_ENV="cloudron",NODE_ENV="production"
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ stdout_logfile=/var/log/supervisor/box.log
|
|||||||
stdout_logfile_maxbytes=50MB
|
stdout_logfile_maxbytes=50MB
|
||||||
stdout_logfile_backups=2
|
stdout_logfile_backups=2
|
||||||
user=yellowtent
|
user=yellowtent
|
||||||
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*,connect-lastmile",NODE_ENV="cloudron"
|
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*,connect-lastmile",BOX_ENV="cloudron",NODE_ENV="production"
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ stderr_logfile=/var/log/supervisor/crashnotifier.log
|
|||||||
stderr_logfile_maxbytes=50MB
|
stderr_logfile_maxbytes=50MB
|
||||||
stderr_logfile_backups=2
|
stderr_logfile_backups=2
|
||||||
user=yellowtent
|
user=yellowtent
|
||||||
environment=HOME="/home/yellowtent",USER="yellowtent",NODE_ENV="cloudron"
|
environment=HOME="/home/yellowtent",USER="yellowtent",BOX_ENV="cloudron",NODE_ENV="production"
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ stdout_logfile=/var/log/supervisor/janitor.log
|
|||||||
stdout_logfile_maxbytes=50MB
|
stdout_logfile_maxbytes=50MB
|
||||||
stdout_logfile_backups=2
|
stdout_logfile_backups=2
|
||||||
user=yellowtent
|
user=yellowtent
|
||||||
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",NODE_ENV="cloudron"
|
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",BOX_ENV="cloudron",NODE_ENV="production"
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ stdout_logfile=/var/log/supervisor/oauthproxy.log
|
|||||||
stdout_logfile_maxbytes=50MB
|
stdout_logfile_maxbytes=50MB
|
||||||
stdout_logfile_backups=2
|
stdout_logfile_backups=2
|
||||||
user=yellowtent
|
user=yellowtent
|
||||||
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",NODE_ENV="cloudron"
|
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",BOX_ENV="cloudron",NODE_ENV="production"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ readonly SETUP_WEBSITE_DIR="/home/yellowtent/setup/website"
|
|||||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
readonly BOX_SRC_DIR="/home/yellowtent/box"
|
readonly BOX_SRC_DIR="/home/yellowtent/box"
|
||||||
readonly DATA_DIR="/home/yellowtent/data"
|
readonly DATA_DIR="/home/yellowtent/data"
|
||||||
|
readonly ADMIN_LOCATION="my" # keep this in sync with constants.js
|
||||||
|
|
||||||
source "${script_dir}/INFRA_VERSION" # this injects INFRA_VERSION
|
source "${script_dir}/INFRA_VERSION" # this injects INFRA_VERSION
|
||||||
|
|
||||||
@@ -14,6 +15,10 @@ echo "Setting up nginx update page"
|
|||||||
|
|
||||||
source "${script_dir}/argparser.sh" "$@" # this injects the arg_* variables used below
|
source "${script_dir}/argparser.sh" "$@" # this injects the arg_* variables used below
|
||||||
|
|
||||||
|
# keep this is sync with config.js appFqdn()
|
||||||
|
admin_fqdn=$([[ "${arg_is_custom_domain}" == "true" ]] && echo "${ADMIN_LOCATION}.${arg_fqdn}" || echo "${ADMIN_LOCATION}-${arg_fqdn}")
|
||||||
|
admin_origin="https://${admin_fqdn}"
|
||||||
|
|
||||||
# copy the website
|
# copy the website
|
||||||
rm -rf "${SETUP_WEBSITE_DIR}" && mkdir -p "${SETUP_WEBSITE_DIR}"
|
rm -rf "${SETUP_WEBSITE_DIR}" && mkdir -p "${SETUP_WEBSITE_DIR}"
|
||||||
cp -r "${script_dir}/splash/website/"* "${SETUP_WEBSITE_DIR}"
|
cp -r "${script_dir}/splash/website/"* "${SETUP_WEBSITE_DIR}"
|
||||||
@@ -24,14 +29,10 @@ infra_version="none"
|
|||||||
if [[ "${arg_retire}" == "true" || "${infra_version}" != "${INFRA_VERSION}" ]]; then
|
if [[ "${arg_retire}" == "true" || "${infra_version}" != "${INFRA_VERSION}" ]]; then
|
||||||
rm -f ${DATA_DIR}/nginx/applications/*
|
rm -f ${DATA_DIR}/nginx/applications/*
|
||||||
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||||
-O "{ \"vhost\": \"~^(.+)\$\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
-O "{ \"vhost\": \"~^(.+)\$\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
||||||
else
|
else
|
||||||
# keep this is sync with config.js appFqdn()
|
|
||||||
readonly ADMIN_LOCATION="my" # keep this in sync with constants.js
|
|
||||||
admin_fqdn=$([[ "${arg_is_custom_domain}" == "true" ]] && echo "${ADMIN_LOCATION}.${arg_fqdn}" || echo "${ADMIN_LOCATION}-${arg_fqdn}")
|
|
||||||
|
|
||||||
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||||
-O "{ \"vhost\": \"${admin_fqdn}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo '{ "update": { "percent": "10", "message": "Updating cloudron software" }, "backup": null }' > "${SETUP_WEBSITE_DIR}/progress.json"
|
echo '{ "update": { "percent": "10", "message": "Updating cloudron software" }, "backup": null }' > "${SETUP_WEBSITE_DIR}/progress.json"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ source "${script_dir}/argparser.sh" "$@" # this injects the arg_* variables used
|
|||||||
|
|
||||||
# keep this is sync with config.js appFqdn()
|
# keep this is sync with config.js appFqdn()
|
||||||
admin_fqdn=$([[ "${arg_is_custom_domain}" == "true" ]] && echo "${ADMIN_LOCATION}.${arg_fqdn}" || echo "${ADMIN_LOCATION}-${arg_fqdn}")
|
admin_fqdn=$([[ "${arg_is_custom_domain}" == "true" ]] && echo "${ADMIN_LOCATION}.${arg_fqdn}" || echo "${ADMIN_LOCATION}-${arg_fqdn}")
|
||||||
|
admin_origin="https://${admin_fqdn}"
|
||||||
|
|
||||||
readonly is_update=$([[ -d "${DATA_DIR}/box" ]] && echo "true" || echo "false")
|
readonly is_update=$([[ -d "${DATA_DIR}/box" ]] && echo "true" || echo "false")
|
||||||
|
|
||||||
@@ -40,6 +41,9 @@ mkdir -p "${DATA_DIR}/box/appicons"
|
|||||||
mkdir -p "${DATA_DIR}/box/mail"
|
mkdir -p "${DATA_DIR}/box/mail"
|
||||||
mkdir -p "${DATA_DIR}/graphite"
|
mkdir -p "${DATA_DIR}/graphite"
|
||||||
|
|
||||||
|
mkdir -p "${DATA_DIR}/mysql"
|
||||||
|
mkdir -p "${DATA_DIR}/postgresql"
|
||||||
|
mkdir -p "${DATA_DIR}/mongodb"
|
||||||
mkdir -p "${DATA_DIR}/snapshots"
|
mkdir -p "${DATA_DIR}/snapshots"
|
||||||
mkdir -p "${DATA_DIR}/addons"
|
mkdir -p "${DATA_DIR}/addons"
|
||||||
mkdir -p "${DATA_DIR}/collectd/collectd.conf.d"
|
mkdir -p "${DATA_DIR}/collectd/collectd.conf.d"
|
||||||
@@ -52,6 +56,9 @@ echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_version
|
|||||||
echo "Cleaning up snapshots"
|
echo "Cleaning up snapshots"
|
||||||
find "${DATA_DIR}/snapshots" -mindepth 1 -maxdepth 1 | xargs --no-run-if-empty btrfs subvolume delete
|
find "${DATA_DIR}/snapshots" -mindepth 1 -maxdepth 1 | xargs --no-run-if-empty btrfs subvolume delete
|
||||||
|
|
||||||
|
# restart mysql to make sure it has latest config
|
||||||
|
service mysql restart
|
||||||
|
|
||||||
readonly mysql_root_password="password"
|
readonly mysql_root_password="password"
|
||||||
mysqladmin -u root -ppassword password password # reset default root password
|
mysqladmin -u root -ppassword password password # reset default root password
|
||||||
mysql -u root -p${mysql_root_password} -e 'CREATE DATABASE IF NOT EXISTS box'
|
mysql -u root -p${mysql_root_password} -e 'CREATE DATABASE IF NOT EXISTS box'
|
||||||
@@ -77,11 +84,15 @@ set_progress "25" "Migrating data"
|
|||||||
sudo -u "${USER}" -H bash <<EOF
|
sudo -u "${USER}" -H bash <<EOF
|
||||||
set -eu
|
set -eu
|
||||||
cd "${BOX_SRC_DIR}"
|
cd "${BOX_SRC_DIR}"
|
||||||
NODE_ENV=cloudron DATABASE_URL=mysql://root:${mysql_root_password}@localhost/box "${BOX_SRC_DIR}/node_modules/.bin/db-migrate" up
|
BOX_ENV=cloudron DATABASE_URL=mysql://root:${mysql_root_password}@localhost/box "${BOX_SRC_DIR}/node_modules/.bin/db-migrate" up
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
set_progress "28" "Setup collectd"
|
set_progress "28" "Setup collectd"
|
||||||
cp "${script_dir}/start/collectd.conf" "${DATA_DIR}/collectd/collectd.conf"
|
cp "${script_dir}/start/collectd.conf" "${DATA_DIR}/collectd/collectd.conf"
|
||||||
|
# collectd 5.4.1 has some bug where we simply cannot get it to create df-vda1
|
||||||
|
mkdir -p "${DATA_DIR}/graphite/whisper/collectd/localhost/"
|
||||||
|
vda1_id=$(blkid -s UUID -o value /dev/vda1)
|
||||||
|
ln -sfF "df-disk_by-uuid_${vda1_id}" "${DATA_DIR}/graphite/whisper/collectd/localhost/df-vda1"
|
||||||
service collectd restart
|
service collectd restart
|
||||||
|
|
||||||
set_progress "30" "Setup nginx"
|
set_progress "30" "Setup nginx"
|
||||||
@@ -95,7 +106,7 @@ ${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/nginx.ejs
|
|||||||
|
|
||||||
# generate these for update code paths as well to overwrite splash
|
# generate these for update code paths as well to overwrite splash
|
||||||
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||||
-O "{ \"vhost\": \"${admin_fqdn}\", \"endpoint\": \"admin\", \"sourceDir\": \"${BOX_SRC_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"admin\", \"sourceDir\": \"${BOX_SRC_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
||||||
|
|
||||||
mkdir -p "${DATA_DIR}/nginx/cert"
|
mkdir -p "${DATA_DIR}/nginx/cert"
|
||||||
echo "${arg_tls_cert}" > ${DATA_DIR}/nginx/cert/host.cert
|
echo "${arg_tls_cert}" > ${DATA_DIR}/nginx/cert/host.cert
|
||||||
@@ -108,7 +119,6 @@ set_progress "40" "Setting up infra"
|
|||||||
${script_dir}/start/setup_infra.sh "${arg_fqdn}"
|
${script_dir}/start/setup_infra.sh "${arg_fqdn}"
|
||||||
|
|
||||||
set_progress "65" "Creating cloudron.conf"
|
set_progress "65" "Creating cloudron.conf"
|
||||||
admin_origin="https://${admin_fqdn}"
|
|
||||||
sudo -u yellowtent -H bash <<EOF
|
sudo -u yellowtent -H bash <<EOF
|
||||||
set -eu
|
set -eu
|
||||||
echo "Creating cloudron.conf"
|
echo "Creating cloudron.conf"
|
||||||
@@ -128,8 +138,7 @@ cat > "${CONFIG_DIR}/cloudron.conf" <<CONF_END
|
|||||||
"password": "${mysql_root_password}",
|
"password": "${mysql_root_password}",
|
||||||
"port": 3306,
|
"port": 3306,
|
||||||
"name": "box"
|
"name": "box"
|
||||||
},
|
}
|
||||||
"model": "${arg_model}"
|
|
||||||
}
|
}
|
||||||
CONF_END
|
CONF_END
|
||||||
|
|
||||||
|
|||||||
@@ -193,12 +193,11 @@ LoadPlugin write_graphite
|
|||||||
</Plugin>
|
</Plugin>
|
||||||
|
|
||||||
<Plugin df>
|
<Plugin df>
|
||||||
Device "/dev/vda1"
|
FSType "tmpfs"
|
||||||
Device "/dev/loop0"
|
MountPoint "/dev"
|
||||||
Device "/dev/loop1"
|
|
||||||
|
|
||||||
ReportByDevice true
|
ReportByDevice true
|
||||||
IgnoreSelected false
|
IgnoreSelected true
|
||||||
|
|
||||||
ValuesAbsolute true
|
ValuesAbsolute true
|
||||||
ValuesPercentage true
|
ValuesPercentage true
|
||||||
|
|||||||
@@ -37,11 +37,9 @@ server {
|
|||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|
||||||
error_page 500 502 503 504 =200 @appstatus;
|
error_page 500 502 503 504 @appstatus;
|
||||||
location @appstatus {
|
location @appstatus {
|
||||||
internal;
|
return 307 <%= adminOrigin %>/appstatus.html?referrer=https://$host$request_uri;
|
||||||
root <%= sourceDir %>/webadmin/dist;
|
|
||||||
rewrite ^/$ /appstatus.html break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ if [[ -n "${existing_containers}" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# graphite
|
# graphite
|
||||||
docker run --restart=always -d --name="graphite" \
|
graphite_container_id=$(docker run --restart=always -d --name="graphite" \
|
||||||
-p 127.0.0.1:2003:2003 \
|
-p 127.0.0.1:2003:2003 \
|
||||||
-p 127.0.0.1:2004:2004 \
|
-p 127.0.0.1:2004:2004 \
|
||||||
-p 127.0.0.1:8000:8000 \
|
-p 127.0.0.1:8000:8000 \
|
||||||
-v "${DATA_DIR}/graphite:/app/data" cloudron/graphite:0.3.1
|
-v "${DATA_DIR}/graphite:/app/data" \
|
||||||
|
"${GRAPHITE_IMAGE}")
|
||||||
|
echo "Graphite container id: ${graphite_container_id}"
|
||||||
|
|
||||||
# mail
|
# mail
|
||||||
mail_container_id=$(docker run --restart=always -d --name="mail" \
|
mail_container_id=$(docker run --restart=always -d --name="mail" \
|
||||||
@@ -39,7 +41,7 @@ mail_container_id=$(docker run --restart=always -d --name="mail" \
|
|||||||
-h "${arg_fqdn}" \
|
-h "${arg_fqdn}" \
|
||||||
-e "DOMAIN_NAME=${arg_fqdn}" \
|
-e "DOMAIN_NAME=${arg_fqdn}" \
|
||||||
-v "${DATA_DIR}/box/mail:/app/data" \
|
-v "${DATA_DIR}/box/mail:/app/data" \
|
||||||
cloudron/mail:0.3.0)
|
"${MAIL_IMAGE}")
|
||||||
echo "Mail container id: ${mail_container_id}"
|
echo "Mail container id: ${mail_container_id}"
|
||||||
|
|
||||||
# mysql
|
# mysql
|
||||||
@@ -52,8 +54,8 @@ EOF
|
|||||||
mysql_container_id=$(docker run --restart=always -d --name="mysql" \
|
mysql_container_id=$(docker run --restart=always -d --name="mysql" \
|
||||||
-h "${arg_fqdn}" \
|
-h "${arg_fqdn}" \
|
||||||
-v "${DATA_DIR}/mysql:/var/lib/mysql" \
|
-v "${DATA_DIR}/mysql:/var/lib/mysql" \
|
||||||
-v "${DATA_DIR}/addons/mysql_vars.sh:/etc/mysql/mysql_vars.sh:r" \
|
-v "${DATA_DIR}/addons/mysql_vars.sh:/etc/mysql/mysql_vars.sh:ro" \
|
||||||
cloudron/mysql:0.3.0)
|
"${MYSQL_IMAGE}")
|
||||||
echo "MySQL container id: ${mysql_container_id}"
|
echo "MySQL container id: ${mysql_container_id}"
|
||||||
|
|
||||||
# postgresql
|
# postgresql
|
||||||
@@ -64,8 +66,8 @@ EOF
|
|||||||
postgresql_container_id=$(docker run --restart=always -d --name="postgresql" \
|
postgresql_container_id=$(docker run --restart=always -d --name="postgresql" \
|
||||||
-h "${arg_fqdn}" \
|
-h "${arg_fqdn}" \
|
||||||
-v "${DATA_DIR}/postgresql:/var/lib/postgresql" \
|
-v "${DATA_DIR}/postgresql:/var/lib/postgresql" \
|
||||||
-v "${DATA_DIR}/addons/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh:r" \
|
-v "${DATA_DIR}/addons/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh:ro" \
|
||||||
cloudron/postgresql:0.3.0)
|
"${POSTGRESQL_IMAGE}")
|
||||||
echo "PostgreSQL container id: ${postgresql_container_id}"
|
echo "PostgreSQL container id: ${postgresql_container_id}"
|
||||||
|
|
||||||
# mongodb
|
# mongodb
|
||||||
@@ -76,8 +78,8 @@ EOF
|
|||||||
mongodb_container_id=$(docker run --restart=always -d --name="mongodb" \
|
mongodb_container_id=$(docker run --restart=always -d --name="mongodb" \
|
||||||
-h "${arg_fqdn}" \
|
-h "${arg_fqdn}" \
|
||||||
-v "${DATA_DIR}/mongodb:/var/lib/mongodb" \
|
-v "${DATA_DIR}/mongodb:/var/lib/mongodb" \
|
||||||
-v "${DATA_DIR}/addons/mongodb_vars.sh:/etc/mongodb_vars.sh:r" \
|
-v "${DATA_DIR}/addons/mongodb_vars.sh:/etc/mongodb_vars.sh:ro" \
|
||||||
cloudron/mongodb:0.3.0)
|
"${MONGODB_IMAGE}")
|
||||||
echo "Mongodb container id: ${mongodb_container_id}"
|
echo "Mongodb container id: ${mongodb_container_id}"
|
||||||
|
|
||||||
if [[ "${infra_version}" == "none" ]]; then
|
if [[ "${infra_version}" == "none" ]]; then
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ function setupRedis(app, callback) {
|
|||||||
name: 'redis-' + app.id,
|
name: 'redis-' + app.id,
|
||||||
Hostname: config.appFqdn(app.location),
|
Hostname: config.appFqdn(app.location),
|
||||||
Tty: true,
|
Tty: true,
|
||||||
Image: 'cloudron/redis:0.3.0',
|
Image: 'cloudron/redis:0.3.1',
|
||||||
Cmd: null,
|
Cmd: null,
|
||||||
Volumes: {},
|
Volumes: {},
|
||||||
VolumesFrom: []
|
VolumesFrom: []
|
||||||
@@ -675,7 +675,7 @@ function setupRedis(app, callback) {
|
|||||||
|
|
||||||
var startOptions = {
|
var startOptions = {
|
||||||
Binds: [
|
Binds: [
|
||||||
redisVarsFile + ':/etc/redis/redis_vars.sh:r',
|
redisVarsFile + ':/etc/redis/redis_vars.sh:ro',
|
||||||
redisDataDir + ':/var/lib/redis:rw'
|
redisDataDir + ':/var/lib/redis:rw'
|
||||||
],
|
],
|
||||||
// On Mac (boot2docker), we have to export the port to external world for port forwarding from Mac to work
|
// On Mac (boot2docker), we have to export the port to external world for port forwarding from Mac to work
|
||||||
|
|||||||
@@ -675,16 +675,16 @@ function autoupdateApps(updateInfo, callback) { // updateInfo is { appId -> { ma
|
|||||||
|
|
||||||
function backupApp(app, addonsToBackup, callback) {
|
function backupApp(app, addonsToBackup, callback) {
|
||||||
assert.strictEqual(typeof app, 'object');
|
assert.strictEqual(typeof app, 'object');
|
||||||
assert.strictEqual(typeof addonsToBackup, 'object');
|
assert(!addonsToBackup || typeof addonsToBackup, 'object');
|
||||||
assert.strictEqual(typeof callback, 'function');
|
assert.strictEqual(typeof callback, 'function');
|
||||||
|
|
||||||
function canBackupApp(app) {
|
function canBackupApp(app) {
|
||||||
// only backup apps that are installed or pending configure. Rest of them are in some
|
// only backup apps that are installed or pending configure. Rest of them are in some
|
||||||
// state not good for consistent backup (i.e addons may not have been setup completely)
|
// state not good for consistent backup (i.e addons may not have been setup completely)
|
||||||
return (app.installationState === appdb.ISTATE_INSTALLED && app.health === appdb.HEALTH_HEALTHY)
|
return (app.installationState === appdb.ISTATE_INSTALLED && app.health === appdb.HEALTH_HEALTHY) ||
|
||||||
|| app.installationState === appdb.ISTATE_PENDING_CONFIGURE
|
app.installationState === appdb.ISTATE_PENDING_CONFIGURE ||
|
||||||
|| app.installationState === appdb.ISTATE_PENDING_BACKUP
|
app.installationState === appdb.ISTATE_PENDING_BACKUP ||
|
||||||
|| app.installationState === appdb.ISTATE_PENDING_UPDATE; // called from apptask
|
app.installationState === appdb.ISTATE_PENDING_UPDATE; // called from apptask
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canBackupApp(app)) return callback(new AppsError(AppsError.BAD_STATE, 'App not healthy'));
|
if (!canBackupApp(app)) return callback(new AppsError(AppsError.BAD_STATE, 'App not healthy'));
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function configureNginx(app, callback) {
|
|||||||
|
|
||||||
var sourceDir = path.resolve(__dirname, '..');
|
var sourceDir = path.resolve(__dirname, '..');
|
||||||
var endpoint = app.accessRestriction ? 'oauthproxy' : 'app';
|
var endpoint = app.accessRestriction ? 'oauthproxy' : 'app';
|
||||||
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, { sourceDir: sourceDir, vhost: config.appFqdn(app.location), port: freePort, endpoint: endpoint });
|
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, { sourceDir: sourceDir, adminOrigin: config.adminOrigin(), vhost: config.appFqdn(app.location), port: freePort, endpoint: endpoint });
|
||||||
|
|
||||||
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf');
|
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf');
|
||||||
debugApp(app, 'writing config to %s', nginxConfigFilename);
|
debugApp(app, 'writing config to %s', nginxConfigFilename);
|
||||||
@@ -189,7 +189,6 @@ function createContainer(app, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
env.push('CLOUDRON=1');
|
env.push('CLOUDRON=1');
|
||||||
env.push('ADMIN_ORIGIN' + '=' + config.adminOrigin()); // ## remove
|
|
||||||
env.push('WEBADMIN_ORIGIN' + '=' + config.adminOrigin());
|
env.push('WEBADMIN_ORIGIN' + '=' + config.adminOrigin());
|
||||||
env.push('API_ORIGIN' + '=' + config.adminOrigin());
|
env.push('API_ORIGIN' + '=' + config.adminOrigin());
|
||||||
|
|
||||||
@@ -202,8 +201,6 @@ function createContainer(app, callback) {
|
|||||||
Tty: true,
|
Tty: true,
|
||||||
Image: app.manifest.dockerImage,
|
Image: app.manifest.dockerImage,
|
||||||
Cmd: null,
|
Cmd: null,
|
||||||
Volumes: {},
|
|
||||||
VolumesFrom: [],
|
|
||||||
Env: env.concat(addonEnv),
|
Env: env.concat(addonEnv),
|
||||||
ExposedPorts: exposedPorts
|
ExposedPorts: exposedPorts
|
||||||
};
|
};
|
||||||
@@ -342,7 +339,8 @@ function startContainer(app, callback) {
|
|||||||
RestartPolicy: {
|
RestartPolicy: {
|
||||||
"Name": "always",
|
"Name": "always",
|
||||||
"MaximumRetryCount": 0
|
"MaximumRetryCount": 0
|
||||||
}
|
},
|
||||||
|
CpuShares: 512 // relative to 1024 for system processes
|
||||||
};
|
};
|
||||||
|
|
||||||
var container = docker.getContainer(app.containerId);
|
var container = docker.getContainer(app.containerId);
|
||||||
@@ -673,7 +671,7 @@ function restore(app, callback) {
|
|||||||
|
|
||||||
// note that configure is called after an infra update as well
|
// note that configure is called after an infra update as well
|
||||||
function configure(app, callback) {
|
function configure(app, callback) {
|
||||||
var locationChanged = app.oldConfig.location !== app.location;
|
var locationChanged = app.oldConfig ? app.oldConfig.location !== app.location : true;
|
||||||
|
|
||||||
async.series([
|
async.series([
|
||||||
updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }),
|
updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }),
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ exports = module.exports = {
|
|||||||
reboot: reboot,
|
reboot: reboot,
|
||||||
migrate: migrate,
|
migrate: migrate,
|
||||||
backup: backup,
|
backup: backup,
|
||||||
ensureBackup: ensureBackup
|
ensureBackup: ensureBackup};
|
||||||
};
|
|
||||||
|
|
||||||
var apps = require('./apps.js'),
|
var apps = require('./apps.js'),
|
||||||
AppsError = require('./apps.js').AppsError,
|
AppsError = require('./apps.js').AppsError,
|
||||||
@@ -108,7 +107,7 @@ CloudronError.NOT_FOUND = 'Not found';
|
|||||||
function initialize(callback) {
|
function initialize(callback) {
|
||||||
assert.strictEqual(typeof callback, 'function');
|
assert.strictEqual(typeof callback, 'function');
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.BOX_ENV !== 'test') {
|
||||||
addMailDnsRecords();
|
addMailDnsRecords();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,13 +270,12 @@ function getConfig(callback) {
|
|||||||
|
|
||||||
function sendHeartbeat() {
|
function sendHeartbeat() {
|
||||||
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/heartbeat';
|
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/heartbeat';
|
||||||
debug('Sending heartbeat ' + url);
|
|
||||||
|
|
||||||
// TODO: this must be a POST
|
// TODO: this must be a POST
|
||||||
superagent.get(url).query({ token: config.token(), version: config.version() }).timeout(10000).end(function (error, result) {
|
superagent.get(url).query({ token: config.token(), version: config.version() }).timeout(10000).end(function (error, result) {
|
||||||
if (error) debug('Error sending heartbeat.', error);
|
if (error) debug('Error sending heartbeat.', error);
|
||||||
else if (result.statusCode !== 200) debug('Server responded to heartbeat with %s %s', result.statusCode, result.text);
|
else if (result.statusCode !== 200) debug('Server responded to heartbeat with %s %s', result.statusCode, result.text);
|
||||||
else debug('Heartbeat successful');
|
else debug('Heartbeat sent to %s', url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,16 +416,22 @@ function update(boxUpdateInfo, callback) {
|
|||||||
var error = locker.lock(locker.OP_BOX_UPDATE);
|
var error = locker.lock(locker.OP_BOX_UPDATE);
|
||||||
if (error) return callback(new CloudronError(CloudronError.BAD_STATE, error.message));
|
if (error) return callback(new CloudronError(CloudronError.BAD_STATE, error.message));
|
||||||
|
|
||||||
progress.set(progress.UPDATE, 0, 'Begin ' + (boxUpdateInfo.update ? 'upgrade': 'update'));
|
|
||||||
|
|
||||||
// initiate the update/upgrade but do not wait for it
|
// initiate the update/upgrade but do not wait for it
|
||||||
if (boxUpdateInfo.upgrade) {
|
if (boxUpdateInfo.upgrade) {
|
||||||
|
debug('Starting upgrade');
|
||||||
doUpgrade(boxUpdateInfo, function (error) {
|
doUpgrade(boxUpdateInfo, function (error) {
|
||||||
|
if (error) {
|
||||||
|
debug('Upgrade failed with error: %s', error);
|
||||||
locker.unlock(locker.OP_BOX_UPDATE);
|
locker.unlock(locker.OP_BOX_UPDATE);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
debug('Starting update');
|
||||||
doUpdate(boxUpdateInfo, function (error) {
|
doUpdate(boxUpdateInfo, function (error) {
|
||||||
|
if (error) {
|
||||||
|
debug('Update failed with error: %s', error);
|
||||||
locker.unlock(locker.OP_BOX_UPDATE);
|
locker.unlock(locker.OP_BOX_UPDATE);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,17 +441,22 @@ function update(boxUpdateInfo, callback) {
|
|||||||
function doUpgrade(boxUpdateInfo, callback) {
|
function doUpgrade(boxUpdateInfo, callback) {
|
||||||
assert(boxUpdateInfo !== null && typeof boxUpdateInfo === 'object');
|
assert(boxUpdateInfo !== null && typeof boxUpdateInfo === 'object');
|
||||||
|
|
||||||
progress.set(progress.UPDATE, 5, 'Create app and box backup');
|
function upgradeError(e) {
|
||||||
|
progress.set(progress.UPDATE, -1, e.message);
|
||||||
|
callback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.set(progress.UPDATE, 5, 'Create app and box backup for upgrade');
|
||||||
|
|
||||||
backupBoxAndApps(function (error) {
|
backupBoxAndApps(function (error) {
|
||||||
if (error) return callback(error);
|
if (error) return upgradeError(error);
|
||||||
|
|
||||||
superagent.post(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/upgrade')
|
superagent.post(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/upgrade')
|
||||||
.query({ token: config.token() })
|
.query({ token: config.token() })
|
||||||
.send({ version: boxUpdateInfo.version })
|
.send({ version: boxUpdateInfo.version })
|
||||||
.end(function (error, result) {
|
.end(function (error, result) {
|
||||||
if (error) return callback(new Error('Error making upgrade request: ' + error));
|
if (error) return upgradeError(new Error('Error making upgrade request: ' + error));
|
||||||
if (result.status !== 202) return callback(new Error('Server not ready to upgrade: ' + result.body));
|
if (result.status !== 202) return upgradeError(new Error('Server not ready to upgrade: ' + result.body));
|
||||||
|
|
||||||
progress.set(progress.UPDATE, 10, 'Updating base system');
|
progress.set(progress.UPDATE, 10, 'Updating base system');
|
||||||
|
|
||||||
@@ -461,44 +470,49 @@ function doUpgrade(boxUpdateInfo, callback) {
|
|||||||
function doUpdate(boxUpdateInfo, callback) {
|
function doUpdate(boxUpdateInfo, callback) {
|
||||||
assert(boxUpdateInfo && typeof boxUpdateInfo === 'object');
|
assert(boxUpdateInfo && typeof boxUpdateInfo === 'object');
|
||||||
|
|
||||||
progress.set(progress.UPDATE, 5, 'Create box backup');
|
function updateError(e) {
|
||||||
|
progress.set(progress.UPDATE, -1, e.message);
|
||||||
|
callback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.set(progress.UPDATE, 5, 'Create box backup for update');
|
||||||
|
|
||||||
backupBox(function (error) {
|
backupBox(function (error) {
|
||||||
if (error) return callback(error);
|
if (error) return updateError(error);
|
||||||
|
|
||||||
// fetch a signed sourceTarballUrl
|
// fetch a signed sourceTarballUrl
|
||||||
superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/sourcetarballurl')
|
superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/sourcetarballurl')
|
||||||
.query({ token: config.token(), boxVersion: boxUpdateInfo.version })
|
.query({ token: config.token(), boxVersion: boxUpdateInfo.version })
|
||||||
.end(function (error, result) {
|
.end(function (error, result) {
|
||||||
if (error) return callback(new Error('Error fetching sourceTarballUrl: ' + error));
|
if (error) return updateError(new Error('Error fetching sourceTarballUrl: ' + error));
|
||||||
if (result.status !== 200) return callback(new Error('Error fetching sourceTarballUrl status: ' + result.status));
|
if (result.status !== 200) return updateError(new Error('Error fetching sourceTarballUrl status: ' + result.status));
|
||||||
if (!safe.query(result, 'body.url')) return callback(new Error('Error fetching sourceTarballUrl response: ' + result.body));
|
if (!safe.query(result, 'body.url')) return updateError(new Error('Error fetching sourceTarballUrl response: ' + result.body));
|
||||||
|
|
||||||
// NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic
|
// NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic
|
||||||
var args = {
|
var args = {
|
||||||
sourceTarballUrl: result.body.url,
|
sourceTarballUrl: result.body.url,
|
||||||
|
|
||||||
// this data is opaque to the installer
|
// IMPORTANT: if you change this, fix up argparser.sh as well. keep these sorted for readability
|
||||||
data: {
|
data: {
|
||||||
boxVersionsUrl: config.get('boxVersionsUrl'),
|
|
||||||
version: boxUpdateInfo.version,
|
|
||||||
apiServerOrigin: config.apiServerOrigin(),
|
apiServerOrigin: config.apiServerOrigin(),
|
||||||
webServerOrigin: config.webServerOrigin(),
|
boxVersionsUrl: config.get('boxVersionsUrl'),
|
||||||
fqdn: config.fqdn(),
|
fqdn: config.fqdn(),
|
||||||
token: config.token(),
|
|
||||||
tlsCert: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf8'),
|
|
||||||
tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'),
|
|
||||||
isCustomDomain: config.isCustomDomain(),
|
isCustomDomain: config.isCustomDomain(),
|
||||||
|
restoreKey: null,
|
||||||
restoreUrl: null,
|
restoreUrl: null,
|
||||||
restoreKey: null
|
tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'),
|
||||||
|
tlsCert: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf8'),
|
||||||
|
token: config.token(),
|
||||||
|
version: boxUpdateInfo.version,
|
||||||
|
webServerOrigin: config.webServerOrigin()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
debug('updating box %j', args);
|
debug('updating box %j', args);
|
||||||
|
|
||||||
superagent.post(INSTALLER_UPDATE_URL).send(args).end(function (error, result) {
|
superagent.post(INSTALLER_UPDATE_URL).send(args).end(function (error, result) {
|
||||||
if (error) return callback(error);
|
if (error) return updateError(error);
|
||||||
if (result.status !== 202) return callback(new Error('Error initiating update: ' + result.body));
|
if (result.status !== 202) return updateError(new Error('Error initiating update: ' + result.body));
|
||||||
|
|
||||||
progress.set(progress.UPDATE, 10, 'Updating cloudron software');
|
progress.set(progress.UPDATE, 10, 'Updating cloudron software');
|
||||||
|
|
||||||
@@ -549,7 +563,7 @@ function backupBoxWithAppBackupIds(appBackupIds, callback) {
|
|||||||
|
|
||||||
backups.getBackupUrl(null /* app */, appBackupIds, function (error, result) {
|
backups.getBackupUrl(null /* app */, appBackupIds, function (error, result) {
|
||||||
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, error.message));
|
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, error.message));
|
||||||
if (error) return callback(new CloudronError.INTERNAL_ERROR, error);
|
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
|
||||||
|
|
||||||
debug('backup: url %s', result.url);
|
debug('backup: url %s', result.url);
|
||||||
|
|
||||||
@@ -573,7 +587,7 @@ function backupBox(callback) {
|
|||||||
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
|
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
|
||||||
|
|
||||||
var appBackupIds = allApps.map(function (app) { return app.lastBackupId; });
|
var appBackupIds = allApps.map(function (app) { return app.lastBackupId; });
|
||||||
appBackupIds = appBackupIds.filter(function (id) { return id !== null }); // remove apps that were never backed up
|
appBackupIds = appBackupIds.filter(function (id) { return id !== null; }); // remove apps that were never backed up
|
||||||
|
|
||||||
backupBoxWithAppBackupIds(appBackupIds, callback);
|
backupBoxWithAppBackupIds(appBackupIds, callback);
|
||||||
});
|
});
|
||||||
@@ -595,25 +609,27 @@ function backupBoxAndApps(callback) {
|
|||||||
++processed;
|
++processed;
|
||||||
|
|
||||||
apps.backupApp(app, app.manifest.addons, function (error, backupId) {
|
apps.backupApp(app, app.manifest.addons, function (error, backupId) {
|
||||||
progress.set(progress.BACKUP, step * processed, app.location);
|
progress.set(progress.BACKUP, step * processed, 'Backing up app at ' + app.location);
|
||||||
|
|
||||||
if (error && error.reason === AppsError.BAD_STATE) {
|
if (error && error.reason === AppsError.BAD_STATE) {
|
||||||
debugApp(app, 'Skipping backup (istate:%s health%s). Reusing %s', app.installationState, app.health, app.lastBackupId);
|
debugApp(app, 'Skipping backup (istate:%s health:%s). using lastBackupId:%s', app.installationState, app.health, app.lastBackupId);
|
||||||
backupId = app.lastBackupId;
|
backupId = app.lastBackupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return iteratorCallback(null, backupId);
|
return iteratorCallback(null, backupId);
|
||||||
});
|
});
|
||||||
}, function appsBackedUp(error, backupIds) {
|
}, function appsBackedUp(error, backupIds) {
|
||||||
if (error) return callback(error);
|
if (error) {
|
||||||
|
progress.set(progress.BACKUP, 100, error.message);
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
backupIds = backupIds.filter(function (id) { return id !== null; }); // remove apps that were never backed up
|
backupIds = backupIds.filter(function (id) { return id !== null; }); // remove apps that were never backed up
|
||||||
|
|
||||||
backupBoxWithAppBackupIds(backupIds, function (error, restoreKey) {
|
backupBoxWithAppBackupIds(backupIds, function (error, restoreKey) {
|
||||||
progress.set(progress.BACKUP, 100, '');
|
progress.set(progress.BACKUP, 100, error ? error.message : '');
|
||||||
callback(error, restoreKey);
|
callback(error, restoreKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
LoadPlugin "table"
|
LoadPlugin "table"
|
||||||
<Plugin table>
|
<Plugin table>
|
||||||
<Table "/sys/fs/cgroup/memory/docker/<%= containerId %>/memory.stat">
|
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.stat">
|
||||||
Instance "<%= appId %>-memory"
|
Instance "<%= appId %>-memory"
|
||||||
Separator " \\n"
|
Separator " \\n"
|
||||||
<Result>
|
<Result>
|
||||||
@@ -10,7 +10,7 @@ LoadPlugin "table"
|
|||||||
</Result>
|
</Result>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Table "/sys/fs/cgroup/memory/docker/<%= containerId %>/memory.max_usage_in_bytes">
|
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.max_usage_in_bytes">
|
||||||
Instance "<%= appId %>-memory"
|
Instance "<%= appId %>-memory"
|
||||||
Separator "\\n"
|
Separator "\\n"
|
||||||
<Result>
|
<Result>
|
||||||
@@ -20,7 +20,7 @@ LoadPlugin "table"
|
|||||||
</Result>
|
</Result>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Table "/sys/fs/cgroup/cpuacct/docker/<%= containerId %>/cpuacct.stat">
|
<Table "/sys/fs/cgroup/cpuacct/system.slice/docker-<%= containerId %>.scope/cpuacct.stat">
|
||||||
Instance "<%= appId %>-cpu"
|
Instance "<%= appId %>-cpu"
|
||||||
Separator " \\n"
|
Separator " \\n"
|
||||||
<Result>
|
<Result>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ exports = module.exports = {
|
|||||||
set: set,
|
set: set,
|
||||||
|
|
||||||
// ifdefs to check environment
|
// ifdefs to check environment
|
||||||
CLOUDRON: process.env.NODE_ENV === 'cloudron',
|
CLOUDRON: process.env.BOX_ENV === 'cloudron',
|
||||||
TEST: process.env.NODE_ENV === 'test',
|
TEST: process.env.BOX_ENV === 'test',
|
||||||
|
|
||||||
// convenience getters
|
// convenience getters
|
||||||
apiServerOrigin: apiServerOrigin,
|
apiServerOrigin: apiServerOrigin,
|
||||||
|
|||||||
@@ -98,12 +98,15 @@ function autoupdatePatternChanged(pattern) {
|
|||||||
gAutoupdaterJob = new CronJob({
|
gAutoupdaterJob = new CronJob({
|
||||||
cronTime: pattern,
|
cronTime: pattern,
|
||||||
onTick: function() {
|
onTick: function() {
|
||||||
debug('Starting autoupdate');
|
|
||||||
var updateInfo = updateChecker.getUpdateInfo();
|
var updateInfo = updateChecker.getUpdateInfo();
|
||||||
if (updateInfo.box) {
|
if (updateInfo.box) {
|
||||||
|
debug('Starting autoupdate to %j', updateInfo.box);
|
||||||
cloudron.update(updateInfo.box, NOOP_CALLBACK);
|
cloudron.update(updateInfo.box, NOOP_CALLBACK);
|
||||||
} else if (updateInfo.apps) {
|
} else if (updateInfo.apps) {
|
||||||
|
debug('Starting app update to %j', updateInfo.apps);
|
||||||
apps.autoupdateApps(updateInfo.apps, NOOP_CALLBACK);
|
apps.autoupdateApps(updateInfo.apps, NOOP_CALLBACK);
|
||||||
|
} else {
|
||||||
|
debug('No auto updates available');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
start: true,
|
start: true,
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ exports = module.exports = {
|
|||||||
|
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
setEnabled: setEnabled,
|
setEnabled: setEnabled,
|
||||||
issueDeveloperToken: issueDeveloperToken
|
issueDeveloperToken: issueDeveloperToken,
|
||||||
|
getNonApprovedApps: getNonApprovedApps
|
||||||
};
|
};
|
||||||
|
|
||||||
var assert = require('assert'),
|
var assert = require('assert'),
|
||||||
|
config = require('./config.js'),
|
||||||
tokendb = require('./tokendb.js'),
|
tokendb = require('./tokendb.js'),
|
||||||
settings = require('./settings.js'),
|
settings = require('./settings.js'),
|
||||||
|
superagent = require('superagent'),
|
||||||
util = require('util');
|
util = require('util');
|
||||||
|
|
||||||
function DeveloperError(reason, errorOrMessage) {
|
function DeveloperError(reason, errorOrMessage) {
|
||||||
@@ -68,3 +71,15 @@ function issueDeveloperToken(user, callback) {
|
|||||||
callback(null, { token: token, expiresAt: expiresAt });
|
callback(null, { token: token, expiresAt: expiresAt });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNonApprovedApps(callback) {
|
||||||
|
assert.strictEqual(typeof callback, 'function');
|
||||||
|
|
||||||
|
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/apps';
|
||||||
|
superagent.get(url).query({ token: config.token(), boxVersion: config.version() }).end(function (error, result) {
|
||||||
|
if (error) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, error));
|
||||||
|
if (result.status !== 200) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body)));
|
||||||
|
|
||||||
|
callback(null, result.body.apps || []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ exports = module.exports = (function () {
|
|||||||
var docker;
|
var docker;
|
||||||
var options = connectOptions(); // the real docker
|
var options = connectOptions(); // the real docker
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.BOX_ENV === 'test') {
|
||||||
// test code runs a docker proxy on this port
|
// test code runs a docker proxy on this port
|
||||||
docker = new Docker({ host: 'http://localhost', port: 5687 });
|
docker = new Docker({ host: 'http://localhost', port: 5687 });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ var gLogger = {
|
|||||||
fatal: console.error
|
fatal: console.error
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var GROUP_USERS_DN = 'cn=users,ou=groups,dc=cloudron';
|
||||||
|
var GROUP_ADMINS_DN = 'cn=admin,ou=groups,dc=cloudron';
|
||||||
|
|
||||||
function start(callback) {
|
function start(callback) {
|
||||||
assert(typeof callback === 'function');
|
assert(typeof callback === 'function');
|
||||||
|
|
||||||
@@ -39,15 +42,21 @@ function start(callback) {
|
|||||||
result.forEach(function (entry) {
|
result.forEach(function (entry) {
|
||||||
var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron');
|
var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron');
|
||||||
|
|
||||||
|
var groups = [ GROUP_USERS_DN ];
|
||||||
|
if (entry.admin) groups.push(GROUP_ADMINS_DN);
|
||||||
|
|
||||||
var tmp = {
|
var tmp = {
|
||||||
dn: dn.toString(),
|
dn: dn.toString(),
|
||||||
attributes: {
|
attributes: {
|
||||||
objectclass: ['user'],
|
objectclass: ['user'],
|
||||||
|
objectcategory: 'person',
|
||||||
cn: entry.id,
|
cn: entry.id,
|
||||||
uid: entry.id,
|
uid: entry.id,
|
||||||
mail: entry.email,
|
mail: entry.email,
|
||||||
displayname: entry.username,
|
displayname: entry.username,
|
||||||
username: entry.username
|
username: entry.username,
|
||||||
|
samaccountname: entry.username, // to support ActiveDirectory clients
|
||||||
|
memberof: groups
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,15 +76,24 @@ function start(callback) {
|
|||||||
user.list(function (error, result){
|
user.list(function (error, result){
|
||||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||||
|
|
||||||
// we only have an admin group
|
var groups = [{
|
||||||
var dn = ldap.parseDN('cn=admin,ou=groups,dc=cloudron');
|
name: 'users',
|
||||||
|
admin: false
|
||||||
|
}, {
|
||||||
|
name: 'admins',
|
||||||
|
admin: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
groups.forEach(function (group) {
|
||||||
|
var dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
|
||||||
|
var members = group.admin ? result.filter(function (entry) { return entry.admin; }) : result;
|
||||||
|
|
||||||
var tmp = {
|
var tmp = {
|
||||||
dn: dn.toString(),
|
dn: dn.toString(),
|
||||||
attributes: {
|
attributes: {
|
||||||
objectclass: ['group'],
|
objectclass: ['group'],
|
||||||
cn: 'admin',
|
cn: group.name,
|
||||||
memberuid: result.filter(function (entry) { return entry.admin; }).map(function(entry) { return entry.id; })
|
memberuid: members.map(function(entry) { return entry.id; })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,6 +101,7 @@ function start(callback) {
|
|||||||
res.send(tmp);
|
res.send(tmp);
|
||||||
debug('ldap group send:', tmp);
|
debug('ldap group send:', tmp);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,6 +50,6 @@ Locker.prototype.unlock = function (operation) {
|
|||||||
this.emit('unlocked', operation);
|
this.emit('unlocked', operation);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
exports = module.exports = new Locker();
|
exports = module.exports = new Locker();
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<%if (format === 'text') { %>
|
||||||
|
|
||||||
|
New <%= type %> from <%= fqdn %>.
|
||||||
|
|
||||||
|
Sender: <%= user.email %>
|
||||||
|
Sent at: <%= new Date().toUTCString() %>
|
||||||
|
|
||||||
|
Subject: <%= subject %>
|
||||||
|
-----------------------------------------------------------
|
||||||
|
<%= description %>
|
||||||
|
|
||||||
|
<% } else { %>
|
||||||
|
|
||||||
|
<% } %>
|
||||||
|
|
||||||
@@ -15,7 +15,12 @@ exports = module.exports = {
|
|||||||
|
|
||||||
sendCrashNotification: sendCrashNotification,
|
sendCrashNotification: sendCrashNotification,
|
||||||
|
|
||||||
appDied: appDied
|
appDied: appDied,
|
||||||
|
|
||||||
|
FEEDBACK_TYPE_FEEDBACK: 'feedback',
|
||||||
|
FEEDBACK_TYPE_TICKET: 'ticket',
|
||||||
|
FEEDBACK_TYPE_APP: 'app',
|
||||||
|
sendFeedback: sendFeedback
|
||||||
};
|
};
|
||||||
|
|
||||||
var assert = require('assert'),
|
var assert = require('assert'),
|
||||||
@@ -277,3 +282,21 @@ function sendCrashNotification(program, context) {
|
|||||||
|
|
||||||
enqueue(mailOptions);
|
enqueue(mailOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendFeedback(user, type, subject, description) {
|
||||||
|
assert.strictEqual(typeof user, 'object');
|
||||||
|
assert.strictEqual(typeof type, 'string');
|
||||||
|
assert.strictEqual(typeof subject, 'string');
|
||||||
|
assert.strictEqual(typeof description, 'string');
|
||||||
|
|
||||||
|
assert(type === exports.FEEDBACK_TYPE_TICKET || type === exports.FEEDBACK_TYPE_FEEDBACK || type === exports.FEEDBACK_TYPE_APP);
|
||||||
|
|
||||||
|
var mailOptions = {
|
||||||
|
from: config.get('adminEmail'),
|
||||||
|
to: 'support@cloudron.io',
|
||||||
|
subject: util.format('[%s] %s - %s', type, config.fqdn(), subject),
|
||||||
|
text: render('feedback.ejs', { fqdn: config.fqdn(), type: type, user: user, subject: subject, description: description, format: 'text'})
|
||||||
|
};
|
||||||
|
|
||||||
|
enqueue(mailOptions);
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,4 +25,4 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="oauth">
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
<% include header %>
|
<% 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="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 col-md-offset-3">
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12" style="text-align: center;">
|
||||||
|
<img width="128" height="128" src="<%= applicationLogo %>"/>
|
||||||
|
<h1>Login to <%= applicationName %> on <%= cloudronName %></h1>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<% if (error) { %>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h4 class="has-error"><%= error %></h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
<form id="loginForm" action="" method="post">
|
<form id="loginForm" action="" method="post">
|
||||||
<input type="hidden" name="_csrf" value="<%= csrf %>"/>
|
<input type="hidden" name="_csrf" value="<%= csrf %>"/>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -30,10 +37,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
(function () {
|
(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; }, {});
|
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;
|
document.getElementById('loginForm').action = '/api/v1/session/login?returnTo=' + search.returnTo;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ var progress = {
|
|||||||
backup: null
|
backup: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We use -1 for percentage to indicate errors
|
||||||
function set(tag, percent, message) {
|
function set(tag, percent, message) {
|
||||||
assert(tag === exports.UPDATE || tag === exports.BACKUP);
|
assert(tag === exports.UPDATE || tag === exports.BACKUP);
|
||||||
assert.strictEqual(typeof percent, 'number');
|
assert.strictEqual(typeof percent, 'number');
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ function installApp(req, res, next) {
|
|||||||
if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string'));
|
if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string'));
|
||||||
|
|
||||||
// allow tests to provide an appId for testing
|
// allow tests to provide an appId for testing
|
||||||
var appId = (process.env.NODE_ENV === 'test' && typeof data.appId === 'string') ? data.appId : uuid.v4();
|
var appId = (process.env.BOX_ENV === 'test' && typeof data.appId === 'string') ? data.appId : uuid.v4();
|
||||||
|
|
||||||
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);
|
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);
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ exports = module.exports = {
|
|||||||
getConfig: getConfig,
|
getConfig: getConfig,
|
||||||
update: update,
|
update: update,
|
||||||
migrate: migrate,
|
migrate: migrate,
|
||||||
setCertificate: setCertificate
|
setCertificate: setCertificate,
|
||||||
|
feedback: feedback
|
||||||
};
|
};
|
||||||
|
|
||||||
var assert = require('assert'),
|
var assert = require('assert'),
|
||||||
cloudron = require('../cloudron.js'),
|
cloudron = require('../cloudron.js'),
|
||||||
config = require('../config.js'),
|
config = require('../config.js'),
|
||||||
progress = require('../progress.js'),
|
progress = require('../progress.js'),
|
||||||
|
mailer = require('../mailer.js'),
|
||||||
CloudronError = cloudron.CloudronError,
|
CloudronError = cloudron.CloudronError,
|
||||||
debug = require('debug')('box:routes/cloudron'),
|
debug = require('debug')('box:routes/cloudron'),
|
||||||
HttpError = require('connect-lastmile').HttpError,
|
HttpError = require('connect-lastmile').HttpError,
|
||||||
@@ -157,3 +159,15 @@ function setCertificate(req, res, next) {
|
|||||||
next(new HttpSuccess(202, {}));
|
next(new HttpSuccess(202, {}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 && 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'));
|
||||||
|
|
||||||
|
mailer.sendFeedback(req.user, req.body.type, req.body.subject, req.body.description);
|
||||||
|
|
||||||
|
next(new HttpSuccess(201, {}));
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ exports = module.exports = {
|
|||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
setEnabled: setEnabled,
|
setEnabled: setEnabled,
|
||||||
status: status,
|
status: status,
|
||||||
login: login
|
login: login,
|
||||||
|
apps: apps
|
||||||
};
|
};
|
||||||
|
|
||||||
var developer = require('../developer.js'),
|
var developer = require('../developer.js'),
|
||||||
@@ -46,3 +47,10 @@ function login(req, res, next) {
|
|||||||
});
|
});
|
||||||
})(req, res, next);
|
})(req, res, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function apps(req, res, next) {
|
||||||
|
developer.getNonApprovedApps(function (error, result) {
|
||||||
|
if (error) return next(new HttpError(500, error));
|
||||||
|
next(new HttpSuccess(200, { apps: result }));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ var assert = require('assert'),
|
|||||||
querystring = require('querystring'),
|
querystring = require('querystring'),
|
||||||
util = require('util'),
|
util = require('util'),
|
||||||
session = require('connect-ensure-login'),
|
session = require('connect-ensure-login'),
|
||||||
|
settings = require('../settings.js'),
|
||||||
tokendb = require('../tokendb'),
|
tokendb = require('../tokendb'),
|
||||||
appdb = require('../appdb'),
|
appdb = require('../appdb'),
|
||||||
url = require('url'),
|
url = require('url'),
|
||||||
@@ -188,26 +189,35 @@ function loginForm(req, res) {
|
|||||||
var u = url.parse(req.session.returnTo, true);
|
var u = url.parse(req.session.returnTo, true);
|
||||||
if (!u.query.client_id) return sendErrorPageOrRedirect(req, res, 'Invalid login request. No client_id provided.');
|
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', {
|
res.render('login', {
|
||||||
adminOrigin: config.adminOrigin(),
|
adminOrigin: config.adminOrigin(),
|
||||||
csrf: req.csrfToken(),
|
csrf: req.csrfToken(),
|
||||||
|
cloudronName: cloudronName,
|
||||||
applicationName: applicationName,
|
applicationName: applicationName,
|
||||||
|
applicationLogo: applicationLogo,
|
||||||
error: req.query.error || null
|
error: req.query.error || null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings.getCloudronName(function (error, name) {
|
||||||
|
if (error) return sendError(req, res, 'Internal Error');
|
||||||
|
|
||||||
|
cloudronName = name;
|
||||||
|
|
||||||
clientdb.get(u.query.client_id, function (error, result) {
|
clientdb.get(u.query.client_id, function (error, result) {
|
||||||
if (error) return sendError(req, res, 'Unknown OAuth client');
|
if (error) return sendError(req, res, 'Unknown OAuth client');
|
||||||
|
|
||||||
// Handle our different types of oauth clients
|
// Handle our different types of oauth clients
|
||||||
var appId = result.appId;
|
var appId = result.appId;
|
||||||
if (appId === constants.ADMIN_CLIENT_ID) {
|
if (appId === constants.ADMIN_CLIENT_ID) {
|
||||||
return render(constants.ADMIN_NAME);
|
return render(constants.ADMIN_NAME, '/api/v1/cloudron/avatar');
|
||||||
} else if (appId === constants.TEST_CLIENT_ID) {
|
} else if (appId === constants.TEST_CLIENT_ID) {
|
||||||
return render(constants.TEST_NAME);
|
return render(constants.TEST_NAME, '/api/v1/cloudron/avatar');
|
||||||
} else if (appId.indexOf('external-') === 0) {
|
} else if (appId.indexOf('external-') === 0) {
|
||||||
return render('External Application');
|
return render('External Application', '/api/v1/cloudron/avatar');
|
||||||
} else if (appId.indexOf('addon-') === 0) {
|
} else if (appId.indexOf('addon-') === 0) {
|
||||||
appId = appId.slice('addon-'.length);
|
appId = appId.slice('addon-'.length);
|
||||||
} else if (appId.indexOf('proxy-') === 0) {
|
} else if (appId.indexOf('proxy-') === 0) {
|
||||||
@@ -218,7 +228,8 @@ function loginForm(req, res) {
|
|||||||
if (error) return sendErrorPageOrRedirect(req, res, 'Unknown Application for those OAuth credentials');
|
if (error) return sendErrorPageOrRedirect(req, res, 'Unknown Application for those OAuth credentials');
|
||||||
|
|
||||||
var applicationName = result.location || config.fqdn();
|
var applicationName = result.location || config.fqdn();
|
||||||
render(applicationName);
|
render(applicationName, '/api/v1/cloudron/avatar');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -573,7 +573,8 @@ describe('App installation', function () {
|
|||||||
docker.getContainer(appEntry.containerId).inspect(function (error, data) {
|
docker.getContainer(appEntry.containerId).inspect(function (error, data) {
|
||||||
expect(error).to.not.be.ok();
|
expect(error).to.not.be.ok();
|
||||||
expect(data.Config.ExposedPorts['7777/tcp']).to.eql({ });
|
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');
|
expect(data.Config.Env).to.contain('CLOUDRON=1');
|
||||||
clientdb.getByAppId('addon-' + appResult.id, function (error, client) {
|
clientdb.getByAppId('addon-' + appResult.id, function (error, client) {
|
||||||
expect(error).to.not.be.ok();
|
expect(error).to.not.be.ok();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function setup(done) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
function addApp(callback) {
|
function addApp(callback) {
|
||||||
var manifest = { version: '0.0.1', manifestVersion: 1, dockerImage: 'foo', healthCheckPath: '/', httpPort: 3, title: 'ok' };
|
var manifest = { version: '0.0.1', manifestVersion: 1, dockerImage: 'foo', healthCheckPath: '/', httpPort: 3, title: 'ok', addons: { } };
|
||||||
appdb.add('appid', 'appStoreId', manifest, 'location', [ ] /* portBindings */, '' /* accessRestriction */, callback);
|
appdb.add('appid', 'appStoreId', manifest, 'location', [ ] /* portBindings */, '' /* accessRestriction */, callback);
|
||||||
}
|
}
|
||||||
], done);
|
], done);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ var token = null; // authentication token
|
|||||||
|
|
||||||
var server;
|
var server;
|
||||||
function setup(done) {
|
function setup(done) {
|
||||||
|
nock.cleanAll();
|
||||||
config.set('version', '0.5.0');
|
config.set('version', '0.5.0');
|
||||||
server.start(done);
|
server.start(done);
|
||||||
}
|
}
|
||||||
@@ -501,6 +502,158 @@ describe('Cloudron', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('feedback', function () {
|
||||||
|
before(function (done) {
|
||||||
|
async.series([
|
||||||
|
setup,
|
||||||
|
|
||||||
|
function (callback) {
|
||||||
|
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
|
||||||
|
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
|
||||||
|
|
||||||
|
config._reset();
|
||||||
|
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||||
|
.query({ setupToken: 'somesetuptoken' })
|
||||||
|
.send({ username: USERNAME, password: PASSWORD, email: EMAIL })
|
||||||
|
.end(function (error, result) {
|
||||||
|
expect(error).to.not.be.ok();
|
||||||
|
expect(result).to.be.ok();
|
||||||
|
expect(scope1.isDone()).to.be.ok();
|
||||||
|
expect(scope2.isDone()).to.be.ok();
|
||||||
|
|
||||||
|
// stash token for further use
|
||||||
|
token = result.body.token;
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
], done);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(cleanup);
|
||||||
|
|
||||||
|
it('fails without token', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: 'ticket', subject: 'some subject', description: 'some description' })
|
||||||
|
.end(function (error, result) {
|
||||||
|
expect(error).to.not.be.ok();
|
||||||
|
expect(result.statusCode).to.equal(401);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails without type', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ 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(400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails with empty type', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: '', 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(400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails with unknown type', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: 'foobar', 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(400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds with ticket type', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: 'ticket', 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('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' })
|
||||||
|
.query({ access_token: token })
|
||||||
|
.end(function (error, result) {
|
||||||
|
expect(error).to.not.be.ok();
|
||||||
|
expect(result.statusCode).to.equal(400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails with empty subject', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: 'ticket', subject: '', description: 'some description' })
|
||||||
|
.query({ access_token: token })
|
||||||
|
.end(function (error, result) {
|
||||||
|
expect(error).to.not.be.ok();
|
||||||
|
expect(result.statusCode).to.equal(400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails with empty description', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: 'ticket', subject: 'some subject', description: '' })
|
||||||
|
.query({ access_token: token })
|
||||||
|
.end(function (error, result) {
|
||||||
|
expect(error).to.not.be.ok();
|
||||||
|
expect(result.statusCode).to.equal(400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds with feedback type', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: 'feedback', 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 subject', function (done) {
|
||||||
|
request.post(SERVER_URL + '/api/v1/cloudron/feedback')
|
||||||
|
.send({ type: 'ticket', description: 'some description' })
|
||||||
|
.query({ access_token: token })
|
||||||
|
.end(function (error, result) {
|
||||||
|
expect(error).to.not.be.ok();
|
||||||
|
expect(result.statusCode).to.equal(400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
|
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
|
||||||
|
source ${SOURCE_DIR}/setup/INFRA_VERSION
|
||||||
|
|
||||||
readonly mysqldatadir="/tmp/mysqldata-$(date +%s)"
|
readonly mysqldatadir="/tmp/mysqldata-$(date +%s)"
|
||||||
readonly postgresqldatadir="/tmp/postgresqldata-$(date +%s)"
|
readonly postgresqldatadir="/tmp/postgresqldata-$(date +%s)"
|
||||||
readonly mongodbdatadir="/tmp/mongodbdata-$(date +%s)"
|
readonly mongodbdatadir="/tmp/mongodbdata-$(date +%s)"
|
||||||
@@ -20,7 +24,7 @@ start_postgresql() {
|
|||||||
|
|
||||||
docker rm -f postgresql 2>/dev/null 1>&2 || true
|
docker rm -f postgresql 2>/dev/null 1>&2 || true
|
||||||
|
|
||||||
docker run -dtP --name=postgresql -v "${postgresqldatadir}:/var/lib/postgresql" -v /tmp/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh cloudron/postgresql:0.3.0 >/dev/null
|
docker run -dtP --name=postgresql -v "${postgresqldatadir}:/var/lib/postgresql" -v /tmp/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh "${POSTGRESQL_IMAGE}" >/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
start_mysql() {
|
start_mysql() {
|
||||||
@@ -36,7 +40,7 @@ start_mysql() {
|
|||||||
|
|
||||||
docker rm -f mysql 2>/dev/null 1>&2 || true
|
docker rm -f mysql 2>/dev/null 1>&2 || true
|
||||||
|
|
||||||
docker run -dP --name=mysql -v "${mysqldatadir}:/var/lib/mysql" -v /tmp/mysql_vars.sh:/etc/mysql/mysql_vars.sh cloudron/mysql:0.3.0 >/dev/null
|
docker run -dP --name=mysql -v "${mysqldatadir}:/var/lib/mysql" -v /tmp/mysql_vars.sh:/etc/mysql/mysql_vars.sh "${MYSQL_IMAGE}" >/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
start_mongodb() {
|
start_mongodb() {
|
||||||
@@ -52,7 +56,7 @@ start_mongodb() {
|
|||||||
|
|
||||||
docker rm -f mongodb 2>/dev/null 1>&2 || true
|
docker rm -f mongodb 2>/dev/null 1>&2 || true
|
||||||
|
|
||||||
docker run -dP --name=mongodb -v "${mongodbdatadir}:/var/lib/mongodb" -v /tmp/mongodb_vars.sh:/etc/mongodb_vars.sh cloudron/mongodb:0.3.0 >/dev/null
|
docker run -dP --name=mongodb -v "${mongodbdatadir}:/var/lib/mongodb" -v /tmp/mongodb_vars.sh:/etc/mongodb_vars.sh "${MONGODB_IMAGE}" >/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
start_mysql
|
start_mysql
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ if [[ "$1" == "--check" ]]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${NODE_ENV}" == "cloudron" ]]; then
|
if [[ "${BOX_ENV}" == "cloudron" ]]; then
|
||||||
readonly app_data_dir="${HOME}/data/$1"
|
readonly app_data_dir="${HOME}/data/$1"
|
||||||
btrfs subvolume create "${app_data_dir}"
|
btrfs subvolume create "${app_data_dir}"
|
||||||
mkdir -p "${app_data_dir}/data"
|
mkdir -p "${app_data_dir}/data"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ if [[ $# == 1 && "$1" == "--check" ]]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${NODE_ENV}" == "cloudron" ]]; then
|
if [[ "${BOX_ENV}" == "cloudron" ]]; then
|
||||||
shutdown -r now
|
shutdown -r now
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ if [[ $# == 1 && "$1" == "--check" ]]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${NODE_ENV}" == "cloudron" ]]; then
|
if [[ "${BOX_ENV}" == "cloudron" ]]; then
|
||||||
/etc/init.d/collectd restart
|
/etc/init.d/collectd restart
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ if [[ "${OSTYPE}" == "darwin"* ]]; then
|
|||||||
export PATH=$PATH:/usr/local/bin
|
export PATH=$PATH:/usr/local/bin
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${NODE_ENV}" == "cloudron" ]]; then
|
if [[ "${BOX_ENV}" == "cloudron" ]]; then
|
||||||
nginx -s reload
|
nginx -s reload
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ if [[ "$1" == "--check" ]]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${NODE_ENV}" == "cloudron" ]]; then
|
if [[ "${BOX_ENV}" == "cloudron" ]]; then
|
||||||
readonly app_data_dir="${HOME}/data/$1"
|
readonly app_data_dir="${HOME}/data/$1"
|
||||||
if [[ -d "${app_data_dir}" ]]; then
|
if [[ -d "${app_data_dir}" ]]; then
|
||||||
find "${app_data_dir}" -mindepth 1 -delete
|
find "${app_data_dir}" -mindepth 1 -delete
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function initializeExpressSync() {
|
|||||||
app.set('view options', { layout: true, debug: true });
|
app.set('view options', { layout: true, debug: true });
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.BOX_ENV === 'test') {
|
||||||
app.use(express.static(path.join(__dirname, '/../webadmin')));
|
app.use(express.static(path.join(__dirname, '/../webadmin')));
|
||||||
} else {
|
} else {
|
||||||
app.use(middleware.morgan('dev', { immediate: false }));
|
app.use(middleware.morgan('dev', { immediate: false }));
|
||||||
@@ -92,6 +92,7 @@ function initializeExpressSync() {
|
|||||||
router.post('/api/v1/developer', developerScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.developer.setEnabled);
|
router.post('/api/v1/developer', developerScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.developer.setEnabled);
|
||||||
router.get ('/api/v1/developer', developerScope, routes.developer.enabled, routes.developer.status);
|
router.get ('/api/v1/developer', developerScope, routes.developer.enabled, routes.developer.status);
|
||||||
router.post('/api/v1/developer/login', routes.developer.enabled, routes.developer.login);
|
router.post('/api/v1/developer/login', routes.developer.enabled, routes.developer.login);
|
||||||
|
router.get ('/api/v1/developer/apps', developerScope, routes.developer.enabled, routes.developer.apps);
|
||||||
|
|
||||||
// private routes
|
// private routes
|
||||||
router.get ('/api/v1/cloudron/config', rootScope, routes.cloudron.getConfig);
|
router.get ('/api/v1/cloudron/config', rootScope, routes.cloudron.getConfig);
|
||||||
@@ -101,6 +102,9 @@ function initializeExpressSync() {
|
|||||||
router.post('/api/v1/cloudron/certificate', rootScope, multipart, routes.cloudron.setCertificate);
|
router.post('/api/v1/cloudron/certificate', rootScope, multipart, routes.cloudron.setCertificate);
|
||||||
router.get ('/api/v1/cloudron/graphs', rootScope, routes.graphs.getGraphs);
|
router.get ('/api/v1/cloudron/graphs', rootScope, routes.graphs.getGraphs);
|
||||||
|
|
||||||
|
// feedback
|
||||||
|
router.post('/api/v1/cloudron/feedback', usersScope, routes.cloudron.feedback);
|
||||||
|
|
||||||
router.get ('/api/v1/profile', profileScope, routes.user.profile);
|
router.get ('/api/v1/profile', profileScope, routes.user.profile);
|
||||||
|
|
||||||
router.get ('/api/v1/users', usersScope, routes.user.list);
|
router.get ('/api/v1/users', usersScope, routes.user.list);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function uninitialize(callback) {
|
|||||||
|
|
||||||
function startNextTask() {
|
function startNextTask() {
|
||||||
if (gPendingTasks.length === 0) return;
|
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());
|
startAppTask(gPendingTasks.shift());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,24 @@
|
|||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
source ${SOURCE_DIR}/setup/INFRA_VERSION
|
||||||
|
|
||||||
# reset sudo timestamp to avoid wrong success
|
# reset sudo timestamp to avoid wrong success
|
||||||
sudo -k || sudo --reset-timestamp
|
sudo -k || sudo --reset-timestamp
|
||||||
|
|
||||||
# checks if all scripts are sudo access
|
# checks if all scripts are sudo access
|
||||||
scripts=("${SOURCE_DIR}/scripts/rmappdir.sh" \
|
scripts=("${SOURCE_DIR}/src/scripts/rmappdir.sh" \
|
||||||
"${SOURCE_DIR}/scripts/createappdir.sh" \
|
"${SOURCE_DIR}/src/scripts/createappdir.sh" \
|
||||||
"${SOURCE_DIR}/scripts/reloadnginx.sh" \
|
"${SOURCE_DIR}/src/scripts/reloadnginx.sh" \
|
||||||
"${SOURCE_DIR}/scripts/backupbox.sh" \
|
"${SOURCE_DIR}/src/scripts/backupbox.sh" \
|
||||||
"${SOURCE_DIR}/scripts/backupapp.sh" \
|
"${SOURCE_DIR}/src/scripts/backupapp.sh" \
|
||||||
"${SOURCE_DIR}/scripts/restoreapp.sh" \
|
"${SOURCE_DIR}/src/scripts/restoreapp.sh" \
|
||||||
"${SOURCE_DIR}/scripts/reboot.sh" \
|
"${SOURCE_DIR}/src/scripts/reboot.sh" \
|
||||||
"${SOURCE_DIR}/scripts/backupswap.sh" \
|
"${SOURCE_DIR}/src/scripts/backupswap.sh" \
|
||||||
"${SOURCE_DIR}/scripts/reloadcollectd.sh")
|
"${SOURCE_DIR}/src/scripts/collectlogs.sh" \
|
||||||
|
"${SOURCE_DIR}/src/scripts/reloadcollectd.sh")
|
||||||
|
|
||||||
for script in "${scripts[@]}"; do
|
for script in "${scripts[@]}"; do
|
||||||
if [[ $(sudo -n "${script}" --check 2>/dev/null) != "OK" ]]; then
|
if [[ $(sudo -n "${script}" --check 2>/dev/null) != "OK" ]]; then
|
||||||
@@ -24,7 +27,7 @@ for script in "${scripts[@]}"; do
|
|||||||
echo "${script} does not have sudo access."
|
echo "${script} does not have sudo access."
|
||||||
echo "You have to add the lines below to /etc/sudoers.d/yellowtent."
|
echo "You have to add the lines below to /etc/sudoers.d/yellowtent."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Defaults!${script} env_keep=\"HOME NODE_ENV\""
|
echo "Defaults!${script} env_keep=\"HOME BOX_ENV\""
|
||||||
echo "${USER} ALL=(ALL) NOPASSWD: ${script}"
|
echo "${USER} ALL=(ALL) NOPASSWD: ${script}"
|
||||||
echo ""
|
echo ""
|
||||||
exit 1
|
exit 1
|
||||||
@@ -36,23 +39,23 @@ if ! docker inspect girish/test:0.2.0 >/dev/null 2>/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! docker inspect cloudron/redis:0.3.0 >/dev/null 2>/dev/null; then
|
if ! docker inspect "${REDIS_IMAGE}" >/dev/null 2>/dev/null; then
|
||||||
echo "docker pull cloudron/redis:0.3.0 for tests to run"
|
echo "docker pull ${REDIS_IMAGE} for tests to run"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! docker inspect cloudron/mysql:0.3.0 >/dev/null 2>/dev/null; then
|
if ! docker inspect "${MYSQL_IMAGE}" >/dev/null 2>/dev/null; then
|
||||||
echo "docker pull cloudron/mysql:0.3.0 for tests to run"
|
echo "docker pull ${MYSQL_IMAGE} for tests to run"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! docker inspect cloudron/postgresql:0.3.0 >/dev/null 2>/dev/null; then
|
if ! docker inspect "${POSTGRESQL_IMAGE}" >/dev/null 2>/dev/null; then
|
||||||
echo "docker pull cloudron/postgresql:0.3.0 for tests to run"
|
echo "docker pull ${POSTGRESQL_IMAGE} for tests to run"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! docker inspect cloudron/mongodb:0.3.0 >/dev/null 2>/dev/null; then
|
if ! docker inspect "${MONGODB_IMAGE}" >/dev/null 2>/dev/null; then
|
||||||
echo "docker pull cloudron/mongodb:0.3.0 for tests to run"
|
echo "docker pull ${MONGODB_IMAGE} for tests to run"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
/* jslint node:true */
|
||||||
|
/* global it:false */
|
||||||
|
/* global describe:false */
|
||||||
|
/* global before:false */
|
||||||
|
/* global after:false */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('supererror', { splatchError: true});
|
||||||
|
|
||||||
|
var database = require('../database.js'),
|
||||||
|
expect = require('expect.js'),
|
||||||
|
EventEmitter = require('events').EventEmitter,
|
||||||
|
async = require('async'),
|
||||||
|
user = require('../user.js'),
|
||||||
|
config = require('../config.js'),
|
||||||
|
ldapServer = require('../ldap.js'),
|
||||||
|
ldap = require('ldapjs');
|
||||||
|
|
||||||
|
var USER_0 = {
|
||||||
|
username: 'foobar0',
|
||||||
|
password: 'password0',
|
||||||
|
email: 'foo0@bar.com'
|
||||||
|
};
|
||||||
|
|
||||||
|
var USER_1 = {
|
||||||
|
username: 'foobar1',
|
||||||
|
password: 'password1',
|
||||||
|
email: 'foo1@bar.com'
|
||||||
|
};
|
||||||
|
|
||||||
|
function setup(done) {
|
||||||
|
async.series([
|
||||||
|
database.initialize.bind(null),
|
||||||
|
database._clear.bind(null),
|
||||||
|
ldapServer.start.bind(null),
|
||||||
|
user.create.bind(null, USER_0.username, USER_0.password, USER_0.email, true, null),
|
||||||
|
user.create.bind(null, USER_1.username, USER_1.password, USER_1.email, false, USER_0)
|
||||||
|
], done);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup(done) {
|
||||||
|
database._clear(done);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Ldap', function () {
|
||||||
|
before(setup);
|
||||||
|
after(cleanup);
|
||||||
|
|
||||||
|
describe('bind', function () {
|
||||||
|
it('fails for nonexisting user', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
client.bind('cn=doesnotexist,ou=users,dc=cloudron', 'password', function (error) {
|
||||||
|
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails with wrong password', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
client.bind('cn=' + USER_0.username + ',ou=users,dc=cloudron', 'wrongpassword', function (error) {
|
||||||
|
expect(error).to.be.a(ldap.InvalidCredentialsError);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
client.bind('cn=' + USER_0.username + ',ou=users,dc=cloudron', USER_0.password, function (error) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search users', function () {
|
||||||
|
it ('fails for non existing tree', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
filter: '(&(l=Seattle)(email=*@foo.com))'
|
||||||
|
};
|
||||||
|
|
||||||
|
client.search('o=example', opts, function (error, result) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
expect(result).to.be.an(EventEmitter);
|
||||||
|
|
||||||
|
result.on('error', function (error) {
|
||||||
|
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
result.on('end', function (result) {
|
||||||
|
done(new Error('Should not succeed. Status ' + result.status));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('succeeds with basic filter', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
filter: 'objectcategory=person'
|
||||||
|
};
|
||||||
|
|
||||||
|
client.search('ou=users,dc=cloudron', opts, function (error, result) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
expect(result).to.be.an(EventEmitter);
|
||||||
|
|
||||||
|
var entries = [];
|
||||||
|
|
||||||
|
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||||
|
result.on('error', done);
|
||||||
|
result.on('end', function (result) {
|
||||||
|
expect(result.status).to.equal(0);
|
||||||
|
expect(entries.length).to.equal(2);
|
||||||
|
expect(entries[0].username).to.equal(USER_0.username);
|
||||||
|
expect(entries[1].username).to.equal(USER_1.username);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('succeeds with username wildcard filter', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
filter: '&(objectcategory=person)(username=foobar*)'
|
||||||
|
};
|
||||||
|
|
||||||
|
client.search('ou=users,dc=cloudron', opts, function (error, result) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
expect(result).to.be.an(EventEmitter);
|
||||||
|
|
||||||
|
var entries = [];
|
||||||
|
|
||||||
|
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||||
|
result.on('error', done);
|
||||||
|
result.on('end', function (result) {
|
||||||
|
expect(result.status).to.equal(0);
|
||||||
|
expect(entries.length).to.equal(2);
|
||||||
|
expect(entries[0].username).to.equal(USER_0.username);
|
||||||
|
expect(entries[1].username).to.equal(USER_1.username);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('succeeds with username filter', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
filter: '&(objectcategory=person)(username=' + USER_0.username + ')'
|
||||||
|
};
|
||||||
|
|
||||||
|
client.search('ou=users,dc=cloudron', opts, function (error, result) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
expect(result).to.be.an(EventEmitter);
|
||||||
|
|
||||||
|
var entries = [];
|
||||||
|
|
||||||
|
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||||
|
result.on('error', done);
|
||||||
|
result.on('end', function (result) {
|
||||||
|
expect(result.status).to.equal(0);
|
||||||
|
expect(entries.length).to.equal(1);
|
||||||
|
expect(entries[0].username).to.equal(USER_0.username);
|
||||||
|
expect(entries[0].memberof.length).to.equal(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search groups', function () {
|
||||||
|
it ('succeeds with basic filter', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
filter: 'objectclass=group'
|
||||||
|
};
|
||||||
|
|
||||||
|
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
expect(result).to.be.an(EventEmitter);
|
||||||
|
|
||||||
|
var entries = [];
|
||||||
|
|
||||||
|
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||||
|
result.on('error', done);
|
||||||
|
result.on('end', function (result) {
|
||||||
|
expect(result.status).to.equal(0);
|
||||||
|
expect(entries.length).to.equal(2);
|
||||||
|
expect(entries[0].cn).to.equal('users');
|
||||||
|
expect(entries[0].memberuid.length).to.equal(2);
|
||||||
|
expect(entries[0].memberuid[0]).to.equal(USER_0.username);
|
||||||
|
expect(entries[0].memberuid[1]).to.equal(USER_1.username);
|
||||||
|
expect(entries[1].cn).to.equal('admins');
|
||||||
|
// if only one entry, the array becomes a string :-/
|
||||||
|
expect(entries[1].memberuid).to.equal(USER_0.username);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('succeeds with cn wildcard filter', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
filter: '&(objectclass=group)(cn=*)'
|
||||||
|
};
|
||||||
|
|
||||||
|
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
expect(result).to.be.an(EventEmitter);
|
||||||
|
|
||||||
|
var entries = [];
|
||||||
|
|
||||||
|
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||||
|
result.on('error', done);
|
||||||
|
result.on('end', function (result) {
|
||||||
|
expect(result.status).to.equal(0);
|
||||||
|
expect(entries.length).to.equal(2);
|
||||||
|
expect(entries[0].cn).to.equal('users');
|
||||||
|
expect(entries[0].memberuid.length).to.equal(2);
|
||||||
|
expect(entries[0].memberuid[0]).to.equal(USER_0.username);
|
||||||
|
expect(entries[0].memberuid[1]).to.equal(USER_1.username);
|
||||||
|
expect(entries[1].cn).to.equal('admins');
|
||||||
|
// if only one entry, the array becomes a string :-/
|
||||||
|
expect(entries[1].memberuid).to.equal(USER_0.username);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds with memberuid filter', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
filter: '&(objectclass=group)(memberuid=' + USER_1.username + ')'
|
||||||
|
};
|
||||||
|
|
||||||
|
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
|
||||||
|
expect(error).to.be(null);
|
||||||
|
expect(result).to.be.an(EventEmitter);
|
||||||
|
|
||||||
|
var entries = [];
|
||||||
|
|
||||||
|
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||||
|
result.on('error', done);
|
||||||
|
result.on('end', function (result) {
|
||||||
|
expect(result.status).to.equal(0);
|
||||||
|
expect(entries.length).to.equal(1);
|
||||||
|
expect(entries[0].cn).to.equal('users');
|
||||||
|
expect(entries[0].memberuid.length).to.equal(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -30,20 +30,14 @@
|
|||||||
// create main application module
|
// create main application module
|
||||||
var app = angular.module('Application', []);
|
var app = angular.module('Application', []);
|
||||||
|
|
||||||
// FIXME this does not work with custom domains!
|
|
||||||
function detectApiOrigin() {
|
|
||||||
var host = window.location.host;
|
|
||||||
var tmp = host.split('.')[0];
|
|
||||||
if (tmp.indexOf('-') === -1) return 'https://my-' + host;
|
|
||||||
else return 'https://my' + tmp.slice(tmp.indexOf('-')) + host.slice(tmp.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
|
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
|
||||||
$scope.apiOrigin = detectApiOrigin();
|
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||||
|
|
||||||
$scope.cloudronName = 'Cloudron';
|
$scope.cloudronName = 'Cloudron';
|
||||||
|
$scope.referrer = search.referrer || null;
|
||||||
|
|
||||||
// try to fetch cloudron status
|
// try to fetch cloudron status
|
||||||
$http.get($scope.apiOrigin + '/api/v1/cloudron/status').success(function(data, status) {
|
$http.get('/api/v1/cloudron/status').success(function(data, status) {
|
||||||
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||||
$scope.cloudronName = data.cloudronName;
|
$scope.cloudronName = data.cloudronName;
|
||||||
document.title = $scope.cloudronName + ' App Error';
|
document.title = $scope.cloudronName + ' App Error';
|
||||||
@@ -64,7 +58,7 @@
|
|||||||
<h1> {{cloudronName}} </h1>
|
<h1> {{cloudronName}} </h1>
|
||||||
|
|
||||||
<h3> <i class="fa fa-frown-o fa-fw text-danger"></i> Something has gone wrong </h3>
|
<h3> <i class="fa fa-frown-o fa-fw text-danger"></i> Something has gone wrong </h3>
|
||||||
This app is currently not running. Please retry later.
|
This app is currently not running. <a href="{{ referrer }}">Please retry later</a>.
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<span class="text-muted"><a href="mailto: support@cloudron.io">Contact Support</a> - Copyright © Cloudron 2014-15</span>
|
<span class="text-muted"><a href="mailto: support@cloudron.io">Contact Support</a> - Copyright © Cloudron 2014-15</span>
|
||||||
|
|||||||
@@ -30,22 +30,13 @@
|
|||||||
// create main application module
|
// create main application module
|
||||||
var app = angular.module('Application', []);
|
var app = angular.module('Application', []);
|
||||||
|
|
||||||
// FIXME this does not work with custom domains!
|
|
||||||
function detectApiOrigin() {
|
|
||||||
var host = window.location.host;
|
|
||||||
var tmp = host.split('.')[0];
|
|
||||||
if (tmp.indexOf('-') === -1) return 'https://my-' + host;
|
|
||||||
else return 'https://my' + tmp.slice(tmp.indexOf('-')) + host.slice(tmp.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
|
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
|
||||||
$scope.apiOrigin = detectApiOrigin();
|
|
||||||
$scope.cloudronName = 'Cloudron';
|
$scope.cloudronName = 'Cloudron';
|
||||||
$scope.webServerOriginLink = '/';
|
$scope.webServerOriginLink = '/';
|
||||||
$scope.errorMessage = '';
|
$scope.errorMessage = '';
|
||||||
|
|
||||||
// try to fetch at least config.json to get appstore url
|
// try to fetch at least config.json to get appstore url
|
||||||
$http.get($scope.apiOrigin + '/config.json').success(function(data, status) {
|
$http.get('/config.json').success(function(data, status) {
|
||||||
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||||
$scope.webServerOriginLink = data.webServerOrigin + '/console.html';
|
$scope.webServerOriginLink = data.webServerOrigin + '/console.html';
|
||||||
}).error(function (data, status) {
|
}).error(function (data, status) {
|
||||||
@@ -54,7 +45,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// try to fetch cloudron status
|
// try to fetch cloudron status
|
||||||
$http.get($scope.apiOrigin + '/api/v1/cloudron/status').success(function(data, status) {
|
$http.get('/api/v1/cloudron/status').success(function(data, status) {
|
||||||
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||||
$scope.cloudronName = data.cloudronName;
|
$scope.cloudronName = data.cloudronName;
|
||||||
document.title = $scope.cloudronName + ' Error';
|
document.title = $scope.cloudronName + ' Error';
|
||||||
@@ -76,7 +67,7 @@
|
|||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<img src="/img/logo_inverted_192.png"/>
|
<img src="/api/v1/cloudron/avatar" onerror="this.src = '/img/logo_inverted_192.png'"/>
|
||||||
<h1> {{cloudronName}} </h1>
|
<h1> {{cloudronName}} </h1>
|
||||||
|
|
||||||
<div ng-show="errorCode == 0">
|
<div ng-show="errorCode == 0">
|
||||||
|
|||||||
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<title> Cloudron </title>
|
<title> Cloudron </title>
|
||||||
|
|
||||||
<link href="/img/favicon.png" rel="icon" type="image/png">
|
<link href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||||
|
|
||||||
<!-- Custom Fonts -->
|
<!-- Custom Fonts -->
|
||||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||||
@@ -117,6 +117,7 @@
|
|||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
|
<a class="navbar-brand navbar-brand-icon" href="index.html"><img src="/api/v1/cloudron/avatar" width="40" height="40"/></a>
|
||||||
<a class="navbar-brand" href="index.html">{{config.cloudronName || 'Cloudron'}}</a>
|
<a class="navbar-brand" href="index.html">{{config.cloudronName || 'Cloudron'}}</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.navbar-header -->
|
<!-- /.navbar-header -->
|
||||||
@@ -142,9 +143,10 @@
|
|||||||
<ul class="dropdown-menu" role="menu">
|
<ul class="dropdown-menu" role="menu">
|
||||||
<li><a href="#/account"><i class="fa fa-user fa-fw"></i> Account</a></li>
|
<li><a href="#/account"><i class="fa fa-user fa-fw"></i> Account</a></li>
|
||||||
<li ng-show="user.admin && config.isDev"><a href="#/dns"><i class="fa fa-wrench fa-fw"></i> DNS Management</a></li>
|
<li ng-show="user.admin && config.isDev"><a href="#/dns"><i class="fa fa-wrench fa-fw"></i> DNS Management</a></li>
|
||||||
<li ng-show="user.admin"><a href="#/upgrade"><i class="fa fa-arrow-up fa-fw"></i> Upgrade</a></li>
|
|
||||||
<li ng-show="user.admin"><a href="#/settings"><i class="fa fa-wrench fa-fw"></i> Settings</a></li>
|
|
||||||
<li ng-show="user.admin"><a href="#/graphs"><i class="fa fa-bar-chart fa-fw"></i> Graphs</a></li>
|
<li ng-show="user.admin"><a href="#/graphs"><i class="fa fa-bar-chart fa-fw"></i> Graphs</a></li>
|
||||||
|
<li ng-show="user.admin"><a href="#/upgrade"><i class="fa fa-arrow-up fa-fw"></i> Upgrade</a></li>
|
||||||
|
<li><a href="#/support"><i class="fa fa-comment fa-fw"></i> Support</a></li>
|
||||||
|
<li ng-show="user.admin"><a href="#/settings"><i class="fa fa-wrench fa-fw"></i> Settings</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li><a href="" ng-click="logout($event)"><i class="fa fa-sign-out fa-fw"></i> Logout</a></li>
|
<li><a href="" ng-click="logout($event)"><i class="fa fa-sign-out fa-fw"></i> Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -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) {
|
AppStore.prototype.getManifest = function (appId, callback) {
|
||||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
|||||||
return callback(new ClientError(status, data));
|
return callback(new ClientError(status, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.update) window.location.href = '/update.html';
|
if (result.update && result.update.percent !== -1) window.location.href = '/update.html';
|
||||||
else callback(new ClientError(status, data));
|
else callback(new ClientError(status, data));
|
||||||
}, function (data, status) {
|
}, function (data, status) {
|
||||||
client.error(data);
|
client.error(data);
|
||||||
@@ -324,6 +324,15 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
|||||||
}).error(defaultErrorHandler(callback));
|
}).error(defaultErrorHandler(callback));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Client.prototype.getNonApprovedApps = function (callback) {
|
||||||
|
if (!this._config.developerMode) return callback(null, []);
|
||||||
|
|
||||||
|
$http.get(client.apiOrigin + '/api/v1/developer/apps').success(function (data, status) {
|
||||||
|
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||||
|
callback(null, data.apps || []);
|
||||||
|
}).error(defaultErrorHandler(callback));
|
||||||
|
};
|
||||||
|
|
||||||
Client.prototype.getApp = function (appId, callback) {
|
Client.prototype.getApp = function (appId, callback) {
|
||||||
var appFound = null;
|
var appFound = null;
|
||||||
this._installedApps.some(function (app) {
|
this._installedApps.some(function (app) {
|
||||||
@@ -460,6 +469,19 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
|||||||
}).error(defaultErrorHandler(callback));
|
}).error(defaultErrorHandler(callback));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Client.prototype.feedback = function (type, subject, description, callback) {
|
||||||
|
var data = {
|
||||||
|
type: type,
|
||||||
|
subject: subject,
|
||||||
|
description: description
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(client.apiOrigin + '/api/v1/cloudron/feedback', data).success(function (data, status) {
|
||||||
|
if (status !== 201) return callback(new ClientError(status, data));
|
||||||
|
callback(null);
|
||||||
|
}).error(defaultErrorHandler(callback));
|
||||||
|
};
|
||||||
|
|
||||||
Client.prototype.createUser = function (username, email, callback) {
|
Client.prototype.createUser = function (username, email, callback) {
|
||||||
var data = {
|
var data = {
|
||||||
username: username,
|
username: username,
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ app.config(['$routeProvider', function ($routeProvider) {
|
|||||||
}).when('/settings', {
|
}).when('/settings', {
|
||||||
controller: 'SettingsController',
|
controller: 'SettingsController',
|
||||||
templateUrl: 'views/settings.html'
|
templateUrl: 'views/settings.html'
|
||||||
|
}).when('/support', {
|
||||||
|
controller: 'SupportController',
|
||||||
|
templateUrl: 'views/support.html'
|
||||||
}).when('/upgrade', {
|
}).when('/upgrade', {
|
||||||
controller: 'UpgradeController',
|
controller: 'UpgradeController',
|
||||||
templateUrl: 'views/upgrade.html'
|
templateUrl: 'views/upgrade.html'
|
||||||
|
|||||||
@@ -86,9 +86,6 @@ angular.module('Application').controller('MainController', ['$scope', '$route',
|
|||||||
if (error && error.statusCode === 401) return $scope.login();
|
if (error && error.statusCode === 401) return $scope.login();
|
||||||
if (error) return $scope.error(error);
|
if (error) return $scope.error(error);
|
||||||
|
|
||||||
// check if we are actually updateing
|
|
||||||
if (Client.getConfig().progress.update) window.location.href = '/update.html';
|
|
||||||
|
|
||||||
Client.refreshUserInfo(function (error, result) {
|
Client.refreshUserInfo(function (error, result) {
|
||||||
if (error) return $scope.error(error);
|
if (error) return $scope.error(error);
|
||||||
|
|
||||||
@@ -122,7 +119,8 @@ angular.module('Application').controller('MainController', ['$scope', '$route',
|
|||||||
|
|
||||||
// wait till the view has loaded until showing a modal dialog
|
// wait till the view has loaded until showing a modal dialog
|
||||||
Client.onConfig(function (config) {
|
Client.onConfig(function (config) {
|
||||||
if (config.progress.update) {
|
// check if we are actually updating
|
||||||
|
if (config.progress.update && config.progress.update.percent !== -1) {
|
||||||
window.location.href = '/update.html';
|
window.location.href = '/update.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,8 @@ app.config(['$routeProvider', function ($routeProvider) {
|
|||||||
controller: 'StepController',
|
controller: 'StepController',
|
||||||
templateUrl: 'views/setup/step2.html'
|
templateUrl: 'views/setup/step2.html'
|
||||||
}).when('/step3', {
|
}).when('/step3', {
|
||||||
controller: 'StepController',
|
|
||||||
templateUrl: 'views/setup/step3.html'
|
|
||||||
}).when('/step4', {
|
|
||||||
controller: 'FinishController',
|
controller: 'FinishController',
|
||||||
templateUrl: 'views/setup/step4.html'
|
templateUrl: 'views/setup/step3.html'
|
||||||
}).otherwise({ redirectTo: '/'});
|
}).otherwise({ redirectTo: '/'});
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
@@ -51,27 +48,51 @@ app.service('Wizard', [ function () {
|
|||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudfacegreen.png'
|
url: '/img/avatars/rubber-duck.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudfaceturquoise.png'
|
url: '/img/avatars/carrot.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassesgreen.png'
|
url: '/img/avatars/cup.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassespink.png'
|
url: '/img/avatars/football.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassesturquoise.png'
|
url: '/img/avatars/owl.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassesyellow.png'
|
url: '/img/avatars/space-rocket.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/armchair.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/cap.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/pan.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/meat.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/umbrella.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/jar.png'
|
||||||
}];
|
}];
|
||||||
this.avatar = {};
|
this.avatar = {};
|
||||||
this.avatarBlob = null;
|
this.avatarBlob = null;
|
||||||
@@ -82,8 +103,9 @@ app.service('Wizard', [ function () {
|
|||||||
|
|
||||||
this.avatar = avatar;
|
this.avatar = avatar;
|
||||||
|
|
||||||
// scale image and get the blob now
|
// scale image and get the blob now. do not use the previewAvatar element here because it is not updated yet
|
||||||
var img = document.getElementById('previewAvatar');
|
var img = document.createElement('img');
|
||||||
|
img.src = avatar.data || avatar.url;
|
||||||
var canvas = document.createElement('canvas');
|
var canvas = document.createElement('canvas');
|
||||||
canvas.width = 256;
|
canvas.width = 256;
|
||||||
canvas.height = 256;
|
canvas.height = 256;
|
||||||
@@ -122,7 +144,7 @@ app.service('Wizard', [ function () {
|
|||||||
return instance;
|
return instance;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
app.controller('StepController', ['$scope', '$location', 'Wizard', function ($scope, $location, Wizard) {
|
app.controller('StepController', ['$scope', '$route', '$location', 'Wizard', function ($scope, $route, $location, Wizard) {
|
||||||
$scope.wizard = Wizard;
|
$scope.wizard = Wizard;
|
||||||
|
|
||||||
$scope.next = function (page, bad) {
|
$scope.next = function (page, bad) {
|
||||||
@@ -143,7 +165,7 @@ app.controller('StepController', ['$scope', '$location', 'Wizard', function ($sc
|
|||||||
};
|
};
|
||||||
|
|
||||||
// cheap way to detect if we are in avatar and name selection step
|
// cheap way to detect if we are in avatar and name selection step
|
||||||
if ($('#previewAvatar').get(0) && $('#avatarFileInput').get(0)) {
|
if ($route.current.templateUrl === 'views/setup/step1.html') {
|
||||||
$('#avatarFileInput').get(0).onchange = function (event) {
|
$('#avatarFileInput').get(0).onchange = function (event) {
|
||||||
var fr = new FileReader();
|
var fr = new FileReader();
|
||||||
fr.onload = function () {
|
fr.onload = function () {
|
||||||
@@ -161,8 +183,16 @@ app.controller('StepController', ['$scope', '$location', 'Wizard', function ($sc
|
|||||||
fr.readAsDataURL(event.target.files[0]);
|
fr.readAsDataURL(event.target.files[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.wizard.setPreviewAvatar($scope.wizard.availableAvatars[0]);
|
// ensure image got loaded before setting the preview avatar
|
||||||
|
var image = document.createElement('img');
|
||||||
|
var randomIndex = Math.floor(Math.random() * $scope.wizard.availableAvatars.length);
|
||||||
|
image.onload = function() {
|
||||||
|
$scope.$apply(function () { $scope.wizard.setPreviewAvatar($scope.wizard.availableAvatars[randomIndex]); });
|
||||||
|
image = null;
|
||||||
|
};
|
||||||
|
image.src = $scope.wizard.availableAvatars[randomIndex].data || $scope.wizard.availableAvatars[randomIndex].url;
|
||||||
}
|
}
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
app.controller('FinishController', ['$scope', '$location', '$timeout', 'Wizard', 'Client', function ($scope, $location, $timeout, Wizard, Client) {
|
app.controller('FinishController', ['$scope', '$location', '$timeout', 'Wizard', 'Client', function ($scope, $location, $timeout, Wizard, Client) {
|
||||||
|
|||||||
@@ -4,20 +4,29 @@
|
|||||||
var app = angular.module('Application', []);
|
var app = angular.module('Application', []);
|
||||||
|
|
||||||
app.controller('Controller', ['$scope', '$http', '$interval', function ($scope, $http, $interval) {
|
app.controller('Controller', ['$scope', '$http', '$interval', function ($scope, $http, $interval) {
|
||||||
var apiOrigin = '';
|
$scope.title = 'Update in progress...';
|
||||||
|
$scope.percent = 0;
|
||||||
|
$scope.message = '';
|
||||||
|
$scope.error = false;
|
||||||
|
|
||||||
function loadWebadmin() {
|
$scope.loadWebadmin = function () {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
}
|
};
|
||||||
|
|
||||||
function fetchProgress() {
|
function fetchProgress() {
|
||||||
$http.get(apiOrigin + '/api/v1/cloudron/progress').success(function(data, status) {
|
$http.get('/api/v1/cloudron/progress').success(function(data, status) {
|
||||||
if (status === 404) return; // just wait until we create the progress.json on the server side
|
if (status === 404) return; // just wait until we create the progress.json on the server side
|
||||||
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||||
if (data.update === null) return loadWebadmin();
|
if (data.update === null) return $scope.loadWebadmin();
|
||||||
|
|
||||||
$('#updateProgressBar').css('width', data.update.percent + '%');
|
if (data.update.percent === -1) {
|
||||||
$('#updateProgressMessage').html(data.update.message);
|
$scope.title = 'Update Error';
|
||||||
|
$scope.error = true;
|
||||||
|
$scope.message = data.update.message;
|
||||||
|
} else {
|
||||||
|
$scope.percent = data.update.percent;
|
||||||
|
$scope.message = data.update.message;
|
||||||
|
}
|
||||||
}).error(function (data, status) {
|
}).error(function (data, status) {
|
||||||
console.error(status, data);
|
console.error(status, data);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
|
|
||||||
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
|
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
|
||||||
$scope.apiOrigin = detectApiOrigin();
|
$scope.apiOrigin = detectApiOrigin();
|
||||||
|
$scope.cloudronAvatar = $scope.apiOrigin + '/api/v1/cloudron/avatar';
|
||||||
$scope.cloudronName = 'Cloudron';
|
$scope.cloudronName = 'Cloudron';
|
||||||
|
|
||||||
$http.get($scope.apiOrigin + '/api/v1/cloudron/status').success(function(data, status) {
|
$http.get($scope.apiOrigin + '/api/v1/cloudron/status').success(function(data, status) {
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<img src="/img/logo_inverted_192.png"/>
|
<img ng-src="{{ cloudronAvatar || '/img/logo_inverted_192.png' }}" onerror="this.src = '/img/logo_inverted_192.png'"/>
|
||||||
<h1> {{cloudronName}} </h1>
|
<h1> {{cloudronName}} </h1>
|
||||||
<p>
|
<p>
|
||||||
There is no app configured for this domain. If you want to put an app at this location,<br/>
|
There is no app configured for this domain. If you want to put an app at this location,<br/>
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
transition: background-color 500ms;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight:hover {
|
.highlight:hover {
|
||||||
@@ -104,6 +103,16 @@ html {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-brand-icon {
|
||||||
|
padding: 5px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav > li > a {
|
||||||
|
@media(min-width:768px) {
|
||||||
|
padding: 13px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
// Apps view
|
// Apps view
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
@@ -111,6 +120,14 @@ html {
|
|||||||
.grid-item {
|
.grid-item {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item:hover .grid-item-bottom {
|
||||||
|
@media(min-width:768px) {
|
||||||
|
opacity: 1;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-item-content {
|
.grid-item-content {
|
||||||
@@ -123,10 +140,39 @@ html {
|
|||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-item-bottom {
|
.grid-item-top-title {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item-bottom-mobile {
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-top: 1px solid #ddd;
|
border-top: 1px solid #ddd;
|
||||||
background-color: white
|
background-color: white;
|
||||||
|
|
||||||
|
@media(min-width:768px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item-bottom {
|
||||||
|
display: none;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
@media(min-width:768px) {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: -10px;
|
||||||
|
opacity: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
transition: all 250ms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
@@ -147,6 +193,18 @@ html {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appstore-item-badge-testing {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appstore-item-content-testing {
|
||||||
|
background-color: #E6E6E6;
|
||||||
|
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||||
|
background-size: 64px 64px;
|
||||||
|
}
|
||||||
|
|
||||||
.appstore-item-content-title {
|
.appstore-item-content-title {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -174,15 +232,53 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.appstore-category-link {
|
.appstore-category-link {
|
||||||
|
display: block;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
color: black;
|
color: black;
|
||||||
color: inherit;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
|
||||||
display: block;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&.category-active {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.category-active {
|
||||||
|
background-color: $navbar-default-link-hover-color;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.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 {
|
.appstore-install-description {
|
||||||
@@ -195,12 +291,14 @@ html {
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appstore-category-link:hover,
|
.appstore-install-title {
|
||||||
.appstore-category-link:focus,
|
display: inline-block;
|
||||||
.appstore-category-link.category-active {
|
margin: 5px 10px;
|
||||||
text-decoration: none;
|
}
|
||||||
background-color: $navbar-default-link-hover-color;
|
|
||||||
color: white;
|
.appstore-install-meta {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: $text-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appstore-item-rating {
|
.appstore-item-rating {
|
||||||
@@ -232,6 +330,10 @@ html {
|
|||||||
background-color: #5CB85C;
|
background-color: #5CB85C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
background-color: $brand-danger;
|
||||||
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -269,6 +371,10 @@ html {
|
|||||||
color: #5CB85C;
|
color: #5CB85C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.text-large {
|
.text-large {
|
||||||
font-size: $font-size-h1;
|
font-size: $font-size-h1;
|
||||||
}
|
}
|
||||||
@@ -294,12 +400,9 @@ html {
|
|||||||
.grid-item-top .progress {
|
.grid-item-top .progress {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadown: none;
|
box-shadown: none;
|
||||||
margin-left: -15px;
|
margin-top: 10px;
|
||||||
margin-right: -15px;
|
|
||||||
margin-bottom: -11px;
|
|
||||||
margin-top: 9px;
|
|
||||||
width: inherit;
|
width: inherit;
|
||||||
height: 2px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-item-top .progress-bar {
|
.grid-item-top .progress-bar {
|
||||||
@@ -308,12 +411,16 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-icon {
|
.app-icon {
|
||||||
min-height: 64px;
|
min-height: 80px;
|
||||||
max-height: 64px;
|
max-height: 80px;
|
||||||
min-width: 64px;
|
min-width: 80px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appstore-install .app-icon {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
// Animations
|
// Animations
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
@@ -597,6 +704,33 @@ footer a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Oauth classes
|
||||||
|
// ----------------------------
|
||||||
|
|
||||||
|
.oauth {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
background: #F7F7F7;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 33px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
max-width: none;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
@media(min-width:768px) {
|
||||||
|
margin-top: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
// Graphs classes
|
// Graphs classes
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
@@ -675,10 +809,6 @@ $graphs-success-alt: lighten(#27CE65, 20%);
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -744,3 +874,17 @@ $graphs-success-alt: lighten(#27CE65, 20%);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Support
|
||||||
|
// ----------------------------
|
||||||
|
|
||||||
|
.support {
|
||||||
|
|
||||||
|
max-width: 600px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,13 +33,19 @@
|
|||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title" id="updateProgressModalLabel">Update in progress...</h4>
|
<h4 class="modal-title">{{title}}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body" ng-show="!error">
|
||||||
<div class="progress progress-striped active">
|
<div class="progress progress-striped active">
|
||||||
<div class="progress-bar progress-bar-success" role="progressbar" id="updateProgressBar" style="width: 0%"></div>
|
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{percent}}%"></div>
|
||||||
</div>
|
</div>
|
||||||
<span id="updateProgressMessage"></span>
|
<span>{{message}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" ng-show="error">
|
||||||
|
<span>{{message}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" ng-show="error">
|
||||||
|
<button type="button" class="btn btn-primary" ng-click="loadWebadmin()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,6 +89,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
<div style="max-width: 600px; margin: 0 auto;">
|
<div style="max-width: 600px; margin: 0 auto;">
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<h1>Account</h1>
|
<h1>Account</h1>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<option value="roleUser">Visible only to Cloudron users</option>
|
<option value="roleUser">Visible only to Cloudron users</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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/>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="form-group" ng-class="{ 'has-error': (appConfigureForm.password.$dirty && appConfigureForm.password.$invalid) || (!appConfigureForm.password.$dirty && appConfigure.error.password) }">
|
<div class="form-group" ng-class="{ 'has-error': (appConfigureForm.password.$dirty && appConfigureForm.password.$invalid) || (!appConfigureForm.password.$dirty && appConfigure.error.password) }">
|
||||||
@@ -182,6 +183,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
<div class="row animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
|
<div class="row animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h1>Installed Applications</h1>
|
<h1>Installed Applications</h1>
|
||||||
@@ -200,11 +204,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 text-left">
|
<div class="col-xs-12 text-center">
|
||||||
<div style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden">{{ app.location || app.fqdn }}</div>
|
<div class="grid-item-top-title">{{ app.location || app.fqdn }}</div>
|
||||||
<div class="text-muted" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden">
|
<div class="text-muted" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden">
|
||||||
{{ app | installationStateLabel }}
|
{{ app | installationStateLabel }}
|
||||||
</div>
|
</div>
|
||||||
|
<br ng-hide="app | installationActive"/>
|
||||||
<div ng-show="app | installationActive">
|
<div ng-show="app | installationActive">
|
||||||
<div class="progress progress-striped active">
|
<div class="progress progress-striped active">
|
||||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
|
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
|
||||||
@@ -213,8 +218,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
<div class="grid-item-bottom-mobile" ng-show="user.admin">
|
||||||
<div class="grid-item-bottom" ng-show="user.admin">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-4 text-left">
|
<div class="col-xs-4 text-left">
|
||||||
<a href="" ng-click="showRestore(app)" ng-show="(app | installError) === true">
|
<a href="" ng-click="showRestore(app)" ng-show="(app | installError) === true">
|
||||||
@@ -238,6 +242,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid-item-bottom" ng-show="user.admin">
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="" ng-click="showUninstall(app)"><i class="fa fa-remove scale"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="(app | installError) === true">
|
||||||
|
<a href="" ng-click="showRestore(app)"><i class="fa fa-undo scale"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="(app | installSuccess) == true">
|
||||||
|
<a href="" ng-click="showConfigure(app)"><i class="fa fa-wrench scale"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- we check the version here because the box updater does not know when an app gets updated -->
|
||||||
|
<div ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
|
||||||
|
<a href="" ng-click="showUpdate(app)"><i class="fa fa-arrow-up text-success scale"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
|||||||
AppStore.getManifest(app.appStoreId, function (error, manifest) {
|
AppStore.getManifest(app.appStoreId, function (error, manifest) {
|
||||||
if (error) return console.error(error);
|
if (error) return console.error(error);
|
||||||
|
|
||||||
$scope.appUpdate.manifest = manifest;
|
$scope.appUpdate.manifest = angular.copy(manifest);
|
||||||
|
|
||||||
// ensure we always operate on objects here
|
// ensure we always operate on objects here
|
||||||
app.portBindings = app.portBindings || {};
|
app.portBindings = app.portBindings || {};
|
||||||
@@ -334,7 +334,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
|||||||
};
|
};
|
||||||
|
|
||||||
// setup all the dialog focus handling
|
// setup all the dialog focus handling
|
||||||
['appConfigureModal', 'appUninstallModal', 'appUpdateModal'].forEach(function (id) {
|
['appConfigureModal', 'appUninstallModal', 'appUpdateModal', 'appRestoreModal'].forEach(function (id) {
|
||||||
$('#' + id).on('shown.bs.modal', function () {
|
$('#' + id).on('shown.bs.modal', function () {
|
||||||
$(this).find("[autofocus]:first").focus();
|
$(this).find("[autofocus]:first").focus();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
|
|
||||||
<!-- Modal install app -->
|
<!-- Modal install app -->
|
||||||
<div class="modal fade" id="appInstallModal" tabindex="-1" role="dialog" aria-labelledby="updateModalLabel" aria-hidden="true">
|
<div class="modal fade appstore-install" id="appInstallModal" tabindex="-1" role="dialog" aria-labelledby="updateModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3 class="modal-title" id="appInstallModalLabel">
|
|
||||||
<img ng-src="{{appInstall.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-icon"/>
|
<img ng-src="{{appInstall.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-icon"/>
|
||||||
{{ appInstall.app.manifest.title }}
|
<h3 class="appstore-install-title">{{ appInstall.app.manifest.title }} <span class="badge badge-danger" ng-show="appInstall.app.publishState === 'testing'">Testing</span></h3>
|
||||||
</h3>
|
<br/>
|
||||||
|
<span class="appstore-install-meta">{{ appInstall.app.manifest.author }}</span>
|
||||||
|
<br/>
|
||||||
|
<span class="appstore-install-meta">{{ appInstall.app.manifest.version }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="collapse" id="collapseInstallForm" data-toggle="false">
|
<div class="collapse" id="collapseInstallForm" data-toggle="false">
|
||||||
@@ -62,6 +64,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<div class="row-no-margin">
|
<div class="row-no-margin">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
@@ -94,11 +137,18 @@
|
|||||||
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'sync' }" category="sync">Media Sync</a>
|
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'sync' }" category="sync">Media Sync</a>
|
||||||
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'git' }" category="git">Code Hosting</a>
|
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'git' }" category="git">Code Hosting</a>
|
||||||
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'wiki' }" category="wiki">Wiki</a>
|
<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>
|
||||||
<div class="col-md-10" ng-show="ready && apps.length">
|
<div class="col-md-10" ng-show="ready && apps.length">
|
||||||
<div class="row-no-margin">
|
<div class="row-no-margin">
|
||||||
<div class="col-sm-1 appstore-item" ng-repeat="app in apps">
|
<div class="col-sm-1 appstore-item" ng-repeat="app in apps">
|
||||||
<div class="appstore-item-content highlight" ng-click="showInstall(app)">
|
<div class="appstore-item-content highlight" ng-click="showInstall(app)" ng-class="{ 'appstore-item-content-testing': app.publishState === 'testing' }">
|
||||||
|
<span class="badge badge-danger appstore-item-badge-testing" ng-show="app.publishState === 'testing'">Testing</span>
|
||||||
<div class="appstore-item-content-icon col-same-height">
|
<div class="appstore-item-content-icon col-same-height">
|
||||||
<img ng-src="{{app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-icon"/>
|
<img ng-src="{{app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-icon"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,7 +162,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-10 animateMeOpacity loading-banner" ng-show="ready && !apps.length">
|
<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>
|
||||||
<div class="col-md-10 animateMeOpacity loading-banner" ng-show="!ready">
|
<div class="col-md-10 animateMeOpacity loading-banner" ng-show="!ready">
|
||||||
<h2><i class="fa fa-spinner fa-pulse"></i> Loading</h2>
|
<h2><i class="fa fa-spinner fa-pulse"></i> Loading</h2>
|
||||||
|
|||||||
@@ -20,6 +20,72 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
|||||||
mediaLinks: []
|
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) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
|
||||||
|
// ensure we have a tags property for further use
|
||||||
|
apps.forEach(function (app) {
|
||||||
|
if (!app.manifest.tags) app.manifest.tags = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.getNonApprovedApps(function (error, result) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
|
||||||
|
// add testing tag to the manifest for UI and search reasons
|
||||||
|
result.forEach(function (app) {
|
||||||
|
if (!app.manifest.tags) app.manifest.tags = [];
|
||||||
|
app.manifest.tags.push('testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
callback(null, apps.concat(result));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO does not support testing apps in search
|
||||||
$scope.search = function () {
|
$scope.search = function () {
|
||||||
if (!$scope.searchString) return $scope.showCategory(null, $scope.cachedCategory);
|
if (!$scope.searchString) return $scope.showCategory(null, $scope.cachedCategory);
|
||||||
|
|
||||||
@@ -49,7 +115,7 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
|||||||
|
|
||||||
$scope.ready = false;
|
$scope.ready = false;
|
||||||
|
|
||||||
AppStore.getApps(function (error, apps) {
|
getAppList(function (error, apps) {
|
||||||
if (error) return $timeout($scope.showCategory.bind(null, event), 1000);
|
if (error) return $timeout($scope.showCategory.bind(null, event), 1000);
|
||||||
|
|
||||||
if (!$scope.category) {
|
if (!$scope.category) {
|
||||||
@@ -105,8 +171,13 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
|||||||
$scope.appInstall.portBindings[env] = $scope.appInstall.app.manifest.tcpPorts[env].defaultValue || 0;
|
$scope.appInstall.portBindings[env] = $scope.appInstall.app.manifest.tcpPorts[env].defaultValue || 0;
|
||||||
$scope.appInstall.portBindingsEnabled[env] = true;
|
$scope.appInstall.portBindingsEnabled[env] = true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showAppNotFound = function (appId, version) {
|
||||||
|
$scope.appNotFound.appId = appId;
|
||||||
|
$scope.appNotFound.version = version;
|
||||||
|
|
||||||
|
$('#appNotFoundModal').modal('show');
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.doInstall = function () {
|
$scope.doInstall = function () {
|
||||||
@@ -156,7 +227,7 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
|||||||
function refresh() {
|
function refresh() {
|
||||||
$scope.ready = false;
|
$scope.ready = false;
|
||||||
|
|
||||||
AppStore.getApps(function (error, apps) {
|
getAppList(function (error, apps) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return $timeout(refresh, 1000);
|
return $timeout(refresh, 1000);
|
||||||
@@ -166,8 +237,27 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
|||||||
|
|
||||||
// show install app dialog immediately if an app id was passed in the query
|
// show install app dialog immediately if an app id was passed in the query
|
||||||
if ($routeParams.appId) {
|
if ($routeParams.appId) {
|
||||||
var found = apps.filter(function (app) { return (app.id === $routeParams.appId); });
|
if ($routeParams.version) {
|
||||||
if (found.length) $scope.showInstall(found[0]);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.ready = true;
|
$scope.ready = true;
|
||||||
@@ -177,7 +267,7 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
|||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
// setup all the dialog focus handling
|
// setup all the dialog focus handling
|
||||||
['appInstallModal'].forEach(function (id) {
|
['appInstallModal', 'feedbackModal'].forEach(function (id) {
|
||||||
$('#' + id).on('shown.bs.modal', function () {
|
$('#' + id).on('shown.bs.modal', function () {
|
||||||
$(this).find("[autofocus]:first").focus();
|
$(this).find("[autofocus]:first").focus();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h1>Graphs</h1>
|
<h1>Graphs</h1>
|
||||||
|
|||||||
@@ -107,6 +107,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div style="max-width: 600px; margin: 0 auto;">
|
||||||
|
<div class="text-left">
|
||||||
|
<h1>Settings</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin">
|
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin">
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<h3>Backups</h3>
|
<h3>Backups</h3>
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.createBackup = {
|
$scope.createBackup = {
|
||||||
busy: false
|
busy: false,
|
||||||
|
percent: 100
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.nameChange = {
|
$scope.nameChange = {
|
||||||
@@ -40,27 +41,51 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
|||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudfacegreen.png'
|
url: '/img/avatars/rubber-duck.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudfaceturquoise.png'
|
url: '/img/avatars/carrot.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassesgreen.png'
|
url: '/img/avatars/cup.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassespink.png'
|
url: '/img/avatars/football.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassesturquoise.png'
|
url: '/img/avatars/owl.png'
|
||||||
}, {
|
}, {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
url: '/img/avatars/cloudglassesyellow.png'
|
url: '/img/avatars/space-rocket.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/armchair.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/cap.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/pan.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/meat.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/umbrella.png'
|
||||||
|
}, {
|
||||||
|
file: null,
|
||||||
|
data: null,
|
||||||
|
url: '/img/avatars/jar.png'
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -192,9 +217,8 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
|||||||
if (error) {
|
if (error) {
|
||||||
console.error('Unable to change developer mode.', error);
|
console.error('Unable to change developer mode.', error);
|
||||||
} else {
|
} else {
|
||||||
$scope.avatar = $scope.avatarChange.avatar;
|
// Do soft reload, since the browser will not update the avatar URLs in the UI
|
||||||
avatarChangeReset();
|
window.location.reload();
|
||||||
$('#avatarChangeModal').modal('hide');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.avatarChange.busy = false;
|
$scope.avatarChange.busy = false;
|
||||||
@@ -219,6 +243,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
|||||||
|
|
||||||
// check if we are done
|
// check if we are done
|
||||||
if (!data.backup || data.backup.percent >= 100) {
|
if (!data.backup || data.backup.percent >= 100) {
|
||||||
|
if (data.backup && data.backup.message) console.error('Backup message: ' + data.backup.message); // backup error message
|
||||||
fetchBackups();
|
fetchBackups();
|
||||||
$scope.createBackup.busy = false;
|
$scope.createBackup.busy = false;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,8 +1,53 @@
|
|||||||
<center>
|
<div class="row">
|
||||||
<h1>Welcome to your Cloudron</h1>
|
<div class="col-md-12 text-center">
|
||||||
|
<h1>Welcome to your Cloudron!</h1>
|
||||||
|
<hr/>
|
||||||
|
<h3 class="">
|
||||||
|
Choose a name and avatar for your Cloudron
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<h3 class="">This is your <b>{{ wizard.hostname }}</b> and all your apps will be installed under this domain.</h3>
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 col-md-offset-4 text-center">
|
||||||
|
<img id="previewAvatar" width="98" height="98" ng-src="{{wizard.avatar.data || wizard.avatar.url}}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<a class="btn btn-primary" href="#/step2" autofocus>Forward</a>
|
|
||||||
</center>
|
<div class="row">
|
||||||
|
<div class="col-md-4 col-md-offset-4 text-center">
|
||||||
|
<div class="form-group" ng-class="{ 'has-error': setup_form.name.$dirty && setup_form.name.$invalid }">
|
||||||
|
<!-- <label class="control-label" for="inputName">Name</label> -->
|
||||||
|
<input type="text" class="form-control" ng-model="wizard.name" id="inputName" name="name" placeholder="Name" ng-enter="next('/step2', setup_form.name.$invalid)" ng-maxlength="512" ng-minlength="1" autofocus required autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 settings-avatar-selector">
|
||||||
|
<input type="file" id="avatarFileInput" style="display: none" accept="image/png"/>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="item" ng-repeat="avatar in wizard.availableAvatars" style="background-image: url('{{avatar.data || avatar.url}}');" ng-click="wizard.setPreviewAvatar(avatar)"></div>
|
||||||
|
<div class="item add" ng-click="showCustomAvatarSelector()"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 text-center">
|
||||||
|
<a class="btn btn-primary" href="#/step2" ng-disabled="setup_form.name.$invalid">Next</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,43 +1,27 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 text-center">
|
<div class="col-md-12 text-center">
|
||||||
<h1>Personalize your Cloudron</h1>
|
<h1>Create an Administrator for <b>{{ wizard.name }}</b></h1>
|
||||||
<h4 class="">
|
<h4 class="">
|
||||||
Make it truly yours, by giving your Cloudron an avatar and name.
|
This admin account is separate from your <a href="https://cloudron.io">cloudron.io</a> account.
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 settings-avatar-selector">
|
|
||||||
<img id="previewAvatar" width="128" height="128" ng-src="{{wizard.avatar.data || wizard.avatar.url}}"/>
|
|
||||||
<input type="file" id="avatarFileInput" style="display: none" accept="image/png"/>
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<div class="item" ng-repeat="avatar in wizard.availableAvatars" style="background-image: url('{{avatar.data || avatar.url}}');" ng-click="wizard.setPreviewAvatar(avatar)"></div>
|
|
||||||
<div class="item add" ng-click="showCustomAvatarSelector()"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 col-md-offset-4 text-center">
|
<div class="col-md-4 col-md-offset-4 text-center">
|
||||||
<div class="form-group" ng-class="{ 'has-error': setup_form.name.$dirty && setup_form.name.$invalid }">
|
<div class="form-group" ng-class="{ 'has-error': setup_form.username.$dirty && setup_form.username.$invalid }">
|
||||||
<!-- <label class="control-label" for="inputName">Name</label> -->
|
<!-- <label class="control-label" for="inputUsername">Username</label> -->
|
||||||
<input type="text" class="form-control" ng-model="wizard.name" id="inputName" name="name" placeholder="Name" ng-enter="next('/step3', setup_form.name.$invalid)" ng-maxlength="512" ng-minlength="1" autofocus required autocomplete="off">
|
<input type="text" class="form-control" ng-model="wizard.username" id="inputUsername" name="username" placeholder="Username" ng-enter="focusNext('inputPassword', setup_form.username.$invalid)" ng-maxlength="512" ng-minlength="3" autofocus required autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-class="{ 'has-error': setup_form.password.$dirty && setup_form.password.$invalid }">
|
||||||
|
<!-- <label class="control-label" for="inputPassword">Password</label> -->
|
||||||
|
<input type="password" class="form-control" ng-model="wizard.password" id="inputPassword" name="password" placeholder="Password" ng-enter="next('/step3', setup_form.password.$invalid)" ng-maxlength="512" ng-minlength="5" required autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 text-center">
|
<div class="col-md-12 text-center">
|
||||||
<a class="btn btn-primary" href="#/step3" ng-disabled="setup_form.name.$invalid">Next</a>
|
<a class="btn btn-primary" href="#/step3" ng-disabled="setup_form.username.$invalid">Done</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,8 @@
|
|||||||
<div class="row">
|
<center>
|
||||||
<div class="col-md-12 text-center">
|
<h1>All done!</h1>
|
||||||
<h1>Create an Administrator for <b>{{ wizard.name }}</b>, your Cloudron</h1>
|
|
||||||
<h4 class="">
|
|
||||||
You can create more users once we are done here.
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br/>
|
<br/>
|
||||||
<div class="row">
|
<br/>
|
||||||
<div class="col-md-4 col-md-offset-4 text-center">
|
<i class="fa fa-spinner fa-pulse fa-5x"></i>
|
||||||
<div class="form-group" ng-class="{ 'has-error': setup_form.username.$dirty && setup_form.username.$invalid }">
|
<br/>
|
||||||
<!-- <label class="control-label" for="inputUsername">Username</label> -->
|
<br/>
|
||||||
<input type="text" class="form-control" ng-model="wizard.username" id="inputUsername" name="username" placeholder="Username" ng-enter="focusNext('inputPassword', setup_form.username.$invalid)" ng-maxlength="512" ng-minlength="3" autofocus required autocomplete="off">
|
</center>
|
||||||
</div>
|
|
||||||
<div class="form-group" ng-class="{ 'has-error': setup_form.password.$dirty && setup_form.password.$invalid }">
|
|
||||||
<!-- <label class="control-label" for="inputPassword">Password</label> -->
|
|
||||||
<input type="password" class="form-control" ng-model="wizard.password" id="inputPassword" name="password" placeholder="Password" ng-enter="next('/step4', setup_form.password.$invalid)" ng-maxlength="512" ng-minlength="5" required autocomplete="off">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 text-center">
|
|
||||||
<a class="btn btn-primary" href="#/step4" ng-disabled="setup_form.username.$invalid">Done</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<center>
|
|
||||||
<h1>All done!</h1>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<i class="fa fa-spinner fa-pulse fa-5x"></i>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</center>
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<div class="content support">
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="text-left">
|
||||||
|
<h1>Support</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-large">
|
||||||
|
<div class="grid-item-top">
|
||||||
|
<div class="row animateMeOpacity">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
If you have any questions, please revise our <a href="{{ config.webServerOrigin }}/faq.html" target="_blank">Frequently Asked Questions</a>. We add more answers as we go, if you couldn't find your answers, just let us know using the forms below.<br/>
|
||||||
|
<br/>
|
||||||
|
For any developer related issues, please see our <a href="{{ config.webServerOrigin }}/documentation.html" target="_blank">Developer documentation</a>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card card-large">
|
||||||
|
<div class="grid-item-top">
|
||||||
|
<div class="row animateMeOpacity">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h3>Feedback</h3>
|
||||||
|
We would love to hear any ideas or feature requests from your side.<br/>
|
||||||
|
If you found any issue or bugs, let us know as well, we will resolve that for you as soon as possible.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<form name="feedbackForm" ng-submit="submitFeedback()">
|
||||||
|
<div class="form-group">
|
||||||
|
<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) }">
|
||||||
|
<input type="text" class="form-control" name="subject" placeholder="Enter your idea or issue" ng-model="feedback.subject" ng-maxlength="512" ng-minlength="1" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.description.$dirty && feedbackForm.description.$invalid) }">
|
||||||
|
<textarea class="form-control" name="description" rows="3" placeholder="Describe your idea or issue" ng-model="feedback.description" ng-minlength="1" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" ng-disabled="feedbackForm.$invalid || feedback.busy"><i class="fa fa-spinner fa-pulse" ng-show="feedback.busy"></i> Submit</button>
|
||||||
|
<span ng-show="feedback.error" class="text-danger text-bold">{{feedback.error}}</span>
|
||||||
|
<span ng-show="feedback.success" class="text-success text-bold">Thank You!</span>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Offset the footer -->
|
||||||
|
<br/><br/>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('Application').controller('SupportController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
|
||||||
|
$scope.config = Client.getConfig();
|
||||||
|
|
||||||
|
$scope.feedback = {
|
||||||
|
error: null,
|
||||||
|
success: false,
|
||||||
|
busy: false,
|
||||||
|
subject: '',
|
||||||
|
type: '',
|
||||||
|
description: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetFeedback() {
|
||||||
|
$scope.feedback.subject = '';
|
||||||
|
$scope.feedback.description = '';
|
||||||
|
$scope.feedback.type = '';
|
||||||
|
|
||||||
|
$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;
|
||||||
|
resetFeedback();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.feedback.busy = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}]);
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
<div class="cloudron-model-item-content shadow" ng-class="{ 'selected': size.slug === currentSize.slug }" style="height: {{ 120 + $index * 30 }}px">
|
<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/> -->
|
<!-- <img src="img/box.png" style="transform: scale({{ size.price/50.0 }});"/><br/> -->
|
||||||
<h3>{{ size.name }}</h3>
|
<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-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>
|
<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>
|
</div>
|
||||||
|
|||||||
@@ -80,6 +80,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<h1>Users <button class="btn btn-primary btn-outline pull-right" data-toggle="modal" data-target="#userAddModal"><i class="fa fa-user-plus"></i> New User</button></h1>
|
<h1>Users <button class="btn btn-primary btn-outline pull-right" data-toggle="modal" data-target="#userAddModal"><i class="fa fa-user-plus"></i> New User</button></h1>
|
||||||
|
|||||||