-
-
-
-
-
Email server is required.
+
-
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/src/ui/static/resources/js/components/system-management/configuration.directive.js b/src/ui/static/resources/js/components/system-management/configuration.directive.js
index bc21345ada..45db3711d4 100644
--- a/src/ui/static/resources/js/components/system-management/configuration.directive.js
+++ b/src/ui/static/resources/js/components/system-management/configuration.directive.js
@@ -18,51 +18,365 @@
angular
.module('harbor.system.management')
+ .constant('defaultPassword', '12345678')
.directive('configuration', configuration);
- ConfigurationController.$inject = [];
+ ConfigurationController.$inject = ['$scope', 'ConfigurationService', 'defaultPassword', '$filter', 'trFilter'];
- function ConfigurationController() {
+ function ConfigurationController($scope, ConfigurationService, defaultPassword, $filter, trFilter) {
var vm = this;
-
- vm.registrationOptions = [
- {
- 'name': 'on',
- 'value': true
+
+ vm.toggleBooleans = [
+ {
+ 'name': 'True',
+ 'value': true
},
{
- 'name': 'off',
+ 'name': 'False',
'value': false
}
];
- vm.currentRegistration = {
- 'name': 'on',
- 'value': true
+
+ vm.toggleCustoms = [
+ {
+ 'name': 'Admin Only',
+ 'value': 'adminonly',
+ },
+ {
+ 'name': 'Everyone',
+ 'value': 'everyone'
+ }
+ ];
+
+ vm.supportedAuths = [
+ {
+ 'name': 'DB auth',
+ 'value': 'db_auth'
+ },
+ {
+ 'name': 'LDAP auth',
+ 'value': 'ldap_auth'
+ }
+ ];
+
+ var confKeyDefinitions = {
+ 'auth_mode': { type: 'auth', attr: 'authMode' },
+ 'self_registration': { type: 'auth', attr: 'selfRegistration' },
+ 'ldap_url': { type: 'auth', attr: 'ldapURL' },
+ 'ldap_search_dn': { type: 'auth', attr: 'ldapSearchDN' },
+ 'ldap_search_password': { type: 'auth', attr: 'ldapSearchPassword' },
+ 'ldap_base_dn': { type: 'auth', attr: 'ldapBaseDN' },
+ 'ldap_uid': { type: 'auth', attr: 'ldapUID' },
+ 'ldap_filter': { type: 'auth', attr: 'ldapFilter' },
+ 'ldap_connection_timeout': { type: 'auth', attr: 'ldapConnectionTimeout' },
+ 'ldap_scope': { type: 'auth', attr: 'ldapScope' },
+ 'email_host': { type: 'email', attr: 'server' },
+ 'email_port': { type: 'email', attr: 'serverPort' },
+ 'email_username': { type: 'email', attr: 'username' },
+ 'email_password': { type: 'email', attr: 'password' },
+ 'email_from': { type: 'email', attr: 'from' },
+ 'email_ssl': { type: 'email', attr: 'SSL' },
+ 'project_creation_restriction': { type: 'system', attr: 'projectCreationRestriction' },
+ 'verify_remote_cert': { type: 'system', attr: 'verifyRemoteCert' }
};
- vm.changeSettings = changeSettings;
+ $scope.auth = {};
+ $scope.email = {};
+ $scope.system = {};
- vm.selectRegistration = selectRegistration;
+ vm.retrieve = retrieve;
- function selectRegistration() {
+ vm.saveAuthConf = saveAuthConf;
+ vm.saveEmailConf = saveEmailConf;
+ vm.saveSystemConf = saveSystemConf;
+
+ vm.gatherUpdateItems = gatherUpdateItems;
+ vm.clearUp = clearUp;
+ vm.hasChanged = hasChanged;
+ vm.setMaskPassword = setMaskPassword;
+ vm.undo = undo;
+
+ vm.pingLDAP = pingLDAP;
+ vm.pingTIP = false;
+ vm.isError = false;
+ vm.pingMessage = '';
+
+ vm.retrieve();
+
+ function retrieve() {
+
+ vm.ldapSearchPasswordChanged = false;
+ vm.emailPasswordChanged = false;
+ vm.changedItems = {};
+ vm.updatedItems = {};
+ vm.warning = {};
+ vm.editable = {};
+ ConfigurationService
+ .get()
+ .then(getConfigurationSuccess, getConfigurationFailed);
+ }
+
+ function getConfigurationSuccess(response) {
+ var data = response.data || [];
+ for(var key in data) {
+ var mappedDef = keyMapping(key);
+ if(mappedDef) {
+ $scope[mappedDef['type']][mappedDef['attr']] = { 'target': mappedDef['type'] + '.' + mappedDef['attr'], 'data': valueMapping(data[key]['value']) };
+ $scope.$watch(mappedDef['type'] + '.' + mappedDef['attr'], onChangedCallback, true);
+ $scope[mappedDef['type']][mappedDef['attr']]['origin'] = { 'target': mappedDef['type'] + '.' + mappedDef['attr'], 'data': valueMapping(data[key]['value']) };
+ vm.editable[mappedDef['type'] + '.' + mappedDef['attr']] = data[key]['editable'];
+ }
+ }
+
+ $scope.auth.ldapSearchPassword = { 'target': 'auth.ldapSearchPassword', 'data': defaultPassword};
+ $scope.email.password = { 'target': 'email.password', 'data': defaultPassword};
+
+ $scope.$watch('auth.ldapSearchPassword', onChangedCallback, true);
+ $scope.$watch('email.password', onChangedCallback, true);
+
+ $scope.auth.ldapSearchPassword.actual = { 'target': 'auth.ldapSearchPassword', 'data': ''};
+ $scope.email.password.actual = { 'target': 'email.password', 'data': ''};
+ }
+
+ function keyMapping(confKey) {
+ for (var key in confKeyDefinitions) {
+ if (confKey === key) {
+ return confKeyDefinitions[key];
+ }
+ }
+ return null;
}
- function changeSettings(system) {
- console.log(system);
+ function valueMapping(value) {
+ switch(value) {
+ case true:
+ return vm.toggleBooleans[0];
+ case false:
+ return vm.toggleBooleans[1];
+ case 'db_auth':
+ return vm.supportedAuths[0];
+ case 'ldap_auth':
+ return vm.supportedAuths[1];
+ case 'adminonly':
+ return vm.toggleCustoms[0];
+ case 'everyone':
+ return vm.toggleCustoms[1];
+ default:
+ return value;
+ }
}
+
+ function onChangedCallback(current, previous) {
+ if(!angular.equals(current, previous)) {
+ var compositeKey = current.target.split(".");
+ vm.changed = false;
+ var changedData = {};
+ switch(current.target) {
+ case 'auth.ldapSearchPassword':
+ if(vm.ldapSearchPasswordChanged) {
+ vm.changed = true;
+ changedData = $scope.auth.ldapSearchPassword.actual.data;
+ }
+ break;
+ case 'email.password':
+ if(vm.emailPasswordChanged) {
+ vm.changed = true;
+ changedData = $scope.email.password.actual.data;
+ }
+ break;
+ default:
+ if(!angular.equals(current.data, $scope[compositeKey[0]][compositeKey[1]]['origin']['data'])) {
+ vm.changed = true;
+ changedData = current.data;
+ }
+ }
+ if(vm.changed) {
+ vm.changedItems[current.target] = changedData;
+ vm.warning[current.target] = true;
+ } else {
+ delete vm.changedItems[current.target];
+ vm.warning[current.target] = false;
+ }
+ }
+ }
+
+ function getConfigurationFailed(response) {
+ console.log('Failed to get configurations.');
+ }
+
+ function updateConfigurationSuccess(response) {
+ $scope.$emit('modalTitle', $filter('tr')('update_configuration_title', []));
+ $scope.$emit('modalMessage', $filter('tr')('successful_update_configuration', []));
+ var emitInfo = {
+ 'confirmOnly': true,
+ 'contentType': 'text/plain',
+ 'action' : function() {
+ vm.retrieve();
+ }
+ };
+ $scope.$emit('raiseInfo', emitInfo);
+ console.log('Updated system configuration successfully.');
+ }
+
+ function updateConfigurationFailed() {
+ $scope.$emit('modalTitle', $filter('tr')('update_configuration_title', []));
+ $scope.$emit('modalMessage', $filter('tr')('failed_to_update_configuration', []));
+ $scope.$emit('raiseError', true);
+ console.log('Failed to update system configurations.');
+ }
+
+ function gatherUpdateItems() {
+ vm.updatedItems = {};
+ for(var key in confKeyDefinitions) {
+ var value = confKeyDefinitions[key];
+ var compositeKey = value.type + '.' + value.attr;
+ for(var itemKey in vm.changedItems) {
+ var item = vm.changedItems[itemKey];
+ if (compositeKey === itemKey) {
+ (typeof item === 'object' && item) ? vm.updatedItems[key] = ((typeof item.value === 'boolean') ? Number(item.value) + '' : item.value) : vm.updatedItems[key] = String(item) || '';
+ }
+ }
+ }
+ }
+
+ function saveAuthConf(auth) {
+ vm.gatherUpdateItems();
+ console.log('auth changed:' + angular.toJson(vm.updatedItems));
+ ConfigurationService
+ .update(vm.updatedItems)
+ .then(updateConfigurationSuccess, updateConfigurationFailed);
+ }
+
+ function saveEmailConf(email) {
+ vm.gatherUpdateItems();
+ console.log('email changed:' + angular.toJson(vm.updatedItems));
+ ConfigurationService
+ .update(vm.updatedItems)
+ .then(updateConfigurationSuccess, updateConfigurationFailed);
+ }
+
+ function saveSystemConf(system) {
+ vm.gatherUpdateItems();
+ console.log('system changed:' + angular.toJson(vm.updatedItems));
+ ConfigurationService
+ .update(vm.updatedItems)
+ .then(updateConfigurationSuccess, updateConfigurationFailed);
+ }
+
+ function clearUp(input) {
+ switch(input.target) {
+ case 'auth.ldapSearchPassword':
+ $scope.auth.ldapSearchPassword.data = '';
+ break;
+ case 'email.password':
+ $scope.email.password.data = '';
+ break;
+ }
+ }
+
+ function hasChanged(input) {
+ switch(input.target) {
+ case 'auth.ldapSearchPassword':
+ vm.ldapSearchPasswordChanged = true;
+ $scope.auth.ldapSearchPassword.actual.data = input.data;
+ break;
+ case 'email.password':
+ vm.emailPasswordChanged = true;
+ $scope.email.password.actual.data = input.data;
+ break;
+ }
+ }
+
+ function setMaskPassword(input) {
+ input.data = defaultPassword;
+ }
+
+ function undo() {
+ vm.retrieve();
+ }
+
+ function pingLDAP(auth) {
+ var keyset = [
+ {'name': 'ldapURL' , 'attr': 'ldap_url'},
+ {'name': 'ldapSearchDN', 'attr': 'ldap_search_dn'},
+ {'name': 'ldapSearchPassword' , 'attr': 'ldap_search_password'},
+ {'name': 'ldapConnectionTimeout', 'attr': 'ldap_connection_timeout'}
+ ];
+ var ldapConf = {};
+
+ for(var i = 0; i < keyset.length; i++) {
+ var key = keyset[i];
+ var value;
+ if(key.name === 'ldapSearchPassword') {
+ value = auth[key.name]['actual']['data'];
+ }else {
+ value = auth[key.name]['data'];
+ }
+ ldapConf[key.attr] = value;
+ }
+
+ vm.pingMessage = $filter('tr')('pinging_target');
+ vm.pingTIP = true;
+ vm.isError = false;
+
+ ConfigurationService
+ .pingLDAP(ldapConf)
+ .then(pingLDAPSuccess, pingLDAPFailed);
+ }
+
+ function pingLDAPSuccess(response) {
+ vm.pingTIP = false;
+ vm.pingMessage = $filter('tr')('successful_ping_target');
+ }
+
+ function pingLDAPFailed(response) {
+ vm.isError = true;
+ vm.pingTIP = false;
+ vm.pingMessage = $filter('tr')('failed_to_ping_target');
+ console.log('Failed to ping LDAP target:' + response.data);
+ }
+
}
- function configuration() {
+ configuration.$inject = ['$filter', 'trFilter'];
+
+ function configuration($filter, trFilter) {
var directive = {
'restrict': 'E',
'templateUrl': '/static/resources/js/components/system-management/configuration.directive.html',
'scope': true,
+ 'link': link,
'controller': ConfigurationController,
'controllerAs': 'vm',
'bindToController': true
};
return directive;
+
+ function link(scope, element, attrs, ctrl) {
+ element.find('#ulTabHeader a').on('click', function(e) {
+ e.preventDefault();
+ ctrl.gatherUpdateItems();
+ if(!angular.equals(ctrl.updatedItems, {})) {
+ var emitInfo = {
+ 'confirmOnly': true,
+ 'contentType': 'text/html',
+ 'action' : function() {
+ return;
+ }
+ };
+ scope.$emit('modalTitle', $filter('tr')('caution'));
+ scope.$emit('modalMessage', $filter('tr')('please_save_changes'));
+ scope.$emit('raiseInfo', emitInfo);
+ scope.$apply();
+ e.stopPropagation();
+ }else{
+ $(this).tab('show');
+ }
+
+ });
+ element.find('#ulTabHeader a:first').trigger('click');
+ }
}
})();
\ No newline at end of file
diff --git a/src/ui/static/resources/js/components/system-management/system-management.directive.js b/src/ui/static/resources/js/components/system-management/system-management.directive.js
index 68d17938d3..f04f54549a 100644
--- a/src/ui/static/resources/js/components/system-management/system-management.directive.js
+++ b/src/ui/static/resources/js/components/system-management/system-management.directive.js
@@ -29,6 +29,7 @@
switch(currentTarget) {
case 'destinations':
case 'replication':
+ case 'configuration':
$location.path('/' + currentTarget);
vm.target = currentTarget;
break;
diff --git a/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.html b/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.html
index ecbd0fafec..ab1641250d 100644
--- a/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.html
+++ b/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.html
@@ -12,10 +12,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+
\ No newline at end of file
diff --git a/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.js b/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.js
index 89de7e4a4f..ffc293f461 100644
--- a/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.js
+++ b/src/ui/static/resources/js/layout/navigation/navigation-admin-options.directive.js
@@ -27,7 +27,9 @@
vm.path = $location.path();
}
- function navigationAdminOptions() {
+ navigationAdminOptions.$inject = ['I18nService'];
+
+ function navigationAdminOptions(I18nService) {
var directive = {
'restrict': 'E',
'templateUrl': '/static/resources/js/layout/navigation/navigation-admin-options.directive.html',
@@ -44,7 +46,14 @@
function link(scope, element, attrs, ctrl) {
var visited = ctrl.path.substring(1);
console.log('visited:' + visited);
-
+
+ var lang = I18nService().getCurrentLanguage();
+ ctrl.customPos = {};
+
+ if(lang === 'zh-CN') {
+ ctrl.customPos = {'position': 'relative', 'left': '14%'};
+ }
+
if(visited) {
element.find('a[tag="' + visited + '"]').addClass('active');
}else{
diff --git a/src/ui/static/resources/js/layout/navigation/navigation-details.directive.js b/src/ui/static/resources/js/layout/navigation/navigation-details.directive.js
index 5d6b06d459..b11cc26238 100644
--- a/src/ui/static/resources/js/layout/navigation/navigation-details.directive.js
+++ b/src/ui/static/resources/js/layout/navigation/navigation-details.directive.js
@@ -35,7 +35,9 @@
vm.path = $location.path();
}
- function navigationDetails() {
+ navigationDetails.$inject = ['I18nService'];
+
+ function navigationDetails(I18nService) {
var directive = {
restrict: 'E',
templateUrl: '/navigation_detail?timestamp=' + new Date().getTime(),
@@ -53,6 +55,13 @@
function link(scope, element, attrs, ctrl) {
+ var lang = I18nService().getCurrentLanguage();
+ ctrl.customPos = {};
+
+ if(lang === 'zh-CN') {
+ ctrl.customPos = {'position': 'relative', 'left': '8%'};
+ }
+
var visited = ctrl.path.substring(1);
if(visited) {
diff --git a/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js b/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js
index de5a023384..650f75aa52 100644
--- a/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js
+++ b/src/ui/static/resources/js/services/i18n/locale_messages_en-US.js
@@ -287,5 +287,57 @@ var locale_messages = {
'confirm_to_toggle_enabled_policy': 'After enabling the replication policy, all repositories under the project will be replicated to the destination registry. Please confirm to continue.',
'confirm_to_toggle_disabled_policy_title': 'Disable Policy',
'confirm_to_toggle_disabled_policy': 'After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue.',
- 'begin_date_is_later_than_end_date': 'Begin date should not be later than end date.'
+ 'begin_date_is_later_than_end_date': 'Begin date should not be later than end date.',
+ 'configuration': 'Configuration',
+ 'authentication': 'Authentication',
+ 'email_settings': 'Email Settings',
+ 'system_settings': 'System Settings',
+ 'authentication_mode': 'Authentication Mode',
+ 'authentication_mode_desc': 'The default authentication mode is db_auth. Set it to ldap_auth when users\' credentials are stored in an LDAP or AD server. Note: this option can only be set once.',
+ 'self_registration': 'Self Registration',
+ 'self_registration_desc': 'Determine whether the self-registration is allowed or not. Set this to off to disable a user\'s self-registration in Harbor. This flag has no effect when users are stored in LDAP or AD.',
+ 'ldap_url': 'LDAP URL',
+ 'ldap_url_desc': 'The URL of an LDAP/AD server.',
+ 'ldap_search_dn': 'LDAP Search DN',
+ 'ldap_search_dn_desc': 'A user\'s DN who has the permission to search the LDAP/AD server. Leave blank if your LDAP/AD server supports anonymous search, otherwise you should configure this DN and LDAP Search Password.',
+ 'ldap_search_password': 'LDAP Search Password',
+ 'ldap_search_password_desc': 'The password of the user for LDAP search. Leave blank if your LDAP/AD server supports anonymous search.',
+ 'ldap_base_dn': 'LDAP Base DN',
+ 'ldap_base_dn_desc': 'The base DN of a node from which to look up a user for authentication. The search scope includes subtree of the node.',
+ 'ldap_uid': 'LDAP UID',
+ 'ldap_uid_desc': 'The attribute used in a search to match a user, it could be uid, cn, email, sAMAccountName or other attributes depending on your LDAP/AD server.',
+ 'ldap_filter': 'LDAP Filter',
+ 'ldap_filter_desc': 'Search filter for LDAP/AD, make sure the syntax of the filter is correct.',
+ 'ldap_connection_timeout': 'LDAP Connection Timeout(sec.)',
+ 'ldap_scope': 'LDAP Scope',
+ 'ldap_scope_desc': 'The scope to search for users.(1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE)',
+ 'email_server': 'Email Server',
+ 'email_server_desc': 'The mail server to send out emails to reset password.',
+ 'email_server_port': 'Email Server Port',
+ 'email_server_port_desc': 'The port of mail server.',
+ 'email_username': 'Email Username',
+ 'email_username_desc': 'The user from whom the password reset email is sent. Usually this is a system email address.',
+ 'email_password': 'Email Password',
+ 'email_password_desc': 'The password of the user from whom the password reset email is sent.',
+ 'email_from': 'Email From',
+ 'email_from_desc': 'The name of the email sender.',
+ 'email_ssl': 'Email SSL',
+ 'email_ssl_desc': 'Whether to enable secure mail transmission.',
+ 'project_creation_restriction': 'Project Creation Restriction',
+ 'project_creation_restriction_desc': 'The flag to control what users have permission to create projects. Be default everyone can create a project, set to "adminonly" such that only admin can create project.',
+ 'verify_remote_cert': 'Verify Remote Cert.',
+ 'verify_remote_cert_desc': 'Determine whether the image replication should verify the certificate of a remote Harbor registry. Set this flag to off when the remote registry uses a self-signed or untrusted certificate.',
+ 'max_job_workers': 'Max Job Workers',
+ 'max_job_workers_desc': 'Maximum number of job workers in job service.',
+ 'please_save_changes': 'Please save changes before leaving this page.',
+ 'undo': 'Undo',
+ 'invalid_port_number': 'Invalid port number.',
+ 'max_job_workers_is_required': 'Maxystem job workers number is required.',
+ 'timeout_is_required': 'Timeout value is required.',
+ 'invalid_timeout': 'Invalid timeout value.',
+ 'ldap_scope_is_required': 'Scope is required.',
+ 'invalid_ldap_scope': 'Invalid Scope value.',
+ 'update_configuration_title': 'Update Configuration',
+ 'successful_update_configuration': 'Update configurations successfully.',
+ 'failed_to_update_configuration': 'Failed to update configuration.'
};
\ No newline at end of file
diff --git a/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js b/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js
index 451351edfb..fbd1bcfc5c 100644
--- a/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js
+++ b/src/ui/static/resources/js/services/i18n/locale_messages_zh-CN.js
@@ -287,5 +287,57 @@ var locale_messages = {
'confirm_to_toggle_enabled_policy': '启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。',
'confirm_to_toggle_disabled_policy_title': '停用策略',
'confirm_to_toggle_disabled_policy': '停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。',
- 'begin_date_is_later_than_end_date': '起始日期不能晚于结束日期。'
+ 'begin_date_is_later_than_end_date': '起始日期不能晚于结束日期。',
+ 'configuration': '设置',
+ 'authentication': '认证设置',
+ 'email_settings': '邮箱设置',
+ 'system_settings': '系统设置',
+ 'authentication_mode': '认证模式',
+ 'authentication_mode_desc': '默认的认证模式是: 数据库认证。 当用户信息存储在LDAP或AD服务器时,应设置为LDAP认证模式。 注意:该选项只能被设置一次。',
+ 'self_registration': '自注册',
+ 'self_registration_desc': '确定是否允许或禁止用户自注册。 关闭该项会禁用Harbor用户注册。 当用户数据存储在LDAP或AD时,该选项无效。 ',
+ 'ldap_url': 'LDAP URL',
+ 'ldap_url_desc': 'LDAP/AD服务URL。',
+ 'ldap_search_dn': 'LDAP Search DN',
+ 'ldap_search_dn_desc': '提供一个拥有检索LDAP/AD权限的用户DN。 如果LDAP/AD允许匿名访问该项可以置空, 否则需提供用户DN和密码。',
+ 'ldap_search_password': 'LDAP Search 密码',
+ 'ldap_search_password_desc': '检索LDAP的用户密码。 如果LDAP/AD允许匿名访问该项可以置空。',
+ 'ldap_base_dn': 'LDAP Base DN',
+ 'ldap_base_dn_desc': '用于查询认证用户的基本DN节点。 检索范围包含子树节点。',
+ 'ldap_uid': 'LDAP UID',
+ 'ldap_uid_desc': '该属性用于检索匹配用户, 可以是uid, cn, Email, sAMAccountName或是其他属性, 取决于LDAP/AD服务。',
+ 'ldap_filter': 'LDAP 过滤器',
+ 'ldap_filter_desc': '用于过滤LDAP/AP检索, 请确保正确语法形式。',
+ 'ldap_connection_timeout': 'LDAP连接超时(秒)',
+ 'ldap_scope': 'LDAP检索范围',
+ 'ldap_scope_desc': '指定用户检索范围。(1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE)',
+ 'email_server': '邮箱服务地址',
+ 'email_server_desc': '用以发送重置密码的邮箱服务地址。',
+ 'email_server_port': '邮箱服务端口号',
+ 'email_server_port_desc': '邮箱服务端口号',
+ 'email_username': '邮箱用户名',
+ 'email_username_desc': '发送重置密码邮箱的用户。 通常是系统邮箱地址。',
+ 'email_password': '邮箱密码',
+ 'email_password_desc': '发送重置密码邮箱的密码。',
+ 'email_from': '寄信人',
+ 'email_from_desc': '邮箱寄信人名。',
+ 'email_ssl': '邮箱SSL',
+ 'email_ssl_desc': '是否启用安全邮箱传输。',
+ 'project_creation_restriction': '项目创建约束',
+ 'project_creation_restriction_desc': '此标志用于控制用户是否有权限创建项目。 默认允许任何人创建项目, 当设置为"adminonly"只允许管理员创建项目。',
+ 'verify_remote_cert': '验证远程服务证书',
+ 'verify_remote_cert_desc': '确定镜像复制是否检查远程Harbor服务证书。 当使用非受信或自签名证书时应该关闭该检查。',
+ 'max_job_workers': '最大任务调度数',
+ 'max_job_workers_desc': '任务调度服务最大调度数。',
+ 'please_save_changes': '请在离开此页之前保存。',
+ 'undo': '撤销',
+ 'invalid_port_number': '无效的端口号。',
+ 'max_job_workers_is_required': '最大任务数为必填项。',
+ 'timeout_is_required': '超时时间为必填项。',
+ 'invalid_timeout': '无效的超时时间。',
+ 'ldap_scope_is_required': '范围值为必填项。',
+ 'invalid_ldap_scope': '无效的范围值。',
+ 'update_configuration_title': '修改设置',
+ 'successful_update_configuration': '修改设置成功。',
+ 'failed_to_update_configuration': '修改设置失败。'
};
\ No newline at end of file
diff --git a/src/ui/static/resources/js/services/system-info/services.system-configuration.js b/src/ui/static/resources/js/services/system-info/services.system-configuration.js
new file mode 100644
index 0000000000..8c168cb904
--- /dev/null
+++ b/src/ui/static/resources/js/services/system-info/services.system-configuration.js
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) 2016 VMware, Inc. All Rights Reserved.
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+(function() {
+
+ 'use strict';
+ angular.module('harbor.services.system.info')
+ .service('ConfigurationService', ConfigurationService);
+
+ ConfigurationService.$inject = ['$http', '$q', '$timeout'];
+
+ function ConfigurationService($http, $q, $timeout) {
+ this.get = get;
+ this.update = update;
+ this.pingLDAP = pingLDAP;
+
+ function get() {
+ return $http.get('/api/configurations');
+ }
+
+ function update(updates) {
+ return $http.put('/api/configurations', updates);
+ }
+
+ function pingLDAP(ldapConf) {
+ return $http
+ .post('/api/ldap/ping', ldapConf);
+ }
+
+ }
+
+})();
\ No newline at end of file
diff --git a/src/ui/views/navigation-detail.htm b/src/ui/views/navigation-detail.htm
index 9fc68cfad3..ad6bdd107b 100644
--- a/src/ui/views/navigation-detail.htm
+++ b/src/ui/views/navigation-detail.htm
@@ -12,7 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+
- // 'repositories' | tr //|
{{ if eq .IsAdmin 1 }}
- // 'replication' | tr //|
diff --git a/src/ui/views/sections/script-include.htm b/src/ui/views/sections/script-include.htm
index 187387a07f..e8d3d9d1a3 100644
--- a/src/ui/views/sections/script-include.htm
+++ b/src/ui/views/sections/script-include.htm
@@ -112,6 +112,7 @@
+