This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/static/js/core-config-setup.js
Joseph Schorr bfd273d16f - Make validation a bit nicer:
- Add timeout to the DB validation
  - Make DB validation exception handling a bit nicer
  - Move the DB validation error message

- Fix bug around RADOS config default for Is Secure
- Allow hiding of the validation box
2015-01-08 15:27:49 -05:00

732 lines
No EOL
23 KiB
JavaScript

angular.module("core-config-setup", ['angularFileUpload'])
.directive('configSetupTool', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/config/config-setup-tool.html',
replace: true,
transclude: true,
restrict: 'C',
scope: {
'isActive': '=isActive'
},
controller: function($rootScope, $scope, $element, $timeout, ApiService) {
$scope.SERVICES = [
{'id': 'redis', 'title': 'Redis'},
{'id': 'registry-storage', 'title': 'Registry Storage'},
{'id': 'ssl', 'title': 'SSL certificate and key', 'condition': function(config) {
return config.PREFERRED_URL_SCHEME == 'https';
}},
{'id': 'ldap', 'title': 'LDAP Authentication', 'condition': function(config) {
return config.AUTHENTICATION_TYPE == 'LDAP';
}},
{'id': 'mail', 'title': 'E-mail Support', 'condition': function(config) {
return config.FEATURE_MAILING;
}},
{'id': 'github-login', 'title': 'Github (Enterprise) Authentication', 'condition': function(config) {
return config.FEATURE_GITHUB_LOGIN;
}},
{'id': 'google-login', 'title': 'Google Authentication', 'condition': function(config) {
return config.FEATURE_GOOGLE_LOGIN;
}},
{'id': 'github-trigger', 'title': 'Github (Enterprise) Build Triggers', 'condition': function(config) {
return config.FEATURE_GITHUB_BUILD;
}}
];
$scope.STORAGE_CONFIG_FIELDS = {
'LocalStorage': [
{'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/some/directory', 'kind': 'text'}
],
'S3Storage': [
{'name': 's3_access_key', 'title': 'AWS Access Key', 'placeholder': 'accesskeyhere', 'kind': 'text'},
{'name': 's3_secret_key', 'title': 'AWS Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
{'name': 's3_bucket', 'title': 'S3 Bucket', 'placeholder': 'my-cool-bucket', 'kind': 'text'},
{'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
],
'GoogleCloudStorage': [
{'name': 'access_key', 'title': 'Cloud Access Key', 'placeholder': 'accesskeyhere', 'kind': 'text'},
{'name': 'secret_key', 'title': 'Cloud Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
{'name': 'bucket_name', 'title': 'GCS Bucket', 'placeholder': 'my-cool-bucket', 'kind': 'text'},
{'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
],
'RadosGWStorage': [
{'name': 'hostname', 'title': 'Rados Server Hostname', 'placeholder': 'my.rados.hostname', 'kind': 'text'},
{'name': 'is_secure', 'title': 'Is Secure', 'placeholder': 'Require SSL', 'kind': 'bool'},
{'name': 'access_key', 'title': 'Access Key', 'placeholder': 'accesskeyhere', 'kind': 'text', 'help_url': 'http://ceph.com/docs/master/radosgw/admin/'},
{'name': 'secret_key', 'title': 'Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
{'name': 'bucket_name', 'title': 'Bucket Name', 'placeholder': 'my-cool-bucket', 'kind': 'text'},
{'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
]
};
$scope.config = null;
$scope.mapped = {
'$hasChanges': false
};
$scope.validating = null;
$scope.savingConfiguration = false;
$scope.getServices = function(config) {
var services = [];
if (!config) { return services; }
for (var i = 0; i < $scope.SERVICES.length; ++i) {
var service = $scope.SERVICES[i];
if (!service.condition || service.condition(config)) {
services.push({
'service': service,
'status': 'validating'
});
}
}
return services;
};
$scope.validationStatus = function(serviceInfos) {
if (!serviceInfos) { return 'validating'; }
var hasError = false;
for (var i = 0; i < serviceInfos.length; ++i) {
if (serviceInfos[i].status == 'validating') {
return 'validating';
}
if (serviceInfos[i].status == 'error') {
hasError = true;
}
}
return hasError ? 'failed' : 'success';
};
$scope.cancelValidation = function() {
$('#validateAndSaveModal').modal('hide');
$scope.validating = null;
$scope.savingConfiguration = false;
};
$scope.validateService = function(serviceInfo) {
var params = {
'service': serviceInfo.service.id
};
ApiService.scValidateConfig({'config': $scope.config}, params).then(function(resp) {
serviceInfo.status = resp.status ? 'success' : 'error';
serviceInfo.errorMessage = $.trim(resp.reason || '');
}, ApiService.errorDisplay('Could not validate configuration. Please report this error.'));
};
$scope.validateAndSave = function() {
$scope.savingConfiguration = false;
$scope.validating = $scope.getServices($scope.config);
$('#validateAndSaveModal').modal({
keyboard: false,
backdrop: 'static'
});
for (var i = 0; i < $scope.validating.length; ++i) {
var serviceInfo = $scope.validating[i];
$scope.validateService(serviceInfo);
}
};
$scope.saveConfiguration = function() {
$scope.savingConfiguration = true;
var data = {
'config': $scope.config,
'hostname': window.location.host
};
ApiService.scUpdateConfig(data).then(function(resp) {
$scope.savingConfiguration = false;
$scope.mapped.$hasChanges = false
}, ApiService.errorDisplay('Could not save configuration. Please report this error.'));
};
var githubSelector = function(key) {
return function(value) {
if (!value || !$scope.config) { return; }
if (!$scope.config[key]) {
$scope.config[key] = {};
}
if (value == 'enterprise') {
if ($scope.config[key]['GITHUB_ENDPOINT'] == 'https://github.com/') {
$scope.config[key]['GITHUB_ENDPOINT'] = '';
}
delete $scope.config[key]['API_ENDPOINT'];
} else if (value == 'hosted') {
$scope.config[key]['GITHUB_ENDPOINT'] = 'https://github.com/';
$scope.config[key]['API_ENDPOINT'] = 'https://api.github.com/';
}
};
};
var getKey = function(config, path) {
var parts = path.split('.');
var current = config;
for (var i = 0; i < parts.length; ++i) {
var part = parts[i];
if (!current[part]) { return null; }
current = current[part];
}
return current;
};
var initializeMappedLogic = function(config) {
var gle = getKey(config, 'GITHUB_LOGIN_CONFIG.GITHUB_ENDPOINT');
var gte = getKey(config, 'GITHUB_TRIGGER_CONFIG.GITHUB_ENDPOINT');
$scope.mapped['GITHUB_LOGIN_KIND'] = gle == 'https://github.com/' ? 'hosted' : 'enterprise';
$scope.mapped['GITHUB_TRIGGER_KIND'] = gte == 'https://github.com/' ? 'hosted' : 'enterprise';
$scope.mapped['redis'] = {};
$scope.mapped['redis']['host'] = getKey(config, 'BUILDLOGS_REDIS.host') || getKey(config, 'USER_EVENTS_REDIS.host');
$scope.mapped['redis']['port'] = getKey(config, 'BUILDLOGS_REDIS.port') || getKey(config, 'USER_EVENTS_REDIS.port');
$scope.mapped['redis']['password'] = getKey(config, 'BUILDLOGS_REDIS.password') || getKey(config, 'USER_EVENTS_REDIS.password');
};
var redisSetter = function(keyname) {
return function(value) {
if (value == null || !$scope.config) { return; }
if (!$scope.config['BUILDLOGS_REDIS']) {
$scope.config['BUILDLOGS_REDIS'] = {};
}
if (!$scope.config['USER_EVENTS_REDIS']) {
$scope.config['USER_EVENTS_REDIS'] = {};
}
if (!value) {
delete $scope.config['BUILDLOGS_REDIS'][keyname];
delete $scope.config['USER_EVENTS_REDIS'][keyname];
return;
}
$scope.config['BUILDLOGS_REDIS'][keyname] = value;
$scope.config['USER_EVENTS_REDIS'][keyname] = value;
};
};
// Add mapped logic.
$scope.$watch('mapped.GITHUB_LOGIN_KIND', githubSelector('GITHUB_LOGIN_CONFIG'));
$scope.$watch('mapped.GITHUB_TRIGGER_KIND', githubSelector('GITHUB_TRIGGER_CONFIG'));
$scope.$watch('mapped.redis.host', redisSetter('host'));
$scope.$watch('mapped.redis.port', redisSetter('port'));
$scope.$watch('mapped.redis.password', redisSetter('password'));
// Add a watch to remove any fields not allowed by the current storage configuration.
// We have to do this otherwise extra fields (which are not allowed) can end up in the
// configuration.
$scope.$watch('config.DISTRIBUTED_STORAGE_CONFIG.local[0]', function(value) {
// Remove any fields not associated with the current kind.
if (!value || !$scope.STORAGE_CONFIG_FIELDS[value]
|| !$scope.config.DISTRIBUTED_STORAGE_CONFIG
|| !$scope.config.DISTRIBUTED_STORAGE_CONFIG.local
|| !$scope.config.DISTRIBUTED_STORAGE_CONFIG.local[1]) { return; }
var allowedFields = $scope.STORAGE_CONFIG_FIELDS[value];
var configObject = $scope.config.DISTRIBUTED_STORAGE_CONFIG.local[1];
// Remove any fields not allowed.
for (var fieldName in configObject) {
if (!configObject.hasOwnProperty(fieldName)) {
continue;
}
var isValidField = $.grep(allowedFields, function(field) {
return field.name == fieldName;
}).length > 0;
if (!isValidField) {
delete configObject[fieldName];
}
}
// Set any boolean fields to false.
for (var i = 0; i < allowedFields.length; ++i) {
if (allowedFields[i].kind == 'bool') {
configObject[allowedFields[i].name] = false;
}
}
});
$scope.$watch('config', function(value) {
$scope.mapped['$hasChanges'] = true;
}, true);
$scope.$watch('isActive', function(value) {
if (!value) { return; }
ApiService.scGetConfig().then(function(resp) {
$scope.config = resp['config'];
initializeMappedLogic($scope.config);
$scope.mapped['$hasChanges'] = false;
});
});
}
};
return directiveDefinitionObject;
})
.directive('configParsedField', function ($timeout) {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-parsed-field.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'binding': '=binding',
'parser': '&parser',
'serializer': '&serializer'
},
controller: function($scope, $element, $transclude) {
$scope.childScope = null;
$transclude(function(clone, scope) {
$scope.childScope = scope;
$scope.childScope['fields'] = {};
$element.append(clone);
});
$scope.childScope.$watch('fields', function(value) {
// Note: We need the timeout here because Angular starts the digest of the
// parent scope AFTER the child scope, which means it can end up one action
// behind. The timeout ensures that the parent scope will be fully digest-ed
// and then we update the binding. Yes, this is a hack :-/.
$timeout(function() {
$scope.binding = $scope.serializer({'fields': value});
});
}, true);
$scope.$watch('binding', function(value) {
var parsed = $scope.parser({'value': value});
for (var key in parsed) {
if (parsed.hasOwnProperty(key)) {
$scope.childScope['fields'][key] = parsed[key];
}
}
});
}
};
return directiveDefinitionObject;
})
.directive('configVariableField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-variable-field.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'binding': '=binding'
},
controller: function($scope, $element) {
$scope.sections = {};
$scope.currentSection = null;
$scope.setSection = function(section) {
$scope.binding = section.value;
};
this.addSection = function(section, element) {
$scope.sections[section.value] = {
'title': section.valueTitle,
'value': section.value,
'element': element
};
element.hide();
if (!$scope.binding) {
$scope.binding = section.value;
}
};
$scope.$watch('binding', function(binding) {
if (!binding) { return; }
if ($scope.currentSection) {
$scope.currentSection.element.hide();
}
if ($scope.sections[binding]) {
$scope.sections[binding].element.show();
$scope.currentSection = $scope.sections[binding];
}
});
}
};
return directiveDefinitionObject;
})
.directive('variableSection', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-variable-field.html',
priority: 1,
require: '^configVariableField',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'value': '@value',
'valueTitle': '@valueTitle'
},
controller: function($scope, $element) {
var parentCtrl = $element.parent().controller('configVariableField');
parentCtrl.addSection($scope, $element);
}
};
return directiveDefinitionObject;
})
.directive('configListField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-list-field.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'binding': '=binding',
'placeholder': '@placeholder',
'defaultValue': '@defaultValue',
'itemTitle': '@itemTitle'
},
controller: function($scope, $element) {
$scope.removeItem = function(item) {
var index = $scope.binding.indexOf(item);
if (index >= 0) {
$scope.binding.splice(index, 1);
}
};
$scope.addItem = function() {
if (!$scope.newItemName) {
return;
}
if (!$scope.binding) {
$scope.binding = [];
}
if ($scope.binding.indexOf($scope.newItemName) >= 0) {
return;
}
$scope.binding.push($scope.newItemName);
$scope.newItemName = null;
};
$scope.$watch('binding', function(binding) {
if (!binding && $scope.defaultValue) {
$scope.binding = eval($scope.defaultValue);
}
});
}
};
return directiveDefinitionObject;
})
.directive('configFileField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-file-field.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'filename': '@filename'
},
controller: function($scope, $element, Restangular, $upload) {
$scope.hasFile = false;
$scope.onFileSelect = function(files) {
if (files.length < 1) { return; }
$scope.uploadProgress = 0;
$scope.upload = $upload.upload({
url: '/api/v1/superuser/config/file/' + $scope.filename,
method: 'POST',
data: {'_csrf_token': window.__token},
file: files[0],
}).progress(function(evt) {
$scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total);
if ($scope.uploadProgress == 100) {
$scope.uploadProgress = null;
$scope.hasFile = true;
}
}).success(function(data, status, headers, config) {
$scope.uploadProgress = null;
$scope.hasFile = true;
});
};
var loadStatus = function(filename) {
Restangular.one('superuser/config/file/' + filename).get().then(function(resp) {
$scope.hasFile = resp['exists'];
});
};
if ($scope.filename) {
loadStatus($scope.filename);
}
}
};
return directiveDefinitionObject;
})
.directive('configBoolField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-bool-field.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'binding': '=binding'
},
controller: function($scope, $element) {
}
};
return directiveDefinitionObject;
})
.directive('configNumericField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-numeric-field.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'binding': '=binding',
'placeholder': '@placeholder',
'defaultValue': '@defaultValue'
},
controller: function($scope, $element) {
$scope.bindinginternal = 0;
$scope.$watch('binding', function(binding) {
if ($scope.binding == 0 && $scope.defaultValue) {
$scope.binding = $scope.defaultValue * 1;
}
$scope.bindinginternal = $scope.binding;
});
$scope.$watch('bindinginternal', function(binding) {
var newValue = $scope.bindinginternal * 1;
if (isNaN(newValue)) {
newValue = 0;
}
$scope.binding = newValue;
});
}
};
return directiveDefinitionObject;
})
.directive('configContactsField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-contacts-field.html',
priority: 1,
replace: false,
transclude: false,
restrict: 'C',
scope: {
'binding': '=binding'
},
controller: function($scope, $element) {
var padItems = function(items) {
// Remove the last item if both it and the second to last items are empty.
if (items.length > 1 && !items[items.length - 2].value && !items[items.length - 1].value) {
items.splice(items.length - 1, 1);
return;
}
// If the last item is non-empty, add a new item.
if (items.length == 0 || items[items.length - 1].value) {
items.push({'value': ''});
return;
}
};
$scope.itemHash = null;
$scope.$watch('items', function(items) {
if (!items) { return; }
padItems(items);
var itemHash = '';
var binding = [];
for (var i = 0; i < items.length; ++i) {
var item = items[i];
if (item.value && (URI(item.value).host() || URI(item.value).path())) {
binding.push(item.value);
itemHash += item.value;
}
}
$scope.itemHash = itemHash;
$scope.binding = binding;
}, true);
$scope.$watch('binding', function(binding) {
if (!binding) { return; }
var current = binding;
var items = [];
var itemHash = '';
for (var i = 0; i < current.length; ++i) {
items.push({'value': current[i]})
itemHash += current[i];
}
if ($scope.itemHash != itemHash) {
$scope.items = items;
}
});
}
};
return directiveDefinitionObject;
})
.directive('configContactField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-contact-field.html',
priority: 1,
replace: false,
transclude: true,
restrict: 'C',
scope: {
'binding': '=binding'
},
controller: function($scope, $element) {
$scope.kind = null;
$scope.value = null;
var updateBinding = function() {
if ($scope.value == null) { return; }
var value = $scope.value || '';
switch ($scope.kind) {
case 'mailto':
$scope.binding = 'mailto:' + value;
return;
case 'tel':
$scope.binding = 'tel:' + value;
return;
case 'irc':
$scope.binding = 'irc://' + value;
return;
default:
$scope.binding = value;
return;
}
};
$scope.$watch('kind', updateBinding);
$scope.$watch('value', updateBinding);
$scope.$watch('binding', function(value) {
if (!value) {
$scope.kind = null;
$scope.value = null;
return;
}
var uri = URI(value);
$scope.kind = uri.scheme();
switch ($scope.kind) {
case 'mailto':
case 'tel':
$scope.value = uri.path();
break;
case 'irc':
$scope.value = value.substr('irc://'.length);
break;
default:
$scope.kind = 'http';
$scope.value = value;
break;
}
});
$scope.getPlaceholder = function(kind) {
switch (kind) {
case 'mailto':
return 'some@example.com';
case 'tel':
return '555-555-5555';
case 'irc':
return 'myserver:port/somechannel';
default:
return 'http://some/url';
}
};
}
};
return directiveDefinitionObject;
})
.directive('configStringField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-string-field.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'binding': '=binding',
'placeholder': '@placeholder',
'pattern': '@pattern',
'defaultValue': '@defaultValue'
},
controller: function($scope, $element) {
$scope.getRegexp = function(pattern) {
if (!pattern) {
pattern = '.*';
}
return new RegExp(pattern);
};
$scope.$watch('binding', function(binding) {
if (!binding && $scope.defaultValue) {
$scope.binding = $scope.defaultValue;
}
});
}
};
return directiveDefinitionObject;
});