-
- Configuration Changes Saved
-
-
-
Your configuration changes have been saved to config.yaml
in the mounted config
- volume and will be applied the next time the container is restarted.
-
-
-
- It is highly recommended that you restart your container now and test these changes!
-
-
-
+
diff --git a/static/directives/cor-loader-inline.html b/static/directives/cor-loader-inline.html
new file mode 100644
index 000000000..39ffb5b99
--- /dev/null
+++ b/static/directives/cor-loader-inline.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/static/directives/cor-loader.html b/static/directives/cor-loader.html
new file mode 100644
index 000000000..112680a22
--- /dev/null
+++ b/static/directives/cor-loader.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/static/directives/cor-step-bar.html b/static/directives/cor-step-bar.html
new file mode 100644
index 000000000..274a2c924
--- /dev/null
+++ b/static/directives/cor-step-bar.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/static/directives/cor-step.html b/static/directives/cor-step.html
new file mode 100644
index 000000000..5339db30e
--- /dev/null
+++ b/static/directives/cor-step.html
@@ -0,0 +1,6 @@
+
+
+ {{ text }}
+
+
+
\ No newline at end of file
diff --git a/static/js/app.js b/static/js/app.js
index aef04c140..4725b0535 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -2225,8 +2225,10 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl, reloadOnSearch: false}).
when('/user/', {title: 'Account Settings', description:'Account settings for ' + title, templateUrl: '/static/partials/user-admin.html',
reloadOnSearch: false, controller: UserAdminCtrl}).
- when('/superuser/', {title: 'Enterprise Registry Setup', description:'Admin panel for ' + title, templateUrl: '/static/partials/super-user.html',
+ when('/superuser/', {title: 'Enterprise Registry Management', description:'Admin panel for ' + title, templateUrl: '/static/partials/super-user.html',
reloadOnSearch: false, controller: SuperUserAdminCtrl, newLayout: true}).
+ when('/setup/', {title: 'Enterprise Registry Setup', description:'Setup for ' + title, templateUrl: '/static/partials/setup.html',
+ reloadOnSearch: false, controller: SetupCtrl, newLayout: true}).
when('/guide/', {title: 'Guide', description:'Guide to using private docker repositories on ' + title,
templateUrl: '/static/partials/guide.html',
controller: GuideCtrl}).
@@ -3908,9 +3910,11 @@ quayApp.directive('registryName', function () {
replace: false,
transclude: true,
restrict: 'C',
- scope: {},
+ scope: {
+ 'isShort': '=isShort'
+ },
controller: function($scope, $element, Config) {
- $scope.name = Config.REGISTRY_TITLE;
+ $scope.name = $scope.isShort ? Config.REGISTRY_TITLE_SHORT : Config.REGISTRY_TITLE;
}
};
return directiveDefinitionObject;
@@ -6865,7 +6869,7 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
if (activeTab) {
changeTab(activeTab);
}
- }, 100); // 100ms to make sure angular has rendered.
+ }, 400); // 400ms to make sure angular has rendered.
});
var initallyChecked = false;
diff --git a/static/js/controllers.js b/static/js/controllers.js
index f4d5f2349..d78ef4bd5 100644
--- a/static/js/controllers.js
+++ b/static/js/controllers.js
@@ -2809,346 +2809,6 @@ function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $tim
loadApplicationInfo();
}
-
-function SuperUserAdminCtrl($scope, $timeout, ApiService, Features, UserService, AngularPollChannel) {
- if (!Features.SUPER_USERS) {
- return;
- }
-
- // Monitor any user changes and place the current user into the scope.
- UserService.updateUserIn($scope);
-
- $scope.configStatus = null;
- $scope.logsCounter = 0;
- $scope.newUser = {};
- $scope.createdUser = null;
- $scope.systemUsage = null;
- $scope.debugServices = null;
- $scope.debugLogs = null;
- $scope.pollChannel = null;
- $scope.logsScrolled = false;
- $scope.csrf_token = window.__token;
-
- $scope.showCreateUser = function() {
- $scope.createdUser = null;
- $('#createUserModal').modal('show');
- };
-
- $scope.viewSystemLogs = function(service) {
- if ($scope.pollChannel) {
- $scope.pollChannel.stop();
- }
-
- $scope.debugService = service;
- $scope.debugLogs = null;
-
- $scope.pollChannel = AngularPollChannel.create($scope, $scope.loadServiceLogs, 2 * 1000 /* 2s */);
- $scope.pollChannel.start();
- };
-
- $scope.loadServiceLogs = function(callback) {
- if (!$scope.debugService) { return; }
-
- var params = {
- 'service': $scope.debugService
- };
-
- var errorHandler = ApiService.errorDisplay('Cannot load system logs. Please contact support.',
- function() {
- callback(false);
- })
-
- ApiService.getSystemLogs(null, params, /* background */true).then(function(resp) {
- $scope.debugLogs = resp['logs'];
- callback(true);
- }, errorHandler);
- };
-
- $scope.loadDebugServices = function() {
- if ($scope.pollChannel) {
- $scope.pollChannel.stop();
- }
-
- $scope.debugService = null;
-
- ApiService.listSystemLogServices().then(function(resp) {
- $scope.debugServices = resp['services'];
- }, ApiService.errorDisplay('Cannot load system logs. Please contact support.'))
- };
-
- $scope.getUsage = function() {
- if ($scope.systemUsage) { return; }
-
- ApiService.getSystemUsage().then(function(resp) {
- $scope.systemUsage = resp;
- }, ApiService.errorDisplay('Cannot load system usage. Please contact support.'))
- }
-
- $scope.loadUsageLogs = function() {
- $scope.logsCounter++;
- };
-
- $scope.loadUsers = function() {
- if ($scope.users) {
- return;
- }
-
- $scope.loadUsersInternal();
- };
-
- $scope.loadUsersInternal = function() {
- ApiService.listAllUsers().then(function(resp) {
- $scope.users = resp['users'];
- $scope.showInterface = true;
- }, function(resp) {
- $scope.users = [];
- $scope.usersError = resp['data']['message'] || resp['data']['error_description'];
- });
- };
-
- $scope.showChangePassword = function(user) {
- $scope.userToChange = user;
- $('#changePasswordModal').modal({});
- };
-
- $scope.createUser = function() {
- $scope.creatingUser = true;
- $scope.createdUser = null;
-
- var errorHandler = ApiService.errorDisplay('Cannot create user', function() {
- $scope.creatingUser = false;
- $('#createUserModal').modal('hide');
- });
-
- ApiService.createInstallUser($scope.newUser, null).then(function(resp) {
- $scope.creatingUser = false;
- $scope.newUser = {};
- $scope.createdUser = resp;
- $scope.loadUsersInternal();
- }, errorHandler)
- };
-
- $scope.showDeleteUser = function(user) {
- if (user.username == UserService.currentUser().username) {
- bootbox.dialog({
- "message": 'Cannot delete yourself!',
- "title": "Cannot delete user",
- "buttons": {
- "close": {
- "label": "Close",
- "className": "btn-primary"
- }
- }
- });
- return;
- }
-
- $scope.userToDelete = user;
- $('#confirmDeleteUserModal').modal({});
- };
-
- $scope.changeUserPassword = function(user) {
- $('#changePasswordModal').modal('hide');
-
- var params = {
- 'username': user.username
- };
-
- var data = {
- 'password': user.password
- };
-
- ApiService.changeInstallUser(data, params).then(function(resp) {
- $scope.loadUsersInternal();
- }, ApiService.errorDisplay('Could not change user'));
- };
-
- $scope.deleteUser = function(user) {
- $('#confirmDeleteUserModal').modal('hide');
-
- var params = {
- 'username': user.username
- };
-
- ApiService.deleteInstallUser(null, params).then(function(resp) {
- $scope.loadUsersInternal();
- }, ApiService.errorDisplay('Cannot delete user'));
- };
-
- $scope.sendRecoveryEmail = function(user) {
- var params = {
- 'username': user.username
- };
-
- ApiService.sendInstallUserRecoveryEmail(null, params).then(function(resp) {
- bootbox.dialog({
- "message": "A recovery email has been sent to " + resp['email'],
- "title": "Recovery email sent",
- "buttons": {
- "close": {
- "label": "Close",
- "className": "btn-primary"
- }
- }
- });
-
- }, ApiService.errorDisplay('Cannot send recovery email'))
- };
-
- $scope.parseDbUri = function(value) {
- if (!value) { return null; }
-
- // Format: mysql+pymysql://
:@/
- var uri = URI(value);
- return {
- 'kind': uri.protocol(),
- 'username': uri.username(),
- 'password': uri.password(),
- 'server': uri.host(),
- 'database': uri.path() ? uri.path().substr(1) : ''
- };
- };
-
- $scope.serializeDbUri = function(fields) {
- if (!fields['server']) { return '' };
-
- try {
- var uri = URI();
- uri = uri && uri.host(fields['server']);
- uri = uri && uri.protocol(fields['kind']);
- uri = uri && uri.username(fields['username']);
- uri = uri && uri.password(fields['password']);
- uri = uri && uri.path('/' + (fields['database'] || ''));
- uri = uri && uri.toString();
- } catch (ex) {
- return '';
- }
-
- return uri;
- };
-
- $scope.createSuperUser = function() {
- $scope.createSuperuserIssue = null;
- $scope.configStep = 'creating-superuser';
- ApiService.scCreateInitialSuperuser($scope.superUser, null).then(function(resp) {
- UserService.load();
- $('#createSuperuserModal').modal('hide');
- $scope.checkContainerStatus();
- }, function(resp) {
- $scope.configStep = 'create-superuser';
- $scope.createSuperuserIssue = ApiService.getErrorMessage(resp, 'Could not create superuser');
- });
- };
-
- $scope.checkContainerStatus = function() {
- var errorHandler = function(resp) {
- if ((resp.status == 404 || resp.status == 502) && $scope.configStep == 'valid-database') {
- // Container has not yet come back up, so we schedule another check.
- $scope.waitForValidConfig();
- return;
- }
-
- return ApiService.errorDisplay('Cannot load status. Please report this to support')(resp);
- };
-
- ApiService.scRegistryStatus(null, null).then(function(resp) {
- $scope.configStatus = resp;
-
- // !dir_exists -> No mounted directory.
- if (!$scope.configStatus.dir_exists) {
- bootbox.dialog({
- "message": "No volume was found mounted at path /conf/stack
. " +
- "Please rerun the container with the volume mounted and refresh this page." +
- "
For more information: " +
- "" +
- "Enterprise Registry Setup Guide",
- "title": "Missing mounted configuration volume",
- "buttons": {},
- "closeButton": false
- });
- return;
- }
-
- // is_testing = False -> valid config
- // ready = False -> no valid superusers yet
- if (!$scope.configStatus.is_testing && !$scope.configStatus.ready) {
- $('#initializeConfigModal').modal('hide');
-
- $scope.superUser = {};
- $scope.configStep = 'create-superuser';
- $('#createSuperuserModal').modal({
- keyboard: false,
- backdrop: 'static'
- });
- return;
- }
-
- // file_exists -> config file, but possibly invalid DB
- // valid_db = False -> invalid DB
- // is_testing = True -> still in testing mode
- if (!$scope.configStatus.file_exists || !$scope.configStatus.valid_db ||
- $scope.configStatus.is_testing) {
- $('#createSuperuserModal').modal('hide');
-
- $scope.databaseUri = '';
- $scope.configStep = 'enter-database';
-
- // Handle the case where they have entered a valid DB config, refreshed, but have not
- // yet restarted the DB container.
- if ($scope.configStatus.file_exists && $scope.configStatus.is_testing) {
- $scope.waitForValidConfig();
- }
-
- $('#initializeConfigModal').modal({
- keyboard: false,
- backdrop: 'static'
- });
- return;
- }
- }, errorHandler, /* background */true);
- };
-
- $scope.waitForValidConfig = function() {
- $scope.configStep = 'valid-database';
- $timeout(function() {
- $scope.checkContainerStatus();
- }, 3000);
- };
-
- $scope.validateDatabase = function() {
- $scope.configStep = 'validating-database';
- $scope.databaseInvalid = null;
-
- var data = {
- 'config': {
- 'DB_URI': $scope.databaseUri
- },
- 'hostname': window.location.host
- };
-
- var params = {
- 'service': 'database'
- };
-
- ApiService.scValidateConfig(data, params).then(function(resp) {
- var status = resp.status;
-
- if (status) {
- $scope.configStep = 'updating-config';
- ApiService.scUpdateConfig(data, null).then(function(resp) {
- $scope.waitForValidConfig();
- }, ApiService.errorDisplay('Cannot update config. Please report this to support'));
- } else {
- $scope.configStep = 'invalid-database';
- $scope.databaseInvalid = resp.reason;
- }
- }, ApiService.errorDisplay('Cannot validate database. Please report this to support'));
- };
-
- // Load the configuration status.
- $scope.checkContainerStatus();
-}
-
function TourCtrl($scope, $location) {
$scope.kind = $location.path().substring('/tour/'.length);
}
diff --git a/static/js/controllers/setup.js b/static/js/controllers/setup.js
new file mode 100644
index 000000000..f5d1b941a
--- /dev/null
+++ b/static/js/controllers/setup.js
@@ -0,0 +1,281 @@
+function SetupCtrl($scope, $timeout, ApiService, Features, UserService, AngularPollChannel, CoreDialog) {
+ if (!Features.SUPER_USERS) {
+ return;
+ }
+
+ // Note: The values of the enumeration are important for isStepFamily. For example,
+ // *all* states under the "configuring db" family must start with "config-db".
+ $scope.States = {
+ // Loading the state of the product.
+ 'LOADING': 'loading',
+
+ // The configuration directory is missing.
+ 'MISSING_CONFIG_DIR': 'missing-config-dir',
+
+ // The config.yaml exists but it is invalid.
+ 'INVALID_CONFIG': 'config-invalid',
+
+ // DB is being configured.
+ 'CONFIG_DB': 'config-db',
+
+ // DB information is being validated.
+ 'VALIDATING_DB': 'config-db-validating',
+
+ // DB information is being saved to the config.
+ 'SAVING_DB': 'config-db-saving',
+
+ // A validation error occurred with the database.
+ 'DB_ERROR': 'config-db-error',
+
+ // Database is being setup.
+ 'DB_SETUP': 'setup-db',
+
+ // Database setup has succeeded.
+ 'DB_SETUP_SUCCESS': 'setup-db-success',
+
+ // An error occurred when setting up the database.
+ 'DB_SETUP_ERROR': 'setup-db-error',
+
+ // The container is being restarted for the database changes.
+ 'DB_RESTARTING': 'setup-db-restarting',
+
+ // A superuser is being configured.
+ 'CREATE_SUPERUSER': 'create-superuser',
+
+ // The superuser is being created.
+ 'CREATING_SUPERUSER': 'create-superuser-creating',
+
+ // An error occurred when setting up the superuser.
+ 'SUPERUSER_ERROR': 'create-superuser-error',
+
+ // The superuser was created successfully.
+ 'SUPERUSER_CREATED': 'create-superuser-created',
+
+ // General configuration is being setup.
+ 'CONFIG': 'config',
+
+ // The configuration is fully valid.
+ 'VALID_CONFIG': 'valid-config',
+
+ // The container is being restarted for the configuration changes.
+ 'CONFIG_RESTARTING': 'config-restarting',
+
+ // The product is ready for use.
+ 'READY': 'ready'
+ }
+
+ $scope.csrf_token = window.__token;
+ $scope.currentStep = $scope.States.LOADING;
+ $scope.errors = {};
+ $scope.stepProgress = [];
+
+ $scope.$watch('currentStep', function(currentStep) {
+ $scope.stepProgress = $scope.getProgress(currentStep);
+
+ switch (currentStep) {
+ case $scope.States.CONFIG:
+ $('#setupModal').modal('hide');
+ break;
+
+ case $scope.States.MISSING_CONFIG_DIR:
+ $scope.showMissingConfigDialog();
+ break;
+
+ case $scope.States.INVALID_CONFIG:
+ $scope.showInvalidConfigDialog();
+ break;
+
+ case $scope.States.DB_SETUP:
+ $scope.performDatabaseSetup();
+ // Fall-through.
+
+ case $scope.States.CREATE_SUPERUSER:
+ case $scope.States.DB_RESTARTING:
+ case $scope.States.CONFIG_DB:
+ case $scope.States.VALID_CONFIG:
+ case $scope.States.READY:
+ $('#setupModal').modal({
+ keyboard: false,
+ backdrop: 'static'
+ });
+ break;
+ }
+ });
+
+ $scope.showSuperuserPanel = function() {
+ $('#setupModal').modal('hide');
+ window.location = '/superuser';
+ };
+
+ $scope.configurationSaved = function() {
+ $scope.currentStep = $scope.States.VALID_CONFIG;
+ };
+
+ $scope.getProgress = function(step) {
+ var isStep = $scope.isStep;
+ var isStepFamily = $scope.isStepFamily;
+ var States = $scope.States;
+
+ return [
+ isStepFamily(step, States.CONFIG_DB),
+ isStepFamily(step, States.DB_SETUP),
+ isStep(step, States.DB_RESTARTING),
+ isStepFamily(step, States.CREATE_SUPERUSER),
+ isStep(step, States.CONFIG),
+ isStep(step, States.VALID_CONFIG),
+ isStep(step, States.CONFIG_RESTARTING),
+ isStep(step, States.READY)
+ ];
+ };
+
+ $scope.isStepFamily = function(step, family) {
+ if (!step) { return false; }
+ return step.indexOf(family) == 0;
+ };
+
+ $scope.isStep = function(step) {
+ for (var i = 1; i < arguments.length; ++i) {
+ if (arguments[i] == step) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ $scope.showInvalidConfigDialog = function() {
+ var message = "The config.yaml
file found in conf/stack
could not be parsed."
+ var title = "Invalid configuration file";
+ CoreDialog.fatal(title, message);
+ };
+
+
+ $scope.showMissingConfigDialog = function() {
+ var message = "A volume should be mounted into the container at /conf/stack
: " +
+ "
docker run -v /path/to/config:/conf/stack
" +
+ "
Once fixed, restart the container. For more information, " +
+ "" +
+ "Read the Setup Guide"
+
+ var title = "Missing configuration volume";
+ CoreDialog.fatal(title, message);
+ };
+
+ $scope.restartContainer = function(restartState) {
+ $scope.currentStep = restartState;
+ ApiService.scShutdownContainer(null, null).then(function(resp) {
+ $timeout(function() {
+ $scope.checkStatus();
+ }, 2000);
+ }, ApiService.errorDisplay('Cannot restart container. Please report this to support.'))
+ };
+
+ $scope.scheduleStatusCheck = function() {
+ $timeout(function() {
+ $scope.checkStatus();
+ }, 3000);
+ };
+
+ $scope.checkStatus = function() {
+ var errorHandler = function(resp) {
+ if (resp.status == 404 || resp.status == 502) {
+ // Container has not yet come back up, so we schedule another check.
+ $scope.scheduleStatusCheck();
+ return;
+ }
+
+ return ApiService.errorDisplay('Cannot load status. Please report this to support')(resp);
+ };
+
+ ApiService.scRegistryStatus(null, null).then(function(resp) {
+ $scope.currentStep = resp['status'];
+ }, errorHandler, /* background */true);
+ };
+
+ $scope.parseDbUri = function(value) {
+ if (!value) { return null; }
+
+ // Format: mysql+pymysql://:@/
+ var uri = URI(value);
+ return {
+ 'kind': uri.protocol(),
+ 'username': uri.username(),
+ 'password': uri.password(),
+ 'server': uri.host(),
+ 'database': uri.path() ? uri.path().substr(1) : ''
+ };
+ };
+
+ $scope.serializeDbUri = function(fields) {
+ if (!fields['server']) { return '' };
+
+ try {
+ var uri = URI();
+ uri = uri && uri.host(fields['server']);
+ uri = uri && uri.protocol(fields['kind']);
+ uri = uri && uri.username(fields['username']);
+ uri = uri && uri.password(fields['password']);
+ uri = uri && uri.path('/' + (fields['database'] || ''));
+ uri = uri && uri.toString();
+ } catch (ex) {
+ return '';
+ }
+
+ return uri;
+ };
+
+ $scope.createSuperUser = function() {
+ $scope.currentStep = $scope.States.CREATING_SUPERUSER;
+ ApiService.scCreateInitialSuperuser($scope.superUser, null).then(function(resp) {
+ UserService.load();
+ $scope.checkStatus();
+ }, function(resp) {
+ $scope.currentStep = $scope.States.SUPERUSER_ERROR;
+ $scope.errors.SuperuserCreationError = ApiService.getErrorMessage(resp, 'Could not create superuser');
+ });
+ };
+
+ $scope.performDatabaseSetup = function() {
+ $scope.currentStep = $scope.States.DB_SETUP;
+ ApiService.scSetupDatabase(null, null).then(function(resp) {
+ if (resp['error']) {
+ $scope.currentStep = $scope.States.DB_SETUP_ERROR;
+ $scope.errors.DatabaseSetupError = resp['error'];
+ } else {
+ $scope.currentStep = $scope.States.DB_SETUP_SUCCESS;
+ }
+ }, ApiService.errorDisplay('Could not setup database. Please report this to support.'))
+ };
+
+ $scope.validateDatabase = function() {
+ $scope.currentStep = $scope.States.VALIDATING_DB;
+ $scope.databaseInvalid = null;
+
+ var data = {
+ 'config': {
+ 'DB_URI': $scope.databaseUri
+ },
+ 'hostname': window.location.host
+ };
+
+ var params = {
+ 'service': 'database'
+ };
+
+ ApiService.scValidateConfig(data, params).then(function(resp) {
+ var status = resp.status;
+
+ if (status) {
+ $scope.currentStep = $scope.States.SAVING_DB;
+ ApiService.scUpdateConfig(data, null).then(function(resp) {
+ $scope.checkStatus();
+ }, ApiService.errorDisplay('Cannot update config. Please report this to support'));
+ } else {
+ $scope.currentStep = $scope.States.DB_ERROR;
+ $scope.errors.DatabaseValidationError = resp.reason;
+ }
+ }, ApiService.errorDisplay('Cannot validate database. Please report this to support'));
+ };
+
+ // Load the initial status.
+ $scope.checkStatus();
+}
\ No newline at end of file
diff --git a/static/js/controllers/superuser.js b/static/js/controllers/superuser.js
new file mode 100644
index 000000000..3efbd02d3
--- /dev/null
+++ b/static/js/controllers/superuser.js
@@ -0,0 +1,205 @@
+function SuperUserAdminCtrl($scope, $timeout, ApiService, Features, UserService, AngularPollChannel, CoreDialog) {
+ if (!Features.SUPER_USERS) {
+ return;
+ }
+
+ // Monitor any user changes and place the current user into the scope.
+ UserService.updateUserIn($scope);
+
+ $scope.configStatus = null;
+ $scope.logsCounter = 0;
+ $scope.newUser = {};
+ $scope.createdUser = null;
+ $scope.systemUsage = null;
+ $scope.debugServices = null;
+ $scope.debugLogs = null;
+ $scope.pollChannel = null;
+ $scope.logsScrolled = false;
+ $scope.csrf_token = window.__token;
+
+ $scope.showCreateUser = function() {
+ $scope.createdUser = null;
+ $('#createUserModal').modal('show');
+ };
+
+ $scope.viewSystemLogs = function(service) {
+ if ($scope.pollChannel) {
+ $scope.pollChannel.stop();
+ }
+
+ $scope.debugService = service;
+ $scope.debugLogs = null;
+
+ $scope.pollChannel = AngularPollChannel.create($scope, $scope.loadServiceLogs, 2 * 1000 /* 2s */);
+ $scope.pollChannel.start();
+ };
+
+ $scope.loadServiceLogs = function(callback) {
+ if (!$scope.debugService) { return; }
+
+ var params = {
+ 'service': $scope.debugService
+ };
+
+ var errorHandler = ApiService.errorDisplay('Cannot load system logs. Please contact support.',
+ function() {
+ callback(false);
+ })
+
+ ApiService.getSystemLogs(null, params, /* background */true).then(function(resp) {
+ $scope.debugLogs = resp['logs'];
+ callback(true);
+ }, errorHandler);
+ };
+
+ $scope.loadDebugServices = function() {
+ if ($scope.pollChannel) {
+ $scope.pollChannel.stop();
+ }
+
+ $scope.debugService = null;
+
+ ApiService.listSystemLogServices().then(function(resp) {
+ $scope.debugServices = resp['services'];
+ }, ApiService.errorDisplay('Cannot load system logs. Please contact support.'))
+ };
+
+ $scope.getUsage = function() {
+ if ($scope.systemUsage) { return; }
+
+ ApiService.getSystemUsage().then(function(resp) {
+ $scope.systemUsage = resp;
+ }, ApiService.errorDisplay('Cannot load system usage. Please contact support.'))
+ }
+
+ $scope.loadUsageLogs = function() {
+ $scope.logsCounter++;
+ };
+
+ $scope.loadUsers = function() {
+ if ($scope.users) {
+ return;
+ }
+
+ $scope.loadUsersInternal();
+ };
+
+ $scope.loadUsersInternal = function() {
+ ApiService.listAllUsers().then(function(resp) {
+ $scope.users = resp['users'];
+ $scope.showInterface = true;
+ }, function(resp) {
+ $scope.users = [];
+ $scope.usersError = resp['data']['message'] || resp['data']['error_description'];
+ });
+ };
+
+ $scope.showChangePassword = function(user) {
+ $scope.userToChange = user;
+ $('#changePasswordModal').modal({});
+ };
+
+ $scope.createUser = function() {
+ $scope.creatingUser = true;
+ $scope.createdUser = null;
+
+ var errorHandler = ApiService.errorDisplay('Cannot create user', function() {
+ $scope.creatingUser = false;
+ $('#createUserModal').modal('hide');
+ });
+
+ ApiService.createInstallUser($scope.newUser, null).then(function(resp) {
+ $scope.creatingUser = false;
+ $scope.newUser = {};
+ $scope.createdUser = resp;
+ $scope.loadUsersInternal();
+ }, errorHandler)
+ };
+
+ $scope.showDeleteUser = function(user) {
+ if (user.username == UserService.currentUser().username) {
+ bootbox.dialog({
+ "message": 'Cannot delete yourself!',
+ "title": "Cannot delete user",
+ "buttons": {
+ "close": {
+ "label": "Close",
+ "className": "btn-primary"
+ }
+ }
+ });
+ return;
+ }
+
+ $scope.userToDelete = user;
+ $('#confirmDeleteUserModal').modal({});
+ };
+
+ $scope.changeUserPassword = function(user) {
+ $('#changePasswordModal').modal('hide');
+
+ var params = {
+ 'username': user.username
+ };
+
+ var data = {
+ 'password': user.password
+ };
+
+ ApiService.changeInstallUser(data, params).then(function(resp) {
+ $scope.loadUsersInternal();
+ }, ApiService.errorDisplay('Could not change user'));
+ };
+
+ $scope.deleteUser = function(user) {
+ $('#confirmDeleteUserModal').modal('hide');
+
+ var params = {
+ 'username': user.username
+ };
+
+ ApiService.deleteInstallUser(null, params).then(function(resp) {
+ $scope.loadUsersInternal();
+ }, ApiService.errorDisplay('Cannot delete user'));
+ };
+
+ $scope.sendRecoveryEmail = function(user) {
+ var params = {
+ 'username': user.username
+ };
+
+ ApiService.sendInstallUserRecoveryEmail(null, params).then(function(resp) {
+ bootbox.dialog({
+ "message": "A recovery email has been sent to " + resp['email'],
+ "title": "Recovery email sent",
+ "buttons": {
+ "close": {
+ "label": "Close",
+ "className": "btn-primary"
+ }
+ }
+ });
+
+ }, ApiService.errorDisplay('Cannot send recovery email'))
+ };
+
+ $scope.checkStatus = function() {
+ ApiService.scRegistryStatus(null, null).then(function(resp) {
+ $scope.configStatus = resp['status'];
+ if ($scope.configStatus == 'ready') {
+ $scope.loadUsers();
+ } else {
+ var message = "Installation of this product has not yet been completed." +
+ "
Please read the " +
+ "" +
+ "Setup Guide"
+
+ var title = "Installation Incomplete";
+ CoreDialog.fatal(title, message);
+ }
+ }, ApiService.errorDisplay('Cannot load status. Please report this to support'), /* background */true);
+ };
+
+ // Load the initial status.
+ $scope.checkStatus();
+}
\ No newline at end of file
diff --git a/static/js/core-config-setup.js b/static/js/core-config-setup.js
index 0b1f42e62..7e312da44 100644
--- a/static/js/core-config-setup.js
+++ b/static/js/core-config-setup.js
@@ -7,7 +7,8 @@ angular.module("core-config-setup", ['angularFileUpload'])
transclude: true,
restrict: 'C',
scope: {
- 'isActive': '=isActive'
+ 'isActive': '=isActive',
+ 'configurationSaved': '&configurationSaved'
},
controller: function($rootScope, $scope, $element, $timeout, ApiService) {
$scope.HOSTNAME_REGEX = '^[a-zA-Z-0-9\.]+(:[0-9]+)?$';
@@ -166,6 +167,10 @@ angular.module("core-config-setup", ['angularFileUpload'])
$scope.saveConfiguration = function() {
$scope.savingConfiguration = true;
+ // Make sure to note that fully verified setup is completed. We use this as a signal
+ // in the setup tool.
+ $scope.config['SETUP_COMPLETE'] = true;
+
var data = {
'config': $scope.config,
'hostname': window.location.host
@@ -173,7 +178,9 @@ angular.module("core-config-setup", ['angularFileUpload'])
ApiService.scUpdateConfig(data).then(function(resp) {
$scope.savingConfiguration = false;
- $scope.mapped.$hasChanges = false
+ $scope.mapped.$hasChanges = false;
+ $('#validateAndSaveModal').modal('hide');
+ $scope.configurationSaved({});
}, ApiService.errorDisplay('Could not save configuration. Please report this error.'));
};
diff --git a/static/js/core-ui.js b/static/js/core-ui.js
index 9a42fd8dc..fc1ea029c 100644
--- a/static/js/core-ui.js
+++ b/static/js/core-ui.js
@@ -1,4 +1,19 @@
angular.module("core-ui", [])
+ .factory('CoreDialog', [function() {
+ var service = {};
+ service['fatal'] = function(title, message) {
+ bootbox.dialog({
+ "title": title,
+ "message": "" + message,
+ "buttons": {},
+ "className": "co-dialog fatal-error",
+ "closeButton": false
+ });
+ };
+
+ return service;
+ }])
+
.directive('corLogBox', function() {
var directiveDefinitionObject = {
priority: 1,
@@ -210,7 +225,7 @@ angular.module("core-ui", [])
$scope.$on('$destroy', function() {
$(window).off("resize", handler);
$(window).off("scroll", handler);
- $internval.stop(stop);
+ $interval.cancel(stop);
});
}
};
@@ -218,6 +233,32 @@ angular.module("core-ui", [])
})
+ .directive('corLoaderInline', function() {
+ var directiveDefinitionObject = {
+ templateUrl: '/static/directives/cor-loader-inline.html',
+ replace: true,
+ restrict: 'C',
+ scope: {
+ },
+ controller: function($rootScope, $scope, $element) {
+ }
+ };
+ return directiveDefinitionObject;
+ })
+
+ .directive('corLoader', function() {
+ var directiveDefinitionObject = {
+ templateUrl: '/static/directives/cor-loader.html',
+ replace: true,
+ restrict: 'C',
+ scope: {
+ },
+ controller: function($rootScope, $scope, $element) {
+ }
+ };
+ return directiveDefinitionObject;
+ })
+
.directive('corTab', function() {
var directiveDefinitionObject = {
priority: 4,
@@ -235,4 +276,54 @@ angular.module("core-ui", [])
}
};
return directiveDefinitionObject;
+ })
+
+ .directive('corStep', function() {
+ var directiveDefinitionObject = {
+ priority: 4,
+ templateUrl: '/static/directives/cor-step.html',
+ replace: true,
+ transclude: false,
+ requires: '^corStepBar',
+ restrict: 'C',
+ scope: {
+ 'icon': '@icon',
+ 'title': '@title',
+ 'text': '@text'
+ },
+ controller: function($rootScope, $scope, $element) {
+ }
+ };
+ return directiveDefinitionObject;
+ })
+
+ .directive('corStepBar', function() {
+ var directiveDefinitionObject = {
+ priority: 4,
+ templateUrl: '/static/directives/cor-step-bar.html',
+ replace: true,
+ transclude: true,
+ restrict: 'C',
+ scope: {
+ 'progress': '=progress'
+ },
+ controller: function($rootScope, $scope, $element) {
+ $scope.$watch('progress', function(progress) {
+ var index = 0;
+ for (var i = 0; i < progress.length; ++i) {
+ if (progress[i]) {
+ index = i;
+ }
+ }
+
+ $element.find('.transclude').children('.co-step-element').each(function(i, elem) {
+ $(elem).removeClass('active');
+ if (i <= index) {
+ $(elem).addClass('active');
+ }
+ });
+ });
+ }
+ };
+ return directiveDefinitionObject;
});
\ No newline at end of file
diff --git a/static/partials/setup.html b/static/partials/setup.html
new file mode 100644
index 000000000..77d9d4f29
--- /dev/null
+++ b/static/partials/setup.html
@@ -0,0 +1,289 @@
+
+
+
+
+
+ Enterprise Registry Setup
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Almost done!
+
Configure your Redis database and other settings below
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ is currently being restarted.
+
+ This can take several minutes. If the container does not restart on its own,
+ please reexecute the docker run
command.
+
+
+
+
+ Installation and setup of is complete. You can
+ now invite users to join, create organizations and start pushing and pulling
+ repositories.
+
+
+
+
+ All configuration has been validated and saved. The container must be restarted to
+ apply the configuration changes.
+
+
+
+
+ The database has been setup and is ready. The container must be restarted to
+ apply the configuration changes.
+
+
+
+
+
+ is currently setting up its database
+ schema.
+
+ This can take several minutes.
+
+
+
+
+
+ Please enter the connection details for your empty database. The schema will be created in the following step.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/partials/super-user.html b/static/partials/super-user.html
index 7800c9b1e..eab840971 100644
--- a/static/partials/super-user.html
+++ b/static/partials/super-user.html
@@ -1,6 +1,6 @@
-
-
+
+
Enterprise Registry Management
@@ -8,11 +8,8 @@
-
-
-
-
+
@@ -24,17 +21,21 @@
+
+
+
-
-
+
-
+
@@ -65,7 +66,7 @@
-
+
@@ -88,9 +89,9 @@
For more information:
See Here.
-
-
-
+
+
+
{{ usersError }}
@@ -207,7 +208,7 @@
-
-
-
-
-
-
-
- Creating super user account.... Please Wait
-
-
-
-
-
-
-
-
-
-
-
-
-
- Your database has been verified as working.
-
-
-
- Please restart the container, which will automatically generate the database's schema.
-
-
-
This operation may take a few minutes.
-
-
- Updating Configuration.... Please Wait
-
-
- Validating Database.... Please Wait
-
-
-
- Could not connect to or validate the database configuration found. Please reconfigure to continue.
-
-
-
- Please enter the connection details for your empty database. The schema will be created in the following step.
-
-
-
-
-
-
-
-
-
diff --git a/test/test_suconfig_api.py b/test/test_suconfig_api.py
index a8f74483b..ca05d8705 100644
--- a/test/test_suconfig_api.py
+++ b/test/test_suconfig_api.py
@@ -21,10 +21,7 @@ class TestSuperUserRegistryStatus(ApiTestCase):
def test_registry_status(self):
with ConfigForTesting():
json = self.getJsonResponse(SuperUserRegistryStatus)
- self.assertTrue(json['is_testing'])
- self.assertTrue(json['valid_db'])
- self.assertFalse(json['file_exists'])
- self.assertFalse(json['ready'])
+ self.assertEquals('config-db', json['status'])
class TestSuperUserConfigFile(ApiTestCase):
diff --git a/util/config/provider.py b/util/config/provider.py
index 7d135d40f..24380eab0 100644
--- a/util/config/provider.py
+++ b/util/config/provider.py
@@ -61,6 +61,12 @@ class BaseProvider(object):
"""
raise NotImplementedError
+ def requires_restart(self, app_config):
+ """ If true, the configuration loaded into memory for the app does not match that on disk,
+ indicating that this container requires a restart.
+ """
+ raise NotImplementedError
+
class FileConfigProvider(BaseProvider):
""" Implementation of the config provider that reads the data from the file system. """
@@ -104,6 +110,16 @@ class FileConfigProvider(BaseProvider):
def save_volume_file(self, filename, flask_file):
flask_file.save(os.path.join(self.config_volume, filename))
+ def requires_restart(self, app_config):
+ file_config = self.get_yaml()
+ if not file_config:
+ return False
+
+ for key in file_config:
+ if app_config.get(key) != file_config[key]:
+ return True
+
+ return False
class TestConfigProvider(BaseProvider):
""" Implementation of the config provider for testing. Everything is kept in-memory instead on
@@ -136,6 +152,9 @@ class TestConfigProvider(BaseProvider):
def save_volume_file(self, filename, flask_file):
self.files[filename] = ''
+ def requires_restart(self, app_config):
+ return False
+
def reset_for_test(self):
self._config['SUPER_USERS'] = ['devtable']
self.files = {}