From 9d4b36a97cc9eb7f3bfdfa88aecd463a50206c6d Mon Sep 17 00:00:00 2001 From: wy65701436 Date: Thu, 23 Mar 2017 00:20:18 -0700 Subject: [PATCH 1/3] add doc for make --- docs/use_make.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/use_make.md diff --git a/docs/use_make.md b/docs/use_make.md new file mode 100644 index 000000000..d8fd774cc --- /dev/null +++ b/docs/use_make.md @@ -0,0 +1,51 @@ +### Variables +Variable | Description +-------------------|------------- +BASEIMAGE | Container base image, default: photon +DEVFLAG | Build model flag, default: dev +COMPILETAG | Compile model flag, default: compile_normal (local golang build) +GOBUILDIMAGE | Golang image to compile harbor go source code. +CLARITYIMAGE | Clarity image that based on Node to compile UI. +NOTARYFLAG | Whether to enable notary in harbor, default:false +HTTPPROXY | Clarity proxy to build UI. + + +### Targets +Target | Description +--------------------|------------- +all | prepare env, compile binaries, build images and install images +prepare | prepare env +compile | compile ui and jobservice code +compile_ui | compile ui binary +compile_jobservice | compile jobservice binary +compile_clarity | compile clarity ui binary +compile_adminserver | compile admin server binary +build | build Harbor docker images (default: using build_photon) +build_photon | build Harbor docker images from Photon OS base image +install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance +start | startup Harbor instance +down | shutdown Harbor instance +package_online | prepare online install package +package_offline | prepare offline install package +pushimage | push Harbor images to specific registry server +clean all | remove binary, Harbor images, specific version docker-compose file, specific version tag and online/offline install package +cleanbinary | remove ui and jobservice binary +cleanimage | remove Harbor images +cleandockercomposefile | remove specific version docker-compose +cleanversiontag | remove specific version tag +cleanpackage | remove online/offline install package +version | set harbor version + +#### EXAMPLE: + +#### Build and run harbor from source code. +make install GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128 + +### Package offline installer +make package_offline GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128 + +### Start harbor with notary +make -e NOTARYFLAG=true start + +### Stop harbor with notary +make -e NOTARYFLAG=true down \ No newline at end of file From 01d9930b5afe8cff06ce16a0db06d468dfceeff8 Mon Sep 17 00:00:00 2001 From: kunw Date: Thu, 23 Mar 2017 15:36:41 +0800 Subject: [PATCH 2/3] Merge latest updates for UX refinements. --- .../harbor-shell/harbor-shell.component.ts | 11 +- .../src/app/config/config.component.1.html | 62 +++++++++ .../src/app/config/config.component.html | 91 +++++++------ src/ui_ng/src/app/config/config.component.ts | 125 +++++++++++++++--- .../app/global-message/message.component.ts | 26 ++-- src/ui_ng/src/app/harbor-routing.module.ts | 6 +- src/ui_ng/src/app/log/audit-log.service.ts | 1 - .../src/app/log/recent-log.component.css | 34 +++-- .../src/app/log/recent-log.component.html | 32 +++-- src/ui_ng/src/app/log/recent-log.component.ts | 23 ++-- .../create-project.component.ts | 2 +- .../list-project/list-project.component.html | 4 +- .../list-project/list-project.component.ts | 5 + .../add-member/add-member.component.html | 8 +- .../member/add-member/add-member.component.ts | 3 + .../app/project/member/member.component.html | 4 +- .../app/project/member/member.component.ts | 16 ++- .../src/app/project/project.component.html | 2 +- .../src/app/project/project.component.ts | 27 +++- src/ui_ng/src/app/project/project.service.ts | 7 + .../create-edit-destination.component.ts | 6 +- .../destination/destination.component.ts | 11 +- .../app/repository/repository.component.ts | 1 + .../tag-repository.component.html | 4 +- .../tag-repository.component.ts | 44 +++--- .../create-edit-policy.component.ts | 2 + .../list-policy/list-policy.component.ts | 8 +- .../route/member-guard-activate.service.ts | 38 ++++++ src/ui_ng/src/app/shared/session.service.ts | 12 ++ src/ui_ng/src/app/shared/shared.const.ts | 3 +- src/ui_ng/src/app/shared/shared.module.ts | 4 +- src/ui_ng/src/i18n/lang/en-lang.json | 34 +++-- src/ui_ng/src/i18n/lang/zh-lang.json | 30 ++++- 33 files changed, 512 insertions(+), 174 deletions(-) create mode 100644 src/ui_ng/src/app/config/config.component.1.html create mode 100644 src/ui_ng/src/app/shared/route/member-guard-activate.service.ts diff --git a/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts b/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts index 765ccf10b..e4c9c56a4 100644 --- a/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts +++ b/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.ts @@ -120,15 +120,8 @@ export class HarborShellComponent implements OnInit, OnDestroy { //Handle the global search event and then let the result page to trigger api doSearch(event: string): void { if (event === "") { - if (!this.isSearchResultsOpened) { - //Will not open search result panel if term is empty - return; - } else { - //If opened, then close the search result panel - this.isSearchResultsOpened = false; - this.searchResultComponet.close(); - return; - } + //Do nothing + return; } //Once this method is called //the search results page must be opened diff --git a/src/ui_ng/src/app/config/config.component.1.html b/src/ui_ng/src/app/config/config.component.1.html new file mode 100644 index 000000000..a4be64f0c --- /dev/null +++ b/src/ui_ng/src/app/config/config.component.1.html @@ -0,0 +1,62 @@ +
+

{{'CONFIG.TITLE' | translate }}

+ + + {{'CONFIG.AUTH' | translate }} + {{'CONFIG.REPLICATION' | translate }} + {{'CONFIG.EMAIL' | translate }} + {{'CONFIG.SYSTEM' | translate }} + + + + + +
+
+
+ + + + + {{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }} + + +
+
+
+
+ + + + +
+
+
+ + + + + {{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}} + +
+
+
+
+
+
+ + + + + +
+
\ No newline at end of file diff --git a/src/ui_ng/src/app/config/config.component.html b/src/ui_ng/src/app/config/config.component.html index 8ab32e45f..9f3656f31 100644 --- a/src/ui_ng/src/app/config/config.component.html +++ b/src/ui_ng/src/app/config/config.component.html @@ -1,39 +1,47 @@

{{'CONFIG.TITLE' | translate }}

- - {{'CONFIG.AUTH' | translate }} - {{'CONFIG.REPLICATION' | translate }} - {{'CONFIG.EMAIL' | translate }} - {{'CONFIG.SYSTEM' | translate }} - - - - - -
-
-
- - - - - {{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }} - - -
-
-
-
- - - - -
-
-
- -
+
+ +
diff --git a/src/ui_ng/src/app/config/config.component.ts b/src/ui_ng/src/app/config/config.component.ts index 15f353489..c40cc3a01 100644 --- a/src/ui_ng/src/app/config/config.component.ts +++ b/src/ui_ng/src/app/config/config.component.ts @@ -16,8 +16,15 @@ import { ConfigurationAuthComponent } from './auth/config-auth.component'; import { ConfigurationEmailComponent } from './email/config-email.component'; import { AppConfigService } from '../app-config.service'; +import { SessionService } from '../shared/session.service'; const fakePass = "fakepassword"; +const TabLinkContentMap = { + "config-auth": "authentication", + "config-replication": "replication", + "config-email": "email", + "config-system": "system_settings" +}; @Component({ selector: 'config', @@ -27,7 +34,7 @@ const fakePass = "fakepassword"; export class ConfigurationComponent implements OnInit, OnDestroy { private onGoing: boolean = false; allConfig: Configuration = new Configuration(); - private currentTabId: string = ""; + private currentTabId: string = "config-auth";//default tab private originalCopy: Configuration; private confirmSub: Subscription; private testingOnGoing: boolean = false; @@ -41,17 +48,76 @@ export class ConfigurationComponent implements OnInit, OnDestroy { private msgService: MessageService, private configService: ConfigurationService, private confirmService: ConfirmationDialogService, - private appConfigService: AppConfigService) { } + private appConfigService: AppConfigService, + private session: SessionService) { } + + private isCurrentTabLink(tabId: string): boolean { + return this.currentTabId === tabId; + } + + private isCurrentTabContent(contentId: string): boolean { + return TabLinkContentMap[this.currentTabId] === contentId; + } + + private hasUnsavedChangesOfCurrentTab(): any { + let allChanges = this.getChanges(); + if (this.isEmpty(allChanges)) { + return null; + } + + let properties = []; + switch (this.currentTabId) { + case "config-auth": + for (let prop in allChanges) { + if (prop.startsWith("ldap_")) { + return allChanges; + } + } + properties = ["auth_mode", "project_creation_restriction", "self_registration"]; + break; + case "config-email": + for (let prop in allChanges) { + if (prop.startsWith("email_")) { + return allChanges; + } + } + return null; + case "config-replication": + properties = ["verify_remote_cert"]; + break; + case "config-system": + properties = ["token_expiration"]; + break; + default: + return null; + } + + for (let prop in allChanges) { + if (properties.indexOf(prop) != -1) { + return allChanges; + } + } + + return null; + } ngOnInit(): void { //First load - this.retrieveConfig(); + //Double confirm the current use has admin role + let currentUser = this.session.getCurrentUser(); + if (currentUser && currentUser.has_admin_role > 0) { + this.retrieveConfig(); + } this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => { if (confirmation && - confirmation.state === ConfirmationState.CONFIRMED && - confirmation.source === ConfirmationTargets.CONFIG) { - this.reset(confirmation.data); + confirmation.state === ConfirmationState.CONFIRMED) { + if (confirmation.source === ConfirmationTargets.CONFIG) { + this.reset(confirmation.data); + } else if (confirmation.source === ConfirmationTargets.CONFIG_TAB) { + this.reset(confirmation.data["changes"]); + this.currentTabId = confirmation.data["tabId"]; + } } }); } @@ -104,8 +170,15 @@ export class ConfigurationComponent implements OnInit, OnDestroy { return this.authConfig && this.authConfig.isValid(); } - public tabLinkChanged(tabLink: any) { - this.currentTabId = tabLink.id; + public tabLinkClick(tabLink: string) { + //Whether has unsave changes in current tab + let changes = this.hasUnsavedChangesOfCurrentTab(); + if (!changes) { + this.currentTabId = tabLink; + return; + } + + this.confirmUnsavedTabChanges(changes, tabLink); } /** @@ -154,14 +227,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy { public cancel(): void { let changes = this.getChanges(); if (!this.isEmpty(changes)) { - let msg = new ConfirmationMessage( - "CONFIG.CONFIRM_TITLE", - "CONFIG.CONFIRM_SUMMARY", - "", - changes, - ConfirmationTargets.CONFIG - ); - this.confirmService.openComfirmDialog(msg); + this.confirmUnsavedChanges(changes); } else { //Inprop situation, should not come here console.error("Nothing changed"); @@ -218,6 +284,33 @@ export class ConfigurationComponent implements OnInit, OnDestroy { }); } + private confirmUnsavedChanges(changes: any) { + let msg = new ConfirmationMessage( + "CONFIG.CONFIRM_TITLE", + "CONFIG.CONFIRM_SUMMARY", + "", + changes, + ConfirmationTargets.CONFIG + ); + + this.confirmService.openComfirmDialog(msg); + } + + private confirmUnsavedTabChanges(changes: any, tabId: string){ + let msg = new ConfirmationMessage( + "CONFIG.CONFIRM_TITLE", + "CONFIG.CONFIRM_SUMMARY", + "", + { + "changes": changes, + "tabId": tabId + }, + ConfirmationTargets.CONFIG_TAB + ); + + this.confirmService.openComfirmDialog(msg); + } + private retrieveConfig(): void { this.onGoing = true; this.configService.getConfiguration() diff --git a/src/ui_ng/src/app/global-message/message.component.ts b/src/ui_ng/src/app/global-message/message.component.ts index 6767c56b4..80e46efae 100644 --- a/src/ui_ng/src/app/global-message/message.component.ts +++ b/src/ui_ng/src/app/global-message/message.component.ts @@ -18,6 +18,7 @@ export class MessageComponent implements OnInit { globalMessage: Message = new Message(); globalMessageOpened: boolean; messageText: string = ""; + private timer: any = null; constructor( private messageService: MessageService, @@ -48,7 +49,7 @@ export class MessageComponent implements OnInit { // Make the message alert bar dismiss after several intervals. //Only for this case - setInterval(() => this.onClose(), dismissInterval); + this.timer = setTimeout(() => this.onClose(), dismissInterval); } ); } @@ -56,15 +57,9 @@ export class MessageComponent implements OnInit { //Translate or refactor the message shown to user translateMessage(msg: Message): void { - if (!msg) { - return; - } - - let key = ""; - if (!msg.message) { - key = "UNKNOWN_ERROR"; - } else { - key = typeof msg.message === "string" ? msg.message.trim() : msg.message; + let key = "UNKNOWN_ERROR", param = ""; + if (msg && msg.message) { + key = (typeof msg.message === "string" ? msg.message.trim() : msg.message); if (key === "") { key = "UNKNOWN_ERROR"; } @@ -73,13 +68,11 @@ export class MessageComponent implements OnInit { //Override key for HTTP 401 and 403 if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) { key = "UNAUTHORIZED_ERROR"; - } - - if (this.globalMessage.statusCode === httpStatusCode.Forbidden) { + } else if (this.globalMessage.statusCode === httpStatusCode.Forbidden) { key = "FORBIDDEN_ERROR"; - } + } - this.translate.get(key).subscribe((res: string) => this.messageText = res); + this.translate.get(key, { 'param': param }).subscribe((res: string) => this.messageText = res); } public get needAuth(): boolean { @@ -98,6 +91,9 @@ export class MessageComponent implements OnInit { } onClose() { + if (this.timer) { + clearTimeout(this.timer); + } this.globalMessageOpened = false; } } \ No newline at end of file diff --git a/src/ui_ng/src/app/harbor-routing.module.ts b/src/ui_ng/src/app/harbor-routing.module.ts index 775d3523c..967a3c402 100644 --- a/src/ui_ng/src/app/harbor-routing.module.ts +++ b/src/ui_ng/src/app/harbor-routing.module.ts @@ -33,6 +33,8 @@ import { AuthCheckGuard } from './shared/route/auth-user-activate.service'; import { SignInGuard } from './shared/route/sign-in-guard-activate.service'; import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deactivate.service'; +import { MemberGuard } from './shared/route/member-guard-activate.service'; + const harborRoutes: Routes = [ { path: '', redirectTo: 'harbor', pathMatch: 'full' }, { path: 'password-reset', component: ResetPasswordComponent }, @@ -79,6 +81,7 @@ const harborRoutes: Routes = [ { path: 'projects/:id', component: ProjectDetailComponent, + canActivate: [MemberGuard], resolve: { projectResolver: ProjectRoutingResolver }, @@ -89,7 +92,8 @@ const harborRoutes: Routes = [ }, { path: 'replication', - component: ReplicationComponent + component: ReplicationComponent, + canActivate: [SystemAdminGuard] }, { path: 'member', diff --git a/src/ui_ng/src/app/log/audit-log.service.ts b/src/ui_ng/src/app/log/audit-log.service.ts index 72e4b1a07..320d589b5 100644 --- a/src/ui_ng/src/app/log/audit-log.service.ts +++ b/src/ui_ng/src/app/log/audit-log.service.ts @@ -44,5 +44,4 @@ export class AuditLogService extends BaseService { .map(response => response.json() as AuditLog[]) .catch(error => this.handleError(error)); } - } \ No newline at end of file diff --git a/src/ui_ng/src/app/log/recent-log.component.css b/src/ui_ng/src/app/log/recent-log.component.css index b92cb32ec..2d08305e0 100644 --- a/src/ui_ng/src/app/log/recent-log.component.css +++ b/src/ui_ng/src/app/log/recent-log.component.css @@ -2,25 +2,18 @@ margin-top: 0px !important; } -.filter-log { - float: right; - margin-right: 24px; - position: relative; - top: 8px; -} - .action-head-pos { - position: relative; - top: 20px; + padding-right: 18px; } .refresh-btn { - position: absolute; - right: -4px; - top: 8px; cursor: pointer; } +.refresh-btn:hover { + color: #00bfff; +} + .custom-lines-button { padding: 0px !important; min-width: 25px !important; @@ -29,4 +22,21 @@ .lines-button-toggole { font-size: 16px; text-decoration: underline; +} + +.log-select { + width: 180px; + 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; } \ No newline at end of file diff --git a/src/ui_ng/src/app/log/recent-log.component.html b/src/ui_ng/src/app/log/recent-log.component.html index 10bfe1963..890890eee 100644 --- a/src/ui_ng/src/app/log/recent-log.component.html +++ b/src/ui_ng/src/app/log/recent-log.component.html @@ -1,20 +1,24 @@
-

{{'SIDE_NAV.LOGS' | translate}}

-
- - - - - - - - - - - +

{{'SIDE_NAV.LOGS' | translate}} + {{logNumber}} +

+
+
+
+
+ +
+
+ + - + +
diff --git a/src/ui_ng/src/app/log/recent-log.component.ts b/src/ui_ng/src/app/log/recent-log.component.ts index b0bbeb474..2c20e921a 100644 --- a/src/ui_ng/src/app/log/recent-log.component.ts +++ b/src/ui_ng/src/app/log/recent-log.component.ts @@ -34,17 +34,22 @@ export class RecentLogComponent implements OnInit { this.retrieveLogs(); } - public get inProgress(): boolean { - return this.onGoing; + private handleOnchange($event: any) { + if (event && event.target && event.srcElement["value"]) { + this.lines = event.srcElement["value"]; + if (this.lines < 10) { + this.lines = 10; + } + this.retrieveLogs(); + } } - public setLines(lines: number): void { - this.lines = lines; - if (this.lines < 10) { - this.lines = 10; - } + public get logNumber(): number { + return this.recentLogs?this.recentLogs.length:0; + } - this.retrieveLogs(); + public get inProgress(): boolean { + return this.onGoing; } public doFilter(terms: string): void { @@ -60,7 +65,7 @@ export class RecentLogComponent implements OnInit { this.retrieveLogs(); } - public formatDateTime(dateTime: string){ + public formatDateTime(dateTime: string) { let dt: Date = new Date(dateTime); return dt.toLocaleString(); } diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.ts b/src/ui_ng/src/app/project/create-project/create-project.component.ts index f06c91079..d05af9694 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.ts +++ b/src/ui_ng/src/app/project/create-project/create-project.component.ts @@ -13,7 +13,6 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com import { TranslateService } from '@ngx-translate/core'; - @Component({ selector: 'create-project', templateUrl: 'create-project.component.html', @@ -50,6 +49,7 @@ export class CreateProjectComponent implements AfterViewChecked { .subscribe( status=>{ this.create.emit(true); + this.messageService.announceMessage(status, 'PROJECT.CREATED_SUCCESS', AlertType.SUCCESS); this.createProjectOpened = false; }, error=>{ diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.html b/src/ui_ng/src/app/project/list-project/list-project.component.html index 7b80c45b8..fa6074706 100644 --- a/src/ui_ng/src/app/project/list-project/list-project.component.html +++ b/src/ui_ng/src/app/project/list-project/list-project.component.html @@ -5,8 +5,8 @@ {{'PROJECT.CREATION_TIME' | translate}} {{'PROJECT.DESCRIPTION' | translate}} - - + + diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.ts b/src/ui_ng/src/app/project/list-project/list-project.component.ts index 81f2c5e7a..c4a5895cb 100644 --- a/src/ui_ng/src/app/project/list-project/list-project.component.ts +++ b/src/ui_ng/src/app/project/list-project/list-project.component.ts @@ -43,6 +43,11 @@ export class ListProjectComponent implements OnInit { return this.mode === ListMode.FULL && this.session.getCurrentUser() != null; } + public get isSystemAdmin(): boolean { + let account = this.session.getCurrentUser(); + return account != null && account.has_admin_role > 0; + } + goToLink(proId: number): void { this.searchTrigger.closeSearch(false); diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.html b/src/ui_ng/src/app/project/member/add-member/add-member.component.html index 7d0df444b..844ddd209 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.html +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.html @@ -19,15 +19,15 @@
- +
- +
- +
@@ -36,6 +36,6 @@
diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts index b5181ba52..a4ef85df3 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts @@ -51,6 +51,7 @@ export class AddMemberComponent implements AfterViewChecked { .addMember(this.projectId, this.member.username, +this.member.role_id) .subscribe( response=>{ + this.messageService.announceMessage(response, 'MEMBER.ADDED_SUCCESS', AlertType.SUCCESS); console.log('Added member successfully.'); this.added.emit(true); this.addMemberOpened = false; @@ -112,9 +113,11 @@ export class AddMemberComponent implements AfterViewChecked { } openAddMemberModal(): void { + this.memberForm.reset(); this.member = new Member(); this.addMemberOpened = true; this.hasChanged = false; + this.member.role_id = 1; } } \ No newline at end of file diff --git a/src/ui_ng/src/app/project/member/member.component.html b/src/ui_ng/src/app/project/member/member.component.html index 62fd7da07..a0563db81 100644 --- a/src/ui_ng/src/app/project/member/member.component.html +++ b/src/ui_ng/src/app/project/member/member.component.html @@ -2,7 +2,7 @@
- +
@@ -18,7 +18,7 @@ {{'MEMBER.NAME' | translate}} {{'MEMBER.ROLE' | translate}} - + diff --git a/src/ui_ng/src/app/project/member/member.component.ts b/src/ui_ng/src/app/project/member/member.component.ts index 55f4555a9..b13b04c3b 100644 --- a/src/ui_ng/src/app/project/member/member.component.ts +++ b/src/ui_ng/src/app/project/member/member.component.ts @@ -40,12 +40,22 @@ export class MemberComponent implements OnInit, OnDestroy { @ViewChild(AddMemberComponent) addMemberComponent: AddMemberComponent; + hasProjectAdminRole: boolean; + constructor(private route: ActivatedRoute, private router: Router, private memberService: MemberService, private messageService: MessageService, private deletionDialogService: ConfirmationDialogService, session: SessionService) { //Get current user from registered resolver. this.currentUser = session.getCurrentUser(); + let projectMembers: Member[] = session.getProjectMembers(); + if(this.currentUser && projectMembers) { + let currentMember = projectMembers.find(m=>m.user_id === this.currentUser.user_id); + if(currentMember) { + this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin'); + } + } + this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => { if (message && message.state === ConfirmationState.CONFIRMED && @@ -54,7 +64,8 @@ export class MemberComponent implements OnInit, OnDestroy { .deleteMember(this.projectId, message.data) .subscribe( response => { - console.log('Successful change role with user ' + message.data); + this.messageService.announceMessage(response, 'MEMBER.DELETED_SUCCESS', AlertType.SUCCESS); + console.log('Successful delete member: ' + message.data); this.retrieve(this.projectId, ''); }, error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER) @@ -85,7 +96,7 @@ export class MemberComponent implements OnInit, OnDestroy { //Get projectId from route params snapshot. this.projectId = +this.route.snapshot.parent.params['id']; console.log('Get projectId from route params snapshot:' + this.projectId); - + this.retrieve(this.projectId, ''); } @@ -102,6 +113,7 @@ export class MemberComponent implements OnInit, OnDestroy { .changeMemberRole(this.projectId, userId, roleId) .subscribe( response => { + this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS); console.log('Successful change role with user ' + userId + ' to roleId ' + roleId); this.retrieve(this.projectId, ''); }, diff --git a/src/ui_ng/src/app/project/project.component.html b/src/ui_ng/src/app/project/project.component.html index 62fdbba6a..1649cd740 100644 --- a/src/ui_ng/src/app/project/project.component.html +++ b/src/ui_ng/src/app/project/project.component.html @@ -3,7 +3,7 @@

{{'PROJECT.PROJECTS' | translate}}

- +
diff --git a/src/ui_ng/src/app/project/project.component.ts b/src/ui_ng/src/app/project/project.component.ts index ed7b4e387..fb82dd380 100644 --- a/src/ui_ng/src/app/project/project.component.ts +++ b/src/ui_ng/src/app/project/project.component.ts @@ -23,6 +23,10 @@ import { Subscription } from 'rxjs/Subscription'; import { State } from 'clarity-angular'; +import { AppConfigService } from '../app-config.service'; +import { SessionService } from '../shared/session.service'; + + const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' }; @Component({ @@ -59,6 +63,8 @@ export class ProjectComponent implements OnInit, OnDestroy { constructor( private projectService: ProjectService, private messageService: MessageService, + private appConfigService: AppConfigService, + private sessionService: SessionService, private deletionDialogService: ConfirmationDialogService) { this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => { if (message && @@ -69,6 +75,7 @@ export class ProjectComponent implements OnInit, OnDestroy { .deleteProject(projectId) .subscribe( response => { + this.messageService.announceMessage(response, 'PROJECT.DELETED_SUCCESS', AlertType.SUCCESS); console.log('Successful delete project with ID:' + projectId); this.retrieve(); }, @@ -76,11 +83,13 @@ export class ProjectComponent implements OnInit, OnDestroy { ); } }); + } ngOnInit(): void { this.projectName = ''; this.isPublic = 0; + } ngOnDestroy(): void { @@ -89,6 +98,19 @@ export class ProjectComponent implements OnInit, OnDestroy { } } + get projectCreationRestriction(): boolean { + let account = this.sessionService.getCurrentUser(); + if(account) { + switch(this.appConfigService.getConfig().project_creation_restriction) { + case 'adminonly': + return (account.has_admin_role === 1); + case 'everyone': + return true; + } + } + return false; + } + retrieve(state?: State): void { if (state) { this.page = state.page.to + 1; @@ -135,7 +157,10 @@ export class ProjectComponent implements OnInit, OnDestroy { this.projectService .toggleProjectPublic(p.project_id, p.public) .subscribe( - response => console.log('Successful toggled project_id:' + p.project_id), + response => { + this.messageService.announceMessage(response, 'PROJECT.TOGGLED_SUCCESS', AlertType.SUCCESS); + console.log('Successful toggled project_id:' + p.project_id); + }, error => this.messageService.announceMessage(error.status, error, AlertType.WARNING) ); } diff --git a/src/ui_ng/src/app/project/project.service.ts b/src/ui_ng/src/app/project/project.service.ts index d78aabaf0..187d791c1 100644 --- a/src/ui_ng/src/app/project/project.service.ts +++ b/src/ui_ng/src/app/project/project.service.ts @@ -70,4 +70,11 @@ export class ProjectService { .catch(error=>Observable.throw(error)); } + checkProjectMember(projectId: number): Observable { + return this.http + .get(`/api/projects/${projectId}/members`) + .map(response=>response.json()) + .catch(error=>Observable.throw(error)); + } + } \ No newline at end of file diff --git a/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts index 549c0086f..cdf9a0004 100644 --- a/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts +++ b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts @@ -108,6 +108,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { .createTarget(this.target) .subscribe( response=>{ + this.messageService.announceMessage(response, 'DESTINATION.CREATED_SUCCESS', AlertType.SUCCESS); console.log('Successful added target.'); this.createEditDestinationOpened = false; this.reload.emit(true); @@ -129,7 +130,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { .get(errorMessageKey) .subscribe(res=>{ this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER); - this.inlineAlert.showInlineError(errorMessageKey); + this.inlineAlert.showInlineError(res); }); } ); @@ -139,6 +140,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { .updateTarget(this.target) .subscribe( response=>{ + this.messageService.announceMessage(response, 'DESTINATION.UPDATED_SUCCESS', AlertType.SUCCESS); console.log('Successful updated target.'); this.createEditDestinationOpened = false; this.reload.emit(true); @@ -158,7 +160,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked { this.translateService .get(errorMessageKey) .subscribe(res=>{ - this.inlineAlert.showInlineError(errorMessageKey); + this.inlineAlert.showInlineError(res); this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER); }); } diff --git a/src/ui_ng/src/app/replication/destination/destination.component.ts b/src/ui_ng/src/app/replication/destination/destination.component.ts index c85b5bbd0..376b79119 100644 --- a/src/ui_ng/src/app/replication/destination/destination.component.ts +++ b/src/ui_ng/src/app/replication/destination/destination.component.ts @@ -43,14 +43,15 @@ export class DestinationComponent implements OnInit { .deleteTarget(targetId) .subscribe( response => { + this.messageService.announceMessage(response, 'DESTINATION.DELETED_SUCCESS', AlertType.SUCCESS); console.log('Successful deleted target with ID:' + targetId); this.reload(); }, - error => this.messageService - .announceMessage(error.status, - 'Failed to delete target with ID:' + targetId + ', error:' + error, - AlertType.DANGER) - ); + error => { + this.messageService + .announceMessage(error.status,'DESTINATION.DELETED_FAILED', AlertType.DANGER); + console.log('Failed to delete target with ID:' + targetId + ', error:' + error); + }); } }); } diff --git a/src/ui_ng/src/app/repository/repository.component.ts b/src/ui_ng/src/app/repository/repository.component.ts index ec9f3e04f..616cb1209 100644 --- a/src/ui_ng/src/app/repository/repository.component.ts +++ b/src/ui_ng/src/app/repository/repository.component.ts @@ -60,6 +60,7 @@ export class RepositoryComponent implements OnInit { .subscribe( response => { this.refresh(); + this.messageService.announceMessage(response, 'REPOSITORY.DELETED_REPO_SUCCESS', AlertType.SUCCESS); console.log('Successful deleted repo:' + repoName); }, error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER) diff --git a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html index 4b15ed9f3..eafde9e11 100644 --- a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html +++ b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html @@ -3,7 +3,7 @@ {{'REPOSITORY.TAG' | translate}} {{'REPOSITORY.PULL_COMMAND' | translate}} - {{'REPOSITORY.SIGNED' | translate}} + {{'REPOSITORY.SIGNED' | translate}} {{'REPOSITORY.AUTHOR' | translate}} {{'REPOSITORY.CREATED' | translate}} {{'REPOSITORY.DOCKER_VERSION' | translate}} @@ -15,7 +15,7 @@ {{t.tag}} {{t.pullCommand}} - + diff --git a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts index cb623a6d7..63ca5c0ab 100644 --- a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts +++ b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts @@ -10,6 +10,7 @@ import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmati import { Subscription } from 'rxjs/Subscription'; +import { Tag } from '../tag'; import { TagView } from '../tag-view'; import { AppConfigService } from '../../app-config.service'; @@ -27,6 +28,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { tags: TagView[]; registryUrl: string; + withNotary: boolean; private subscription: Subscription; @@ -52,6 +54,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { .subscribe( response => { this.retrieve(); + this.messageService.announceMessage(response, 'REPOSITORY.DELETED_TAG_SUCCESS', AlertType.SUCCESS); console.log('Deleted repo:' + this.repoName + ' with tag:' + tagName); }, error => this.messageService.announceMessage(error.status, 'Failed to delete tag:' + tagName + ' under repo:' + this.repoName, AlertType.DANGER) @@ -68,6 +71,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { this.repoName = this.route.snapshot.params['repo']; this.tags = []; this.registryUrl = this.appConfigService.getConfig().registry_url; + this.withNotary = this.appConfigService.getConfig().with_notary; this.retrieve(); } @@ -79,25 +83,35 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { retrieve() { this.tags = []; + if(this.withNotary) { this.repositoryService .listTagsWithVerifiedSignatures(this.repoName) .subscribe( - items => { - items.forEach(t => { - let tag = new TagView(); - tag.tag = t.tag; - let data = JSON.parse(t.manifest.history[0].v1Compatibility); - tag.architecture = data['architecture']; - tag.author = data['author']; - tag.signed = t.signed; - tag.created = data['created']; - tag.dockerVersion = data['docker_version']; - tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag; - tag.os = data['os']; - this.tags.push(tag); - }); - }, + items => this.listTags(items), error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER)); + } else { + this.repositoryService + .listTags(this.repoName) + .subscribe( + items => this.listTags(items), + error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER)); + } + } + + private listTags(tags: Tag[]): void { + tags.forEach(t => { + let tag = new TagView(); + tag.tag = t.tag; + let data = JSON.parse(t.manifest.history[0].v1Compatibility); + tag.architecture = data['architecture']; + tag.author = data['author']; + tag.signed = t.signed; + tag.created = data['created']; + tag.dockerVersion = data['docker_version']; + tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag; + tag.os = data['os']; + this.tags.push(tag); + }); } deleteTag(tag: TagView) { diff --git a/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts index bf9699454..2c4caa413 100644 --- a/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts +++ b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts @@ -183,6 +183,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked { .createPolicy(this.getPolicyByForm()) .subscribe( response=>{ + this.messageService.announceMessage(response, 'REPLICATION.CREATED_SUCCESS', AlertType.SUCCESS); console.log('Successful created policy: ' + response); this.createEditPolicyOpened = false; this.reload.emit(true); @@ -199,6 +200,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked { .createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm()) .subscribe( response=>{ + this.messageService.announceMessage(response, 'REPLICATION.UPDATED_SUCCESS', AlertType.SUCCESS); console.log('Successful created policy and target:' + response); this.createEditPolicyOpened = false; this.reload.emit(true); diff --git a/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts b/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts index 523241764..39dedb2ad 100644 --- a/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts +++ b/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts @@ -50,7 +50,10 @@ export class ListPolicyComponent implements OnDestroy { this.replicationService .enablePolicy(policy.id, policy.enabled) .subscribe( - res => console.log('Successful toggled policy status'), + response => { + this.messageService.announceMessage(response, 'REPLICATION.TOGGLED_SUCCESS', AlertType.SUCCESS); + console.log('Successful toggled policy status') + }, error => this.messageService.announceMessage(error.status, "Failed to toggle policy status.", AlertType.DANGER) ); } @@ -67,10 +70,11 @@ export class ListPolicyComponent implements OnDestroy { .deletePolicy(message.data) .subscribe( response => { + this.messageService.announceMessage(response, 'REPLICATION.DELETED_SUCCESS', AlertType.SUCCESS); console.log('Successful delete policy with ID:' + message.data); this.reload.emit(true); }, - error => this.messageService.announceMessage(error.status, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER) + error => this.messageService.announceMessage(error.status, 'REPLICATION.DELETED_FAILED', AlertType.DANGER) ); } } diff --git a/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts b/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts new file mode 100644 index 000000000..a1d4fc74f --- /dev/null +++ b/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, Router, + ActivatedRouteSnapshot, + RouterStateSnapshot, + CanActivateChild +} from '@angular/router'; +import { SessionService } from '../../shared/session.service'; +import { ProjectService } from '../../project/project.service'; +import { CommonRoutes } from '../../shared/shared.const'; + +@Injectable() +export class MemberGuard implements CanActivate, CanActivateChild { + constructor( + private sessionService: SessionService, + private projectService: ProjectService, + private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise | boolean { + let projectId: number = route.params['id']; + return new Promise((resolve, reject) => { + this.projectService.checkProjectMember(projectId) + .subscribe( + res=>{ + this.sessionService.setProjectMembers(res); + return resolve(true) + }, + error => { + this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); + return resolve(false); + }); + }); + } + + canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise | boolean { + return this.canActivate(route, state); + } +} diff --git a/src/ui_ng/src/app/shared/session.service.ts b/src/ui_ng/src/app/shared/session.service.ts index 33c234c64..213d262be 100644 --- a/src/ui_ng/src/app/shared/session.service.ts +++ b/src/ui_ng/src/app/shared/session.service.ts @@ -3,6 +3,8 @@ import { Headers, Http, URLSearchParams } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { SessionUser } from './session-user'; +import { Member } from '../project/member/member'; + import { SignInCredential } from './sign-in-credential'; import { enLang } from '../shared/shared.const' @@ -27,6 +29,8 @@ const langMap = { export class SessionService { currentUser: SessionUser = null; + projectMembers: Member[]; + private headers = new Headers({ "Content-Type": 'application/json' }); @@ -143,4 +147,12 @@ export class SessionService { }) .catch(error => this.handleError(error)); } + + setProjectMembers(projectMembers: Member[]): void { + this.projectMembers = projectMembers; + } + + getProjectMembers(): Member[] { + return this.projectMembers; + } } \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/shared.const.ts b/src/ui_ng/src/app/shared/shared.const.ts index f73949688..536ed59bd 100644 --- a/src/ui_ng/src/app/shared/shared.const.ts +++ b/src/ui_ng/src/app/shared/shared.const.ts @@ -24,7 +24,8 @@ export const enum ConfirmationTargets { REPOSITORY, TAG, CONFIG, - CONFIG_ROUTE + CONFIG_ROUTE, + CONFIG_TAB }; export const enum ActionType { diff --git a/src/ui_ng/src/app/shared/shared.module.ts b/src/ui_ng/src/app/shared/shared.module.ts index 5c6d2a42c..29ae3dba3 100644 --- a/src/ui_ng/src/app/shared/shared.module.ts +++ b/src/ui_ng/src/app/shared/shared.module.ts @@ -32,6 +32,7 @@ import { StatisticsComponent } from './statictics/statistics.component'; import { StatisticsPanelComponent } from './statictics/statistics-panel.component'; import { SignInGuard } from './route/sign-in-guard-activate.service'; import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service'; +import { MemberGuard } from './route/member-guard-activate.service'; @NgModule({ imports: [ @@ -79,7 +80,8 @@ import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate. SystemAdminGuard, AuthCheckGuard, SignInGuard, - LeavingConfigRouteDeactivate + LeavingConfigRouteDeactivate, + MemberGuard ] }) export class SharedModule { diff --git a/src/ui_ng/src/i18n/lang/en-lang.json b/src/ui_ng/src/i18n/lang/en-lang.json index f1c99ff45..7faa7e7df 100644 --- a/src/ui_ng/src/i18n/lang/en-lang.json +++ b/src/ui_ng/src/i18n/lang/en-lang.json @@ -132,7 +132,10 @@ "DELETION_TITLE": "Confirm project deletion", "DELETION_SUMMARY": "Do you want to delete project {{param}}?", "FILTER_PLACEHOLDER": "Filter Projects", - "REPLICATION_RULE": "Replication Rule" + "REPLICATION_RULE": "Replication Rule", + "CREATED_SUCCESS": "Created project successfully.", + "DELETED_SUCCESS": "Deleted project successfully.", + "TOGGLED_SUCCESS": "Toggled project successfully." }, "PROJECT_DETAIL": { "REPOSITORIES": "Repositories", @@ -159,7 +162,10 @@ "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}}?" + "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", @@ -235,7 +241,12 @@ "TOGGLE_ENABLE_TITLE": "Enable Policy", "CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication policy, all repositories under the project will be replicated to the destination registry. Please confirm to continue.", "TOGGLE_DISABLE_TITLE": "Disable Policy", - "CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue." + "CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue.", + "CREATED_SUCCESS": "Created policy successfully.", + "UPDATED_SUCCESS": "Updated policy successfully.", + "DELETED_SUCCESS": "Deleted policy successfully.", + "DELETED_FAILED": "Deleted policy failed.", + "TOGGLED_SUCCESS": "Toggled policy status successfully." }, "DESTINATION": { "NEW_ENDPOINT": "New Endpoint", @@ -257,7 +268,11 @@ "INVALID_NAME": "Invalid destination name.", "FAILED_TO_GET_TARGET": "Failed to get endpoint.", "CREATION_TIME": "Creation Time", - "ITEMS": "item(s)" + "ITEMS": "item(s)", + "CREATED_SUCCESS": "Created destination successfully.", + "UPDATED_SUCCESS": "Updated destination successfully.", + "DELETED_SUCCESS": "Deleted destination successfully.", + "DELETED_FAILED": "Deleted destination failed." }, "REPOSITORY": { "COPY_ID": "Copy ID", @@ -286,7 +301,9 @@ "SHOW_DETAILS": "Show Details", "REPOSITORIES": "Repositories", "ITEMS": "item(s)", - "POP_REPOS": "Popular Repositories" + "POP_REPOS": "Popular Repositories", + "DELETED_REPO_SUCCESS": "Deleted repository successfully.", + "DELETED_TAG_SUCCESS": "Deleted tag successfully." }, "ALERT": { "FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?" @@ -310,7 +327,7 @@ "EMAIL": "Email", "SYSTEM": "System Settings", "CONFIRM_TITLE": "Confirm to cancel", - "CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to leave?", + "CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to discard?", "SAVE_SUCCESS": "Configurations have been successfully saved", "MAIL_SERVER": "Email Server", "MAIL_SERVER_PORT": "Email Server Port", @@ -386,7 +403,8 @@ "IN_PROGRESS": "Search...", "BACK": "Back" }, - "UNKNOWN_ERROR": "Some unknown errors HAVE occurred. Please try again later", + "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 the operation", - "FORBIDDEN_ERROR": "You are not allowed to perform this operation" + "FORBIDDEN_ERROR": "You are not allowed to perform this operation", + "GENERAL_ERROR": "Errors have occurred when performing service call: {{param}}" } \ No newline at end of file diff --git a/src/ui_ng/src/i18n/lang/zh-lang.json b/src/ui_ng/src/i18n/lang/zh-lang.json index bd2b0ef05..efac765dc 100644 --- a/src/ui_ng/src/i18n/lang/zh-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-lang.json @@ -132,7 +132,10 @@ "DELETION_TITLE": "删除项目确认", "DELETION_SUMMARY": "你确认删除项目 {{param}}?", "FILTER_PLACEHOLDER": "过滤项目", - "REPLICATION_RULE": "复制策略" + "REPLICATION_RULE": "复制策略", + "CREATED_SUCCESS": "创建项目成功。", + "DELETED_SUCCESS": "删除项目成功。", + "TOGGLED_SUCCESS": "切换状态成功。" }, "PROJECT_DETAIL": { "REPOSITORIES": "镜像仓库", @@ -159,7 +162,10 @@ "UNKNOWN_ERROR": "添加成员时发生未知错误。", "FILTER_PLACEHOLDER": "过滤成员", "DELETION_TITLE": "删除项目成员确认", - "DELETION_SUMMARY": "你确认删除项目成员 {{param}}?" + "DELETION_SUMMARY": "你确认删除项目成员 {{param}}?", + "ADDED_SUCCESS": "新增成员成功。", + "DELETED_SUCCESS": "删除成员成功", + "SWITCHED_SUCCESS": "切换角色成功" }, "AUDIT_LOG": { "USERNAME": "用户名", @@ -235,7 +241,12 @@ "TOGGLE_ENABLE_TITLE": "启用策略", "CONFIRM_TOGGLE_ENABLE_POLICY": "启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。", "TOGGLE_DISABLE_TITLE": "停用策略", - "CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。" + "CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。", + "CREATED_SUCCESS": "创建复制策略成功。", + "UPDATED_SUCCESS": "更新复制策略成功。", + "DELETED_SUCCESS": "删除复制策略成功。", + "DELETED_FAILED": "删除复制策略失败。", + "TOGGLED_SUCCESS": "切换复制策略状态成功。" }, "DESTINATION": { "NEW_ENDPOINT": "新建目标", @@ -257,7 +268,11 @@ "INVALID_NAME": "无效的目标名称。", "FAILED_TO_GET_TARGET": "获取目标失败。", "CREATION_TIME": "创建时间", - "ITEMS": "条记录" + "ITEMS": "条记录", + "CREATED_SUCCESS": "创建目标成功。", + "UPDATED_SUCCESS": "更新目标成功。", + "DELETED_SUCCESS": "删除目标成功。", + "DELETED_FAILED": "删除目标失败。" }, "REPOSITORY": { "COPY_ID": "复制ID", @@ -286,7 +301,9 @@ "SHOW_DETAILS": "显示详细", "REPOSITORIES": "镜像仓库", "ITEMS": "条记录", - "POP_REPOS": "受欢迎的镜像库" + "POP_REPOS": "受欢迎的镜像库", + "DELETED_REPO_SUCCESS": "删除镜像仓库成功。", + "DELETED_TAG_SUCCESS": "删除镜像标签成功。" }, "ALERT": { "FORM_CHANGE_CONFIRMATION": "表单内容改变,确认取消?" @@ -388,5 +405,6 @@ }, "UNKNOWN_ERROR": "发生未知错误,请稍后再试", "UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续", - "FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限" + "FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限", + "GENERAL_ERROR": "调用后台服务时出现错误: {{param}}" } \ No newline at end of file From 801a83ffd5e60d92a16f1a75f80b1a44e0d63c67 Mon Sep 17 00:00:00 2001 From: kunw Date: Thu, 23 Mar 2017 15:48:45 +0800 Subject: [PATCH 3/3] Add restriction for repositories and tags. --- .../list-repository.component.html | 2 +- .../list-repository.component.ts | 17 ++++++++++++++++- .../tag-repository.component.html | 2 +- .../tag-repository/tag-repository.component.ts | 18 +++++++++++++++++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/ui_ng/src/app/repository/list-repository/list-repository.component.html b/src/ui_ng/src/app/repository/list-repository/list-repository.component.html index 77ebb0461..6d483b344 100644 --- a/src/ui_ng/src/app/repository/list-repository/list-repository.component.html +++ b/src/ui_ng/src/app/repository/list-repository/list-repository.component.html @@ -3,7 +3,7 @@ {{'REPOSITORY.TAGS_COUNT' | translate}} {{'REPOSITORY.PULL_COUNT' | translate}} - + diff --git a/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts b/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts index 16d699b65..df2490d55 100644 --- a/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts +++ b/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts @@ -7,6 +7,9 @@ import { SearchTriggerService } from '../../base/global-search/search-trigger.se import { SessionService } from '../../shared/session.service'; import { ListMode } from '../../shared/shared.const'; +import { SessionUser } from '../../shared/session-user'; +import { Member } from '../../project/member/member'; + @Component({ selector: 'list-repository', templateUrl: 'list-repository.component.html' @@ -25,10 +28,22 @@ export class ListRepositoryComponent { pageOffset: number = 1; + hasProjectAdminRole: boolean; + constructor( private router: Router, private searchTrigger: SearchTriggerService, - private session: SessionService) { } + private session: SessionService) { + //Get current user from registered resolver. + let currentUser = session.getCurrentUser(); + let projectMembers: Member[] = session.getProjectMembers(); + if(currentUser && projectMembers) { + let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id); + if(currentMember) { + this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin'); + } + } + } deleteRepo(repoName: string) { this.delete.emit(repoName); diff --git a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html index eafde9e11..5dff71967 100644 --- a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html +++ b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.html @@ -10,7 +10,7 @@ {{'REPOSITORY.ARCHITECTURE' | translate}} {{'REPOSITORY.OS' | translate}} - + {{t.tag}} diff --git a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts index 63ca5c0ab..6b7211c29 100644 --- a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts +++ b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts @@ -15,6 +15,9 @@ import { TagView } from '../tag-view'; import { AppConfigService } from '../../app-config.service'; +import { SessionService } from '../../shared/session.service'; +import { Member } from '../../project/member/member'; + @Component({ moduleId: module.id, selector: 'tag-repository', @@ -26,6 +29,8 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { projectId: number; repoName: string; + hasProjectAdminRole: boolean; + tags: TagView[]; registryUrl: string; withNotary: boolean; @@ -37,7 +42,18 @@ export class TagRepositoryComponent implements OnInit, OnDestroy { private messageService: MessageService, private deletionDialogService: ConfirmationDialogService, private repositoryService: RepositoryService, - private appConfigService: AppConfigService) { + private appConfigService: AppConfigService, + private session: SessionService){ + + let currentUser = session.getCurrentUser(); + let projectMembers: Member[] = session.getProjectMembers(); + if(currentUser && projectMembers) { + let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id); + if(currentMember) { + this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin'); + } + } + this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe( message => { if (message &&