app.portBindings and newManifest.tcpPorts may be null
This commit is contained in:
102
webadmin/src/js/appstore.js
Normal file
102
webadmin/src/js/appstore.js
Normal file
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular:false */
|
||||
|
||||
angular.module('Application').service('AppStore', ['$http', 'Client', function ($http, Client) {
|
||||
|
||||
function AppStoreError(statusCode, message) {
|
||||
Error.call(this);
|
||||
this.name = this.constructor.name;
|
||||
this.statusCode = statusCode;
|
||||
if (typeof message == 'string') {
|
||||
this.message = message;
|
||||
} else {
|
||||
this.message = JSON.stringify(message);
|
||||
}
|
||||
}
|
||||
|
||||
function AppStore() {
|
||||
this._appsCache = [];
|
||||
}
|
||||
|
||||
AppStore.prototype.getApps = function (callback) {
|
||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||
|
||||
var that = this;
|
||||
|
||||
$http.get(Client.getConfig().apiServerOrigin + '/api/v1/apps', { params: { boxVersion: Client.getConfig().version } }).success(function (data, status) {
|
||||
if (status !== 200) return callback(new AppStoreError(status, data));
|
||||
|
||||
angular.copy(data.apps, that._appsCache);
|
||||
|
||||
return callback(null, that._appsCache);
|
||||
}).error(function (data, status) {
|
||||
return callback(new AppStoreError(status, data));
|
||||
});
|
||||
};
|
||||
|
||||
AppStore.prototype.getAppsFast = function (callback) {
|
||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||
|
||||
if (this._appsCache.length !== 0) return callback(null, this._appsCache);
|
||||
|
||||
this.getApps(callback);
|
||||
};
|
||||
|
||||
AppStore.prototype.getAppById = function (appId, callback) {
|
||||
var that = this;
|
||||
|
||||
// check cache
|
||||
for (var app in this._appsCache) {
|
||||
if (this._appsCache[app].id === appId) return callback(null, this._appsCache[app]);
|
||||
}
|
||||
|
||||
this.getApps(function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// recheck cache
|
||||
for (var app in that._appsCache) {
|
||||
if (that._appsCache[app].id === appId) return callback(null, that._appsCache[app]);
|
||||
}
|
||||
|
||||
callback(new AppStoreError(404, 'Not found'));
|
||||
});
|
||||
};
|
||||
|
||||
AppStore.prototype.getManifest = function (appId, callback) {
|
||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||
|
||||
var manifestUrl = Client.getConfig().apiServerOrigin + '/api/v1/apps/' + appId;
|
||||
console.log('Getting the manifest of ', appId, manifestUrl);
|
||||
$http.get(manifestUrl).success(function (data, status) {
|
||||
if (status !== 200) return callback(new AppStoreError(status, data));
|
||||
return callback(null, data.manifest);
|
||||
}).error(function (data, status) {
|
||||
return callback(new AppStoreError(status, data));
|
||||
});
|
||||
};
|
||||
|
||||
AppStore.prototype.getSizes = function (callback) {
|
||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||
|
||||
$http.get(Client.getConfig().apiServerOrigin + '/api/v1/sizes').success(function (data, status) {
|
||||
if (status !== 200) return callback(new AppStoreError(status, data));
|
||||
return callback(null, data.sizes);
|
||||
}).error(function (data, status) {
|
||||
return callback(new AppStoreError(status, data));
|
||||
});
|
||||
};
|
||||
|
||||
AppStore.prototype.getRegions = function (callback) {
|
||||
if (Client.getConfig().apiServerOrigin === null) return callback(new AppStoreError(420, 'Enhance Your Calm'));
|
||||
|
||||
$http.get(Client.getConfig().apiServerOrigin + '/api/v1/regions').success(function (data, status) {
|
||||
if (status !== 200) return callback(new AppStoreError(status, data));
|
||||
return callback(null, data.regions);
|
||||
}).error(function (data, status) {
|
||||
return callback(new AppStoreError(status, data));
|
||||
});
|
||||
};
|
||||
|
||||
return new AppStore();
|
||||
}]);
|
||||
612
webadmin/src/js/client.js
Normal file
612
webadmin/src/js/client.js
Normal file
@@ -0,0 +1,612 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global EventSource */
|
||||
|
||||
angular.module('Application').service('Client', ['$http', 'md5', 'Notification', function ($http, md5, Notification) {
|
||||
var client = null;
|
||||
|
||||
function ClientError(statusCode, messageOrObject) {
|
||||
Error.call(this);
|
||||
this.name = this.constructor.name;
|
||||
this.statusCode = statusCode;
|
||||
if (messageOrObject === null || typeof messageOrObject === 'undefined') {
|
||||
this.message = 'Empty message or object';
|
||||
} else if (typeof messageOrObject === 'string') {
|
||||
this.message = messageOrObject;
|
||||
} else if (messageOrObject.message) {
|
||||
this.message = messageOrObject.message;
|
||||
} else {
|
||||
this.message = JSON.stringify(messageOrObject);
|
||||
}
|
||||
}
|
||||
|
||||
function defaultErrorHandler(callback) {
|
||||
return function (data, status) {
|
||||
if (status === 401) return client.logout();
|
||||
if (status === 503) {
|
||||
// this could indicate a update/upgrade/restore/migration
|
||||
client.progress(function (error, result) {
|
||||
if (error) {
|
||||
client.error(error);
|
||||
return callback(new ClientError(status, data));
|
||||
}
|
||||
|
||||
if (result.update) window.location.href = '/update.html';
|
||||
else callback(new ClientError(status, data));
|
||||
}, function (data, status) {
|
||||
client.error(data);
|
||||
return callback(new ClientError(status, data));
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (status >= 500) {
|
||||
client.error(data);
|
||||
return callback(new ClientError(status, data));
|
||||
}
|
||||
|
||||
var obj = data;
|
||||
try {
|
||||
obj = JSON.parse(data);
|
||||
} catch (e) {}
|
||||
callback(new ClientError(status, obj));
|
||||
};
|
||||
}
|
||||
|
||||
function Client() {
|
||||
this._ready = false;
|
||||
this._configListener = [];
|
||||
this._readyListener = [];
|
||||
this._userInfo = {
|
||||
username: null,
|
||||
email: null,
|
||||
admin: false
|
||||
};
|
||||
this._token = null;
|
||||
this._config = {
|
||||
apiServerOrigin: null,
|
||||
webServerOrigin: null,
|
||||
fqdn: null,
|
||||
ip: null,
|
||||
revision: null,
|
||||
update: { box: null, apps: null },
|
||||
isDev: false,
|
||||
progress: {},
|
||||
isCustomDomain: false,
|
||||
developerMode: false,
|
||||
region: null,
|
||||
size: null,
|
||||
cloudronName: null
|
||||
};
|
||||
this._installedApps = [];
|
||||
this._clientId = '<%= oauth.clientId %>';
|
||||
this._clientSecret = '<%= oauth.clientSecret %>';
|
||||
this.apiOrigin = '<%= oauth.apiOrigin %>';
|
||||
|
||||
this.setToken(localStorage.token);
|
||||
}
|
||||
|
||||
Client.prototype.error = function (error) {
|
||||
var message = '';
|
||||
|
||||
if (typeof error === 'object') {
|
||||
message = error.message || error;
|
||||
} else {
|
||||
message = error;
|
||||
}
|
||||
|
||||
Notification.error({ title: 'Cloudron Error', message: message, delay: 5000 });
|
||||
};
|
||||
|
||||
Client.prototype.setReady = function () {
|
||||
if (this._ready) return;
|
||||
|
||||
this._ready = true;
|
||||
this._readyListener.forEach(function (callback) {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.onReady = function (callback) {
|
||||
if (this._ready) callback();
|
||||
this._readyListener.push(callback);
|
||||
};
|
||||
|
||||
Client.prototype.onConfig = function (callback) {
|
||||
this._configListener.push(callback);
|
||||
callback(this._config);
|
||||
};
|
||||
|
||||
Client.prototype.setUserInfo = function (userInfo) {
|
||||
// In order to keep the angular bindings alive, set each property individually
|
||||
this._userInfo.username = userInfo.username;
|
||||
this._userInfo.email = userInfo.email;
|
||||
this._userInfo.admin = !!userInfo.admin;
|
||||
this._userInfo.gravatar = 'https://www.gravatar.com/avatar/' + md5.createHash(userInfo.email.toLowerCase()) + '.jpg?s=24&d=mm';
|
||||
this._userInfo.gravatarHuge = 'https://www.gravatar.com/avatar/' + md5.createHash(userInfo.email.toLowerCase()) + '.jpg?s=128&d=mm';
|
||||
};
|
||||
|
||||
Client.prototype.setConfig = function (config) {
|
||||
var that = this;
|
||||
|
||||
angular.copy(config, this._config);
|
||||
|
||||
this._configListener.forEach(function (callback) {
|
||||
callback(that._config);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.getInstalledApps = function () {
|
||||
return this._installedApps;
|
||||
};
|
||||
|
||||
Client.prototype.getUserInfo = function () {
|
||||
return this._userInfo;
|
||||
};
|
||||
|
||||
Client.prototype.getConfig = function () {
|
||||
return this._config;
|
||||
};
|
||||
|
||||
Client.prototype.setToken = function (token) {
|
||||
$http.defaults.headers.common.Authorization = 'Bearer ' + token;
|
||||
if (!token) localStorage.removeItem('token');
|
||||
else localStorage.token = token;
|
||||
this._token = token;
|
||||
};
|
||||
|
||||
/*
|
||||
* Rest API wrappers
|
||||
*/
|
||||
Client.prototype.config = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/cloudron/config').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.userInfo = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/profile').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.changeDeveloperMode = function (enabled, password, callback) {
|
||||
var that = this;
|
||||
|
||||
var data = { password: password, enabled: enabled };
|
||||
$http.post(client.apiOrigin + '/api/v1/developer', data).success(function (data, status) {
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
// will get overriden after polling for config, but ensures quick UI update
|
||||
that._config.developerMode = enabled;
|
||||
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.changeCloudronName = function (name, callback) {
|
||||
var that = this;
|
||||
|
||||
var data = { name: name };
|
||||
$http.post(client.apiOrigin + '/api/v1/settings/cloudron_name', data).success(function (data, status) {
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
// will get overriden after polling for config, but ensures quick UI update
|
||||
that._config.cloudronName = name;
|
||||
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.changeCloudronAvatar = function (avatarFile, callback) {
|
||||
var fd = new FormData();
|
||||
fd.append('avatar', avatarFile);
|
||||
|
||||
$http.post(client.apiOrigin + '/api/v1/settings/cloudron_avatar', fd, {
|
||||
headers: { 'Content-Type': undefined },
|
||||
transformRequest: angular.identity
|
||||
}).success(function(data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.installApp = function (id, manifest, title, config, callback) {
|
||||
var that = this;
|
||||
var data = { appStoreId: id, manifest: manifest, location: config.location, portBindings: config.portBindings, accessRestriction: config.accessRestriction };
|
||||
$http.post(client.apiOrigin + '/api/v1/apps/install', data).success(function (data, status) {
|
||||
if (status !== 202 || typeof data !== 'object') return defaultErrorHandler(callback);
|
||||
|
||||
// put new app with amended title in cache
|
||||
data.manifest = { title: title };
|
||||
var icons = that.getAppIconUrls(data);
|
||||
data.iconUrl = icons.cloudron;
|
||||
data.iconUrlStore = icons.store;
|
||||
data.progress = 0;
|
||||
|
||||
that._installedApps.push(data);
|
||||
|
||||
callback(null, data.id);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.restoreApp = function (appId, password, callback) {
|
||||
var data = { password: password };
|
||||
$http.post(client.apiOrigin + '/api/v1/apps/' + appId + '/restore', data).success(function (data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.uninstallApp = function (appId, password, callback) {
|
||||
var data = { password: password };
|
||||
$http.post(client.apiOrigin + '/api/v1/apps/' + appId + '/uninstall', data).success(function (data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.configureApp = function (id, password, config, callback) {
|
||||
var data = { appId: id, password: password, location: config.location, portBindings: config.portBindings, accessRestriction: config.accessRestriction };
|
||||
$http.post(client.apiOrigin + '/api/v1/apps/' + id + '/configure', data).success(function (data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.updateApp = function (id, manifest, portBindings, password, callback) {
|
||||
$http.post(client.apiOrigin + '/api/v1/apps/' + id + '/update', { manifest: manifest, password: password, portBindings: portBindings }).success(function (data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.startApp = function (id, callback) {
|
||||
var data = { };
|
||||
$http.post(client.apiOrigin + '/api/v1/apps/' + id + '/start', data).success(function (data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.stopApp = function (id, callback) {
|
||||
var data = { };
|
||||
$http.post(client.apiOrigin + '/api/v1/apps/' + id + '/stop', data).success(function (data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.progress = function (callback, errorCallback) {
|
||||
// this is used in the defaultErrorHandler itself, and avoids a loop
|
||||
if (typeof errorCallback !== 'function') errorCallback = defaultErrorHandler(callback);
|
||||
|
||||
$http.get(client.apiOrigin + '/api/v1/cloudron/progress').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(errorCallback);
|
||||
};
|
||||
|
||||
Client.prototype.version = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/cloudron/status').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.isServerFirstTime = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/cloudron/status').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, !data.activated);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.getBackups = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/backups').success(function (data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data.backups);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.backup = function (callback) {
|
||||
$http.post(client.apiOrigin + '/api/v1/backups').success(function(data, status) {
|
||||
if (status !== 202 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.getApps = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/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) {
|
||||
var appFound = null;
|
||||
this._installedApps.some(function (app) {
|
||||
if (app.id === appId) {
|
||||
appFound = app;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (appFound) return callback(null, appFound);
|
||||
else return callback(new Error('App not found'));
|
||||
};
|
||||
|
||||
Client.prototype.getAppLogStream = function (appId) {
|
||||
var source = new EventSource('/api/v1/apps/' + appId + '/logstream');
|
||||
return source;
|
||||
};
|
||||
|
||||
Client.prototype.getAppLogUrl = function (appId) {
|
||||
return '/api/v1/apps/' + appId + '/logs?access_token=' + this._token;
|
||||
};
|
||||
|
||||
Client.prototype.getAppIconUrls = function (app) {
|
||||
return {
|
||||
cloudron: this.apiOrigin + app.iconUrl + '?access_token=' + this._token,
|
||||
store: this._config.apiServerOrigin + '/api/v1/apps/' + app.appStoreId + '/versions/' + app.manifest.version + '/icon'
|
||||
};
|
||||
};
|
||||
|
||||
Client.prototype.setAdmin = function (username, admin, callback) {
|
||||
var payload = {
|
||||
username: username,
|
||||
admin: admin
|
||||
};
|
||||
|
||||
$http.post(client.apiOrigin + '/api/v1/users/' + username + '/admin', payload).success(function (data, status) {
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.createAdmin = function (username, password, email, name, setupToken, callback) {
|
||||
var payload = {
|
||||
username: username,
|
||||
password: password,
|
||||
email: email,
|
||||
name: name
|
||||
};
|
||||
|
||||
var that = this;
|
||||
|
||||
$http.post(client.apiOrigin + '/api/v1/cloudron/activate?setupToken=' + setupToken, payload).success(function(data, status) {
|
||||
if (status !== 201 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
|
||||
that.setToken(data.token);
|
||||
that.setUserInfo({ username: username, email: email, admin: true });
|
||||
|
||||
callback(null, data.activated);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.listUsers = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/users').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.getOAuthClients = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/oauth/clients').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data.clients);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.delTokensByClientId = function (id, callback) {
|
||||
$http.delete(client.apiOrigin + '/api/v1/oauth/clients/' + id + '/tokens').success(function(data, status) {
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.update = function (password, callback) {
|
||||
$http.post(client.apiOrigin + '/api/v1/cloudron/update', { password: password }).success(function(data, status) {
|
||||
if (status !== 202 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.reboot = function (callback) {
|
||||
$http.get(client.apiOrigin + '/api/v1/cloudron/reboot').success(function(data, status) {
|
||||
if (status !== 202 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.migrate = function (size, region, password, callback) {
|
||||
$http.post(client.apiOrigin + '/api/v1/cloudron/migrate', { size: size, region: region, password: password }).success(function(data, status) {
|
||||
if (status !== 202 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.setCertificate = function (certificateFile, keyFile, callback) {
|
||||
console.log('will set certificate');
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('certificate', certificateFile);
|
||||
fd.append('key', keyFile);
|
||||
|
||||
$http.post(client.apiOrigin + '/api/v1/cloudron/certificate', fd, {
|
||||
headers: { 'Content-Type': undefined },
|
||||
transformRequest: angular.identity
|
||||
}).success(function(data, status) {
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.graphs = function (targets, from, callback) {
|
||||
var config = {
|
||||
params: {
|
||||
target: targets,
|
||||
format: 'json',
|
||||
from: from
|
||||
}
|
||||
};
|
||||
|
||||
$http.get(client.apiOrigin + '/api/v1/cloudron/graphs', config).success(function (data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.createUser = function (username, email, callback) {
|
||||
var data = {
|
||||
username: username,
|
||||
email: email
|
||||
};
|
||||
|
||||
$http.post(client.apiOrigin + '/api/v1/users', data).success(function(data, status) {
|
||||
if (status !== 201 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.removeUser = function (userId, password, callback) {
|
||||
var data = {
|
||||
password: password
|
||||
};
|
||||
|
||||
$http({ method: 'DELETE', url: '/api/v1/users/' + userId, data: data, headers: { 'Content-Type': 'application/json' }}).success(function(data, status) {
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
callback(null);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.changePassword = function (currentPassword, newPassword, callback) {
|
||||
var data = {
|
||||
password: currentPassword,
|
||||
newPassword: newPassword
|
||||
};
|
||||
|
||||
$http.post(client.apiOrigin + '/api/v1/users/' + this._userInfo.username + '/password', data).success(function(data, status) {
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.changeEmail = function (email, password, callback) {
|
||||
var data = {
|
||||
password: password,
|
||||
email: email
|
||||
};
|
||||
|
||||
$http.put(client.apiOrigin + '/api/v1/users/' + this._userInfo.username, data).success(function(data, status) {
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.refreshUserInfo = function (callback) {
|
||||
var that = this;
|
||||
|
||||
callback = typeof callback === 'function' ? callback : function () {};
|
||||
|
||||
this.userInfo(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
that.setUserInfo(result);
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.refreshConfig = function (callback) {
|
||||
var that = this;
|
||||
|
||||
callback = typeof callback === 'function' ? callback : function () {};
|
||||
|
||||
this.config(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
that.setConfig(result);
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.refreshInstalledApps = function (callback) {
|
||||
var that = this;
|
||||
|
||||
callback = typeof callback === 'function' ? callback : function () {};
|
||||
|
||||
this.getApps(function (error, apps) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// insert or update new apps
|
||||
apps.forEach(function (app) {
|
||||
var found = false;
|
||||
|
||||
for (var i = 0; i < that._installedApps.length; ++i) {
|
||||
if (that._installedApps[i].id === app.id) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var tmp = {};
|
||||
angular.copy(app, tmp);
|
||||
|
||||
var icons = that.getAppIconUrls(tmp);
|
||||
tmp.iconUrl = icons.cloudron;
|
||||
tmp.iconUrlStore = icons.store;
|
||||
|
||||
// extract progress percentage
|
||||
var installationProgress = tmp.installationProgress || '';
|
||||
var progress = parseInt(installationProgress.split(',')[0]);
|
||||
if (isNaN(progress)) progress = 0;
|
||||
tmp.progress = progress;
|
||||
|
||||
if (found !== false) {
|
||||
angular.copy(tmp, that._installedApps[found]);
|
||||
} else {
|
||||
that._installedApps.push(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
// filter out old entries, going backwards to allow splicing
|
||||
for(var i = that._installedApps.length - 1; i >= 0; --i) {
|
||||
if (!apps.some(function (elem) { return (elem.id === that._installedApps[i].id); })) {
|
||||
that._installedApps.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.logout = function () {
|
||||
this.setToken(null);
|
||||
this._userInfo = {};
|
||||
|
||||
// logout from OAuth session
|
||||
window.location.href = client.apiOrigin + '/api/v1/session/logout';
|
||||
};
|
||||
|
||||
Client.prototype.exchangeCodeForToken = function (code, callback) {
|
||||
var data = {
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
redirect_uri: window.location.protocol + '//' + window.location.host,
|
||||
client_id: this._clientId,
|
||||
client_secret: this._clientSecret
|
||||
};
|
||||
|
||||
$http.post(client.apiOrigin + '/api/v1/oauth/token?response_type=token&client_id=' + this._clientId, data).success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data));
|
||||
|
||||
callback(null, data.access_token);
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
client = new Client();
|
||||
return client;
|
||||
}]);
|
||||
23
webadmin/src/js/error.js
Normal file
23
webadmin/src/js/error.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', []);
|
||||
|
||||
app.controller('ErrorController', ['$scope', '$http', function ($scope, $http) {
|
||||
$scope.webServerOriginLink = '/';
|
||||
$scope.errorMessage = '';
|
||||
|
||||
// try to fetch at least config.json to get appstore url
|
||||
$http.get('config.json').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||
$scope.webServerOriginLink = data.webServerOrigin + '/console.html';
|
||||
}).error(function (data, status) {
|
||||
if (status === 404) console.error('No config.json found');
|
||||
else console.error(status, data);
|
||||
});
|
||||
|
||||
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; }, {});
|
||||
|
||||
$scope.errorCode = search.errorCode || 0;
|
||||
$scope.errorContext = search.errorContext || '';
|
||||
}]);
|
||||
213
webadmin/src/js/index.js
Normal file
213
webadmin/src/js/index.js
Normal file
@@ -0,0 +1,213 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular:false */
|
||||
|
||||
// deal with accessToken in the query, this is passed for example on password reset
|
||||
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; }, {});
|
||||
if (search.accessToken) localStorage.token = search.accessToken;
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'slick', 'ui-notification']);
|
||||
|
||||
// setup all major application routes
|
||||
app.config(['$routeProvider', function ($routeProvider) {
|
||||
$routeProvider.when('/', {
|
||||
redirectTo: '/apps'
|
||||
}).when('/users', {
|
||||
controller: 'UsersController',
|
||||
templateUrl: 'views/users.html'
|
||||
}).when('/appstore', {
|
||||
controller: 'AppStoreController',
|
||||
templateUrl: 'views/appstore.html'
|
||||
}).when('/appstore/:appId', {
|
||||
controller: 'AppStoreController',
|
||||
templateUrl: 'views/appstore.html'
|
||||
}).when('/apps', {
|
||||
controller: 'AppsController',
|
||||
templateUrl: 'views/apps.html'
|
||||
}).when('/dns', {
|
||||
controller: 'DnsController',
|
||||
templateUrl: 'views/dns.html'
|
||||
}).when('/account', {
|
||||
controller: 'AccountController',
|
||||
templateUrl: 'views/account.html'
|
||||
}).when('/graphs', {
|
||||
controller: 'GraphsController',
|
||||
templateUrl: 'views/graphs.html'
|
||||
}).when('/settings', {
|
||||
controller: 'SettingsController',
|
||||
templateUrl: 'views/settings.html'
|
||||
}).when('/upgrade', {
|
||||
controller: 'UpgradeController',
|
||||
templateUrl: 'views/upgrade.html'
|
||||
}).otherwise({ redirectTo: '/'});
|
||||
}]);
|
||||
|
||||
// keep in sync with appdb.js
|
||||
var ISTATES = {
|
||||
PENDING_INSTALL: 'pending_install',
|
||||
PENDING_CONFIGURE: 'pending_configure',
|
||||
PENDING_UNINSTALL: 'pending_uninstall',
|
||||
PENDING_RESTORE: 'pending_restore',
|
||||
PENDING_UPDATE: 'pending_update',
|
||||
PENDING_BACKUP: 'pending_backup',
|
||||
ERROR: 'error',
|
||||
INSTALLED: 'installed'
|
||||
};
|
||||
var HSTATES = {
|
||||
HEALTHY: 'healthy',
|
||||
UNHEALTHY: 'unhealthy',
|
||||
ERROR: 'error',
|
||||
DEAD: 'dead'
|
||||
};
|
||||
|
||||
app.filter('installError', function () {
|
||||
return function (app) {
|
||||
if (app.installationState === ISTATES.ERROR) return true;
|
||||
if (app.installationState === ISTATES.INSTALLED) {
|
||||
// app.health can also be null to indicate insufficient data
|
||||
if (app.health === HSTATES.UNHEALTHY || app.health === HSTATES.ERROR || app.health === HSTATES.DEAD) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('installSuccess', function () {
|
||||
return function (app) {
|
||||
return app.installationState === ISTATES.INSTALLED;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('installationActive', function () {
|
||||
return function(app) {
|
||||
if (app.installationState === ISTATES.ERROR) return false;
|
||||
if (app.installationState === ISTATES.INSTALLED) return false;
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('installationStateLabel', function() {
|
||||
return function(app) {
|
||||
var waiting = app.progress === 0 ? ' (Waiting)' : '';
|
||||
|
||||
switch (app.installationState) {
|
||||
case ISTATES.PENDING_INSTALL: return 'Installing...' + waiting;
|
||||
case ISTATES.PENDING_CONFIGURE: return 'Configuring...' + waiting;
|
||||
case ISTATES.PENDING_UNINSTALL: return 'Uninstalling...' + waiting;
|
||||
case ISTATES.PENDING_RESTORE: return 'Restoring...' + waiting;
|
||||
case ISTATES.PENDING_UPDATE: return 'Updating...' + waiting;
|
||||
case ISTATES.PENDING_BACKUP: return 'Backing up...' + waiting;
|
||||
case ISTATES.ERROR: return 'Error';
|
||||
case ISTATES.INSTALLED: {
|
||||
if (app.runState === 'running') {
|
||||
if (!app.health) return 'Starting...'; // no data yet
|
||||
if (app.health === HSTATES.HEALTHY) return 'Running';
|
||||
return 'Not responding'; // dead/exit/unhealthy
|
||||
} else if (app.runState === 'pending_start') return 'Starting...';
|
||||
else if (app.runState === 'pending_stop') return 'Stopping...';
|
||||
else if (app.runState === 'stopped') return 'Stopped';
|
||||
else return app.runState;
|
||||
break;
|
||||
}
|
||||
default: return app.installationState;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('readyToUpdate', function () {
|
||||
return function (apps) {
|
||||
return apps.every(function (app) {
|
||||
return (app.installationState === ISTATES.ERROR) || (app.installationState === ISTATES.INSTALLED);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('inProgressApps', function () {
|
||||
return function (apps) {
|
||||
return apps.filter(function (app) {
|
||||
return app.installationState !== ISTATES.ERROR && app.installationState !== ISTATES.INSTALLED;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('applicationLink', function() {
|
||||
return function(app) {
|
||||
if (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY) {
|
||||
return 'https://' + app.fqdn;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('accessRestrictionLabel', function() {
|
||||
return function (input) {
|
||||
if (input === '') return 'public';
|
||||
if (input === 'roleUser') return 'private';
|
||||
if (input === 'roleAdmin') return 'private (Admins only)';
|
||||
|
||||
return input;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyHref', function () {
|
||||
return function (input) {
|
||||
if (!input) return input;
|
||||
if (input.indexOf('http://') === 0) return input.slice('http://'.length);
|
||||
if (input.indexOf('https://') === 0) return input.slice('https://'.length);
|
||||
return input;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyDate', function () {
|
||||
// http://ejohn.org/files/pretty.js
|
||||
return function prettyDate(time) {
|
||||
var date = new Date(time),
|
||||
diff = (((new Date()).getTime() - date.getTime()) / 1000) + 30, // add 30seconds for clock skew
|
||||
day_diff = Math.floor(diff / 86400);
|
||||
|
||||
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31)
|
||||
return;
|
||||
|
||||
return day_diff == 0 && (
|
||||
diff < 60 && 'just now' ||
|
||||
diff < 120 && '1 minute ago' ||
|
||||
diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
|
||||
diff < 7200 && '1 hour ago' ||
|
||||
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
|
||||
day_diff == 1 && 'Yesterday' ||
|
||||
day_diff < 7 && day_diff + ' days ago' ||
|
||||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago';
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('markdown2html', function () {
|
||||
var converter = new showdown.Converter();
|
||||
|
||||
return function (text) {
|
||||
return converter.makeHtml(text);
|
||||
};
|
||||
});
|
||||
|
||||
// custom directive for dynamic names in forms
|
||||
// See http://stackoverflow.com/questions/23616578/issue-registering-form-control-with-interpolated-name#answer-23617401
|
||||
app.directive('laterName', function () { // (2)
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: ['?ngModel', '^?form'], // (3)
|
||||
link: function postLink(scope, elem, attrs, ctrls) {
|
||||
attrs.$set('name', attrs.laterName);
|
||||
|
||||
var modelCtrl = ctrls[0]; // (3)
|
||||
var formCtrl = ctrls[1]; // (3)
|
||||
if (modelCtrl && formCtrl) {
|
||||
modelCtrl.$name = attrs.name; // (4)
|
||||
formCtrl.$addControl(modelCtrl); // (2)
|
||||
scope.$on('$destroy', function () {
|
||||
formCtrl.$removeControl(modelCtrl); // (5)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
140
webadmin/src/js/main.js
Normal file
140
webadmin/src/js/main.js
Normal file
@@ -0,0 +1,140 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('Application').controller('MainController', ['$scope', '$route', '$interval', 'Client', function ($scope, $route, $interval, Client) {
|
||||
$scope.initialized = false;
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.installedApps = Client.getInstalledApps();
|
||||
$scope.config = {};
|
||||
|
||||
$scope.update = {
|
||||
busy: false,
|
||||
error: {},
|
||||
password: ''
|
||||
};
|
||||
|
||||
$scope.isActive = function (url) {
|
||||
if (!$route.current) return false;
|
||||
return $route.current.$$route.originalPath.indexOf(url) === 0;
|
||||
};
|
||||
|
||||
$scope.logout = function (event) {
|
||||
event.stopPropagation();
|
||||
$scope.initialized = false;
|
||||
Client.logout();
|
||||
};
|
||||
|
||||
$scope.login = function () {
|
||||
var callbackURL = window.location.protocol + '//' + window.location.host + '/login_callback.html';
|
||||
var scope = 'root,profile,apps,roleAdmin';
|
||||
|
||||
// generate a state id to protect agains csrf
|
||||
var state = Math.floor((1 + Math.random()) * 0x1000000000000).toString(16).substring(1);
|
||||
window.localStorage.oauth2State = state;
|
||||
|
||||
window.location.href = Client.apiOrigin + '/api/v1/oauth/dialog/authorize?response_type=token&client_id=' + Client._clientId + '&redirect_uri=' + callbackURL + '&scope=' + scope + '&state=' + state;
|
||||
};
|
||||
|
||||
$scope.setup = function () {
|
||||
window.location.href = '/error.html?errorCode=1';
|
||||
};
|
||||
|
||||
$scope.error = function (error) {
|
||||
console.error(error);
|
||||
window.location.href = '/error.html';
|
||||
};
|
||||
|
||||
$scope.showUpdateModal = function (form) {
|
||||
$scope.update.error.password = null;
|
||||
$scope.update.password = '';
|
||||
|
||||
form.$setPristine();
|
||||
form.$setUntouched();
|
||||
|
||||
$('#updateModal').modal('show');
|
||||
};
|
||||
|
||||
$scope.doUpdate = function () {
|
||||
$scope.update.error.password = null;
|
||||
|
||||
$scope.update.busy = true;
|
||||
Client.update($scope.update.password, function (error) {
|
||||
if (error) {
|
||||
if (error.statusCode === 403) {
|
||||
$scope.update.error.password = 'Incorrect password';
|
||||
$scope.update.password = '';
|
||||
$('#inputUpdatePassword').focus();
|
||||
} else {
|
||||
console.error('Unable to update.', error);
|
||||
}
|
||||
$scope.update.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = '/update.html';
|
||||
});
|
||||
};
|
||||
|
||||
Client.isServerFirstTime(function (error, isFirstTime) {
|
||||
if (error) return $scope.error(error);
|
||||
if (isFirstTime) return $scope.setup();
|
||||
|
||||
// we use the config request as an indicator if the token is still valid
|
||||
// TODO we should probably attach such a handler for each request, as the token can get invalid
|
||||
// at any time!
|
||||
if (localStorage.token) {
|
||||
Client.refreshConfig(function (error) {
|
||||
if (error && error.statusCode === 401) return $scope.login();
|
||||
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) {
|
||||
if (error) return $scope.error(error);
|
||||
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
if (error) return $scope.error(error);
|
||||
|
||||
// kick off installed apps and config polling
|
||||
var refreshAppsTimer = $interval(Client.refreshInstalledApps.bind(Client), 2000);
|
||||
var refreshConfigTimer = $interval(Client.refreshConfig.bind(Client), 5000);
|
||||
var refreshUserInfoTimer = $interval(Client.refreshUserInfo.bind(Client), 5000);
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$interval.cancel(refreshAppsTimer);
|
||||
$interval.cancel(refreshConfigTimer);
|
||||
$interval.cancel(refreshUserInfoTimer);
|
||||
});
|
||||
|
||||
// now mark the Client to be ready
|
||||
Client.setReady();
|
||||
|
||||
$scope.config = Client.getConfig();
|
||||
|
||||
$scope.initialized = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$scope.login();
|
||||
}
|
||||
});
|
||||
|
||||
// wait till the view has loaded until showing a modal dialog
|
||||
Client.onConfig(function (config) {
|
||||
if (config.progress.update) {
|
||||
window.location.href = '/update.html';
|
||||
}
|
||||
|
||||
if (config.cloudronName) {
|
||||
document.title = config.cloudronName;
|
||||
}
|
||||
});
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['updateModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find("[autofocus]:first").focus();
|
||||
});
|
||||
});
|
||||
}]);
|
||||
215
webadmin/src/js/setup.js
Normal file
215
webadmin/src/js/setup.js
Normal file
@@ -0,0 +1,215 @@
|
||||
'use strict';
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['ngRoute', 'ngAnimate', 'angular-md5', 'ui-notification']);
|
||||
|
||||
app.directive('ngEnter', function () {
|
||||
return function (scope, element, attrs) {
|
||||
element.bind('keydown keypress', function (event) {
|
||||
if(event.which === 13) {
|
||||
scope.$apply(function (){
|
||||
scope.$eval(attrs.ngEnter);
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// setup all major application routes
|
||||
app.config(['$routeProvider', function ($routeProvider) {
|
||||
$routeProvider.when('/', {
|
||||
redirectTo: '/step1'
|
||||
}).when('/step1', {
|
||||
controller: 'StepController',
|
||||
templateUrl: 'views/setup/step1.html'
|
||||
}).when('/step2', {
|
||||
controller: 'StepController',
|
||||
templateUrl: 'views/setup/step2.html'
|
||||
}).when('/step3', {
|
||||
controller: 'StepController',
|
||||
templateUrl: 'views/setup/step3.html'
|
||||
}).when('/step4', {
|
||||
controller: 'FinishController',
|
||||
templateUrl: 'views/setup/step4.html'
|
||||
}).otherwise({ redirectTo: '/'});
|
||||
}]);
|
||||
|
||||
app.service('Wizard', [ function () {
|
||||
var instance = null;
|
||||
|
||||
function Wizard() {
|
||||
this.username = '';
|
||||
this.email = '';
|
||||
this.password = '';
|
||||
this.name = '';
|
||||
this.availableAvatars = [{
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/avatar_0.png',
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/cloudfacegreen.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/cloudfaceturquoise.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/cloudglassesgreen.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/cloudglassespink.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/cloudglassesturquoise.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/cloudglassesyellow.png'
|
||||
}];
|
||||
this.avatar = {};
|
||||
this.avatarBlob = null;
|
||||
}
|
||||
|
||||
Wizard.prototype.setPreviewAvatar = function (avatar) {
|
||||
var that = this;
|
||||
|
||||
this.avatar = avatar;
|
||||
|
||||
// scale image and get the blob now
|
||||
var img = document.getElementById('previewAvatar');
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = 256;
|
||||
canvas.height = 256;
|
||||
|
||||
var imageDimensionRatio = img.width / img.height;
|
||||
var canvasDimensionRatio = canvas.width / canvas.height;
|
||||
var renderableHeight, renderableWidth, xStart, yStart;
|
||||
|
||||
if (imageDimensionRatio > canvasDimensionRatio) {
|
||||
renderableHeight = canvas.height;
|
||||
renderableWidth = img.width * (renderableHeight / img.height);
|
||||
xStart = (canvas.width - renderableWidth) / 2;
|
||||
yStart = 0;
|
||||
} else if (imageDimensionRatio < canvasDimensionRatio) {
|
||||
renderableWidth = canvas.width;
|
||||
renderableHeight = img.height * (renderableWidth / img.width);
|
||||
xStart = 0;
|
||||
yStart = (canvas.height - renderableHeight) / 2;
|
||||
} else {
|
||||
renderableHeight = canvas.height;
|
||||
renderableWidth = canvas.width;
|
||||
xStart = 0;
|
||||
yStart = 0;
|
||||
}
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(img, xStart, yStart, renderableWidth, renderableHeight);
|
||||
|
||||
canvas.toBlob(function (blob) {
|
||||
that.avatarBlob = blob;
|
||||
});
|
||||
};
|
||||
|
||||
instance = new Wizard();
|
||||
return instance;
|
||||
}]);
|
||||
|
||||
app.controller('StepController', ['$scope', '$location', 'Wizard', function ($scope, $location, Wizard) {
|
||||
$scope.wizard = Wizard;
|
||||
|
||||
$scope.next = function (page, bad) {
|
||||
if (!bad) $location.path(page);
|
||||
};
|
||||
|
||||
$scope.focusNext = function (elemId, bad) {
|
||||
if (!bad) $('#' + elemId).focus();
|
||||
};
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
$('a[autofocus]').focus();
|
||||
$('input[autofocus]').focus();
|
||||
});
|
||||
|
||||
$scope.showCustomAvatarSelector = function () {
|
||||
$('#avatarFileInput').click();
|
||||
};
|
||||
|
||||
// cheap way to detect if we are in avatar and name selection step
|
||||
if ($('#previewAvatar').get(0) && $('#avatarFileInput').get(0)) {
|
||||
$('#avatarFileInput').get(0).onchange = function (event) {
|
||||
var fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
$scope.$apply(function () {
|
||||
var tmp = {
|
||||
file: event.target.files[0],
|
||||
data: fr.result,
|
||||
url: null
|
||||
};
|
||||
|
||||
$scope.wizard.availableAvatars.push(tmp);
|
||||
$scope.wizard.setPreviewAvatar(tmp);
|
||||
});
|
||||
};
|
||||
fr.readAsDataURL(event.target.files[0]);
|
||||
};
|
||||
|
||||
$scope.wizard.setPreviewAvatar($scope.wizard.availableAvatars[0]);
|
||||
}
|
||||
}]);
|
||||
|
||||
app.controller('FinishController', ['$scope', '$location', '$timeout', 'Wizard', 'Client', function ($scope, $location, $timeout, Wizard, Client) {
|
||||
$scope.wizard = Wizard;
|
||||
|
||||
Client.createAdmin($scope.wizard.username, $scope.wizard.password, $scope.wizard.email, $scope.wizard.name, $scope.setupToken, function (error) {
|
||||
if (error) {
|
||||
console.error('Internal error', error);
|
||||
window.location.href = '/error.html';
|
||||
return;
|
||||
}
|
||||
|
||||
Client.changeCloudronAvatar($scope.wizard.avatarBlob, function (error) {
|
||||
if (error) return console.error('Unable to set avatar.', error);
|
||||
|
||||
window.location.href = '/';
|
||||
});
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller('SetupController', ['$scope', '$location', 'Client', 'Wizard', function ($scope, $location, Client, Wizard) {
|
||||
$scope.initialized = false;
|
||||
|
||||
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own
|
||||
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; }, {});
|
||||
|
||||
if (!search.setupToken) return window.location.href = '/error.html?errorCode=2';
|
||||
$scope.setupToken = search.setupToken;
|
||||
|
||||
if (!search.email) return window.location.href = '/error.html?errorCode=3';
|
||||
Wizard.email = search.email;
|
||||
|
||||
Wizard.hostname = window.location.host.indexOf('my-') === 0 ? window.location.host.slice(3) : window.location.host;
|
||||
|
||||
Client.isServerFirstTime(function (error, isFirstTime) {
|
||||
if (error) {
|
||||
window.location.href = '/error.html';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFirstTime) {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
$location.path('/step1');
|
||||
|
||||
$scope.initialized = true;
|
||||
});
|
||||
}]);
|
||||
29
webadmin/src/js/update.js
Normal file
29
webadmin/src/js/update.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', []);
|
||||
|
||||
app.controller('Controller', ['$scope', '$http', '$interval', function ($scope, $http, $interval) {
|
||||
var apiOrigin = '';
|
||||
|
||||
function loadWebadmin() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
function fetchProgress() {
|
||||
$http.get(apiOrigin + '/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 !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||
if (data.update === null) return loadWebadmin();
|
||||
|
||||
$('#updateProgressBar').css('width', data.update.percent + '%');
|
||||
$('#updateProgressMessage').html(data.update.message);
|
||||
}).error(function (data, status) {
|
||||
console.error(status, data);
|
||||
});
|
||||
}
|
||||
|
||||
$interval(fetchProgress, 2000);
|
||||
|
||||
fetchProgress();
|
||||
}]);
|
||||
Reference in New Issue
Block a user