Implement new step-by-step setup

This commit is contained in:
Joseph Schorr 2015-01-23 17:19:15 -05:00
parent 28d319ad26
commit c8229b9c8a
20 changed files with 1393 additions and 599 deletions

View file

@ -366,10 +366,6 @@
bottom: 0px;
}
.config-setup-tool .cor-floating-bottom-bar {
text-align: right;
}
.config-setup-tool .cor-floating-bottom-bar button i.fa {
margin-right: 6px;
}
@ -418,3 +414,285 @@
font-family: Consolas, "Lucida Console", Monaco, monospace;
font-size: 12px;
}
.co-m-loader, .co-m-inline-loader {
min-width: 28px; }
.co-m-loader {
display: block;
position: absolute;
left: 50%;
top: 50%;
margin: -11px 0 0 -13px; }
.co-m-inline-loader {
display: inline-block;
cursor: default; }
.co-m-inline-loader:hover {
text-decoration: none; }
.co-m-loader-dot__one, .co-m-loader-dot__two, .co-m-loader-dot__three {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
animation-fill-mode: both;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
-ms-animation-fill-mode: both;
-o-animation-fill-mode: both;
animation-name: bouncedelay;
animation-duration: 1s;
animation-timing-function: ease-in-out;
animation-delay: 0;
animation-direction: normal;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
animation-play-state: running;
-webkit-animation-name: bouncedelay;
-webkit-animation-duration: 1s;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-delay: 0;
-webkit-animation-direction: normal;
-webkit-animation-iteration-count: infinite;
-webkit-animation-fill-mode: forwards;
-webkit-animation-play-state: running;
-moz-animation-name: bouncedelay;
-moz-animation-duration: 1s;
-moz-animation-timing-function: ease-in-out;
-moz-animation-delay: 0;
-moz-animation-direction: normal;
-moz-animation-iteration-count: infinite;
-moz-animation-fill-mode: forwards;
-moz-animation-play-state: running;
display: inline-block;
height: 6px;
width: 6px;
background: #419eda;
border-radius: 100%;
display: inline-block; }
.co-m-loader-dot__one {
animation-delay: -0.32s;
-webkit-animation-delay: -0.32s;
-moz-animation-delay: -0.32s;
-ms-animation-delay: -0.32s;
-o-animation-delay: -0.32s; }
.co-m-loader-dot__two {
animation-delay: -0.16s;
-webkit-animation-delay: -0.16s;
-moz-animation-delay: -0.16s;
-ms-animation-delay: -0.16s;
-o-animation-delay: -0.16s; }
@-webkit-keyframes bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0.25, 0.25);
-moz-transform: scale(0.25, 0.25);
-ms-transform: scale(0.25, 0.25);
-o-transform: scale(0.25, 0.25);
transform: scale(0.25, 0.25); }
40% {
-webkit-transform: scale(1, 1);
-moz-transform: scale(1, 1);
-ms-transform: scale(1, 1);
-o-transform: scale(1, 1);
transform: scale(1, 1); } }
@-moz-keyframes bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0.25, 0.25);
-moz-transform: scale(0.25, 0.25);
-ms-transform: scale(0.25, 0.25);
-o-transform: scale(0.25, 0.25);
transform: scale(0.25, 0.25); }
40% {
-webkit-transform: scale(1, 1);
-moz-transform: scale(1, 1);
-ms-transform: scale(1, 1);
-o-transform: scale(1, 1);
transform: scale(1, 1); } }
@-ms-keyframes bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0.25, 0.25);
-moz-transform: scale(0.25, 0.25);
-ms-transform: scale(0.25, 0.25);
-o-transform: scale(0.25, 0.25);
transform: scale(0.25, 0.25); }
40% {
-webkit-transform: scale(1, 1);
-moz-transform: scale(1, 1);
-ms-transform: scale(1, 1);
-o-transform: scale(1, 1);
transform: scale(1, 1); } }
@keyframes bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0.25, 0.25);
-moz-transform: scale(0.25, 0.25);
-ms-transform: scale(0.25, 0.25);
-o-transform: scale(0.25, 0.25);
transform: scale(0.25, 0.25); }
40% {
-webkit-transform: scale(1, 1);
-moz-transform: scale(1, 1);
-ms-transform: scale(1, 1);
-o-transform: scale(1, 1);
transform: scale(1, 1); } }
.co-dialog .modal-body {
padding: 10px;
min-height: 100px;
}
.co-dialog .modal-content {
border-radius: 0px;
}
.co-dialog.fatal-error .modal-content {
padding-left: 175px;
}
.co-dialog.fatal-error .alert-icon-container-container {
position: absolute;
top: -36px;
left: -175px;
bottom: 20px;
}
.co-dialog.fatal-error .alert-icon-container {
height: 100%;
display: table;
}
.co-dialog.fatal-error .alert-icon {
display: table-cell;
vertical-align: middle;
border-right: 1px solid #eee;
margin-right: 20px;
}
.co-dialog.fatal-error .alert-icon:before {
content: "\f071";
font-family: FontAwesome;
font-size: 60px;
padding-left: 50px;
padding-right: 50px;
color: #c53c3f;
text-align: center;
}
.co-dialog .modal-header .cor-step-bar {
float: right;
}
.co-dialog .modal-footer.working {
text-align: left;
}
.co-dialog .modal-footer.working .cor-loader-inline {
margin-right: 10px;
}
.co-dialog .modal-footer .left-align {
float: left;
vertical-align: middle;
font-size: 16px;
margin-top: 8px;
}
.co-dialog .modal-footer .left-align i.fa-warning {
color: #ffba35;
display: inline-block;
margin-right: 6px;
}
.co-dialog .modal-footer .left-align i.fa-check {
color: green;
display: inline-block;
margin-right: 6px;
}
.co-step-bar .co-step-element {
cursor: default;
display: inline-block;
width: 28px;
height: 28px;
position: relative;
color: #ddd;
text-align: center;
line-height: 24px;
font-size: 16px;
}
.co-step-bar .co-step-element.text {
margin-left: 24px;
background: white;
}
.co-step-bar .co-step-element.icon {
margin-left: 22px;
}
.co-step-bar .co-step-element:first-child {
margin-left: 0px;
}
.co-step-bar .co-step-element.active {
color: #53a3d9;
}
.co-step-bar .co-step-element:first-child:before {
display: none;
}
.co-step-bar .co-step-element:before {
content: "";
position: absolute;
top: 12px;
width: 14px;
border-top: 2px solid #ddd;
}
.co-step-bar .co-step-element.icon:before {
left: -20px;
}
.co-step-bar .co-step-element.text:before {
left: -22px;
}
.co-step-bar .co-step-element.active:before {
border-top: 2px solid #53a3d9;
}
.co-step-bar .co-step-element.text {
border-radius: 100%;
border: 2px solid #ddd;
}
.co-step-bar .co-step-element.text.active {
border: 2px solid #53a3d9;
}
@media screen and (min-width: 900px) {
.co-dialog .modal-dialog {
width: 800px;
}
}
.co-alert .co-step-bar {
float: right;
margin-top: 6px;
}

View file

@ -553,42 +553,15 @@
</div>
<!-- Modal message dialog -->
<div class="modal fade initial-setup-modal" id="validateAndSaveModal">
<div class="modal co-dialog fade initial-setup-modal" id="validateAndSaveModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"
ng-show="mapped.$hasChanges && validationStatus(validating) == 'validating'">
Validating Configuration...
</h4>
<h4 class="modal-title"
ng-show="mapped.$hasChanges && validationStatus(validating) == 'failed'">
<i class="fa fa-warning"></i> Configuration Validation Failed
</h4>
<h4 class="modal-title"
ng-show="mapped.$hasChanges && validationStatus(validating) == 'success'">
<i class="fa fa-check-circle"></i> Configuration Validation Succeeded!
</h4>
<h4 class="modal-title" ng-show="!mapped.$hasChanges">
Configuration Changes Saved
<h4 class="modal-title">
Checking your settings
</h4>
</div>
<div class="modal-body" ng-show="!mapped.$hasChanges">
<div class="verified">
<i class="fa fa-check-circle"></i> Configuration Changes Saved
</div>
<p>Your configuration changes have been saved to <code>config.yaml</code> in the mounted config
volume and will be applied the next time the <span class="registry-title"></span> container is restarted.</p>
<p>
<strong>
It is highly recommended that you restart your container now and test these changes!
</strong>
</p>
</div>
<div class="modal-body" ng-show="mapped.$hasChanges">
<div class="modal-body">
<div class="service-verification">
<div class="service-verification-row" ng-repeat="serviceInfo in validating">
<span class="quay-spinner" ng-show="serviceInfo.status == 'validating'"></span>
@ -601,33 +574,49 @@
</div>
</div>
<div class="modal-footer" ng-show="!mapped.$hasChanges">
<button class="btn btn-default" data-dismiss="modal">
Close
<!-- Footer: Saving configuration -->
<div class="modal-footer working" ng-show="savingConfiguration">
<span class="cor-loader-inline"></span> Saving Configuration...
</div>
<!-- Footer: Validating -->
<div class="modal-footer working"
ng-show="!savingConfiguration && validationStatus(validating) == 'validating'">
<span class="cor-loader-inline"></span> Validating settings...
<button class="btn btn-default" ng-click="cancelValidation()">
Stop Validating
</button>
</div>
<div class="modal-footer" ng-show="mapped.$hasChanges">
<span ng-show="validating.length == 0">Please Wait...</span>
<button class="btn btn-default"
ng-show="validationStatus(validating) == 'validating'"
ng-click="cancelValidation()">
Stop Validating
</button>
<!-- Footer: Valid Config -->
<div class="modal-footer"
ng-show="!savingConfiguration && validationStatus(validating) == 'success'">
<span class="left-align">
<i class="fa fa-check"></i>
Configuration Validated
</span>
<button class="btn btn-primary"
ng-show="validationStatus(validating) == 'success'"
ng-click="saveConfiguration()"
ng-disabled="savingConfiguration">
<i class="fa fa-upload" style="margin-right: 10px;"></i>Save Configuration
</button>
<button class="btn btn-default"
ng-show="validationStatus(validating) == 'failed'"
data-dismiss="modal">
Continue Editing Configuration
</div>
<!-- Footer: Invalid Config -->
<div class="modal-footer"
ng-show="!savingConfiguration && validationStatus(validating) == 'failed'">
<span class="left-align">
<i class="fa fa-warning"></i>
Problem Detected
</span>
<button class="btn btn-default" data-dismiss="modal">
Continue Editing
</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View file

@ -0,0 +1,5 @@
<div class="co-m-inline-loader co-an-fade-in-out">
<div class="co-m-loader-dot__one"></div>
<div class="co-m-loader-dot__two"></div>
<div class="co-m-loader-dot__three"></div>
</div>

View file

@ -0,0 +1,5 @@
<div class="co-m-loader co-an-fade-in-out">
<div class="co-m-loader-dot__one"></div>
<div class="co-m-loader-dot__two"></div>
<div class="co-m-loader-dot__three"></div>
</div>

View file

@ -0,0 +1,3 @@
<div class="co-step-bar">
<span class="transclude" ng-transclude/>
</div>

View file

@ -0,0 +1,6 @@
<span ng-class="text ? 'co-step-element text' : 'co-step-element icon'">
<span data-title="{{ title }}" bs-tooltip>
<span class="text" ng-if="text">{{ text }}</span>
<i class="fa fa-lg" ng-if="icon" ng-class="'fa-' + icon"></i>
</span>
</span>

View file

@ -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;

View file

@ -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://<username>:<url escaped password>@<hostname>/<database_name>
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 <code>/conf/stack</code>. " +
"Please rerun the container with the volume mounted and refresh this page." +
"<br><br>For more information: " +
"<a href='https://coreos.com/docs/enterprise-registry/initial-setup/'>" +
"Enterprise Registry Setup Guide</a>",
"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);
}

View file

@ -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 <code>config.yaml</code> file found in <code>conf/stack</code> 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 <code>/conf/stack</code>: " +
"<br><br><pre>docker run -v /path/to/config:/conf/stack</pre>" +
"<br>Once fixed, restart the container. For more information, " +
"<a href='https://coreos.com/docs/enterprise-registry/initial-setup/'>" +
"Read the Setup Guide</a>"
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://<username>:<url escaped password>@<hostname>/<database_name>
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();
}

View file

@ -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." +
"<br><br>Please read the " +
"<a href='https://coreos.com/docs/enterprise-registry/initial-setup/'>" +
"Setup Guide</a>"
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();
}

View file

@ -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.'));
};

View file

@ -1,4 +1,19 @@
angular.module("core-ui", [])
.factory('CoreDialog', [function() {
var service = {};
service['fatal'] = function(title, message) {
bootbox.dialog({
"title": title,
"message": "<div class='alert-icon-container-container'><div class='alert-icon-container'><div class='alert-icon'></div></div></div>" + 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;
});

289
static/partials/setup.html Normal file
View file

@ -0,0 +1,289 @@
<div>
<div class="cor-loader" ng-show="currentStep == States.LOADING"></div>
<div class="page-content" quay-show="Features.SUPER_USERS && currentStep == States.CONFIG">
<div class="cor-title">
<span class="cor-title-link"></span>
<span class="cor-title-content">Enterprise Registry Setup</span>
</div>
<div class="cor-tab-panel" style="padding: 20px;">
<div class="co-alert alert alert-info">
<span class="cor-step-bar" progress="stepProgress">
<span class="cor-step" title="Configure Database" text="1"></span>
<span class="cor-step" title="Setup Database" icon="database"></span>
<span class="cor-step" title="Container Restart" icon="refresh"></span>
<span class="cor-step" title="Create Superuser" text="2"></span>
<span class="cor-step" title="Configure Registry" text="3"></span>
<span class="cor-step" title="Validate Configuration" text="4"></span>
<span class="cor-step" title="Container Restart" icon="refresh"></span>
<span class="cor-step" title="Setup Complete" icon="check"></span>
</span>
<div><strong>Almost done!</strong></div>
<div>Configure your Redis database and other settings below</div>
</div>
<div class="config-setup-tool" is-active="isStep(currentStep, States.CONFIG)"
configuration-saved="configurationSaved()"></div>
</div>
</div>
</div>
<!-- Modal message dialog -->
<div class="co-dialog modal fade initial-setup-modal" id="setupModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Header -->
<div class="modal-header">
<span class="cor-step-bar" progress="stepProgress">
<span class="cor-step" title="Configure Database" text="1"></span>
<span class="cor-step" title="Setup Database" icon="database"></span>
<span class="cor-step" title="Container Restart" icon="refresh"></span>
<span class="cor-step" title="Create Superuser" text="2"></span>
<span class="cor-step" title="Configure Registry" text="3"></span>
<span class="cor-step" title="Validate Configuration" text="4"></span>
<span class="cor-step" title="Container Restart" icon="refresh"></span>
<span class="cor-step" title="Setup Complete" icon="check"></span>
</span>
<h4 class="modal-title"><span><span class="registry-name" is-short="true"></span> Setup</h4>
</div>
<form id="superuserForm" name="superuserForm" ng-submit="createSuperUser()">
<!-- Content: CREATE_SUPERUSER or SUPERUSER_ERROR or CREATING_SUPERUSER -->
<div class="modal-body config-setup-tool-element" style="padding: 20px"
ng-show="isStep(currentStep, States.CREATE_SUPERUSER, States.SUPERUSER_ERROR, States.CREATING_SUPERUSER)">
<p>A superuser is the main administrator of your <span class="registry-name" is-short="true"></span>. Only superusers can edit configuration settings.</p>
<div class="form-group">
<label>Username</label>
<input class="form-control" type="text" ng-model="superUser.username"
ng-pattern="/^[a-z0-9_]{4,30}$/" required>
<div class="help-text">Minimum 4 characters in length</div>
</div>
<div class="form-group">
<label>Email address</label>
<input class="form-control" type="email" ng-model="superUser.email" required>
</div>
<div class="form-group">
<label>Password</label>
<input class="form-control" type="password" ng-model="superUser.password"
ng-pattern="/^[^\s]+$/"
ng-minlength="8" required>
<div class="help-text">Minimum 8 characters in length</div>
</div>
<div class="form-group">
<label>Repeat Password</label>
<input class="form-control" type="password" ng-model="superUser.repeatPassword"
match="superUser.password" required>
</div>
</div>
<!-- Footer: CREATE_SUPERUSER or SUPERUSER_ERROR -->
<div class="modal-footer"
ng-show="isStep(currentStep, States.CREATE_SUPERUSER, States.SUPERUSER_ERROR)">
<button type="submit" class="btn btn-primary" ng-disabled="!superuserForm.$valid">
Create Super User
</button>
</div>
</form>
<!-- Content: DB_RESTARTING or CONFIG_RESTARTING -->
<div class="modal-body" style="padding: 20px;"
ng-show="isStep(currentStep, States.DB_RESTARTING, States.CONFIG_RESTARTING)">
<i class="fa fa-lg fa-refresh" style="margin-right: 10px;"></i>
<span class="registry-name"></span> is currently being restarted.
<br><br>
This can take several minutes. If the container does not restart on its own,
please reexecute the <code>docker run</code> command.
</div>
<!-- Content: READY -->
<div class="modal-body" style="padding: 20px;"
ng-show="isStep(currentStep, States.READY)">
Installation and setup of <span class="registry-name"></span> is complete. You can
now invite users to join, create organizations and start pushing and pulling
repositories.
</div>
<!-- Content: VALID_CONFIG -->
<div class="modal-body" style="padding: 20px;"
ng-show="isStep(currentStep, States.VALID_CONFIG)">
All configuration has been validated and saved. The container must be restarted to
apply the configuration changes.
</div>
<!-- Content: DB_SETUP_SUCCESS -->
<div class="modal-body" style="padding: 20px;"
ng-show="isStep(currentStep, States.DB_SETUP_SUCCESS)">
The database has been setup and is ready. The container must be restarted to
apply the configuration changes.
</div>
<!-- Content: DB_SETUP or DB_SETUP_ERROR -->
<div class="modal-body" style="padding: 20px;"
ng-show="isStep(currentStep, States.DB_SETUP, States.DB_SETUP_ERROR)">
<i class="fa fa-lg fa-database" style="margin-right: 10px;"></i>
<span class="registry-name"></span> is currently setting up its database
schema.
<br><br>
This can take several minutes.
</div>
<!-- Content: CONFIG_DB or DB_ERROR or VALIDATING_DB or SAVING_DB -->
<div class="modal-body validate-database config-setup-tool-element"
ng-show="isStep(currentStep, States.CONFIG_DB, States.DB_ERROR, States.VALIDATING_DB, States.SAVING_DB)">
<p>
Please enter the connection details for your <strong>empty</strong> database. The schema will be created in the following step.</p>
</p>
<div class="config-parsed-field" binding="databaseUri"
parser="parseDbUri(value)"
serializer="serializeDbUri(fields)">
<table class="config-table">
<tr>
<td class="non-input">Database Type:</td>
<td>
<select ng-model="fields.kind">
<option value="mysql+pymysql">MySQL</option>
<option value="postgresql">Postgres</option>
</select>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Database Server:</td>
<td>
<span class="config-string-field" binding="fields.server"
placeholder="dbserverhost"></span>
<div class="help-text">
The server (and optionally, custom port) where the database lives
</div>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Username:</td>
<td>
<span class="config-string-field" binding="fields.username"
placeholder="someuser"></span>
<div class="help-text">This user must have <strong>full access</strong> to the database</div>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Password:</td>
<td>
<input class="form-control" type="password" ng-model="fields.password"></span>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Database Name:</td>
<td>
<span class="config-string-field" binding="fields.database"
placeholder="registry-database"></span>
</td>
</tr>
</table>
</div>
</div>
<!-- Footer: CREATING_SUPERUSER -->
<div class="modal-footer working" ng-show="isStep(currentStep, States.CREATING_SUPERUSER)">
<span class="cor-loader-inline"></span> Creating superuser...
</div>
<!-- Footer: SUPERUSER_ERROR -->
<div class="modal-footer alert alert-warning"
ng-show="isStep(currentStep, States.SUPERUSER_ERROR)">
{{ errors.SuperuserCreationError }}
</div>
<!-- Footer: DB_SETUP_ERROR -->
<div class="modal-footer alert alert-warning"
ng-show="isStep(currentStep, States.DB_SETUP_ERROR)">
Database Setup Failed. Please report this to support: {{ errors.DatabaseSetupError }}
</div>
<!-- Footer: DB_ERROR -->
<div class="modal-footer alert alert-warning" ng-show="isStep(currentStep, States.DB_ERROR)">
Database Validation Issue: {{ errors.DatabaseValidationError }}
</div>
<!-- Footer: CONFIG_DB or DB_ERROR -->
<div class="modal-footer"
ng-show="isStep(currentStep, States.CONFIG_DB, States.DB_ERROR)">
<span class="left-align" ng-show="isStep(currentStep, States.DB_ERROR)">
<i class="fa fa-warning"></i>
Problem Detected
</span>
<button type="submit" class="btn btn-primary" ng-disabled="!databaseUri"
ng-click="validateDatabase()">
Validate Database Settings
</button>
</div>
<!-- Footer: READY -->
<div class="modal-footer"
ng-show="isStep(currentStep, States.READY)">
<span class="left-align">
<i class="fa fa-check"></i>
Installation Complete!
</span>
<a href="javascript:void(0)" ng-click="showSuperuserPanel()" class="btn btn-primary">
View Superuser Panel
</a>
</div>
<!-- Footer: VALID_CONFIG -->
<div class="modal-footer"
ng-show="isStep(currentStep, States.VALID_CONFIG)">
<span class="left-align">
<i class="fa fa-check"></i>
Configuration Validated and Saved
</span>
<button type="submit" class="btn btn-primary"
ng-click="restartContainer(States.CONFIG_RESTARTING)">
Restart Container
</button>
</div>
<!-- Footer: DB_SETUP_SUCCESS -->
<div class="modal-footer"
ng-show="isStep(currentStep, States.DB_SETUP_SUCCESS)">
<span class="left-align">
<i class="fa fa-check"></i>
Database Setup and Ready
</span>
<button type="submit" class="btn btn-primary"
ng-click="restartContainer(States.DB_RESTARTING)">
Restart Container
</button>
</div>
<!-- Footer: DB_SETUP -->
<div class="modal-footer working" ng-show="isStep(currentStep, States.DB_SETUP)">
<span class="cor-loader-inline"></span> Setting up database...
</div>
<!-- Footer: SAVING_DB -->
<div class="modal-footer working" ng-show="isStep(currentStep, States.SAVING_DB)">
<span class="cor-loader-inline"></span> Saving database configuration...
</div>
<!-- Footer: VALIDATING_DB -->
<div class="modal-footer working" ng-show="isStep(currentStep, States.VALIDATING_DB)">
<span class="cor-loader-inline"></span> Testing database settings...
</div>
<!-- Footer: DB_RESTARTING or CONFIG_RESTARTING-->
<div class="modal-footer working"
ng-show="isStep(currentStep, States.DB_RESTARTING, States.CONFIG_RESTARTING)">
<span class="cor-loader-inline"></span> Waiting for container to restart...
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View file

@ -1,6 +1,6 @@
<div>
<div class="quay-spinner" ng-show="!configStatus"></div>
<div class="page-content" quay-show="Features.SUPER_USERS && configStatus.ready">
<div class="cor-loader" ng-show="!configStatus"></div>
<div class="page-content" quay-show="Features.SUPER_USERS && configStatus == 'ready'">
<div class="cor-title">
<span class="cor-title-link"></span>
<span class="cor-title-content">Enterprise Registry Management</span>
@ -8,11 +8,8 @@
<div class="cor-tab-panel">
<div class="cor-tabs">
<span class="cor-tab" tab-active="true" tab-title="Registry Settings" tab-target="#setup"
tab-init="loadConfig()">
<i class="fa fa-cog"></i>
</span>
<span class="cor-tab" tab-title="Manage Users" tab-target="#users" tab-init="loadUsers()">
<span class="cor-tab" tab-active="true" tab-title="Manage Users"
tab-target="#users" tab-init="loadUsers()">
<i class="fa fa-group"></i>
</span>
<span class="cor-tab" tab-title="Container Usage" tab-target="#usage-counter" tab-init="getUsage()">
@ -24,17 +21,21 @@
<span class="cor-tab" tab-title="Internal Logs and Debugging" tab-target="#debug" tab-init="loadDebugServices()">
<i class="fa fa-bug"></i>
</span>
<span class="cor-tab" tab-title="Registry Settings" tab-target="#setup"
tab-init="loadConfig()">
<i class="fa fa-cog"></i>
</span>
</div> <!-- /cor-tabs -->
<div class="cor-tab-content">
<!-- Setup tab -->
<div id="setup" class="tab-pane active">
<div class="config-setup-tool" is-active="configStatus.ready"></div>
<div id="setup" class="tab-pane">
<div class="config-setup-tool" is-active="configStatus == 'ready'"></div>
</div>
<!-- Debugging tab -->
<div id="debug" class="tab-pane">
<div class="quay-spinner" ng-show="!debugServices"></div>
<div class="cor-loader" ng-show="!debugServices"></div>
<div role="tabpanel" ng-show="debugServices">
<!-- Nav tabs -->
@ -65,7 +66,7 @@
<!-- Usage tab -->
<div id="usage-counter" class="tab-pane">
<div class="quay-spinner" ng-show="systemUsage == null"></div>
<div class="cor-loader" ng-show="systemUsage == null"></div>
<div class="usage-chart" total="systemUsage.allowed" limit="systemUsageLimit"
current="systemUsage.usage" usage-title="Deployed Containers"></div>
@ -88,9 +89,9 @@
For more information: <a href="https://coreos.com/products/enterprise-registry/plans/">See Here</a>.
</div> <!-- /usage-counter tab-->
<!-- Users tab -->
<div id="users" class="tab-pane">
<div class="quay-spinner" ng-show="!users"></div>
<!-- Users tab -->
<div id="users" class="tab-pane active">
<div class="cor-loader" ng-show="!users"></div>
<div class="alert alert-error" ng-show="usersError">
{{ usersError }}
</div>
@ -207,7 +208,7 @@
</table>
</div>
<div class="modal-body" ng-show="creatingUser">
<div class="quay-spinner"></div>
<div class="cor-loader"></div>
</div>
<div class="modal-body" ng-show="!creatingUser && !createdUser">
<div class="form-group">
@ -263,176 +264,4 @@
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div> <!-- /page-content -->
<!-- Modal message dialog -->
<div class="modal fade initial-setup-modal" id="createSuperuserModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"><span><span class="registry-name"></span> Setup</h4></span>
</div>
<div class="modal-body config-setup-tool-element" ng-show="configStep == 'creating-superuser'">
Creating super user account.... Please Wait
</div>
<form id="superuserForm" name="superuserForm" ng-submit="createSuperUser()">
<div class="modal-body config-setup-tool-element" ng-show="configStep == 'create-superuser'">
<p>A super user account is required to manage the <span class="registry-name"></span>
installation. Please enter details for the new account below.</p>
<div class="form-group">
<label>Username</label>
<input class="form-control" type="text" ng-model="superUser.username"
ng-pattern="/^[a-z0-9_]{4,30}$/" required>
<div class="help-text">Minimum 4 characters in length</div>
</div>
<div class="form-group">
<label>Email address</label>
<input class="form-control" type="email" ng-model="superUser.email" required>
</div>
<div class="form-group">
<label>Password</label>
<input class="form-control" type="password" ng-model="superUser.password"
ng-pattern="/^[^\s]+$/"
ng-minlength="8" required>
<div class="help-text">Minimum 8 characters in length</div>
</div>
<div class="form-group">
<label>Repeat Password</label>
<input class="form-control" type="password" ng-model="superUser.repeatPassword"
match="superUser.password" required>
</div>
</div>
<div class="modal-footer alert alert-warning" ng-show="createSuperuserIssue">
{{ createSuperuserIssue }}
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" ng-disabled="!superuserForm.$valid"
ng-show="configStep == 'create-superuser'">
Create Super User
</button>
<span class="modal-body config-setup-tool-element" ng-show="configStep == 'creating-superuser'">
<span class="quay-spinner"></span>
Creating account...
</span>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal message dialog -->
<div class="modal fade initial-setup-modal" id="initializeConfigModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"><span><span class="registry-name"></span> Setup</h4></span>
</div>
<div class="modal-body config-setup-tool-element valid-database" ng-show="configStep == 'valid-database'">
<div class="verified">
<i class="fa fa-check-circle"></i> Your database has been verified as working.
</div>
<p>
<strong>Please restart the <span class="registry-name"></span> container</strong>, which will automatically generate the database's schema.
</p>
<p>This operation may take a few minutes.</p>
</div>
<div class="modal-body config-setup-tool-element" ng-show="configStep == 'updating-config'">
Updating Configuration.... Please Wait
</div>
<div class="modal-body config-setup-tool-element" ng-show="configStep == 'validating-database'">
Validating Database.... Please Wait
</div>
<div class="modal-body config-setup-tool-element"
ng-show="configStep == 'enter-database' || configStep == 'invalid-database'">
<div class="alert alert-warning" ng-show="configStatus.has_file">
Could not connect to or validate the database configuration found. Please reconfigure to continue.
</div>
<p>
Please enter the connection details for your <strong>empty</strong> database. The schema will be created in the following step.</p>
</p>
<div class="config-parsed-field" binding="databaseUri"
parser="parseDbUri(value)"
serializer="serializeDbUri(fields)">
<table class="config-table">
<tr>
<td class="non-input">Database Type:</td>
<td>
<select ng-model="fields.kind">
<option value="mysql+pymysql">MySQL</option>
<option value="postgresql">Postgres</option>
</select>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Database Server:</td>
<td>
<span class="config-string-field" binding="fields.server"
placeholder="dbserverhost"></span>
<div class="help-text">
The server (and optionally, custom port) where the database lives
</div>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Database Name:</td>
<td>
<span class="config-string-field" binding="fields.database"
placeholder="registry-database"></span>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Username:</td>
<td>
<span class="config-string-field" binding="fields.username"
placeholder="someuser"></span>
<div class="help-text">This user must have <strong>full access</strong> to the database</div>
</td>
</tr>
<tr ng-show="fields.kind">
<td>Password:</td>
<td>
<input class="form-control" type="password" ng-model="fields.password"></span>
</td>
</tr>
</table>
</div>
</div>
<div class="modal-footer alert alert-warning" ng-show="databaseInvalid">
Database Validation Issue: {{ databaseInvalid }}
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" ng-disabled="!databaseUri"
ng-click="validateDatabase()"
ng-show="configStep == 'enter-database' || configStep == 'invalid-database'">
Confirm Database
</button>
<span class="modal-body config-setup-tool-element" ng-show="configStep == 'validating-database'">
<span class="quay-spinner"></span>
Validating Database...
</span>
<span class="modal-body config-setup-tool-element" ng-show="configStep == 'updating-config'">
<span class="quay-spinner"></span>
Updating Configuration...
</span>
<span class="modal-body config-setup-tool-element" ng-show="configStep == 'valid-database'">
<span class="quay-spinner"></span>
Waiting For Updated Container...
</span>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>