Build code base for shareable UI components

This commit is contained in:
Steven Zou 2017-05-04 17:09:55 +08:00
parent 25004bbd1f
commit e057117d2e
31 changed files with 2124 additions and 185 deletions

View File

@ -9,9 +9,9 @@
"test:once": "karma start karma.conf.js --single-run", "test:once": "karma start karma.conf.js --single-run",
"pree2e": "webdriver-manager update", "pree2e": "webdriver-manager update",
"e2e": "protractor", "e2e": "protractor",
"cleanup": "rimraf dist/bundles dist/src dist/index.d.ts dist/index.metadata.json dist/index.js dist/index.js.map dist/LICENSE dist/AUTHORS", "cleanup": "rimraf dist/bundles dist/src dist/index.d.ts dist/index.ngsummary.json dist/index.metadata.json dist/index.js dist/index.js.map dist/LICENSE dist/AUTHORS",
"copy": "copyfiles -f LICENSE AUTHORS pkg/package.json dist", "copy": "copyfiles -f LICENSE AUTHORS pkg/package.json dist",
"transpile": "ngc", "transpile": "ngc -p tsconfig.json",
"package": "rollup -c", "package": "rollup -c",
"minify": "uglifyjs dist/bundles/harborui.umd.js --screw-ie8 --compress --mangle --comments --output dist/bundles/harborui.umd.min.js", "minify": "uglifyjs dist/bundles/harborui.umd.js --screw-ie8 --compress --mangle --comments --output dist/bundles/harborui.umd.min.js",
"build": "npm run cleanup && npm run transpile && npm run package && npm run minify && npm run copy" "build": "npm run cleanup && npm run transpile && npm run package && npm run minify && npm run copy"
@ -36,7 +36,10 @@
"rxjs": "^5.0.1", "rxjs": "^5.0.1",
"ts-helpers": "^1.1.1", "ts-helpers": "^1.1.1",
"zone.js": "^0.8.4", "zone.js": "^0.8.4",
"mutationobserver-shim": "^0.3.2" "mutationobserver-shim": "^0.3.2",
"@ngx-translate/core": "^6.0.0",
"@ngx-translate/http-loader": "0.0.3",
"angular2-cookie": "^1.2.6"
}, },
"devDependencies": { "devDependencies": {
"@angular/cli": "^1.0.0", "@angular/cli": "^1.0.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "harbor-ui", "name": "harbor-ui",
"version": "0.1.0", "version": "0.1.1",
"description": "Harbor shared UI components based on Clarity and Angular4", "description": "Harbor shared UI components based on Clarity and Angular4",
"author": "Harbor", "author": "Harbor",
"module": "index.js", "module": "index.js",
@ -36,6 +36,10 @@
"core-js": "^2.4.1", "core-js": "^2.4.1",
"rxjs": "^5.0.1", "rxjs": "^5.0.1",
"ts-helpers": "^1.1.1", "ts-helpers": "^1.1.1",
"zone.js": "^0.8.4" "zone.js": "^0.8.4",
"mutationobserver-shim": "^0.3.2",
"@ngx-translate/core": "^6.0.0",
"@ngx-translate/http-loader": "0.0.3",
"angular2-cookie": "^1.2.6"
} }
} }

View File

@ -4,9 +4,34 @@ export default {
sourceMap: false, sourceMap: false,
format: 'umd', format: 'umd',
moduleName: 'harborui', moduleName: 'harborui',
external: [
'@angular/animations',
'@angular/core',
'@angular/common',
'@angular/forms',
'@angular/platform-browser',
'@angular/http',
'clarity-angular',
'@ngx-translate/core',
'@ngx-translate/http-loader',
'rxjs',
'rxjs/Subject',
'rxjs/Observable',
'rxjs/add/observable/of',
'rxjs/add/operator/toPromise',
'rxjs/add/operator/debounceTime',
'rxjs/add/operator/distinctUntilChanged'
],
globals: { globals: {
'@angular/core': 'ng.core', '@angular/core': 'ng.core',
'rxjs/Observable': 'Rx', '@angular/animations': 'ng.animations',
'@angular/common': 'ng.common',
'@angular/forms': 'ng.forms',
'@angular/http': 'ng.http',
'@angular/platform-browser': 'ng.platformBrowser',
'rxjs': 'rxjs',
'rxjs/Subject': 'rxjs.Subject',
'rxjs/Observable': 'rxjs/Observable',
'rxjs/ReplaySubject': 'Rx', 'rxjs/ReplaySubject': 'Rx',
'rxjs/add/operator/map': 'Rx.Observable.prototype', 'rxjs/add/operator/map': 'Rx.Observable.prototype',
'rxjs/add/operator/mergeMap': 'Rx.Observable.prototype', 'rxjs/add/operator/mergeMap': 'Rx.Observable.prototype',
@ -14,5 +39,16 @@ export default {
'rxjs/add/operator/toPromise': 'Rx.Observable.prototype', 'rxjs/add/operator/toPromise': 'Rx.Observable.prototype',
'rxjs/add/observable/of': 'Rx.Observable', 'rxjs/add/observable/of': 'Rx.Observable',
'rxjs/add/observable/throw': 'Rx.Observable' 'rxjs/add/observable/throw': 'Rx.Observable'
},
onwarn: function(warning) {
// Skip certain warnings
// should intercept ... but doesn't in some rollup versions
if (warning.code === 'THIS_IS_UNDEFINED') { return; }
// intercepts in some rollup versions
if (typeof warning === 'string' && warning.indexOf("The 'this' keyword is equivalent to 'undefined'") > -1) { return; }
// console.warn everything else
console.warn(warning.message);
} }
} }

View File

@ -0,0 +1,70 @@
import { Injectable } from "@angular/core";
/**
* Declare interface for error handling
*
* @export
* @abstract
* @class ErrorHandler
*/
export abstract class ErrorHandler {
/**
* Send message with error level
*
* @abstract
* @param {*} error
*
* @memberOf ErrorHandler
*/
abstract error(error: any): void;
/**
* Send message with warning level
*
* @abstract
* @param {*} warning
*
* @memberOf ErrorHandler
*/
abstract warning(warning: any): void;
/**
* Send message with info level
*
* @abstract
* @param {*} info
*
* @memberOf ErrorHandler
*/
abstract info(info: any): void;
/**
* Handle log message
*
* @abstract
* @param {*} log
*
* @memberOf ErrorHandler
*/
abstract log(log: any): void;
}
@Injectable()
export class DefaultErrorHandler extends ErrorHandler {
public error(error: any): void {
console.error("[Default error handler]: ", error);
}
public warning(warning: any): void {
console.warn("[Default warning handler]: ", warning);
}
public info(info: any): void {
console.info("[Default info handler]: ", info);
}
public log(log: any): void {
console.log("[Default log handler]: ", log);
}
}

View File

@ -0,0 +1 @@
export * from './error-handler';

View File

@ -0,0 +1,57 @@
// Copyright (c) 2017 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.
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { FILTER_TEMPLATE, FILTER_STYLES } from './filter.template';
@Component({
selector: 'hbr-filter',
styles: [FILTER_STYLES],
template: FILTER_TEMPLATE
})
export class FilterComponent implements OnInit {
placeHolder: string = "";
filterTerms = new Subject<string>();
@Output("filter") private filterEvt = new EventEmitter<string>();
@Input() currentValue: string;
@Input("filterPlaceholder")
public set flPlaceholder(placeHolder: string) {
this.placeHolder = placeHolder;
}
ngOnInit(): void {
this.filterTerms
.debounceTime(500)
//.distinctUntilChanged()
.subscribe(terms => {
this.filterEvt.emit(terms);
});
}
valueChange(): void {
//Send out filter terms
this.filterTerms.next(this.currentValue.trim());
}
}

View File

@ -0,0 +1,17 @@
/**
* Define template resources for filter component
*/
export const FILTER_TEMPLATE: string = `
<span>
<clr-icon shape="filter" size="12" class="is-solid filter-icon"></clr-icon>
<input type="text" style="padding-left: 15px;" (keyup)="valueChange()" placeholder="{{placeHolder}}" [(ngModel)]="currentValue"/>
</span>
`;
export const FILTER_STYLES: string = `
.filter-icon {
position: relative;
right: -12px;
}
`;

View File

@ -0,0 +1,8 @@
import { Type } from "@angular/core";
import { FilterComponent } from './filter.component';
export * from "./filter.component";
export const FILTER_DIRECTIVES: Type<any>[] = [
FilterComponent
];

View File

@ -1,6 +1,7 @@
import { NgModule, ModuleWithProviders, Provider } from '@angular/core'; import { NgModule, ModuleWithProviders, Provider, APP_INITIALIZER, Inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SYSTEMINFO_DIRECTIVES } from './system/index'; import { LOG_DIRECTIVES } from './log/index';
import { FILTER_DIRECTIVES } from './filter/index';
import { SERVICE_CONFIG, IServiceConfig } from './service.config'; import { SERVICE_CONFIG, IServiceConfig } from './service.config';
import { import {
AccessLogService, AccessLogService,
@ -14,17 +15,28 @@ import {
TagService, TagService,
TagDefaultService TagDefaultService
} from './service/index'; } from './service/index';
import {
ErrorHandler,
DefaultErrorHandler
} from './error-handler/index';
import { SharedModule } from './shared/shared.module';
import { DEFAULT_LANG_COOKIE_KEY, DEFAULT_SUPPORTING_LANGS, DEFAULT_LANG } from './utils';
import { TranslateService } from '@ngx-translate/core';
/** /**
* Declare default service configuration * Declare default service configuration; all the endpoints will be defined in
* this default configuration.
*/ */
export const DefaultServiceConfig: IServiceConfig = { export const DefaultServiceConfig: IServiceConfig = {
systemInfoEndpoint: "/api/system", systemInfoEndpoint: "/api/system",
repositoryBaseEndpoint: "", repositoryBaseEndpoint: "",
logBaseEndpoint: "", logBaseEndpoint: "/api/logs",
targetBaseEndpoint: "", targetBaseEndpoint: "",
replicationRuleEndpoint: "", replicationRuleEndpoint: "",
replicationJobEndpoint: "" replicationJobEndpoint: "",
langCookieKey: DEFAULT_LANG_COOKIE_KEY,
supportedLangs: DEFAULT_SUPPORTING_LANGS,
enablei18Support: false
}; };
/** /**
@ -37,6 +49,9 @@ export interface HarborModuleConfig {
//Service endpoints //Service endpoints
config?: Provider, config?: Provider,
//Handling error messages
errorHandler?: Provider,
//Service implementation for log //Service implementation for log
logService?: Provider, logService?: Provider,
@ -53,12 +68,52 @@ export interface HarborModuleConfig {
tagService?: Provider tagService?: Provider
} }
/**
*
*
* @export
* @param {AppConfigService} configService
* @returns
*/
export function initConfig(translateService: TranslateService, config: IServiceConfig) {
return (init);
function init() {
let selectedLang: string = DEFAULT_LANG;
translateService.addLangs(config.supportedLangs ? config.supportedLangs : [DEFAULT_LANG]);
translateService.setDefaultLang(DEFAULT_LANG);
if (config.enablei18Support) {
//If user has selected lang, then directly use it
let langSetting = this.cookie.get(config.langCookieKey);
if (!langSetting || langSetting.trim() === "") {
//Use browser lang
langSetting = translateService.getBrowserCultureLang().toLowerCase();
}
if (config.supportedLangs && config.supportedLangs.length > 0) {
if (config.supportedLangs.find(lang => lang === langSetting)) {
selectedLang = langSetting;
}
}
}
translateService.use(selectedLang);
};
}
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule SharedModule
], ],
declarations: [SYSTEMINFO_DIRECTIVES], declarations: [
exports: [SYSTEMINFO_DIRECTIVES] LOG_DIRECTIVES,
FILTER_DIRECTIVES
],
exports: [
LOG_DIRECTIVES,
FILTER_DIRECTIVES
]
}) })
export class HarborLibraryModule { export class HarborLibraryModule {
@ -67,11 +122,20 @@ export class HarborLibraryModule {
ngModule: HarborLibraryModule, ngModule: HarborLibraryModule,
providers: [ providers: [
config.config || { provide: SERVICE_CONFIG, useValue: DefaultServiceConfig }, config.config || { provide: SERVICE_CONFIG, useValue: DefaultServiceConfig },
config.errorHandler || { provide: ErrorHandler, useClass: DefaultErrorHandler },
config.logService || { provide: AccessLogService, useClass: AccessLogDefaultService }, config.logService || { provide: AccessLogService, useClass: AccessLogDefaultService },
config.endpointService || { provide: EndpointService, useClass: EndpointDefaultService }, config.endpointService || { provide: EndpointService, useClass: EndpointDefaultService },
config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService }, config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService },
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService }, config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
config.tagService || { provide: TagService, useClass: TagDefaultService } config.tagService || { provide: TagService, useClass: TagDefaultService },
//Do initializing
TranslateService,
{
provide: APP_INITIALIZER,
useFactory: initConfig,
deps: [TranslateService, SERVICE_CONFIG],
multi: true
},
] ]
}; };
} }
@ -81,6 +145,7 @@ export class HarborLibraryModule {
ngModule: HarborLibraryModule, ngModule: HarborLibraryModule,
providers: [ providers: [
config.config || { provide: SERVICE_CONFIG, useValue: DefaultServiceConfig }, config.config || { provide: SERVICE_CONFIG, useValue: DefaultServiceConfig },
config.errorHandler || { provide: ErrorHandler, useClass: DefaultErrorHandler },
config.logService || { provide: AccessLogService, useClass: AccessLogDefaultService }, config.logService || { provide: AccessLogService, useClass: AccessLogDefaultService },
config.endpointService || { provide: EndpointService, useClass: EndpointDefaultService }, config.endpointService || { provide: EndpointService, useClass: EndpointDefaultService },
config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService }, config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService },

View File

@ -0,0 +1,448 @@
{
"APP_TITLE": {
"VMW_HARBOR": "VMware Harbor",
"HARBOR": "Harbor",
"VIC": "vSphere Integrated Containers",
"MGMT": "Management",
"REG": "Registry"
},
"SIGN_IN": {
"REMEMBER": "Remember me",
"INVALID_MSG": "Invalid user name or password.",
"FORGOT_PWD": "Forgot password",
"HEADER_LINK": "Sign In"
},
"SIGN_UP": {
"TITLE": "Sign Up"
},
"BUTTON": {
"CANCEL": "CANCEL",
"OK": "OK",
"DELETE": "DELETE",
"LOG_IN": "LOG IN",
"SIGN_UP_LINK": "Sign up for an account",
"SIGN_UP": "SIGN UP",
"CONFIRM": "CONFIRM",
"SEND": "SEND",
"SAVE": "SAVE",
"TEST_MAIL": "TEST MAIL SERVER",
"CLOSE": "CLOSE",
"TEST_LDAP": "TEST LDAP SERVER",
"MORE_INFO": "More info...",
"YES": "YES",
"NO": "NO",
"NEGATIVE": "NEGATIVE"
},
"TOOLTIP": {
"EMAIL": "Email should be a valid email address like name@example.com.",
"USER_NAME": "Cannot contain special characters and maximum length should be 20 characters.",
"FULL_NAME": "Maximum length should be 20 characters.",
"COMMENT": "Length of comment should be less than 20 characters.",
"CURRENT_PWD": "Current password is required.",
"PASSWORD": "Password should be at least 8 characters with at least 1 uppercase, 1 lowercase and 1 number.",
"CONFIRM_PWD": "Passwords do not match.",
"SIGN_IN_USERNAME": "Username is required.",
"SIGN_IN_PWD": "Password is required.",
"SIGN_UP_MAIL": "Email is only used for resetting your password.",
"SIGN_UP_REAL_NAME": "First and last name",
"ITEM_REQUIRED": "Field is required.",
"NUMBER_REQUIRED": "Field is required and should be numbers.",
"PORT_REQUIRED": "Field is required and should be valid port number.",
"EMAIL_EXISTING": "Email address already exists.",
"USER_EXISTING": "Username is already in use."
},
"PLACEHOLDER": {
"CURRENT_PWD": "Enter current password",
"NEW_PWD": "Enter new password",
"CONFIRM_PWD": "Confirm new password",
"USER_NAME": "Enter username",
"MAIL": "Enter email address",
"FULL_NAME": "Enter full name",
"SIGN_IN_NAME": "Username",
"SIGN_IN_PWD": "Password"
},
"PROFILE": {
"TITLE": "User Profile",
"USER_NAME": "Username",
"EMAIL": "Email",
"FULL_NAME": "First and last name",
"COMMENT": "Comments",
"PASSWORD": "Password",
"SAVE_SUCCESS": "User profile saved successfully."
},
"CHANGE_PWD": {
"TITLE": "Change Password",
"CURRENT_PWD": "Current Password",
"NEW_PWD": "New Password",
"CONFIRM_PWD": "Confirm Password",
"SAVE_SUCCESS": "User password changed successfully.",
"PASS_TIPS": "At least 8 characters with 1 uppercase, 1 lowercase and 1 number"
},
"ACCOUNT_SETTINGS": {
"PROFILE": "User Profile",
"CHANGE_PWD": "Change Password",
"ABOUT": "About",
"LOGOUT": "Log Out",
"ROOT_CERT": "Download Root Cert"
},
"GLOBAL_SEARCH": {
"PLACEHOLDER": "Search Harbor...",
"PLACEHOLDER_VIC": "Search Registry..."
},
"SIDE_NAV": {
"DASHBOARD": "Dashboard",
"PROJECTS": "Projects",
"SYSTEM_MGMT": {
"NAME": "Administration",
"USER": "Users",
"REPLICATION": "Replication",
"CONFIG": "Configuration"
},
"LOGS": "Logs"
},
"USER": {
"ADD_ACTION": "USER",
"ENABLE_ADMIN_ACTION": "Set as Administrator",
"DISABLE_ADMIN_ACTION": "Revoke Administrator",
"DEL_ACTION": "Delete",
"FILTER_PLACEHOLDER": "Filter users",
"COLUMN_NAME": "Name",
"COLUMN_ADMIN": "Administrator",
"COLUMN_EMAIL": "Email",
"COLUMN_REG_NAME": "Registration time",
"IS_ADMIN": "Yes",
"IS_NOT_ADMIN": "No",
"ADD_USER_TITLE": "New User",
"SAVE_SUCCESS": "New user created successfully.",
"DELETION_TITLE": "Confirm user deletion",
"DELETION_SUMMARY": "Do you want to delete user {{param}}?",
"DELETE_SUCCESS": "User deleted successfully.",
"ITEMS": "item(s)"
},
"PROJECT": {
"PROJECTS": "Projects",
"NAME": "Project Name",
"ROLE": "Role",
"PUBLIC_OR_PRIVATE": "Access Level",
"REPO_COUNT": "Repositories Count",
"CREATION_TIME": "Creation Time",
"PUBLIC": "Public",
"PRIVATE": "Private",
"MAKE": "Make",
"NEW_POLICY": "New Replication Rule",
"DELETE": "Delete",
"MY_PROJECTS": "All Projects",
"PUBLIC_PROJECTS": "Public Projects",
"PROJECT": "Project",
"NEW_PROJECT": "New Project",
"NAME_IS_REQUIRED": "Project name is required.",
"NAME_MINIMUM_LENGTH": "Project name is too short, it should be greater than 2 characters.",
"NAME_ALREADY_EXISTS": "Project name already exists.",
"NAME_IS_ILLEGAL": "Project name is invalid.",
"UNKNOWN_ERROR": "An unknown error occurred while creating the project.",
"ITEMS": "item(s)",
"DELETION_TITLE": "Confirm project deletion",
"DELETION_SUMMARY": "Do you want to delete project {{param}}?",
"FILTER_PLACEHOLDER": "Filter Projects",
"REPLICATION_RULE": "Replication Rule",
"CREATED_SUCCESS": "Created project successfully.",
"DELETED_SUCCESS": "Deleted project successfully.",
"TOGGLED_SUCCESS": "Toggled project successfully.",
"FAILED_TO_DELETE_PROJECT": "Project contains repositories or replication rules cannot be deleted.",
"INLINE_HELP_PUBLIC": "When a project is set to public, anyone has read permission to the repositories under this project, and the user does not need to run \"docker login\" before pulling images under this project."
},
"PROJECT_DETAIL": {
"REPOSITORIES": "Repositories",
"REPLICATION": "Replication",
"USERS": "Members",
"LOGS": "Logs",
"PROJECTS": "Projects"
},
"MEMBER": {
"NEW_MEMBER": "New Member",
"MEMBER": "Member",
"NAME": "Name",
"ROLE": "Role",
"SYS_ADMIN": "System Admin",
"PROJECT_ADMIN": "Project Admin",
"DEVELOPER": "Developer",
"GUEST": "Guest",
"DELETE": "Delete",
"ITEMS": "item(s)",
"ACTIONS": "Actions",
"USERNAME_IS_REQUIRED": "Username is required",
"USERNAME_DOES_NOT_EXISTS": "Username does not exist.",
"USERNAME_ALREADY_EXISTS": "Username already exists.",
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
"FILTER_PLACEHOLDER": "Filter Members",
"DELETION_TITLE": "Confirm project member deletion",
"DELETION_SUMMARY": "Do you want to delete project member {{param}}?",
"ADDED_SUCCESS": "Added member successfully.",
"DELETED_SUCCESS": "Deleted member successfully.",
"SWITCHED_SUCCESS": "Switched member role successfully."
},
"AUDIT_LOG": {
"USERNAME": "Username",
"REPOSITORY_NAME": "Repository Name",
"TAGS": "Tags",
"OPERATION": "Operation",
"OPERATIONS": "Operations",
"TIMESTAMP": "Timestamp",
"ALL_OPERATIONS": "All Operations",
"PULL": "Pull",
"PUSH": "Push",
"CREATE": "Create",
"DELETE": "Delete",
"OTHERS": "Others",
"ADVANCED": "Advanced",
"SIMPLE": "Simple",
"ITEMS": "item(s)",
"FILTER_PLACEHOLDER": "Filter Logs",
"INVALID_DATE": "Invalid date."
},
"REPLICATION": {
"REPLICATION_RULE": "Replication Rule",
"NEW_REPLICATION_RULE": "New Replication Rule",
"ENDPOINTS": "Endpoints",
"FILTER_POLICIES_PLACEHOLDER": "Filter Rules",
"FILTER_JOBS_PLACEHOLDER": "Filter Jobs",
"DELETION_TITLE": "Confirm Rule Deletion",
"DELETION_SUMMARY": "Do you want to delete rule {{param}}?",
"FILTER_TARGETS_PLACEHOLDER": "Filter Endpoints",
"DELETION_TITLE_TARGET": "Confirm Endpoint Deletion",
"DELETION_SUMMARY_TARGET": "Do you want to delete the endpoint {{param}}?",
"ADD_POLICY": "New Replication Rule",
"EDIT_POLICY": "Edit",
"EDIT_POLICY_TITLE": "Edit Replication Rule",
"DELETE_POLICY": "Delete",
"TEST_CONNECTION": "Test Connection",
"TESTING_CONNECTION": "Testing Connection...",
"TEST_CONNECTION_SUCCESS": "Connection tested successfully.",
"TEST_CONNECTION_FAILURE": "Failed to ping endpoint.",
"NAME": "Name",
"PROJECT": "Project",
"NAME_IS_REQUIRED": "Name is required.",
"DESCRIPTION": "Description",
"ENABLE": "Enable",
"DISABLE": "Disable",
"DESTINATION_NAME": "Endpoint Name",
"DESTINATION_NAME_IS_REQUIRED": "Endpoint name is required.",
"NEW_DESTINATION": "New Endpoint",
"DESTINATION_URL": "Endpoint URL",
"DESTINATION_URL_IS_REQUIRED": "Endpoint URL is required.",
"DESTINATION_USERNAME": "Username",
"DESTINATION_PASSWORD": "Password",
"ALL_STATUS": "All Status",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"LAST_START_TIME": "Last Start Time",
"ACTIVATION": "Activation",
"REPLICATION_JOBS": "Replication Jobs",
"ALL": "All",
"PENDING": "Pending",
"RUNNING": "Running",
"ERROR": "Error",
"RETRYING": "Retrying",
"STOPPED": "Stopped",
"FINISHED": "Finished",
"CANCELED": "Canceled",
"SIMPLE": "Simple",
"ADVANCED": "Advanced",
"STATUS": "Status",
"OPERATION": "Operation",
"CREATION_TIME": "Start Time",
"END_TIME": "End Time",
"LOGS": "Logs",
"ITEMS": "item(s)",
"TOGGLE_ENABLE_TITLE": "Enable Rule",
"CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication rule, all repositories under the project will be replicated to the destination registry. \nPlease confirm to continue.",
"TOGGLE_DISABLE_TITLE": "Disable Rule",
"CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the rule, all unfinished replication jobs of this rule will be stopped and canceled. \nPlease confirm to continue.",
"CREATED_SUCCESS": "Created replication rule successfully.",
"UPDATED_SUCCESS": "Updated replication rule successfully.",
"DELETED_SUCCESS": "Deleted replication rule successfully.",
"DELETED_FAILED": "Deleted replication rule failed.",
"TOGGLED_SUCCESS": "Toggled replication rule status successfully.",
"CANNOT_EDIT": "Replication rule cannot be changed while it is enabled.",
"POLICY_ALREADY_EXISTS": "Replication rule already exists.",
"FAILED_TO_DELETE_POLICY_ENABLED": "Cannot delete rule: rule has unfinished job(s) or rule is enabled.",
"FOUND_ERROR_IN_JOBS": "Found errors in the replication job(s), please check.",
"INVALID_DATE": "Invalid date."
},
"DESTINATION": {
"NEW_ENDPOINT": "New Endpoint",
"ENDPOINT": "Endpoint",
"NAME": "Endpoint Name",
"NAME_IS_REQUIRED": "Endpoint name is required.",
"URL": "Endpoint URL",
"URL_IS_REQUIRED": "Endpoint URL is required.",
"USERNAME": "Username",
"PASSWORD": "Password",
"TEST_CONNECTION": "Test Connection",
"TITLE_EDIT": "Edit Endpoint",
"TITLE_ADD": "Create Endpoint",
"DELETE": "Delete Endpoint",
"TESTING_CONNECTION": "Testing Connection...",
"TEST_CONNECTION_SUCCESS": "Connection tested successfully.",
"TEST_CONNECTION_FAILURE": "Failed to ping endpoint.",
"CONFLICT_NAME": "Endpoint name or URL already exists.",
"INVALID_NAME": "Invalid endpoint name.",
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
"CREATION_TIME": "Creation Time",
"ITEMS": "item(s)",
"CREATED_SUCCESS": "Created endpoint successfully.",
"UPDATED_SUCCESS": "Updated endpoint successfully.",
"DELETED_SUCCESS": "Deleted endpoint successfully.",
"DELETED_FAILED": "Deleted endpoint failed.",
"CANNOT_EDIT": "Endpoint cannot be changed while the replication rule is enabled.",
"FAILED_TO_DELETE_TARGET_IN_USED": "Failed to delete the endpoint in use."
},
"REPOSITORY": {
"COPY_ID": "Copy ID",
"COPY_PARENT_ID": "Copy Parent ID",
"DELETE": "Delete",
"NAME": "Name",
"TAGS_COUNT": "Tags",
"PULL_COUNT": "Pulls",
"PULL_COMMAND": "Pull Command",
"MY_REPOSITORY": "My Repository",
"PUBLIC_REPOSITORY": "Public Repository",
"DELETION_TITLE_REPO": "Confirm Repository Deletion",
"DELETION_SUMMARY_REPO": "Do you want to delete repository {{param}}?",
"DELETION_TITLE_TAG": "Confirm Tag Deletion",
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
"DELETION_TITLE_TAG_DENIED": "Signed tag cannot be deleted",
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n{{param}}",
"FILTER_FOR_REPOSITORIES": "Filter Repositories",
"TAG": "Tag",
"SIGNED": "Signed",
"AUTHOR": "Author",
"CREATED": "Creation Time",
"DOCKER_VERSION": "Docker Version",
"ARCHITECTURE": "Architecture",
"OS": "OS",
"SHOW_DETAILS": "Show Details",
"REPOSITORIES": "Repositories",
"ITEMS": "item(s)",
"POP_REPOS": "Popular Repositories",
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
"DELETED_TAG_SUCCESS": "Deleted tag successfully.",
"COPY": "Copy"
},
"ALERT": {
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet. Do you want to cancel?"
},
"RESET_PWD": {
"TITLE": "Reset Password",
"CAPTION": "Enter your email to reset your password",
"EMAIL": "Email",
"SUCCESS": "Mail with password resetting link is successfully sent. You can close this dialog and check your mailbox.",
"CAPTION2": "Enter your new password",
"RESET_OK": "Password has been successfully reset. Click OK to login with new password."
},
"RECENT_LOG": {
"SUB_TITLE": "Show",
"SUB_TITLE_SUFIX": "logs"
},
"CONFIG": {
"TITLE": "Configuration",
"AUTH": "Authentication",
"REPLICATION": "Replication",
"EMAIL": "Email",
"SYSTEM": "System Settings",
"CONFIRM_TITLE": "Confirm to cancel",
"CONFIRM_SUMMARY": "Some changes have not been saved. Do you want to discard them?",
"SAVE_SUCCESS": "Configuration has been successfully saved.",
"MAIL_SERVER": "Email Server",
"MAIL_SERVER_PORT": "Email Server Port",
"MAIL_USERNAME": "Email Username",
"MAIL_PASSWORD": "Email Password",
"MAIL_FROM": "Email From",
"MAIL_SSL": "Email SSL",
"SSL_TOOLTIP": "Enable SSL for email server connection",
"VERIFY_REMOTE_CERT": "Verify Remote Cert",
"TOKEN_EXPIRATION": "Token Expiration (Minutes)",
"AUTH_MODE": "Auth Mode",
"PRO_CREATION_RESTRICTION": "Project Creation",
"SELF_REGISTRATION": "Allow Self-Registration",
"AUTH_MODE_DB": "Database",
"AUTH_MODE_LDAP": "LDAP",
"SCOPE_BASE": "Base",
"SCOPE_ONE_LEVEL": "OneLevel",
"SCOPE_SUBTREE": "Subtree",
"PRO_CREATION_EVERYONE": "Everyone",
"PRO_CREATION_ADMIN": "Admin Only",
"TOOLTIP": {
"SELF_REGISTRATION": "Enable sign up.",
"VERIFY_REMOTE_CERT": "Determine whether the image replication should verify the certificate of a remote Harbor registry. Uncheck this box when the remote registry uses a self-signed or untrusted certificate.",
"AUTH_MODE": "By default the authentication mode is database, i.e. the credentials are stored in a local database. Set it to LDAP if you want to verify a user's credential against an LDAP server.",
"LDAP_SEARCH_DN": "A user's DN who has the permission to search the LDAP/AD server. If your LDAP/AD server does not support anonymous search, you should configure this DN and ldap_search_pwd.",
"LDAP_BASE_DN": "The base DN from which to look up a user in LDAP/AD.",
"LDAP_UID": "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.",
"LDAP_SCOPE": "The scope to search for users",
"TOKEN_EXPIRATION": "The expiration time (in minutes) of a token created by the token service. Default is 30 minutes.",
"PRO_CREATION_RESTRICTION": "The flag to define what users have permission to create projects. By default, everyone can create a project. Set to 'Admin Only' so that only an administrator can create a project."
},
"LDAP": {
"URL": "LDAP URL",
"SEARCH_DN": "LDAP Search DN",
"SEARCH_PWD": "LDAP Search Password",
"BASE_DN": "LDAP Base DN",
"FILTER": "LDAP Filter",
"UID": "LDAP UID",
"SCOPE": "LDAP Scope"
},
"TEST_MAIL_SUCCESS": "Connection to mail server is verified.",
"TEST_LDAP_SUCCESS": "Connection to LDAP server is verified.",
"TEST_MAIL_FAILED": "Failed to verify mail server with error: {{param}}.",
"TEST_LDAP_FAILED": "Failed to verify LDAP server with error: {{param}}.",
"LEAVING_CONFIRMATION_TITLE": "Confirm to leave",
"LEAVING_CONFIRMATION_SUMMARY": "Changes have not been saved yet. Do you want to leave current page?"
},
"PAGE_NOT_FOUND": {
"MAIN_TITLE": "Page not found",
"SUB_TITLE": "Redirecting to main page in",
"UNIT": "seconds..."
},
"ABOUT": {
"VERSION": "Version",
"BUILD": "Build",
"COPYRIGHT": "Copyright 1998-2017 VMware, Inc. All rights reserved. This product is protected by U.S. and international property laws. VMware products are covered by one or more patents listed at",
"COPYRIGHT_SUFIX": ".",
"TRADEMARK": "VMware is a registered trademark or trademark of VMware, Inc. in the United States and other jurisdictions. All other marks and names mentioned herein may be trademark of their respective companies.",
"END_USER_LICENSE": "End User License Agreement",
"OPEN_SOURCE_LICENSE": "Open Source/Third Party License"
},
"START_PAGE": {
"GETTING_START": "",
"GETTING_START_TITLE": "Getting Started"
},
"TOP_REPO": "Popular Repositories",
"STATISTICS": {
"TITLE": "STATISTICS",
"PRO_ITEM": "PROJECTS",
"REPO_ITEM": "REPOSITORIES",
"INDEX_MY": "MY",
"INDEX_MY_PROJECTS": "MY PROJECTS",
"INDEX_MY_REPOSITORIES": "MY REPOSITORIES",
"INDEX_PUB": "PUBLIC",
"INDEX_TOTAL": "TOTAL",
"STORAGE": "STORAGE",
"LIMIT": "Limit"
},
"SEARCH": {
"IN_PROGRESS": "Search...",
"BACK": "Back"
},
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later.",
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action.",
"FORBIDDEN_ERROR": "You do not have the proper privileges to perform the action.",
"GENERAL_ERROR": "Errors have occurred when performing service call: {{param}}.",
"BAD_REQUEST_ERROR": "We are unable to perform your action because of a bad request.",
"NOT_FOUND_ERROR": "Your request cannot be completed because the object does not exist.",
"CONFLICT_ERROR": "We are unable to perform your action because your submission has conflicts.",
"PRECONDITION_FAILED": "We are unable to perform your action because of a precondition failure.",
"SERVER_ERROR": "We are unable to perform your action because internal server errors have occurred.",
"INCONRRECT_OLD_PWD": "The old password is incorrect.",
"UNKNOWN": "n/a"
}

View File

@ -0,0 +1,447 @@
{
"APP_TITLE": {
"VMW_HARBOR": "VMware Harbor",
"HARBOR": "Harbor",
"VIC": "Contenedores Integrados vSphere",
"MGMT": "Administración",
"REG": "Registro"
},
"SIGN_IN": {
"REMEMBER": "Recordarme",
"INVALID_MSG": "Nombre o contraseña no válidos.",
"FORGOT_PWD": "Olvidé mi contraseña",
"HEADER_LINK": "Identificarse"
},
"SIGN_UP": {
"TITLE": "Registrarse"
},
"BUTTON": {
"CANCEL": "CANCELAR",
"OK": "OK",
"DELETE": "ELIMINAR",
"LOG_IN": "IDENTIFICARSE",
"SIGN_UP_LINK": "Registrar una cuenta",
"SIGN_UP": "REGISTRARSE",
"CONFIRM": "CONFIRMAR",
"SEND": "ENVIAR",
"SAVE": "GUARDAR",
"TEST_MAIL": "COMPROBAR SERVIDOR DE CORREO",
"CLOSE": "CERRAR",
"TEST_LDAP": "COMPROBAR SERVIDOR LDAP",
"MORE_INFO": "Más información...",
"YES": "SI",
"NO": "NO",
"NEGATIVE": "NEGATIVO"
},
"TOOLTIP": {
"EMAIL": "El email debe ser una dirección válida como nombre@ejemplo.com.",
"USER_NAME": "Debe tener una longitud máxima de 20 caracteres y no puede contener caracteres especiales.",
"FULL_NAME": "La longitud máxima debería ser de 20 caracteres.",
"COMMENT": "La longitud del comentario debería ser menor de 20 caracteres.",
"CURRENT_PWD": "Es obligatorio introducir la contraseña actual.",
"PASSWORD": "La contraseña debería tener al menos 8 caracteres, con al menos 1 letra mayúscula, 1 letra minúscula y 1 número.",
"CONFIRM_PWD": "Las contraseñas no coinciden.",
"SIGN_IN_USERNAME": "El nombre de usuario es obligatorio.",
"SIGN_IN_PWD": "La contraseña es obligatoria.",
"SIGN_UP_MAIL": "La dirección de email solamente se utilizar para restablecer la contraseña.",
"SIGN_UP_REAL_NAME": "Nombre y apellidos",
"ITEM_REQUIRED": "Campo obligatorio.",
"NUMBER_REQUIRED": "El campo es obligatorio y debería ser un número.",
"PORT_REQUIRED": "El campo es obligatorio y debería ser un número de puerto válido.",
"EMAIL_EXISTING": "Esa dirección de email ya existe.",
"USER_EXISTING": "Ese nombre de usuario ya existe."
},
"PLACEHOLDER": {
"CURRENT_PWD": "Introduzca la contraseña actual",
"NEW_PWD": "Introduzca la nueva contraseña",
"CONFIRM_PWD": "Confirme la nueva contraseña",
"USER_NAME": "Introduzca nombre de usuario",
"MAIL": "Introduzca dirección de email",
"FULL_NAME": "Introduzca nombre completo",
"SIGN_IN_NAME": "Nombre de usuario",
"SIGN_IN_PWD": "Contraseña"
},
"PROFILE": {
"TITLE": "Perfil de usuario",
"USER_NAME": "Nombre de usuario",
"EMAIL": "Email",
"FULL_NAME": "Nombre y apellidos",
"COMMENT": "Comentarios",
"PASSWORD": "Contraseña",
"SAVE_SUCCESS": "Perfil de usuario guardado satisfactoriamente."
},
"CHANGE_PWD": {
"TITLE": "Cambiar contraseña",
"CURRENT_PWD": "Contraseña actual",
"NEW_PWD": "Nueva contraseña",
"CONFIRM_PWD": "Confirmar contraseña",
"SAVE_SUCCESS": "Contraseña de usuario guardada satisfactoriamente.",
"PASS_TIPS": "Al menos 8 caracteres con 1 letra mayúscula, 1 minúscula y 1 número"
},
"ACCOUNT_SETTINGS": {
"PROFILE": "Perfil de usuario",
"CHANGE_PWD": "Cambiar contraseña",
"ABOUT": "Acerca de",
"LOGOUT": "Cerrar sesión",
"ROOT_CERT": "Descargar Certificado Raíz"
},
"GLOBAL_SEARCH": {
"PLACEHOLDER": "Buscar en Harbor...",
"PLACEHOLDER_VIC": "Buscar en el registro..."
},
"SIDE_NAV": {
"DASHBOARD": "Panel",
"PROJECTS": "Proyectos",
"SYSTEM_MGMT": {
"NAME": "Administración",
"USER": "Usuarios",
"REPLICATION": "Replicación",
"CONFIG": "Configuración"
},
"LOGS": "Logs"
},
"USER": {
"ADD_ACTION": "USUARIO",
"ENABLE_ADMIN_ACTION": "Marcar como Administrador",
"DISABLE_ADMIN_ACTION": "Desmarcar como Administrador",
"DEL_ACTION": "Eliminar",
"FILTER_PLACEHOLDER": "Filtrar usuarios",
"COLUMN_NAME": "Nombre",
"COLUMN_ADMIN": "Administrador",
"COLUMN_EMAIL": "Email",
"COLUMN_REG_NAME": "Fecha de registro",
"IS_ADMIN": "Si",
"IS_NOT_ADMIN": "No",
"ADD_USER_TITLE": "Nuevo usuario",
"SAVE_SUCCESS": "Nuevo usuario guardado satisfactoriamente.",
"DELETION_TITLE": "Confirmar eliminación de usuario",
"DELETION_SUMMARY": "¿Quiere eliminar el usuario {{param}}?",
"DELETE_SUCCESS": "Usuario eliminado satisfactoriamente.",
"ITEMS": "elemento(s)"
},
"PROJECT": {
"PROJECTS": "Proyectos",
"NAME": "Nombre del Proyecto",
"ROLE": "Rol",
"PUBLIC_OR_PRIVATE": "Público",
"ACCESS_LEVEL": "Nivel de acceso",
"REPO_COUNT": "Contador de repositorios",
"CREATION_TIME": "Fecha de creación",
"PUBLIC": "Público",
"PRIVATE": "Privado",
"MAKE": "Hacer",
"NEW_POLICY": "Nueva regla de replicación",
"DELETE": "Eliminar",
"MY_PROJECTS": "Todos los proyectos",
"PUBLIC_PROJECTS": "Proyectos Públicos",
"PROJECT": "Proyecto",
"NEW_PROJECT": "Nuevo proyecto",
"NAME_IS_REQUIRED": "El nombre del proyecto es obligatorio.",
"NAME_MINIMUM_LENGTH": "El nombre del proyecto es demasiado corto, debe ser mayor de 2 caracteres.",
"NAME_ALREADY_EXISTS": "Ya existe un proyecto con ese nombre.",
"NAME_IS_ILLEGAL": "El nombre del proyecto no es valido.",
"UNKNOWN_ERROR": "Ha ocurrido un error al crear el proyecto.",
"ITEMS": "elemento(s)",
"DELETION_TITLE": "Confirmar eliminación del proyecto",
"DELETION_SUMMARY": "¿Quiere eliminar el proyecto {{param}}?",
"FILTER_PLACEHOLDER": "Filtrar proyectos",
"REPLICATION_RULE": "Reglas de replicación",
"CREATED_SUCCESS": "Proyecto creado satisfactoriamente.",
"DELETED_SUCCESS": "Proyecto eliminado satisfactoriamente.",
"TOGGLED_SUCCESS": "Proyecto alternado satisfactoriamente.",
"FAILED_TO_DELETE_PROJECT": "Los proyectos que contienen repositorios o reglas de replicación no pueden eliminarse.",
"INLINE_HELP_PUBLIC": "Cuando un proyecto se marca como público, todo el mundo tiene permisos de lectura sobre los repositorio de dicho proyecto, y no hace falta hacer \"docker login\" antes de subir imágenes a ellos."
},
"PROJECT_DETAIL": {
"REPOSITORIES": "Repositorios",
"REPLICATION": "Replicación",
"USERS": "Miembros",
"LOGS": "Logs",
"PROJECTS": "Proyectos"
},
"MEMBER": {
"NEW_MEMBER": "Nuevo miembro",
"MEMBER": "Miembro",
"NAME": "Nombre",
"ROLE": "Rol",
"SYS_ADMIN": "Administrador del sistema",
"PROJECT_ADMIN": "Administrador del proyecto",
"DEVELOPER": "Desarrollador",
"GUEST": "Invitado",
"DELETE": "Eliminar",
"ITEMS": "elemento(s)",
"ACTIONS": "Acciones",
"USERNAME_IS_REQUIRED": "El nombre de usuario es obligatorio",
"USERNAME_DOES_NOT_EXISTS": "Ese nombre de usuario no existe.",
"USERNAME_ALREADY_EXISTS": "Ese nombre de usuario ya existe.",
"UNKNOWN_ERROR": "Ha ocurrido un error al añadir el miembro.",
"FILTER_PLACEHOLDER": "Filtrar Miembros",
"DELETION_TITLE": "Confirmar eliminación de miembro de un proyecto",
"DELETION_SUMMARY": "¿Quiere eliminar el miembro {{param}} del proyecto?",
"ADDED_SUCCESS": "Miembro añadido satisfactoriamente.",
"DELETED_SUCCESS": "Miembro eliminado satisfactoriamente",
"SWITCHED_SUCCESS": "Rol del miembro cambiado satisfactoriamente."
},
"AUDIT_LOG": {
"USERNAME": "Nombre de usuario",
"REPOSITORY_NAME": "Nombre del Repositorio",
"TAGS": "Etiquetas",
"OPERATION": "Operación",
"OPERATIONS": "Operaciones",
"TIMESTAMP": "Fecha",
"ALL_OPERATIONS": "Todas las operaciones",
"PULL": "Pull",
"PUSH": "Push",
"CREATE": "Crear",
"DELETE": "Eliminar",
"OTHERS": "Otros",
"ADVANCED": "Avanzado",
"SIMPLE": "Simple",
"ITEMS": "elemento(s)",
"FILTER_PLACEHOLDER": "Filtrar logs",
"INVALID_DATE": "Fecha invalida."
},
"REPLICATION": {
"REPLICATION_RULE": "Reglas de Replicación",
"NEW_REPLICATION_RULE": "Nueva Regla de Replicación",
"ENDPOINTS": "Endpoints",
"FILTER_POLICIES_PLACEHOLDER": "Filtrar Reglas",
"FILTER_JOBS_PLACEHOLDER": "Filtrar Trabajos",
"DELETION_TITLE": "Confirmar Eliminación de Regla",
"DELETION_SUMMARY": "¿Quiere eliminar la regla {{param}}?",
"FILTER_TARGETS_PLACEHOLDER": "Filtrar Endpoints",
"DELETION_TITLE_TARGET": "Confirmar Eliminación de Endpoint",
"DELETION_SUMMARY_TARGET": "¿Quiere eliminar el endpoint {{param}}?",
"ADD_POLICY": "Nueva Regla de Replicación",
"EDIT_POLICY": "Editar",
"EDIT_POLICY_TITLE": "Editar Regla de Replicación",
"DELETE_POLICY": "Eliminar",
"TEST_CONNECTION": "Comprobar conexión",
"TESTING_CONNECTION": "Comprobando conexión...",
"TEST_CONNECTION_SUCCESS": "Conexión comprobada satisfactoriamente.",
"TEST_CONNECTION_FAILURE": "Fallo al conectar con el endpoint.",
"NAME": "Nombre",
"PROJECT": "Proyecto",
"NAME_IS_REQUIRED": "El nombre es obligatorio.",
"DESCRIPTION": "Descripción",
"ENABLE": "Activar",
"DISABLE": "Desactivar",
"DESTINATION_NAME": "Nombre del Endpoint",
"DESTINATION_NAME_IS_REQUIRED": "El nombre del endpoint es obligatorio.",
"NEW_DESTINATION": "Nuevo Endpoint",
"DESTINATION_URL": "URL del Endpoint",
"DESTINATION_URL_IS_REQUIRED": "La URL del endpoint es obligatoria.",
"DESTINATION_USERNAME": "Nombre de usuario",
"DESTINATION_PASSWORD": "Contraseña",
"ALL_STATUS": "Todos los estados",
"ENABLED": "Activado",
"DISABLED": "Desactivado",
"LAST_START_TIME": "Última Fecha de Inicio",
"ACTIVATION": "Activación",
"REPLICATION_JOBS": "Trabajos de Replicación",
"ALL": "Todos",
"PENDING": "Pendiente",
"RUNNING": "Ejecutando",
"ERROR": "Error",
"RETRYING": "Reintentando",
"STOPPED": "Parado",
"FINISHED": "Terminado",
"CANCELED": "Cancelado",
"SIMPLE": "Simple",
"ADVANCED": "Avanzado",
"STATUS": "Estado",
"OPERATION": "Operación",
"CREATION_TIME": "Fecha de Inicio",
"END_TIME": "Fecha de Finalización",
"LOGS": "Logs",
"ITEMS": "elemento(s)",
"TOGGLE_ENABLE_TITLE": "Activar Regla",
"CONFIRM_TOGGLE_ENABLE_POLICY": "Después de la activación de esta regla, todos los repositorios de este proyecto serán replicados al registro de destino.\nPor favor, confirme para continuar.",
"TOGGLE_DISABLE_TITLE": "Desactivar Regla",
"CONFIRM_TOGGLE_DISABLE_POLICY": "Después de la desactivación de la regla, todos los trabajos de replicación no finalizados serán interrumpidos y cancelados.\nPor favor, confirme para continuar.",
"CREATED_SUCCESS": "Regla de replicación creada satisfactoriamente.",
"UPDATED_SUCCESS": "Regla de replicación actualizada satisfactoriamente.",
"DELETED_SUCCESS": "Regla de replicación eliminada satisfactoriamente.",
"DELETED_FAILED": "Fallo al eliminar la regla de replicación.",
"TOGGLED_SUCCESS": "Regla de replicación cambiada satisfactoriamente.",
"CANNOT_EDIT": "La regla de replicación no se puede cambiar mientras esté activa.",
"POLICY_ALREADY_EXISTS": "La regla de replicación ya existe.",
"FAILED_TO_DELETE_POLICY_ENABLED": "No se puede eliminar la regla: tiene trabajo(s) sin finalizar o está activa.",
"FOUND_ERROR_IN_JOBS": "Se han encontrado errores en el trabajo de replicación. Por favor, compruébelos.",
"INVALID_DATE": "Fecha invalida."
},
"DESTINATION": {
"NEW_ENDPOINT": "Nuevo Endpoint",
"ENDPOINT": "Endpoint",
"NAME": "Nombre del Endpoint",
"NAME_IS_REQUIRED": "El nombre del endpoint es obligatorio.",
"URL": "URL del Endpoint",
"URL_IS_REQUIRED": "La URL del endpoint es obligatoria.",
"USERNAME": "Nombre de usuario",
"PASSWORD": "Contraseña",
"TEST_CONNECTION": "Comprobar conexión",
"TITLE_EDIT": "Editar Endpoint",
"TITLE_ADD": "Crear Endpoint",
"DELETE": "Eliminar Endpoint",
"TESTING_CONNECTION": "Comprobar conexión...",
"TEST_CONNECTION_SUCCESS": "Conexión comprobada satisfactoriamente.",
"TEST_CONNECTION_FAILURE": "Fallo al comprobar el endpoint.",
"CONFLICT_NAME": "El nombre del endpoint ya existe.",
"INVALID_NAME": "El nombre del endpoint no es válido.",
"FAILED_TO_GET_TARGET": "Fallo al obtener el endpoint.",
"CREATION_TIME": "Fecha de creación",
"ITEMS": "elemento(s)",
"CREATED_SUCCESS": "Endpoint creado satisfactoriamente.",
"UPDATED_SUCCESS": "Endpoint actualizado satisfactoriamente.",
"DELETED_SUCCESS": "Endpoint eliminado satisfactoriamente.",
"DELETED_FAILED": "Ha fallado la eliminación del endpoint.",
"CANNOT_EDIT": "El endpoint no puede ser cambiado mientras la regla de replicación está activa.",
"FAILED_TO_DELETE_TARGET_IN_USED": "Fallo al eliminar el endpoint en uso."
},
"REPOSITORY": {
"COPY_ID": "Copiar ID",
"COPY_PARENT_ID": "Copiar ID padre",
"DELETE": "Eliminar",
"NAME": "Nombre",
"TAGS_COUNT": "Etiquetas",
"PULL_COUNT": "Pulls",
"PULL_COMMAND": "Comando Pull",
"MY_REPOSITORY": "Mi Repositorio",
"PUBLIC_REPOSITORY": "Repositorio Público",
"DELETION_TITLE_REPO": "Confirmar Eliminación de Repositorio",
"DELETION_SUMMARY_REPO": "¿Quiere eliminar el repositorio {{param}}?",
"DELETION_TITLE_TAG": "Confirmación de Eliminación de Etiqueta",
"DELETION_SUMMARY_TAG": "¿Quiere eliminar la etiqueta {{param}}?",
"DELETION_TITLE_TAG_DENIED": "La etiqueta firmada no puede ser eliminada",
"DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n{{param}}",
"FILTER_FOR_REPOSITORIES": "Filtrar Repositorios",
"TAG": "Etiqueta",
"SIGNED": "Firmada",
"AUTHOR": "Autor",
"CREATED": "Fecha de creación",
"DOCKER_VERSION": "Version de Docker",
"ARCHITECTURE": "Arquitectura",
"OS": "SO",
"SHOW_DETAILS": "Mostrar Detalles",
"REPOSITORIES": "Repositorios",
"ITEMS": "elemento(s)",
"POP_REPOS": "Repositorios Populares",
"DELETED_REPO_SUCCESS": "Repositorio eliminado satisfactoriamente.",
"DELETED_TAG_SUCCESS": "Etiqueta eliminada satisfactoriamente.",
"COPY": "Copiar"
},
"ALERT": {
"FORM_CHANGE_CONFIRMATION": "Algunos cambios no se han guardado aún. ¿Quiere cancelar?"
},
"RESET_PWD": {
"TITLE": "Reiniciar Contraseña",
"CAPTION": "Introduzca el email para reiniciar la contraseña",
"EMAIL": "Email",
"SUCCESS": "El email con el enlace para restablecer la contraseña ha sido enviado satisfactoriamente. Puedes cerrar éste diálogo y comprobar tu bandeja de entrada de emails.",
"CAPTION2": "Introduce tu nueva contraseña",
"RESET_OK": "La contraseña ha sido reiniciada satisfactoriamente. Haz click en OK para identificarte con la nueva contraseña."
},
"RECENT_LOG": {
"SUB_TITLE": "Mostrar",
"SUB_TITLE_SUFIX": "logs"
},
"CONFIG": {
"TITLE": "Configuración",
"AUTH": "Autentificación",
"REPLICATION": "Replicación",
"EMAIL": "Email",
"SYSTEM": "Opciones del Sistema",
"CONFIRM_TITLE": "Confirma cancelación",
"CONFIRM_SUMMARY": "Algunos cambios no han sido guardados aún. ¿Quiere descartarlos?",
"SAVE_SUCCESS": "La configuración ha sido guardada satisfactoriamente.",
"MAIL_SERVER": "Servidor de email",
"MAIL_SERVER_PORT": "Puerto del servidor de email",
"MAIL_USERNAME": "Usuario del servidor de email",
"MAIL_PASSWORD": "Contraseña del servidor de email",
"MAIL_FROM": "Email De",
"MAIL_SSL": "Email SSL",
"SSL_TOOLTIP": "Activar SSL en conexiones al servidor de correo",
"VERIFY_REMOTE_CERT": "Verificar Certificado Remoto",
"TOKEN_EXPIRATION": "Expiración del Token (Minutos)",
"AUTH_MODE": "Modo de autentificación",
"PRO_CREATION_RESTRICTION": "Creación de Proyecto",
"SELF_REGISTRATION": "Permitir auto-registro",
"AUTH_MODE_DB": "Base de datos",
"AUTH_MODE_LDAP": "LDAP",
"SCOPE_BASE": "Base",
"SCOPE_ONE_LEVEL": "UnNivel",
"SCOPE_SUBTREE": "Subárbol",
"PRO_CREATION_EVERYONE": "Todos",
"PRO_CREATION_ADMIN": "Solo Administradores",
"TOOLTIP": {
"SELF_REGISTRATION": "Activar registro.",
"VERIFY_REMOTE_CERT": "Determina si la replicación de la imagen debería verificar el certificado de un registro Harbor remoto. Desmarque esta opción cuando el registro remoto use un certificado de confianza o autofirmado.",
"AUTH_MODE": "Por defecto el modo de autentificación es base de datos, es decir, las credenciales se almacenan en una base de datos local. Seleccione LDAP si quiere verificar las credenciales de usuarios a través del servidor LDAP.",
"LDAP_SEARCH_DN": "Un DN de usuario que tenga permisos para buscar el servidor LDAP/AD. Si el servidor LDAP/AD no soporta búsquedas anónimas, debería configurar este DN y ldap_search_pwd.",
"LDAP_BASE_DN": "La base DN para buscar un usuario en el LDAP/AD.",
"LDAP_UID": "El atributo usado en una búsqueda para encontrar un usuario. Debe ser el uid, cn, email, sAMAccountName u otro atributo dependiendo del LDAP/AD.",
"LDAP_SCOPE": "El ámbito de búsqueda para usuarios",
"TOKEN_EXPIRATION": "El tiempo de expiración (en minutos) del token creado por el servicio de tokens. Por defecto son 30 minutos.",
"PRO_CREATION_RESTRICTION": "Marca para definir qué usuarios tienen permisos para crear proyectos. Por defecto, todos pueden crear proyectos. Seleccione 'Solo Administradores' para que solamente los administradores puedan crear proyectos."
},
"LDAP": {
"URL": "LDAP URL",
"SEARCH_DN": "LDAP Buscar DN",
"SEARCH_PWD": "LDAP Buscar Contraseña",
"BASE_DN": "LDAP Base DN",
"FILTER": "LDAP Filtro",
"UID": "LDAP UID",
"SCOPE": "LDAP Ámbito"
},
"TEST_MAIL_SUCCESS": "La conexión al servidor de correo ha sido verificada.",
"TEST_LDAP_SUCCESS": "La conexión al servidor LDAP ha sido verificada.",
"TEST_MAIL_FAILED": "Fallo al verificar el servidor de correo con el error: {{param}}.",
"TEST_LDAP_FAILED": "Fallo al verificar el servidor LDAP con el error: {{param}}.",
"LEAVING_CONFIRMATION_TITLE": "Confirme la salida",
"LEAVING_CONFIRMATION_SUMMARY": "Los cambios no han sido guardados aún. ¿Quiere abandonar la página actual?"
},
"PAGE_NOT_FOUND": {
"MAIN_TITLE": "Página no encontrada",
"SUB_TITLE": "Redirigiendo a la página principal en",
"UNIT": "segundos..."
},
"ABOUT": {
"VERSION": "Versión",
"BUILD": "Construir",
"COPYRIGHT": "Copyright 1998-2017 VMware, Inc. Todos los derechos reservados. Este producto está protegido por E.U. y las leyes de propiedad internacionales. Los productos de VMware estan cubiertos por una o más patentes listadas en",
"COPYRIGHT_SUFIX": ".",
"TRADEMARK": "VMware es una marca registrada o marca de VMware, Inc. en los Estados Unidos y otras jurisdicciones. Todas las demás marcas y nombres mencionados son marcas de sus respectivas compañías.",
"END_USER_LICENSE": "Contrato de Usuario Final (EULA)",
"OPEN_SOURCE_LICENSE": "Código Abierto/Licencias de Terceros"
},
"START_PAGE": {
"GETTING_START": "",
"GETTING_START_TITLE": "Comenzar"
},
"TOP_REPO": "Repositorios Populares",
"STATISTICS": {
"TITLE": "ESTADÍSTICAS",
"PRO_ITEM": "PROYECTOS",
"REPO_ITEM": "REPOSITORIOS",
"INDEX_MY": "MI",
"INDEX_PUB": "PÚBLICO",
"INDEX_TOTAL": "TOTAL",
"STORAGE": "ALMACENAMIENTO",
"LIMIT": "Límite"
},
"SEARCH": {
"IN_PROGRESS": "Buscar...",
"BACK": "Volver"
},
"UNKNOWN_ERROR": "Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo más tarde.",
"UNAUTHORIZED_ERROR": "La sesión no es válida o ha caducado. Necesita identificarse de nuevo para llevar a cabo esa acción.",
"FORBIDDEN_ERROR": "No tienes permisos para llevar a cabo esa acción.",
"GENERAL_ERROR": "Han ocurrido errores cuando se llamaba al servicio: {{param}}.",
"BAD_REQUEST_ERROR": "No hemos podido llevar la acción debido a una solicitud incorrecta.",
"NOT_FOUND_ERROR": "La solicitud no puede ser completada porque el objeto no existe.",
"CONFLICT_ERROR": "No hemos podido llevar a cabo la acción debido a un conflicto.",
"PRECONDITION_FAILED": "No hemos podido llevar a cabo la acción debido a un error de precondición.",
"SERVER_ERROR": "No hemos podido llevar a cabo la acción debido a un error interno.",
"INCONRRECT_OLD_PWD": "La contraseña antigua no es correcta.",
"UNKNOWN": "n/a"
}

View File

@ -0,0 +1,448 @@
{
"APP_TITLE": {
"VMW_HARBOR": "VMware Harbor",
"HARBOR": "Harbor",
"VIC": "vSphere Integrated Containers",
"MGMT": "Management",
"REG": "Registry"
},
"SIGN_IN": {
"REMEMBER": "记住我",
"INVALID_MSG": "用户名或者密码不正确。",
"FORGOT_PWD": "忘记密码",
"HEADER_LINK": "登录"
},
"SIGN_UP": {
"TITLE": "注册"
},
"BUTTON": {
"CANCEL": "取消",
"OK": "确定",
"DELETE": "删除",
"LOG_IN": "登录",
"SIGN_UP_LINK": "注册账号",
"SIGN_UP": "注册",
"CONFIRM": "确定",
"SEND": "发送",
"SAVE": "保存",
"TEST_MAIL": "测试邮件服务器",
"CLOSE": "关闭",
"TEST_LDAP": "测试LDAP服务器",
"MORE_INFO": "更多信息...",
"YES": "是",
"NO": "否",
"NEGATIVE": "否"
},
"TOOLTIP": {
"EMAIL": "请使用正确的邮箱地址比如name@example.com。",
"USER_NAME": "不能包含特殊字符且长度不能超过20。",
"FULL_NAME": "长度不能超过20。",
"COMMENT": "长度不能超过20。",
"CURRENT_PWD": "当前密码为必填项。",
"PASSWORD": "密码长度至少为8且需包含至少一个大写字符一个小写字符和一个数字。",
"CONFIRM_PWD": "密码输入不一致。",
"SIGN_IN_USERNAME": "用户名为必填项。",
"SIGN_IN_PWD": "密码为必填项。",
"SIGN_UP_MAIL": "邮件地址仅用来重置您的密码。",
"SIGN_UP_REAL_NAME": "全名",
"ITEM_REQUIRED": "此项为必填项。",
"NUMBER_REQUIRED": "此项为必填项且为数字。",
"PORT_REQUIRED": "此项为必填项且为合法端口号。",
"EMAIL_EXISTING": "邮件地址已经存在。",
"USER_EXISTING": "用户名已经存在。"
},
"PLACEHOLDER": {
"CURRENT_PWD": "输入当前密码",
"NEW_PWD": "输入新密码",
"CONFIRM_PWD": "确认新密码",
"USER_NAME": "输入用户名称",
"MAIL": "输入邮箱地址",
"FULL_NAME": "输入全名",
"SIGN_IN_NAME": "用户名",
"SIGN_IN_PWD": "密码"
},
"PROFILE": {
"TITLE": "用户设置",
"USER_NAME": "用户名",
"EMAIL": "邮箱",
"FULL_NAME": "全名",
"COMMENT": "注释",
"PASSWORD": "密码",
"SAVE_SUCCESS": "成功保存用户设置。"
},
"CHANGE_PWD": {
"TITLE": "修改密码",
"CURRENT_PWD": "当前密码",
"NEW_PWD": "新密码",
"CONFIRM_PWD": "确认密码",
"SAVE_SUCCESS": "成功更改用户密码。",
"PASS_TIPS": "至少8个字符且需包含至少一个大写字符、小写字符或者数字"
},
"ACCOUNT_SETTINGS": {
"PROFILE": "用户设置",
"CHANGE_PWD": "修改密码",
"ABOUT": "关于",
"LOGOUT": "退出",
"ROOT_CERT": "下载根证书"
},
"GLOBAL_SEARCH": {
"PLACEHOLDER": "搜索 Harbor...",
"PLACEHOLDER_VIC": "搜索 Registry..."
},
"SIDE_NAV": {
"DASHBOARD": "仪表板",
"PROJECTS": "项目",
"SYSTEM_MGMT": {
"NAME": "系统管理",
"USER": "用户管理",
"REPLICATION": "复制管理",
"CONFIG": "配置管理"
},
"LOGS": "日志"
},
"USER": {
"ADD_ACTION": "用户",
"ENABLE_ADMIN_ACTION": "设置为管理员",
"DISABLE_ADMIN_ACTION": "取消管理员",
"DEL_ACTION": "删除",
"FILTER_PLACEHOLDER": "过滤用户",
"COLUMN_NAME": "用户名",
"COLUMN_ADMIN": "管理员",
"COLUMN_EMAIL": "邮件",
"COLUMN_REG_NAME": "注册时间",
"IS_ADMIN": "是",
"IS_NOT_ADMIN": "否",
"ADD_USER_TITLE": "创建用户",
"SAVE_SUCCESS": "成功创建用户。",
"DELETION_TITLE": "删除用户确认",
"DELETION_SUMMARY": "你确认删除用户 {{param}}?",
"DELETE_SUCCESS": "成功删除用户。",
"ITEMS": "条记录"
},
"PROJECT": {
"PROJECTS": "项目",
"NAME": "项目名称",
"ROLE": "角色",
"PUBLIC_OR_PRIVATE": "访问级别",
"REPO_COUNT": "镜像仓库数",
"CREATION_TIME": "创建时间",
"PUBLIC": "公开",
"PRIVATE": "私有",
"MAKE": "设为",
"NEW_POLICY": "新建规则",
"DELETE": "删除",
"MY_PROJECTS": "所有项目",
"PUBLIC_PROJECTS": "公开项目",
"PROJECT": "项目",
"NEW_PROJECT": "新建项目",
"NAME_IS_REQUIRED": "项目名称为必填项",
"NAME_MINIMUM_LENGTH": "项目名称长度过短至少多于2个字符。",
"NAME_ALREADY_EXISTS": "项目名称已存在。",
"NAME_IS_ILLEGAL": "项目名称非法。",
"UNKNOWN_ERROR": "创建项目时发生未知错误。",
"ITEMS": "条记录",
"DELETION_TITLE": "删除项目确认",
"DELETION_SUMMARY": "你确认删除项目 {{param}}",
"FILTER_PLACEHOLDER": "过滤项目",
"REPLICATION_RULE": "复制规则",
"CREATED_SUCCESS": "成功创建项目。",
"DELETED_SUCCESS": "成功删除项目。",
"TOGGLED_SUCCESS": "切换状态成功。",
"FAILED_TO_DELETE_PROJECT": "项目包含镜像仓库或复制规则,无法删除。",
"INLINE_HELP_PUBLIC": "当项目设为公开后任何人都有此项目下镜像的读权限。命令行用户不需要“docker login”就可以拉取此项目下的镜像。"
},
"PROJECT_DETAIL": {
"REPOSITORIES": "镜像仓库",
"REPLICATION": "复制",
"USERS": "成员",
"LOGS": "日志",
"PROJECTS": "项目"
},
"MEMBER": {
"NEW_MEMBER": "新建成员",
"MEMBER": "成员",
"NAME": "姓名",
"ROLE": "角色",
"SYS_ADMIN": "系统管理员",
"PROJECT_ADMIN": "项目管理员",
"DEVELOPER": "开发人员",
"GUEST": "访客",
"DELETE": "删除",
"ITEMS": "条记录",
"ACTIONS": "操作",
"USERNAME_IS_REQUIRED": "用户名为必填项。",
"USERNAME_DOES_NOT_EXISTS": "用户名不存在。",
"USERNAME_ALREADY_EXISTS": "用户名已存在。",
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
"FILTER_PLACEHOLDER": "过滤成员",
"DELETION_TITLE": "删除项目成员确认",
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?",
"ADDED_SUCCESS": "成功新增成员。",
"DELETED_SUCCESS": "成功删除成员。",
"SWITCHED_SUCCESS": "切换角色成功。"
},
"AUDIT_LOG": {
"USERNAME": "用户名",
"REPOSITORY_NAME": "镜像名称",
"TAGS": "标签",
"OPERATION": "操作",
"OPERATIONS": "操作",
"TIMESTAMP": "时间戳",
"ALL_OPERATIONS": "所有操作",
"PULL": "Pull",
"PUSH": "Push",
"CREATE": "Create",
"DELETE": "Delete",
"OTHERS": "其他",
"ADVANCED": "高级检索",
"SIMPLE": "简单检索",
"ITEMS": "条记录",
"FILTER_PLACEHOLDER": "过滤日志",
"INVALID_DATE": "无效日期。"
},
"REPLICATION": {
"REPLICATION_RULE": "复制规则",
"NEW_REPLICATION_RULE": "新建规则",
"ENDPOINTS": "目标",
"FILTER_POLICIES_PLACEHOLDER": "过滤规则",
"FILTER_JOBS_PLACEHOLDER": "过滤任务",
"DELETION_TITLE": "删除规则确认",
"DELETION_SUMMARY": "确认删除规则 {{param}}?",
"FILTER_TARGETS_PLACEHOLDER": "过滤目标",
"DELETION_TITLE_TARGET": "删除目标确认",
"DELETION_SUMMARY_TARGET": "确认删除目标 {{param}}?",
"ADD_POLICY": "新建规则",
"EDIT_POLICY": "修改",
"EDIT_POLICY_TITLE": "修改规则",
"DELETE_POLICY": "删除",
"TEST_CONNECTION": "测试连接",
"TESTING_CONNECTION": "正在测试连接...",
"TEST_CONNECTION_SUCCESS": "测试连接成功。",
"TEST_CONNECTION_FAILURE": "测试连接失败。",
"NAME": "名称",
"PROJECT": "项目",
"NAME_IS_REQUIRED": "名称为必填项。",
"DESCRIPTION": "描述",
"ENABLE": "启用",
"DISABLE": "停用",
"DESTINATION_NAME": "目标名",
"DESTINATION_NAME_IS_REQUIRED": "目标名称为必填项。",
"NEW_DESTINATION": "创建目标",
"DESTINATION_URL": "目标URL",
"DESTINATION_URL_IS_REQUIRED": "目标URL为必填项。",
"DESTINATION_USERNAME": "用户名",
"DESTINATION_PASSWORD": "密码",
"ALL_STATUS": "所有状态",
"ENABLED": "启用",
"DISABLED": "停用",
"LAST_START_TIME": "上次起始时间",
"ACTIVATION": "活动状态",
"REPLICATION_JOBS": "复制任务",
"ALL": "全部",
"PENDING": "挂起",
"RUNNING": "运行中",
"ERROR": "错误",
"RETRYING": "重试中",
"STOPPED": "已停止",
"FINISHED": "已完成",
"CANCELED": "已取消",
"SIMPLE": "简单检索",
"ADVANCED": "高级检索",
"STATUS": "状态",
"OPERATION": "操作",
"CREATION_TIME": "创建时间",
"END_TIME": "结束时间",
"LOGS": "日志",
"ITEMS": "条记录",
"TOGGLE_ENABLE_TITLE": "启用规则",
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用规则后,该项目下的所有镜像仓库将复制到目标实例。\n请确认继续。",
"TOGGLE_DISABLE_TITLE": "停用规则",
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用规则后,所有未完成的复制任务将被终止和取消。\n请确认继续。",
"CREATED_SUCCESS": "创建复制规则成功。",
"UPDATED_SUCCESS": "更新复制规则成功。",
"DELETED_SUCCESS": "删除复制规则成功。",
"DELETED_FAILED": "删除复制规则失败。",
"TOGGLED_SUCCESS": "切换复制规则状态成功。",
"CANNOT_EDIT": "当复制规则启用时无法修改。",
"POLICY_ALREADY_EXISTS": "规则已存在。",
"FAILED_TO_DELETE_POLICY_ENABLED": "删除复制规则失败: 仍有未完成的任务或规则未停用。",
"FOUND_ERROR_IN_JOBS": "复制任务中包含错误,请检查。",
"INVALID_DATE": "无效日期。"
},
"DESTINATION": {
"NEW_ENDPOINT": "新建目标",
"ENDPOINT": "目标",
"NAME": "目标名",
"NAME_IS_REQUIRED": "目标名为必填项。",
"URL": "目标URL",
"URL_IS_REQUIRED": "目标URL为必填项。",
"USERNAME": "用户名",
"PASSWORD": "密码",
"TEST_CONNECTION": "测试连接",
"TITLE_EDIT": "编辑目标",
"TITLE_ADD": "新建目标",
"DELETE": "删除目标",
"TESTING_CONNECTION": "正在测试连接...",
"TEST_CONNECTION_SUCCESS": "测试连接成功。",
"TEST_CONNECTION_FAILURE": "测试连接失败。",
"CONFLICT_NAME": "目标名或目标URL已存在。",
"INVALID_NAME": "无效的目标名称。",
"FAILED_TO_GET_TARGET": "获取目标失败。",
"CREATION_TIME": "创建时间",
"ITEMS": "条记录",
"CREATED_SUCCESS": "成功创建目标。",
"UPDATED_SUCCESS": "成功更新目标。",
"DELETED_SUCCESS": "成功删除目标。",
"DELETED_FAILED": "删除目标失败。",
"CANNOT_EDIT": "当复制规则启用时目标无法修改。",
"FAILED_TO_DELETE_TARGET_IN_USED": "无法删除正在使用的目标。"
},
"REPOSITORY": {
"COPY_ID": "复制ID",
"COPY_PARENT_ID": "复制父级ID",
"DELETE": "删除",
"NAME": "名称",
"TAGS_COUNT": "标签数",
"PULL_COUNT": "下载数",
"PULL_COMMAND": "Pull命令",
"MY_REPOSITORY": "我的仓库",
"PUBLIC_REPOSITORY": "公共仓库",
"DELETION_TITLE_REPO": "删除镜像仓库确认",
"DELETION_SUMMARY_REPO": "确认删除镜像仓库 {{param}}?",
"DELETION_TITLE_TAG": "删除镜像标签确认",
"DELETION_SUMMARY_TAG": "确认删除镜像标签 {{param}}?",
"DELETION_TITLE_TAG_DENIED": "已签名的镜像不能被删除",
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}",
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
"TAG": "标签",
"SIGNED": "已签名",
"AUTHOR": "作者",
"CREATED": "创建时间",
"DOCKER_VERSION": "Docker版本",
"ARCHITECTURE": "架构",
"OS": "操作系统",
"SHOW_DETAILS": "显示详细",
"REPOSITORIES": "镜像仓库",
"ITEMS": "条记录",
"POP_REPOS": "受欢迎的镜像仓库",
"DELETED_REPO_SUCCESS": "成功删除镜像仓库。",
"DELETED_TAG_SUCCESS": "成功删除镜像标签。",
"COPY": "复制"
},
"ALERT": {
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认是否取消?"
},
"RESET_PWD": {
"TITLE": "重置密码",
"CAPTION": "输入用来重置密码的邮箱",
"EMAIL": "邮箱",
"SUCCESS": "重置密码邮件已成功发送. 请关闭对话框并检查邮箱。",
"CAPTION2": "请输入您的新密码",
"RESET_OK": "密码重置成功,点击确定按钮重新登录。"
},
"RECENT_LOG": {
"SUB_TITLE": "显示",
"SUB_TITLE_SUFIX": "条日志"
},
"CONFIG": {
"TITLE": "配置",
"AUTH": "认证模式",
"REPLICATION": "复制",
"EMAIL": "邮箱",
"SYSTEM": "系统设置",
"CONFIRM_TITLE": "确认取消",
"CONFIRM_SUMMARY": "配置项有改动, 确定取消?",
"SAVE_SUCCESS": "变更的配置项成功保存。",
"MAIL_SERVER": "邮件服务器",
"MAIL_SERVER_PORT": "邮件服务器端口",
"MAIL_USERNAME": "用户名",
"MAIL_PASSWORD": "密码",
"MAIL_FROM": "邮件来源",
"MAIL_SSL": "邮件 SSL",
"SSL_TOOLTIP": "启用SSL到邮件服务器连接。",
"VERIFY_REMOTE_CERT": "验证远程证书",
"TOKEN_EXPIRATION": "令牌过期时间(分钟)",
"AUTH_MODE": "认证模式",
"PRO_CREATION_RESTRICTION": "项目创建",
"SELF_REGISTRATION": "允许自注册",
"AUTH_MODE_DB": "数据库",
"AUTH_MODE_LDAP": "LDAP",
"SCOPE_BASE": "本层",
"SCOPE_ONE_LEVEL": "下一层",
"SCOPE_SUBTREE": "子树",
"PRO_CREATION_EVERYONE": "所有人",
"PRO_CREATION_ADMIN": "仅管理员",
"TOOLTIP": {
"SELF_REGISTRATION": "激活注册功能。",
"VERIFY_REMOTE_CERT": "确定镜像复制是否要验证远程Harbor实例的证书。如果远程实例使用的是自签或者非信任证书不要勾选此项。",
"AUTH_MODE": "默认认证模式为数据库认证即用户凭证存储在本地数据库。如果使用LDAP来认证用户则设置为LDAP。",
"LDAP_SEARCH_DN": "有搜索权限的LDAP用户DN。如果LDAP服务器不支持匿名搜索则需要配置此DN及其密码。",
"LDAP_BASE_DN": "用来在LDAP和AD中搜寻用户的基础DN。",
"LDAP_UID": "在搜索中用来匹配用户的属性可以是uid,cn,email,sAMAccountName或者其它LDAP/AD服务器支持的属性。",
"LDAP_SCOPE": "搜索用户的范围。",
"TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间分钟默认为30分钟。",
"PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。"
},
"LDAP": {
"URL": "LDAP URL",
"SEARCH_DN": "LDAP搜索DN",
"SEARCH_PWD": "LDAP搜索密码",
"BASE_DN": "LDAP基础DN",
"FILTER": "LDAP过滤器",
"UID": "LDAP用户UID的属性",
"SCOPE": "LDAP搜索范围"
},
"TEST_MAIL_SUCCESS": "邮件服务器的连通正常。",
"TEST_LDAP_SUCCESS": "LDAP服务器的连通正常。",
"TEST_MAIL_FAILED": "验证邮件服务器失败,错误: {{param}}。",
"TEST_LDAP_FAILED": "验证LDAP服务器失败错误: {{param}}。",
"LEAVING_CONFIRMATION_TITLE": "确定离开",
"LEAVING_CONFIRMATION_SUMMARY": "有未保存的配置更改, 确认离开当前页面?"
},
"PAGE_NOT_FOUND": {
"MAIN_TITLE": "页面不存在",
"SUB_TITLE": "正在重定向到首页:",
"UNIT": "秒..."
},
"ABOUT": {
"VERSION": "版本",
"BUILD": "构建",
"COPYRIGHT": "版权所有 © 1998-2017 VMware, Inc. 保留所有权利。此产品受美国及其他国家/地区的版权和知识产权以及国际条约保护。VMware产品受",
"COPYRIGHT_SUFIX": "上列出的一项或多项专利保护。",
"TRADEMARK": "VMware徽标及设计都是VMware, Inc.在美国和/或其他法律辖区的注册商标或者商标。此处提到的其他所有商标和名称分别是其各自公司的商标。",
"END_USER_LICENSE": "终端用户许可协议",
"OPEN_SOURCE_LICENSE": "开源/第三方许可协议"
},
"START_PAGE": {
"GETTING_START": "",
"GETTING_START_TITLE": "从这开始"
},
"TOP_REPO": "受欢迎的镜像仓库",
"STATISTICS": {
"TITLE": "统计",
"PRO_ITEM": "项目",
"REPO_ITEM": "镜像仓库",
"INDEX_MY": "私有",
"INDEX_MY_PROJECTS": "我的项目",
"INDEX_MY_REPOSITORIES": "我的镜像仓库",
"INDEX_PUB": "公开",
"INDEX_TOTAL": "总计",
"STORAGE": "存储",
"LIMIT": "容量"
},
"SEARCH": {
"IN_PROGRESS": "搜索中...",
"BACK": "返回"
},
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限。",
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}。",
"BAD_REQUEST_ERROR": "错误请求, 操作无法完成。",
"NOT_FOUND_ERROR": "对象不存在, 请求无法完成。",
"CONFLICT_ERROR": "请求包含冲突, 操作无法完成。",
"PRECONDITION_FAILED": "验证前置条件失败, 无法执行操作。",
"SERVER_ERROR": "服务器出现内部错误,请求无法完成。",
"INCONRRECT_OLD_PWD": "旧密码不正确。",
"UNKNOWN": "未知"
}

View File

@ -0,0 +1,21 @@
// Copyright (c) 2017 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.
import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core';
export class MyMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) {
const missingText:string = "{Harbor}";
return params.key || missingText;
}
}

View File

@ -1,4 +1,7 @@
export * from './harbor-library.module'; export * from './harbor-library.module';
export * from './system/index';
export * from './service.config'; export * from './service.config';
export * from './service/index'; export * from './service/index';
export * from './error-handler/index';
//export * from './utils';
export * from './log/index';
export * from './filter/index';

View File

@ -0,0 +1,8 @@
import { Type } from "@angular/core";
import { RecentLogComponent } from './recent-log.component';
export * from "./recent-log.component";
export const LOG_DIRECTIVES: Type<any>[] = [
RecentLogComponent
];

View File

@ -0,0 +1,90 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { DebugElement } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { AccessLog, RequestQueryParams } from '../service/index';
import { RecentLogComponent } from './recent-log.component';
import { AccessLogService } from '../service/access-log.service';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ErrorHandler } from '../error-handler/index';
import { SharedModule } from '../shared/shared.module';
import { FilterComponent } from '../filter/filter.component';
describe('RecentLogComponent', () => {
let component: RecentLogComponent;
let fixture: ComponentFixture<RecentLogComponent>;
let serviceConfig: IServiceConfig;
let logService: AccessLogService;
beforeEach(async(() => {
const testConfig: IServiceConfig = {
logBaseEndpoint: "/api/logs/testing"
};
class MockLogService extends AccessLogService {
public getAuditLogs(projectId: number | string, queryParams?: RequestQueryParams): Observable<AccessLog[]> | Promise<AccessLog[]> | AccessLog[] {
return Observable.of([]);
}
public getRecentLogs(lines: number): Observable<AccessLog[]> | Promise<AccessLog[]> | AccessLog[] {
let mockData: AccessLog[] = [{
log_id: 23,
user_id: 45,
project_id: 11,
repo_name: "myproject/",
repo_tag: "N/A",
operation: "create",
op_time: "2017-04-11T10:26:22Z",
username: "user91"
}, {
log_id: 18,
user_id: 1,
project_id: 5,
repo_name: "demo2/vmware/harbor-ui",
repo_tag: "0.6",
operation: "push",
op_time: "2017-03-09T02:29:59Z",
username: "admin"
}];
return Observable.of(mockData);
}
}
TestBed.configureTestingModule({
imports: [
SharedModule
],
declarations: [FilterComponent, RecentLogComponent],
providers: [
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: testConfig },
{ provide: AccessLogService, useClass: MockLogService }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RecentLogComponent);
component = fixture.componentInstance;
serviceConfig = TestBed.get(SERVICE_CONFIG);
logService = TestBed.get(AccessLogService);
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should inject the SERVICE_CONFIG', () => {
expect(serviceConfig).toBeTruthy();
expect(serviceConfig.logBaseEndpoint).toEqual("/api/logs/testing");
});
it('should inject the AccessLogService', () => {
expect(logService).toBeTruthy();
});
});

View File

@ -0,0 +1,103 @@
// Copyright (c) 2017 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.
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import {
AccessLogService,
AccessLog
} from '../service/index';
import { ErrorHandler } from '../error-handler/index';
import { Observable } from 'rxjs/Observable';
import { toPromise } from '../utils';
import { LOG_TEMPLATE, LOG_STYLES } from './recent-log.template';
@Component({
selector: 'hbr-log',
styles: [LOG_STYLES],
template: LOG_TEMPLATE
})
export class RecentLogComponent implements OnInit {
recentLogs: AccessLog[];
logsCache: AccessLog[];
onGoing: boolean = false;
lines: number = 10; //Support 10, 25 and 50
currentTerm: string;
constructor(
private logService: AccessLogService,
private errorHandler: ErrorHandler) { }
ngOnInit(): void {
this.retrieveLogs();
}
handleOnchange($event: any) {
this.currentTerm = '';
if ($event && $event.target && $event.target["value"]) {
this.lines = $event.target["value"];
if (this.lines < 10) {
this.lines = 10;
}
this.retrieveLogs();
}
}
public get logNumber(): number {
return this.recentLogs ? this.recentLogs.length : 0;
}
public get inProgress(): boolean {
return this.onGoing;
}
public doFilter(terms: string): void {
if (terms.trim() === "") {
this.recentLogs = this.logsCache.filter(log => log.username != "");
return;
}
this.currentTerm = terms;
this.recentLogs = this.logsCache.filter(log => this.isMatched(terms, log));
}
public refresh(): void {
this.retrieveLogs();
}
retrieveLogs(): void {
if (this.lines < 10) {
this.lines = 10;
}
this.onGoing = true;
toPromise<AccessLog[]>(this.logService.getRecentLogs(this.lines))
.then(response => {
this.onGoing = false;
this.logsCache = response; //Keep the data
this.recentLogs = this.logsCache.filter(log => log.username != "");//To display
})
.catch(error => {
this.onGoing = false;
this.errorHandler.error(error);
});
}
isMatched(terms: string, log: AccessLog): boolean {
let reg = new RegExp('.*' + terms + '.*', 'i');
return reg.test(log.username) ||
reg.test(log.repo_name) ||
reg.test(log.operation) ||
reg.test(log.repo_tag);
}
}

View File

@ -0,0 +1,89 @@
/**
* Define the inline template and styles with ts variables
*/
export const LOG_TEMPLATE: string = `
<div>
<h2 class="h2-log-override">{{'SIDE_NAV.LOGS' | translate}}</h2>
<div class="row flex-items-xs-between flex-items-xs-bottom">
<div></div>
<div class="action-head-pos">
<div class="select log-select">
<select id="log_display_num" (change)="handleOnchange($event)">
<option value="10">{{'RECENT_LOG.SUB_TITLE' | translate}} 10 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
<option value="25">{{'RECENT_LOG.SUB_TITLE' | translate}} 25 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
<option value="50">{{'RECENT_LOG.SUB_TITLE' | translate}} 50 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
</select>
</div>
<div class="item-divider"></div>
<hbr-filter filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)" [currentValue]="currentTerm"></hbr-filter>
<span (click)="refresh()" class="refresh-btn">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
</span>
</div>
</div>
<div>
<clr-datagrid>
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'AUDIT_LOG.TAGS' | translate}}</clr-dg-column>
<clr-dg-column>{{'AUDIT_LOG.OPERATION' | translate}}</clr-dg-column>
<clr-dg-column>{{'AUDIT_LOG.TIMESTAMP' | translate}}</clr-dg-column>
<clr-dg-row *clrDgItems="let l of recentLogs">
<clr-dg-cell>{{l.username}}</clr-dg-cell>
<clr-dg-cell>{{l.repo_name}}</clr-dg-cell>
<clr-dg-cell>{{l.repo_tag}}</clr-dg-cell>
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
<clr-dg-cell>{{l.op_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{ (recentLogs ? recentLogs.length : 0) }} {{'AUDIT_LOG.ITEMS' | translate}}</clr-dg-footer>
</clr-datagrid>
</div>
</div>
`;
export const LOG_STYLES: string = `
.h2-log-override {
margin-top: 0px !important;
}
.action-head-pos {
padding-right: 18px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #00bfff;
}
.custom-lines-button {
padding: 0px !important;
min-width: 25px !important;
}
.lines-button-toggole {
font-size: 16px;
text-decoration: underline;
}
.log-select {
width: 130px;
display: inline-block;
top: 1px;
}
.item-divider {
height: 24px;
display: inline-block;
width: 1px;
background-color: #ccc;
opacity: 0.55;
margin-left: 12px;
top: 8px;
position: relative;
}
`;

View File

@ -3,7 +3,7 @@ import { OpaqueToken } from '@angular/core';
export let SERVICE_CONFIG = new OpaqueToken("service.config"); export let SERVICE_CONFIG = new OpaqueToken("service.config");
export interface IServiceConfig { export interface IServiceConfig {
systemInfoEndpoint: string; systemInfoEndpoint?: string;
/** /**
* The base endpoint of the service used to handle the repositories of registry and/or tags of repository. * The base endpoint of the service used to handle the repositories of registry and/or tags of repository.
@ -17,7 +17,7 @@ export interface IServiceConfig {
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
repositoryBaseEndpoint: string; repositoryBaseEndpoint?: string;
/** /**
* The base endpoint of the service used to handle the recent access logs. * The base endpoint of the service used to handle the recent access logs.
@ -25,7 +25,7 @@ export interface IServiceConfig {
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
logBaseEndpoint: string; logBaseEndpoint?: string;
/** /**
* The base endpoint of the service used to handle the registry targets. * The base endpoint of the service used to handle the registry targets.
@ -38,7 +38,7 @@ export interface IServiceConfig {
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
targetBaseEndpoint: string; targetBaseEndpoint?: string;
/** /**
* The base endpoint of the service used to handle the replication rules. * The base endpoint of the service used to handle the replication rules.
@ -50,7 +50,7 @@ export interface IServiceConfig {
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
replicationRuleEndpoint: string; replicationRuleEndpoint?: string;
/** /**
@ -60,5 +60,29 @@ export interface IServiceConfig {
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
replicationJobEndpoint: string; replicationJobEndpoint?: string;
/**
* The cookie key used to store the current used language preference.
*
* @type {string}
* @memberOf IServiceConfig
*/
langCookieKey?: string,
/**
* Declare what languages are supported.
*
* @type {string[]}
* @memberOf IServiceConfig
*/
supportedLangs?: string[],
/**
* To determine whether to not enable the i18 multiple languages supporting.
*
* @type {boolean}
* @memberOf IServiceConfig
*/
enablei18Support?: boolean
} }

View File

@ -1,15 +1,27 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { AccessLogService, AccessLogDefaultService } from './access-log.service'; import { AccessLogService, AccessLogDefaultService } from './access-log.service';
import { SharedModule } from '../shared/shared.module';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
describe('AccessLogService', () => { describe('AccessLogService', () => {
beforeEach(() => { beforeEach(() => {
const mockConfig:IServiceConfig = {
logBaseEndpoint:"/api/logs/testing"
};
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [
SharedModule
],
providers: [ providers: [
AccessLogDefaultService, AccessLogDefaultService,
{ {
provide: AccessLogService, provide: AccessLogService,
useClass: AccessLogDefaultService useClass: AccessLogDefaultService
},{
provide: SERVICE_CONFIG,
useValue: mockConfig
}] }]
}); });
}); });

View File

@ -1,8 +1,11 @@
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { RequestQueryParams } from './RequestQueryParams'; import { RequestQueryParams } from './RequestQueryParams';
import { AccessLog } from './interface'; import { AccessLog } from './interface';
import { Injectable } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of'; import 'rxjs/add/observable/of';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { Http, URLSearchParams } from '@angular/http';
import { HTTP_JSON_OPTIONS } from '../utils';
/** /**
* Define service methods to handle the access log related things. * Define service methods to handle the access log related things.
@ -25,7 +28,7 @@ export abstract class AccessLogService {
* *
* @memberOf AccessLogService * @memberOf AccessLogService
*/ */
abstract getAuditLogs(projectId: number | string, queryParams?: RequestQueryParams): Observable<AccessLog[]> | AccessLog[]; abstract getAuditLogs(projectId: number | string, queryParams?: RequestQueryParams): Observable<AccessLog[]> | Promise<AccessLog[]> | AccessLog[];
/** /**
* Get the recent logs. * Get the recent logs.
@ -36,7 +39,7 @@ export abstract class AccessLogService {
* *
* @memberOf AccessLogService * @memberOf AccessLogService
*/ */
abstract getRecentLogs(lines: number): Observable<AccessLog[]> | AccessLog[]; abstract getRecentLogs(lines: number): Observable<AccessLog[]> | Promise<AccessLog[]> | AccessLog[];
} }
/** /**
@ -48,11 +51,24 @@ export abstract class AccessLogService {
*/ */
@Injectable() @Injectable()
export class AccessLogDefaultService extends AccessLogService { export class AccessLogDefaultService extends AccessLogService {
public getAuditLogs(projectId: number | string, queryParams?: RequestQueryParams): Observable<AccessLog[]> | AccessLog[] { constructor(
private http: Http,
@Inject(SERVICE_CONFIG) private config: IServiceConfig) {
super();
}
public getAuditLogs(projectId: number | string, queryParams?: RequestQueryParams): Observable<AccessLog[]> | Promise<AccessLog[]> | AccessLog[] {
return Observable.of([]); return Observable.of([]);
} }
public getRecentLogs(lines: number): Observable<AccessLog[]> | AccessLog[] { public getRecentLogs(lines: number): Observable<AccessLog[]> | Promise<AccessLog[]> | AccessLog[] {
return Observable.of([]); let url: string = this.config.logBaseEndpoint ? this.config.logBaseEndpoint : "";
if (url === '') {
url = '/api/logs';
}
return this.http.get(url+`?lines=${lines}`, HTTP_JSON_OPTIONS).toPromise()
.then(response => response.json() as AccessLog[])
.catch(error => Promise.reject(error));
} }
} }

View File

@ -23,7 +23,7 @@ export abstract class EndpointService {
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract getEndpoints(endpointName?: string, queryParams?: RequestQueryParams): Observable<Endpoint[]> | Endpoint[]; abstract getEndpoints(endpointName?: string, queryParams?: RequestQueryParams): Observable<Endpoint[]> | Promise<Endpoint[]> | Endpoint[];
/** /**
* Get the specified endpoint. * Get the specified endpoint.
@ -34,7 +34,7 @@ export abstract class EndpointService {
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract getEndpoint(endpointId: number | string): Observable<Endpoint> | Endpoint; abstract getEndpoint(endpointId: number | string): Observable<Endpoint> | Promise<Endpoint> | Endpoint;
/** /**
* Create new endpoint. * Create new endpoint.
@ -45,7 +45,7 @@ export abstract class EndpointService {
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract createEndpoint(endpoint: Endpoint): Observable<any> | any; abstract createEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any;
/** /**
* Update the specified endpoint. * Update the specified endpoint.
@ -57,7 +57,7 @@ export abstract class EndpointService {
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract updateEndpoint(endpointId: number | string, endpoint: Endpoint): Observable<any> | any; abstract updateEndpoint(endpointId: number | string, endpoint: Endpoint): Observable<any> | Promise<any> | any;
/** /**
* Delete the specified endpoint. * Delete the specified endpoint.
@ -68,7 +68,7 @@ export abstract class EndpointService {
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract deleteEndpoint(endpointId: number | string): Observable<any> | any; abstract deleteEndpoint(endpointId: number | string): Observable<any> | Promise<any> | any;
/** /**
* Ping the specified endpoint. * Ping the specified endpoint.
@ -79,7 +79,7 @@ export abstract class EndpointService {
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract pingEndpoint(endpoint: Endpoint): Observable<any> | any; abstract pingEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any;
} }
/** /**
@ -91,27 +91,27 @@ export abstract class EndpointService {
*/ */
@Injectable() @Injectable()
export class EndpointDefaultService extends EndpointService { export class EndpointDefaultService extends EndpointService {
public getEndpoints(endpointName?: string, queryParams?: RequestQueryParams): Observable<Endpoint[]> | Endpoint[] { public getEndpoints(endpointName?: string, queryParams?: RequestQueryParams): Observable<Endpoint[]> | Promise<Endpoint[]> | Endpoint[] {
return Observable.of([]); return Observable.of([]);
} }
public getEndpoint(endpointId: number | string): Observable<Endpoint> | Endpoint { public getEndpoint(endpointId: number | string): Observable<Endpoint> | Promise<Endpoint> | Endpoint {
return Observable.of({}); return Observable.of({});
} }
public createEndpoint(endpoint: Endpoint): Observable<any> | any { public createEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any {
return Observable.of({}); return Observable.of({});
} }
public updateEndpoint(endpointId: number | string, endpoint: Endpoint): Observable<any> | any { public updateEndpoint(endpointId: number | string, endpoint: Endpoint): Observable<any> | Promise<any> | any {
return Observable.of({}); return Observable.of({});
} }
public deleteEndpoint(endpointId: number | string): Observable<any> | any { public deleteEndpoint(endpointId: number | string): Observable<any> | Promise<any> | any {
return Observable.of({}); return Observable.of({});
} }
public pingEndpoint(endpoint: Endpoint): Observable<any> | any { public pingEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any {
return Observable.of({}); return Observable.of({});
} }
} }

View File

@ -60,4 +60,15 @@ export interface ReplicationJob { }
* @export * @export
* @interface AccessLog * @interface AccessLog
*/ */
export interface AccessLog { } export interface AccessLog {
log_id: number,
project_id: number,
repo_name: string,
repo_tag: string,
operation: string,
op_time: string | Date,
user_id: number,
username: string,
keywords?: string, //NOT used now
guid?: string //NOT used now
}

View File

@ -0,0 +1,49 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpModule } from '@angular/http';
import { ClarityModule } from 'clarity-angular';
import { FormsModule } from '@angular/forms';
import { TranslateModule, TranslateLoader, TranslateService, MissingTranslationHandler } from "@ngx-translate/core";
import { MyMissingTranslationHandler } from '../i18n/missing-trans.handler';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { Http } from '@angular/http';
export function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, 'i18n/lang/', '-lang.json');
}
/**
*
* Module for sharing common modules
*
* @export
* @class SharedModule
*/
@NgModule({
imports: [
CommonModule,
HttpModule,
FormsModule,
ClarityModule.forRoot(),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (HttpLoaderFactory),
deps: [Http]
},
missingTranslationHandler: {
provide: MissingTranslationHandler,
useClass: MyMissingTranslationHandler
}
})
],
exports: [
CommonModule,
HttpModule,
FormsModule,
ClarityModule,
TranslateModule
]
})
export class SharedModule { }

View File

@ -1,8 +0,0 @@
import {Type} from "@angular/core";
import { SystemComponent } from './system.component';
export * from "./system.component";
export const SYSTEMINFO_DIRECTIVES: Type<any>[] = [
SystemComponent
];

View File

@ -1,29 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { SystemInfoService } from './system-info.service';
import { HttpModule } from '@angular/http';
import { SERVICE_CONFIG, IServiceConfig } from '../../service.config';
export const testConfig: IServiceConfig = {
systemInfoEndpoint: "/api/systeminfo",
repositoryBaseEndpoint: "",
logBaseEndpoint: "",
targetBaseEndpoint: "",
replicationRuleEndpoint: "",
replicationJobEndpoint: ""
};
describe('SysteninfoService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
{ provide: SERVICE_CONFIG, useValue: testConfig },
SystemInfoService]
});
});
it('should be initialized', inject([SystemInfoService], (service: SystemInfoService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -1,29 +0,0 @@
import { Injectable, Inject } from '@angular/core';
import { Http, RequestOptions, Headers } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { SERVICE_CONFIG, IServiceConfig } from '../../service.config';
@Injectable()
export class SystemInfoService {
httpOptions = new RequestOptions({
headers: new Headers({
"Content-Type": 'application/json'
})
});
constructor(
private http: Http,
@Inject(SERVICE_CONFIG) private config: IServiceConfig) { }
getSystemInfo(): Promise<any> {
if(this.config.systemInfoEndpoint.trim() === "") {
return Promise.reject("500: Internal error");
}
return this.http.get(this.config.systemInfoEndpoint, this.httpOptions).toPromise()
.then(response => response.json())
.catch(error => console.error("Get systeminfo error: ", error));
}
}

View File

@ -1,42 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpModule } from '@angular/http';
import { SystemComponent } from './system.component';
import { SystemInfoService } from './providers/system-info.service';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
export const testConfig: IServiceConfig = {
systemInfoEndpoint: "/api/systeminfo",
repositoryBaseEndpoint: "",
logBaseEndpoint: "",
targetBaseEndpoint: "",
replicationRuleEndpoint: "",
replicationJobEndpoint: ""
};
describe('SystemComponent', () => {
let component: SystemComponent;
let fixture: ComponentFixture<SystemComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
declarations: [SystemComponent],
providers: [
{ provide: SERVICE_CONFIG, useValue: testConfig },
SystemInfoService
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SystemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,34 +0,0 @@
import { Component, OnInit, Input } from '@angular/core';
import { SystemInfoService } from './providers/system-info.service';
@Component({
selector: 'hbr-system',
template: `
<pre>
{{info}}
</pre>
`,
styles: [],
providers: [SystemInfoService]
})
export class SystemComponent implements OnInit {
_systemInfo: string = "Loading...";
constructor(private systemService: SystemInfoService) { }
public get info(): string {
return this._systemInfo;
}
ngOnInit() {
this.getInfo();
}
getInfo(): void {
this.systemService.getSystemInfo()
.then((res: any) => this._systemInfo = JSON.stringify(res))
.catch(error => console.error("Retrieve system info error: ", error));
}
}

View File

@ -0,0 +1,45 @@
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
import { RequestOptions, Headers } from '@angular/http';
/**
* Convert the different async channels to the Promise<T> type.
*
* @export
* @template T
* @param {(Observable<T> | Promise<T> | T)} async
* @returns {Promise<T>}
*/
export function toPromise<T>(async: Observable<T> | Promise<T> | T): Promise<T> {
if (!async) {
return Promise.reject("Bad argument");
}
if (async instanceof Observable) {
let obs: Observable<T> = async;
return obs.toPromise();
} else {
return Promise.resolve(async);
}
}
/**
* The default cookie key used to store current used language preference.
*/
export const DEFAULT_LANG_COOKIE_KEY = 'harbor-lang';
/**
* Declare what languages are supported now.
*/
export const DEFAULT_SUPPORTING_LANGS = ['en-us', 'zh-cn', 'es-es'];
/**
* The default language.
*/
export const DEFAULT_LANG = 'en-us';
export const HTTP_JSON_OPTIONS: RequestOptions = new RequestOptions({
headers: new Headers({
"Content-Type": 'application/json'
})
});

View File

@ -4,6 +4,7 @@
"rootDir": ".", "rootDir": ".",
"declaration": true, "declaration": true,
"stripInternal": true, "stripInternal": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"strictNullChecks": true, "strictNullChecks": true,
"noImplicitAny": true, "noImplicitAny": true,
@ -11,6 +12,10 @@
"moduleResolution": "node", "moduleResolution": "node",
"paths": { "paths": {
"@angular/core": ["node_modules/@angular/core"], "@angular/core": ["node_modules/@angular/core"],
"@angular/common": ["node_modules/@angular/common"],
"@angular/forms": ["node_modules/@angular/forms"],
"@angular/http": ["node_modules/@angular/http"],
"clarity-angular": ["node_modules/clarity-angular"],
"rxjs/*": ["node_modules/rxjs/*"] "rxjs/*": ["node_modules/rxjs/*"]
}, },
"outDir": "dist", "outDir": "dist",
@ -41,6 +46,7 @@
"index.ts" "index.ts"
], ],
"angularCompilerOptions": { "angularCompilerOptions": {
"genDir": "dist",
"strictMetadataEmit": true "strictMetadataEmit": true
} }
} }