Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6840f352d | |||
| 6456874f97 | |||
| 66b4a4b02a | |||
| 7e36b3f8e5 | |||
| 12061cc707 | |||
| afcc62ecf6 | |||
| bec6850c98 | |||
| d253a06bab | |||
| 857c5c69b1 | |||
| 766fc49f39 | |||
| 941e09ca9f | |||
| 2466a97fb8 | |||
| 81f92f5182 | |||
| 91e1d442ff | |||
| a1d6ae2296 | |||
| b529fd3bea | |||
| bf319cf593 | |||
| 15eedd2a84 | |||
| d0cd3d1c32 | |||
| 747786d0c8 | |||
| b232255170 | |||
| bd2982ea69 | |||
| 1c948cc83c | |||
| ccde1e51ad | |||
| 03ec940352 | |||
| bd5b15e279 | |||
| b6897a4577 | |||
| f7225523ec | |||
| 9d9509525c | |||
| b1dbb3570b | |||
| c075160e5d | |||
| 612ceba98a | |||
| 7d5e0040bc | |||
| d6e19d2000 | |||
| a01d5db2a0 |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -18,6 +18,7 @@ arg_version=""
|
||||
arg_web_server_origin=""
|
||||
arg_backup_key=""
|
||||
arg_aws=""
|
||||
arg_dns_config=""
|
||||
|
||||
args=$(getopt -o "" -l "data:,retire" -n "$0" -- "$@")
|
||||
eval set -- "${args}"
|
||||
@@ -49,6 +50,9 @@ EOF
|
||||
arg_aws=$(echo "$2" | $json aws)
|
||||
[[ "${arg_aws}" == "null" ]] && arg_aws=""
|
||||
|
||||
arg_dns_config=$(echo "$2" | $json dnsConfig)
|
||||
[[ "${arg_dns_config}" == "null" ]] && arg_dns_config=""
|
||||
|
||||
shift 2
|
||||
;;
|
||||
--) break;;
|
||||
|
||||
@@ -155,6 +155,14 @@ cat > "${BOX_SRC_DIR}/webadmin/dist/config.json" <<CONF_END
|
||||
CONF_END
|
||||
EOF
|
||||
|
||||
# Add DNS Configuration
|
||||
if [[ ! -z "${arg_dns_config}" ]]; then
|
||||
echo "Add DNS Config"
|
||||
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO settings (name, value) VALUES (\"dns_config\", '$arg_dns_config')" box
|
||||
fi
|
||||
|
||||
# Add webadmin oauth client
|
||||
# The domain might have changed, therefor we have to update the record
|
||||
# !!! This needs to be in sync with the webadmin, specifically login_callback.js
|
||||
|
||||
@@ -19,11 +19,12 @@ exports = module.exports = {
|
||||
backup: backup,
|
||||
ensureBackup: ensureBackup,
|
||||
|
||||
isActivatedSync: isActivatedSync,
|
||||
isConfiguredSync: isConfiguredSync,
|
||||
|
||||
events: new (require('events').EventEmitter)(),
|
||||
|
||||
EVENT_ACTIVATED: 'activated'
|
||||
EVENT_ACTIVATED: 'activated',
|
||||
EVENT_CONFIGURED: 'configured'
|
||||
};
|
||||
|
||||
var apps = require('./apps.js'),
|
||||
@@ -64,7 +65,7 @@ var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
|
||||
var gUpdatingDns = false, // flag for dns update reentrancy
|
||||
gCloudronDetails = null, // cached cloudron details like region,size...
|
||||
gIsActivated = null; // cached activation state so that return value is synchronous. null means we are not initialized yet
|
||||
gIsConfigured = null; // cached configured state so that return value is synchronous. null means we are not initialized yet
|
||||
|
||||
function debugApp(app, args) {
|
||||
assert(!app || typeof app === 'object');
|
||||
@@ -115,27 +116,59 @@ CloudronError.NOT_FOUND = 'Not found';
|
||||
function initialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.events.on(settings.DNS_CONFIG_KEY, function() { addDnsRecords(); });
|
||||
exports.events.on(exports.EVENT_CONFIGURED, addDnsRecords);
|
||||
|
||||
userdb.count(function (error, count) {
|
||||
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
|
||||
|
||||
gIsActivated = count !== 0;
|
||||
|
||||
if (gIsActivated) addDnsRecords(); // reboot/restore/upgrade
|
||||
|
||||
callback(null);
|
||||
});
|
||||
syncConfigState(callback);
|
||||
}
|
||||
|
||||
function uninitialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
exports.events.removeListener(exports.EVENT_CONFIGURED, addDnsRecords);
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
function isActivatedSync() {
|
||||
return gIsActivated === true;
|
||||
function isConfiguredSync() {
|
||||
return gIsConfigured === true;
|
||||
}
|
||||
|
||||
function isConfigured(callback) {
|
||||
// set of rules to see if we have the configs required for cloudron to function
|
||||
// note this checks for missing configs and not invalid configs
|
||||
|
||||
settings.getDnsConfig(function (error, dnsConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (!dnsConfig) return callback(null, false);
|
||||
|
||||
var isConfigured = (config.isCustomDomain() && dnsConfig.provider === 'route53') ||
|
||||
(!config.isCustomDomain() && dnsConfig.provider === 'caas');
|
||||
|
||||
callback(null, isConfigured);
|
||||
});
|
||||
}
|
||||
|
||||
function syncConfigState(callback) {
|
||||
assert(!gIsConfigured);
|
||||
|
||||
callback = callback || NOOP_CALLBACK;
|
||||
|
||||
isConfigured(function (error, configured) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug('syncConfigState: configured = %s', configured);
|
||||
|
||||
if (configured) {
|
||||
exports.events.emit(exports.EVENT_CONFIGURED);
|
||||
} else {
|
||||
settings.events.once(settings.DNS_CONFIG_KEY, function () { syncConfigState(); }); // check again later
|
||||
}
|
||||
|
||||
gIsConfigured = configured;
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function setTimeZone(ip, callback) {
|
||||
@@ -189,8 +222,6 @@ function activate(username, password, email, ip, callback) {
|
||||
tokendb.add(token, tokendb.PREFIX_USER + userObject.id, result.id, expires, '*', function (error) {
|
||||
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
|
||||
|
||||
gIsActivated = true;
|
||||
|
||||
// EE API is sync. do not keep the REST API reponse waiting
|
||||
process.nextTick(function () { exports.events.emit(exports.EVENT_ACTIVATED); });
|
||||
|
||||
@@ -328,15 +359,15 @@ function txtRecordsWithSpf(callback) {
|
||||
if (i == txtRecords.length) {
|
||||
txtRecords[i] = '"v=spf1 a:' + config.fqdn() + ' ~all"';
|
||||
} else {
|
||||
txtRecords[i] = '"v=spf1 a:' + config.fqdn() + txtRecords[i].slice('"v=spf1"'.length);
|
||||
txtRecords[i] = '"v=spf1 a:' + config.fqdn() + ' ' + txtRecords[i].slice('"v=spf1 '.length);
|
||||
}
|
||||
|
||||
return callback(null, txtRecords);
|
||||
});
|
||||
}
|
||||
|
||||
function addDnsRecords(callback) {
|
||||
callback = callback || NOOP_CALLBACK;
|
||||
function addDnsRecords() {
|
||||
var callback = NOOP_CALLBACK;
|
||||
|
||||
if (process.env.BOX_ENV === 'test') return callback();
|
||||
|
||||
@@ -362,6 +393,7 @@ function addDnsRecords(callback) {
|
||||
var records = [ ];
|
||||
if (config.isCustomDomain()) {
|
||||
records.push(webadminRecord);
|
||||
records.push(dkimRecord);
|
||||
} else {
|
||||
records.push(nakedDomainRecord);
|
||||
records.push(webadminRecord);
|
||||
@@ -381,7 +413,11 @@ function addDnsRecords(callback) {
|
||||
|
||||
async.eachSeries(records, function (record, iteratorCallback) {
|
||||
subdomains.update(record.subdomain, record.type, record.values, iteratorCallback);
|
||||
}, retryCallback);
|
||||
}, function (error) {
|
||||
if (error) debug('addDnsRecords: failed to update : %s. will retry', error);
|
||||
|
||||
retryCallback(error);
|
||||
});
|
||||
});
|
||||
}, function (error) {
|
||||
gUpdatingDns = false;
|
||||
|
||||
@@ -75,9 +75,7 @@ function initConfig() {
|
||||
data.fqdn = 'localhost';
|
||||
|
||||
data.token = null;
|
||||
data.mailServer = null;
|
||||
data.adminEmail = null;
|
||||
data.mailDnsRecordIds = [ ];
|
||||
data.boxVersionsUrl = null;
|
||||
data.version = null;
|
||||
data.isCustomDomain = false;
|
||||
|
||||
@@ -38,9 +38,6 @@ var NOOP_CALLBACK = function (error) { if (error) console.error(error); };
|
||||
function initialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.events.on(settings.TIME_ZONE_KEY, recreateJobs);
|
||||
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
|
||||
|
||||
gHeartbeatJob = new CronJob({
|
||||
cronTime: '00 */1 * * * *', // every minute
|
||||
onTick: cloudron.sendHeartbeat,
|
||||
@@ -48,7 +45,7 @@ function initialize(callback) {
|
||||
});
|
||||
cloudron.sendHeartbeat(); // latest unpublished version of CronJob has runOnInit
|
||||
|
||||
if (cloudron.isActivatedSync()) {
|
||||
if (cloudron.isConfiguredSync()) {
|
||||
recreateJobs(callback);
|
||||
} else {
|
||||
cloudron.events.on(cloudron.EVENT_ACTIVATED, recreateJobs);
|
||||
@@ -110,14 +107,20 @@ function recreateJobs(unusedTimeZone, callback) {
|
||||
timeZone: allSettings[settings.TIME_ZONE_KEY]
|
||||
});
|
||||
|
||||
settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
|
||||
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
|
||||
autoupdatePatternChanged(allSettings[settings.AUTOUPDATE_PATTERN_KEY]);
|
||||
|
||||
settings.events.removeListener(settings.TIME_ZONE_KEY, recreateJobs);
|
||||
settings.events.on(settings.TIME_ZONE_KEY, recreateJobs);
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
}
|
||||
|
||||
function autoupdatePatternChanged(pattern) {
|
||||
assert.strictEqual(typeof pattern, 'string');
|
||||
assert(gBoxUpdateCheckerJob);
|
||||
|
||||
debug('Auto update pattern changed to %s', pattern);
|
||||
|
||||
@@ -149,6 +152,9 @@ function uninitialize(callback) {
|
||||
|
||||
cloudron.events.removeListener(cloudron.EVENT_ACTIVATED, recreateJobs);
|
||||
|
||||
settings.events.removeListener(settings.TIME_ZONE_KEY, recreateJobs);
|
||||
settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
|
||||
|
||||
if (gAutoupdaterJob) gAutoupdaterJob.stop();
|
||||
gAutoupdaterJob = null;
|
||||
|
||||
|
||||
@@ -82,8 +82,6 @@ function add(zoneName, subdomain, type, values, callback) {
|
||||
Type: type,
|
||||
Name: fqdn,
|
||||
ResourceRecords: records,
|
||||
Weight: 0,
|
||||
SetIdentifier: fqdn,
|
||||
TTL: 1
|
||||
}
|
||||
}]
|
||||
@@ -102,8 +100,6 @@ function add(zoneName, subdomain, type, values, callback) {
|
||||
return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, error.message));
|
||||
}
|
||||
|
||||
debug('addSubdomain: success. changeInfoId:%j', result);
|
||||
|
||||
callback(null, result.ChangeInfo.Id);
|
||||
});
|
||||
});
|
||||
@@ -166,8 +162,6 @@ function del(zoneName, subdomain, type, values, callback) {
|
||||
assert(util.isArray(values));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('add: %s for zone %s of type %s with values %j', subdomain, zoneName, type, values);
|
||||
|
||||
getZoneByName(zoneName, function (error, zone) {
|
||||
if (error) return callback(error);
|
||||
|
||||
@@ -178,8 +172,6 @@ function del(zoneName, subdomain, type, values, callback) {
|
||||
Name: fqdn,
|
||||
Type: type,
|
||||
ResourceRecords: records,
|
||||
Weight: 0,
|
||||
SetIdentifier: fqdn,
|
||||
TTL: 1
|
||||
};
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ var gMailQueue = [ ],
|
||||
function initialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (cloudron.isActivatedSync()) {
|
||||
if (cloudron.isConfiguredSync()) {
|
||||
checkDns();
|
||||
} else {
|
||||
cloudron.events.on(cloudron.EVENT_ACTIVATED, checkDns);
|
||||
cloudron.events.on(cloudron.EVENT_CONFIGURED, checkDns);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
@@ -60,7 +60,7 @@ function initialize(callback) {
|
||||
function uninitialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
cloudron.events.removeListener(cloudron.EVENT_ACTIVATED, checkDns);
|
||||
cloudron.events.removeListener(cloudron.EVENT_CONFIGURED, checkDns);
|
||||
|
||||
// TODO: interrupt processQueue as well
|
||||
clearTimeout(gCheckDnsTimerId);
|
||||
@@ -125,11 +125,14 @@ function checkDns() {
|
||||
}
|
||||
|
||||
debug('checkDns: SPF check passed. commencing mail processing');
|
||||
gDnsReady = true;
|
||||
processQueue();
|
||||
});
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
assert(gDnsReady);
|
||||
|
||||
docker.getContainer('mail').inspect(function (error, data) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
<title> Cloudron Login </title>
|
||||
|
||||
<link href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css">
|
||||
|
||||
@@ -28,7 +28,5 @@ exports = module.exports = {
|
||||
CLOUDRON_AVATAR_FILE: path.join(config.baseDir(), 'data/box/avatar.png'),
|
||||
CLOUDRON_DEFAULT_AVATAR_FILE: path.join(__dirname + '/../assets/avatar.png'),
|
||||
|
||||
FAVICON_FILE: path.join(__dirname + '/../assets/favicon.ico'),
|
||||
|
||||
UPDATE_CHECKER_FILE: path.join(config.baseDir(), 'data/box/updatechecker.json')
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ function setCertificate(req, res, next) {
|
||||
if (!req.body.key || typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
|
||||
|
||||
settings.setCertificate(req.body.cert, req.body.key, function (error) {
|
||||
if (error && error.reason === SettingsError.INVALID_CERT) return next(new HttpError(400, 'cert not applicable'));
|
||||
if (error && error.reason === SettingsError.INVALID_CERT) return next(new HttpError(400, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
@@ -134,7 +134,7 @@ function setAdminCertificate(req, res, next) {
|
||||
if (!req.body.key || typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
|
||||
|
||||
settings.setAdminCertificate(req.body.cert, req.body.key, function (error) {
|
||||
if (error && error.reason === SettingsError.INVALID_CERT) return next(new HttpError(400, 'cert not applicable'));
|
||||
if (error && error.reason === SettingsError.INVALID_CERT) return next(new HttpError(400, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
|
||||
@@ -215,7 +215,7 @@ describe('Settings API', function () {
|
||||
it('set succeeds', function (done) {
|
||||
request.post(SERVER_URL + '/api/v1/settings/cloudron_avatar')
|
||||
.query({ access_token: token })
|
||||
.attach('avatar', paths.FAVICON_FILE)
|
||||
.attach('avatar', paths.CLOUDRON_DEFAULT_AVATAR_FILE)
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(202);
|
||||
done();
|
||||
@@ -227,7 +227,7 @@ describe('Settings API', function () {
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(200);
|
||||
expect(res.body.toString()).to.eql(fs.readFileSync(paths.FAVICON_FILE, 'utf-8'));
|
||||
expect(res.body.toString()).to.eql(fs.readFileSync(paths.CLOUDRON_DEFAULT_AVATAR_FILE, 'utf-8'));
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,6 @@ function initializeExpressSync() {
|
||||
.use(json)
|
||||
.use(urlencoded)
|
||||
.use(middleware.cookieParser())
|
||||
.use(middleware.favicon(paths.FAVICON_FILE)) // used when serving oauth login page
|
||||
.use(middleware.cors({ origins: [ '*' ], allowCredentials: true }))
|
||||
.use(middleware.session({ secret: 'yellow is blue', resave: true, saveUninitialized: true, cookie: { path: '/', httpOnly: true, secure: false, maxAge: 600000 } }))
|
||||
.use(passport.initialize())
|
||||
|
||||
@@ -290,6 +290,8 @@ function getAll(callback) {
|
||||
});
|
||||
}
|
||||
|
||||
// note: https://tools.ietf.org/html/rfc4346#section-7.4.2 (certificate_list) requires that the
|
||||
// servers certificate appears first (and not the intermediate cert)
|
||||
function validateCertificate(cert, key, fqdn) {
|
||||
assert(cert === null || typeof cert === 'string');
|
||||
assert(key === null || typeof key === 'string');
|
||||
@@ -303,7 +305,7 @@ function validateCertificate(cert, key, fqdn) {
|
||||
try {
|
||||
content = x509.parseCert(cert);
|
||||
} catch (e) {
|
||||
return new Error('invalid cert');
|
||||
return new Error('invalid cert: ' + e.message);
|
||||
}
|
||||
|
||||
// check expiration
|
||||
@@ -318,7 +320,7 @@ function validateCertificate(cert, key, fqdn) {
|
||||
|
||||
// check domain
|
||||
var domains = content.altNames.concat(content.subject.commonName);
|
||||
if (!domains.some(matchesDomain)) return new Error('cert is not valid for this domain');
|
||||
if (!domains.some(matchesDomain)) return new Error(util.format('cert is not valid for this domain. Expecting %s in %j', fqdn, domains));
|
||||
|
||||
// http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#verify
|
||||
var certModulus = safe.child_process.execSync('openssl x509 -noout -modulus', { encoding: 'utf8', input: cert });
|
||||
|
||||
@@ -10,6 +10,7 @@ exports = module.exports = {
|
||||
var appdb = require('./appdb.js'),
|
||||
assert = require('assert'),
|
||||
child_process = require('child_process'),
|
||||
cloudron = require('./cloudron.js'),
|
||||
debug = require('debug')('box:taskmanager'),
|
||||
locker = require('./locker.js'),
|
||||
_ = require('underscore');
|
||||
@@ -18,12 +19,41 @@ var gActiveTasks = { };
|
||||
var gPendingTasks = [ ];
|
||||
|
||||
var TASK_CONCURRENCY = 5;
|
||||
var NOOP_CALLBACK = function (error) { console.error(error); };
|
||||
var NOOP_CALLBACK = function (error) { if (error) console.error(error); };
|
||||
|
||||
function initialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// resume app installs and uninstalls
|
||||
locker.on('unlocked', startNextTask);
|
||||
|
||||
if (cloudron.isConfiguredSync()) {
|
||||
resumeTasks();
|
||||
} else {
|
||||
cloudron.events.on(cloudron.EVENT_CONFIGURED, resumeTasks);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
function uninitialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
gPendingTasks = [ ]; // clear this first, otherwise stopAppTask will resume them
|
||||
for (var appId in gActiveTasks) {
|
||||
stopAppTask(appId);
|
||||
}
|
||||
|
||||
cloudron.events.removeListener(cloudron.EVENT_CONFIGURED, resumeTasks);
|
||||
locker.removeListener('unlocked', startNextTask);
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
|
||||
// resume app installs and uninstalls
|
||||
function resumeTasks(callback) {
|
||||
callback = callback || NOOP_CALLBACK;
|
||||
|
||||
appdb.getAll(function (error, apps) {
|
||||
if (error) return callback(error);
|
||||
|
||||
@@ -36,21 +66,6 @@ function initialize(callback) {
|
||||
|
||||
callback(null);
|
||||
});
|
||||
|
||||
locker.on('unlocked', startNextTask);
|
||||
}
|
||||
|
||||
function uninitialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
gPendingTasks = [ ]; // clear this first, otherwise stopAppTask will resume them
|
||||
for (var appId in gActiveTasks) {
|
||||
stopAppTask(appId);
|
||||
}
|
||||
|
||||
locker.removeListener('unlocked', startNextTask);
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
function startNextTask() {
|
||||
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1021 B |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -147,6 +147,7 @@
|
||||
<li><a href="#/account"><i class="fa fa-user fa-fw"></i> Account</a></li>
|
||||
<li ng-show="user.admin"><a href="#/graphs"><i class="fa fa-bar-chart fa-fw"></i> Graphs</a></li>
|
||||
<li><a href="#/support"><i class="fa fa-comment fa-fw"></i> Support</a></li>
|
||||
<li ng-show="user.admin"><a href="#/certs"><i class="fa fa-certificate fa-fw"></i> DNS & Certs</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><a href="" ng-click="logout($event)"><i class="fa fa-sign-out fa-fw"></i> Logout</a></li>
|
||||
|
||||
@@ -31,6 +31,9 @@ app.config(['$routeProvider', function ($routeProvider) {
|
||||
}).when('/graphs', {
|
||||
controller: 'GraphsController',
|
||||
templateUrl: 'views/graphs.html'
|
||||
}).when('/certs', {
|
||||
controller: 'CertsController',
|
||||
templateUrl: 'views/certs.html'
|
||||
}).when('/settings', {
|
||||
controller: 'SettingsController',
|
||||
templateUrl: 'views/settings.html'
|
||||
|
||||
@@ -150,8 +150,24 @@ app.service('Wizard', [ function () {
|
||||
app.controller('StepController', ['$scope', '$route', '$location', 'Wizard', function ($scope, $route, $location, Wizard) {
|
||||
$scope.wizard = Wizard;
|
||||
|
||||
$scope.next = function (page, bad) {
|
||||
if (!bad) $location.path(page);
|
||||
$scope.next = function (bad) {
|
||||
if (bad) return;
|
||||
|
||||
var current = $location.path();
|
||||
var next = '';
|
||||
|
||||
if (current === '/step1') {
|
||||
next = '/step2';
|
||||
} else if (current === '/step2') {
|
||||
if (Wizard.dnsConfig.provider === 'caas') next = '/step4';
|
||||
else next = '/step3';
|
||||
} else if (current === '/step3') {
|
||||
next = '/step4';
|
||||
} else {
|
||||
next = '/step1';
|
||||
}
|
||||
|
||||
$location.path(next);
|
||||
};
|
||||
|
||||
$scope.focusNext = function (elemId, bad) {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<div style="max-width: 600px; margin: 0 auto;">
|
||||
<div class="text-left">
|
||||
<h1>DNS & Certs</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="text-left">
|
||||
<h3>DNS Credentials</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>Currently only Amazon <a href="https://aws.amazon.com/route53/">Route53</a> is supported. Let us know if you require a different DNS provider <a href="#/support">here</a>.</p>
|
||||
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Access Key Id</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ dnsConfig.accessKeyId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Secret Access Key</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;"><i>hidden</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;"></td>
|
||||
<td class="text-right" style="vertical-align: top;"><span class="text-success" ng-show="dnsCredentials.success"><b>Done</b></span> <button class="btn btn-outline btn-xs btn-primary" ng-show="!dnsCredentials.formVisible" ng-click="showDnsCredentialsForm()">Change</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="collapse" id="collapseDnsCredentialsForm" data-toggle="false">
|
||||
<p>The security credentials have to be valid for full Route53 access.</p>
|
||||
<form name="dnsCredentialsForm" ng-submit="setDnsCredentials()">
|
||||
<fieldset>
|
||||
<div class="has-error text-center" ng-show="dnsCredentials.error">{{ dnsCredentials.error }}</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': false }">
|
||||
<label class="control-label" for="dnsCredentialsAccessKeyId">Access Key Id</label>
|
||||
<input type="text" class="form-control" ng-model="dnsCredentials.accessKeyId" id="dnsCredentialsAccessKeyId" name="accessKeyId" ng-disabled="dnsCredentials.busy" ng-minlength="16" ng-maxlength="32" required>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }">
|
||||
<label class="control-label" for="dnsCredentialsSecretAccessKey">Secret Access Key</label>
|
||||
<input type="text" class="form-control" ng-model="dnsCredentials.secretAccessKey" id="dnsCredentialsSecretAccessKey" name="secretAccessKey" ng-disabled="dnsCredentials.busy" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="dnsCredentialsForm.$invalid || busy"><i class="fa fa-spinner fa-pulse" ng-show="dnsCredentials.busy"></i> Save</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="text-left">
|
||||
<h3>SSL Certificates</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form name="defaultCertForm" ng-submit="setDefaultCert()">
|
||||
<fieldset>
|
||||
<label class="control-label" for="defaultCertInput">Fallback Certificate</label>
|
||||
<p>This certificate has to be wildcard certificates and will be used for all apps, which were not configured to use a specific certificate.</p>
|
||||
<div class="has-error text-center" ng-show="defaultCert.error">{{ defaultCert.error }}</div>
|
||||
<div class="text-success text-center" ng-show="defaultCert.success"><b>Upload successful</b></div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.cert.$dirty && defaultCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="defaultCertFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Certificate" ng-model="defaultCert.certificateFileName" id="defaultCertInput" name="cert" onclick="getElementById('defaultCertFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('defaultCertFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.key.$dirty && defaultCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="defaultKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Key" ng-model="defaultCert.keyFileName" id="defaultKeyInput" name="key" onclick="getElementById('defaultKeyFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('defaultKeyFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="defaultCertForm.$invalid || busy"><i class="fa fa-spinner fa-pulse" ng-show="defaultCert.busy"></i> Upload</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form name="adminCertForm" ng-submit="setAdminCert()">
|
||||
<fieldset>
|
||||
<label class="control-label" for="adminCertInput">Settings Certificate</label>
|
||||
<p>This certificate will be used for this Settings application.</p>
|
||||
<div class="has-error text-center" ng-show="adminCert.error">{{ adminCert.error }}</div>
|
||||
<div class="text-success text-center" ng-show="adminCert.success"><b>Upload successful</b></div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!adminCert.cert.$dirty && adminCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="adminCertFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Certificate" ng-model="adminCert.certificateFileName" id="adminCertInput" name="cert" onclick="getElementById('adminCertFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('adminCertFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!adminCert.key.$dirty && adminCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="adminKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Key" ng-model="adminCert.keyFileName" id="adminKeyInput" name="key" onclick="getElementById('adminKeyFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('adminKeyFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="adminCertForm.$invalid || busy"><i class="fa fa-spinner fa-pulse" ng-show="adminCert.busy"></i> Upload</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,151 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('Application').controller('CertsController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
|
||||
Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });
|
||||
|
||||
$scope.defaultCert = {
|
||||
error: null,
|
||||
success: false,
|
||||
busy: false,
|
||||
certificateFile: null,
|
||||
certificateFileName: '',
|
||||
keyFile: null,
|
||||
keyFileName: ''
|
||||
};
|
||||
|
||||
$scope.adminCert = {
|
||||
error: null,
|
||||
success: false,
|
||||
busy: false,
|
||||
certificateFile: null,
|
||||
certificateFileName: '',
|
||||
keyFile: null,
|
||||
keyFileName: ''
|
||||
};
|
||||
|
||||
$scope.dnsCredentials = {
|
||||
error: null,
|
||||
success: false,
|
||||
busy: false,
|
||||
formVisible: false,
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
provider: 'route53'
|
||||
};
|
||||
|
||||
function readFileLocally(obj, file, fileName) {
|
||||
return function (event) {
|
||||
$scope.$apply(function () {
|
||||
obj[file] = null;
|
||||
obj[fileName] = event.target.files[0].name;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (result) {
|
||||
if (!result.target || !result.target.result) return console.error('Unable to read local file');
|
||||
obj[file] = result.target.result;
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById('defaultCertFileInput').onchange = readFileLocally($scope.defaultCert, 'certificateFile', 'certificateFileName');
|
||||
document.getElementById('defaultKeyFileInput').onchange = readFileLocally($scope.defaultCert, 'keyFile', 'keyFileName');
|
||||
document.getElementById('adminCertFileInput').onchange = readFileLocally($scope.adminCert, 'certificateFile', 'certificateFileName');
|
||||
document.getElementById('adminKeyFileInput').onchange = readFileLocally($scope.adminCert, 'keyFile', 'keyFileName');
|
||||
|
||||
$scope.setDefaultCert = function () {
|
||||
$scope.defaultCert.busy = true;
|
||||
$scope.defaultCert.error = null;
|
||||
$scope.defaultCert.success = false;
|
||||
|
||||
Client.setCertificate($scope.defaultCert.certificateFile, $scope.defaultCert.keyFile, function (error) {
|
||||
if (error) {
|
||||
$scope.defaultCert.error = error.message;
|
||||
} else {
|
||||
$scope.defaultCert.success = true;
|
||||
$scope.defaultCert.certificateFileName = '';
|
||||
$scope.defaultCert.keyFileName = '';
|
||||
}
|
||||
|
||||
$scope.defaultCert.busy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setAdminCert = function () {
|
||||
$scope.adminCert.busy = true;
|
||||
$scope.adminCert.error = null;
|
||||
$scope.adminCert.success = false;
|
||||
|
||||
Client.setAdminCertificate($scope.adminCert.certificateFile, $scope.adminCert.keyFile, function (error) {
|
||||
if (error) {
|
||||
$scope.adminCert.error = error.message;
|
||||
} else {
|
||||
$scope.adminCert.success = true;
|
||||
$scope.adminCert.certificateFileName = '';
|
||||
$scope.adminCert.keyFileName = '';
|
||||
}
|
||||
|
||||
$scope.adminCert.busy = false;
|
||||
|
||||
// attempt to reload to make the browser get the new certs
|
||||
window.location.reload(true);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setDnsCredentials = function () {
|
||||
$scope.dnsCredentials.busy = true;
|
||||
$scope.dnsCredentials.error = null;
|
||||
$scope.dnsCredentials.success = false;
|
||||
|
||||
var data = {
|
||||
provider: $scope.dnsCredentials.provider,
|
||||
accessKeyId: $scope.dnsCredentials.accessKeyId,
|
||||
secretAccessKey: $scope.dnsCredentials.secretAccessKey
|
||||
};
|
||||
|
||||
Client.setDnsConfig(data, function (error) {
|
||||
if (error) {
|
||||
$scope.dnsCredentials.error = error.message;
|
||||
} else {
|
||||
$scope.dnsCredentials.success = true;
|
||||
|
||||
$scope.dnsConfig.accessKeyId = $scope.dnsCredentials.accessKeyId;
|
||||
$scope.dnsConfig.secretAccessKey = $scope.dnsCredentials.secretAccessKey;
|
||||
|
||||
$scope.dnsCredentials.accessKeyId = '';
|
||||
$scope.dnsCredentials.secretAccessKey = '';
|
||||
|
||||
$('#collapseDnsCredentialsForm').collapse('hide');
|
||||
$scope.dnsCredentials.formVisible = false;
|
||||
|
||||
// attempt to reload to make the browser get the new certs
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
$scope.dnsCredentials.busy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showDnsCredentialsForm = function () {
|
||||
$scope.dnsCredentials.busy = false;
|
||||
$scope.dnsCredentials.success = false;
|
||||
$scope.dnsCredentials.error = null;
|
||||
$scope.dnsCredentials.accessKeyId = '';
|
||||
$scope.dnsCredentials.secretAccessKey = '';
|
||||
$scope.dnsCredentialsForm.$setPristine();
|
||||
$scope.dnsCredentialsForm.$setUntouched();
|
||||
|
||||
$scope.dnsCredentials.formVisible = true;
|
||||
$('#collapseDnsCredentialsForm').collapse('show');
|
||||
$('#dnsCredentialsAccessKeyId').focus();
|
||||
};
|
||||
|
||||
Client.onReady(function () {
|
||||
Client.getDnsConfig(function (error, result) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.dnsConfig = result;
|
||||
});
|
||||
});
|
||||
}]);
|
||||
@@ -85,6 +85,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin">
|
||||
<div class="text-left">
|
||||
<h3>About</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;" ng-show="user.admin">
|
||||
<div class="row">
|
||||
<div class="col-xs-4" style="min-width: 150px;">
|
||||
<div class="settings-avatar" ng-click="showChangeAvatar()" style="background-image: url('{{avatar.data || avatar.url}}');">
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-8">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Model</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.size }} - {{ config.region }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Version</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.version }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin">
|
||||
<div class="text-left">
|
||||
<h3>Backups</h3>
|
||||
@@ -115,154 +143,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin">
|
||||
<div class="text-left">
|
||||
<h3>About</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;" ng-show="user.admin">
|
||||
<div class="row">
|
||||
<div class="col-xs-4" style="min-width: 150px;">
|
||||
<div class="settings-avatar" ng-click="showChangeAvatar()" style="background-image: url('{{avatar.data || avatar.url}}');">
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-8">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Model</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.size }} - {{ config.region }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Version</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.version }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="text-left">
|
||||
<h3>SSL Certificates</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form name="defaultCertForm" ng-submit="setDefaultCert()">
|
||||
<fieldset>
|
||||
<label class="control-label" for="defaultCertInput">Fallback Certificate</label>
|
||||
<p>This certificate has to be wildcard certificates and will be used for all apps, which were not configured to use a specific certificate.</p>
|
||||
<div class="has-error text-center" ng-show="defaultCert.error">{{ defaultCert.error }}</div>
|
||||
<div class="text-success text-center" ng-show="defaultCert.success"><b>Upload successful</b></div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.cert.$dirty && defaultCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="defaultCertFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Certificate" ng-model="defaultCert.certificateFileName" id="defaultCertInput" name="cert" onclick="getElementById('defaultCertFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('defaultCertFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.key.$dirty && defaultCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="defaultKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Key" ng-model="defaultCert.keyFileName" id="defaultKeyInput" name="key" onclick="getElementById('defaultKeyFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('defaultKeyFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="defaultCertForm.$invalid || busy"><i class="fa fa-spinner fa-pulse" ng-show="defaultCert.busy"></i> Upload</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form name="adminCertForm" ng-submit="setAdminCert()">
|
||||
<fieldset>
|
||||
<label class="control-label" for="adminCertInput">Settings Certificate</label>
|
||||
<p>This certificate will be used for this Settings application.</p>
|
||||
<div class="has-error text-center" ng-show="adminCert.error">{{ adminCert.error }}</div>
|
||||
<div class="text-success text-center" ng-show="adminCert.success"><b>Upload successful</b></div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!adminCert.cert.$dirty && adminCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="adminCertFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Certificate" ng-model="adminCert.certificateFileName" id="adminCertInput" name="cert" onclick="getElementById('adminCertFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('adminCertFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!adminCert.key.$dirty && adminCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="adminKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Key" ng-model="adminCert.keyFileName" id="adminKeyInput" name="key" onclick="getElementById('adminKeyFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('adminKeyFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="adminCertForm.$invalid || busy"><i class="fa fa-spinner fa-pulse" ng-show="adminCert.busy"></i> Upload</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="text-left">
|
||||
<h3>DNS Credentials</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;" ng-show="user.admin && config.isCustomDomain">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>Currently only Amazon <a href="https://aws.amazon.com/route53/">Route53</a> is supported. Let us know if you require a different DNS provider <a href="#/support">here</a>.</p>
|
||||
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Access Key Id</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ dnsConfig.accessKeyId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;">Secret Access Key</td>
|
||||
<td class="text-right" style="vertical-align: top; white-space: nowrap;"><i>hidden</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted" style="vertical-align: top;"></td>
|
||||
<td class="text-right" style="vertical-align: top;"><span class="text-success" ng-show="dnsCredentials.success"><b>Done</b></span> <button class="btn btn-outline btn-xs btn-primary" ng-show="!dnsCredentials.formVisible" ng-click="showDnsCredentialsForm()">Change</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="collapse" id="collapseDnsCredentialsForm" data-toggle="false">
|
||||
<p>The security credentials have to be valid for full Route53 access.</p>
|
||||
<form name="dnsCredentialsForm" ng-submit="setDnsCredentials()">
|
||||
<fieldset>
|
||||
<div class="has-error text-center" ng-show="dnsCredentials.error">{{ dnsCredentials.error }}</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': false }">
|
||||
<label class="control-label" for="dnsCredentialsAccessKeyId">Access Key Id</label>
|
||||
<input type="text" class="form-control" ng-model="dnsCredentials.accessKeyId" id="dnsCredentialsAccessKeyId" name="accessKeyId" ng-disabled="dnsCredentials.busy" ng-minlength="16" ng-maxlength="32" required>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }">
|
||||
<label class="control-label" for="dnsCredentialsSecretAccessKey">Secret Access Key</label>
|
||||
<input type="text" class="form-control" ng-model="dnsCredentials.secretAccessKey" id="dnsCredentialsSecretAccessKey" name="secretAccessKey" ng-disabled="dnsCredentials.busy" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="dnsCredentialsForm.$invalid || busy"><i class="fa fa-spinner fa-pulse" ng-show="dnsCredentials.busy"></i> Save</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin">
|
||||
<div class="text-left">
|
||||
<h3>Developer Mode</h3>
|
||||
|
||||
@@ -84,124 +84,6 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
}]
|
||||
};
|
||||
|
||||
$scope.defaultCert = {
|
||||
error: null,
|
||||
success: false,
|
||||
busy: false,
|
||||
certificateFile: null,
|
||||
certificateFileName: '',
|
||||
keyFile: null,
|
||||
keyFileName: ''
|
||||
};
|
||||
|
||||
$scope.adminCert = {
|
||||
error: null,
|
||||
success: false,
|
||||
busy: false,
|
||||
certificateFile: null,
|
||||
certificateFileName: '',
|
||||
keyFile: null,
|
||||
keyFileName: ''
|
||||
};
|
||||
|
||||
$scope.dnsCredentials = {
|
||||
error: null,
|
||||
success: false,
|
||||
busy: false,
|
||||
formVisible: false,
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
provider: 'route53'
|
||||
};
|
||||
|
||||
function readFileLocally(obj, file, fileName) {
|
||||
return function (event) {
|
||||
$scope.$apply(function () {
|
||||
obj[file] = null;
|
||||
obj[fileName] = event.target.files[0].name;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (result) {
|
||||
if (!result.target || !result.target.result) return console.error('Unable to read local file');
|
||||
obj[file] = result.target.result;
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById('defaultCertFileInput').onchange = readFileLocally($scope.defaultCert, 'certificateFile', 'certificateFileName');
|
||||
document.getElementById('defaultKeyFileInput').onchange = readFileLocally($scope.defaultCert, 'keyFile', 'keyFileName');
|
||||
document.getElementById('adminCertFileInput').onchange = readFileLocally($scope.adminCert, 'certificateFile', 'certificateFileName');
|
||||
document.getElementById('adminKeyFileInput').onchange = readFileLocally($scope.adminCert, 'keyFile', 'keyFileName');
|
||||
|
||||
$scope.setDefaultCert = function () {
|
||||
$scope.defaultCert.busy = true;
|
||||
$scope.defaultCert.error = null;
|
||||
$scope.defaultCert.success = false;
|
||||
|
||||
Client.setCertificate($scope.defaultCert.certificateFile, $scope.defaultCert.keyFile, function (error) {
|
||||
if (error) {
|
||||
$scope.defaultCert.error = error.message;
|
||||
} else {
|
||||
$scope.defaultCert.success = true;
|
||||
$scope.defaultCert.certificateFileName = '';
|
||||
$scope.defaultCert.keyFileName = '';
|
||||
}
|
||||
|
||||
$scope.defaultCert.busy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setAdminCert = function () {
|
||||
$scope.adminCert.busy = true;
|
||||
$scope.adminCert.error = null;
|
||||
$scope.adminCert.success = false;
|
||||
|
||||
Client.setAdminCertificate($scope.adminCert.certificateFile, $scope.adminCert.keyFile, function (error) {
|
||||
if (error) {
|
||||
$scope.adminCert.error = error.message;
|
||||
} else {
|
||||
$scope.adminCert.success = true;
|
||||
$scope.adminCert.certificateFileName = '';
|
||||
$scope.adminCert.keyFileName = '';
|
||||
}
|
||||
|
||||
$scope.adminCert.busy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setDnsCredentials = function () {
|
||||
$scope.dnsCredentials.busy = true;
|
||||
$scope.dnsCredentials.error = null;
|
||||
$scope.dnsCredentials.success = false;
|
||||
|
||||
var data = {
|
||||
provider: $scope.dnsCredentials.provider,
|
||||
accessKeyId: $scope.dnsCredentials.accessKeyId,
|
||||
secretAccessKey: $scope.dnsCredentials.secretAccessKey
|
||||
};
|
||||
|
||||
Client.setDnsConfig(data, function (error) {
|
||||
if (error) {
|
||||
$scope.dnsCredentials.error = error.message;
|
||||
} else {
|
||||
$scope.dnsCredentials.success = true;
|
||||
|
||||
$scope.dnsConfig.accessKeyId = $scope.dnsCredentials.accessKeyId;
|
||||
$scope.dnsConfig.secretAccessKey = $scope.dnsCredentials.secretAccessKey;
|
||||
|
||||
$scope.dnsCredentials.accessKeyId = '';
|
||||
$scope.dnsCredentials.secretAccessKey = '';
|
||||
|
||||
$('#collapseDnsCredentialsForm').collapse('hide');
|
||||
$scope.dnsCredentials.formVisible = false;
|
||||
}
|
||||
|
||||
$scope.dnsCredentials.busy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setPreviewAvatar = function (avatar) {
|
||||
$scope.avatarChange.avatar = avatar;
|
||||
};
|
||||
@@ -347,20 +229,6 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showDnsCredentialsForm = function () {
|
||||
$scope.dnsCredentials.busy = false;
|
||||
$scope.dnsCredentials.success = false;
|
||||
$scope.dnsCredentials.error = null;
|
||||
$scope.dnsCredentials.accessKeyId = '';
|
||||
$scope.dnsCredentials.secretAccessKey = '';
|
||||
$scope.dnsCredentialsForm.$setPristine();
|
||||
$scope.dnsCredentialsForm.$setUntouched();
|
||||
|
||||
$scope.dnsCredentials.formVisible = true;
|
||||
$('#collapseDnsCredentialsForm').collapse('show');
|
||||
$('#dnsCredentialsAccessKeyId').focus();
|
||||
};
|
||||
|
||||
$scope.showChangeDeveloperMode = function () {
|
||||
developerModeChangeReset();
|
||||
$('#developerModeChangeModal').modal('show');
|
||||
@@ -395,13 +263,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
Client.onReady(function () {
|
||||
fetchBackups();
|
||||
|
||||
$scope.avatar.url = '//my-' + $scope.config.fqdn + '/api/v1/cloudron/avatar';
|
||||
|
||||
Client.getDnsConfig(function (error, result) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.dnsConfig = result;
|
||||
});
|
||||
$scope.avatar.url = ($scope.config.isCustomDomain ? '//my.' : '//my-') + $scope.config.fqdn + '/api/v1/cloudron/avatar';
|
||||
});
|
||||
|
||||
// setup all the dialog focus handling
|
||||
|
||||
@@ -38,6 +38,6 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<a class="btn btn-primary" href="#/step2">Next</a>
|
||||
<button class="btn btn-primary" ng-click="next()">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
</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">
|
||||
<input type="password" class="form-control" ng-model="wizard.password" id="inputPassword" name="password" placeholder="Password" ng-enter="next(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">
|
||||
<button class="btn btn-primary" ng-click="next('/step3', setup_form.username.$invalid || setup_form.password.$invalid)" ng-disabled="setup_form.username.$invalid || setup_form.password.$invalid">Done</button>
|
||||
<button class="btn btn-primary" ng-click="next(setup_form.username.$invalid || setup_form.password.$invalid)" ng-disabled="setup_form.username.$invalid || setup_form.password.$invalid">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': setup_form.secretAccessKey.$dirty && setup_form.secretAccessKey.$invalid }">
|
||||
<!-- <label class="control-label" for="inputPassword">Password</label> -->
|
||||
<input type="text" class="form-control" ng-model="wizard.dnsConfig.secretAccessKey" id="inputSecretAccessKey" name="secretAccessKey" placeholder="Secret Access Key" ng-enter="next('/step4', setup_form.secretAccessKey.$invalid)" ng-maxlength="512" ng-minlength="3" required autocomplete="off">
|
||||
<input type="text" class="form-control" ng-model="wizard.dnsConfig.secretAccessKey" id="inputSecretAccessKey" name="secretAccessKey" placeholder="Secret Access Key" ng-enter="next(setup_form.secretAccessKey.$invalid)" ng-maxlength="512" ng-minlength="3" required autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<button class="btn btn-primary" ng-click="next('/step4', setup_form.accessKeyId.$invalid || setup_form.secretAccessKey.$invalid)" ng-disabled="setup_form.accessKeyId.$invalid || setup_form.secretAccessKey.$invalid">Done</button>
|
||||
<button class="btn btn-primary" ng-click="next(setup_form.accessKeyId.$invalid || setup_form.secretAccessKey.$invalid)" ng-disabled="setup_form.accessKeyId.$invalid || setup_form.secretAccessKey.$invalid">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||