Commit 79263a8b authored by Steven Zou's avatar Steven Zou Committed by GitHub
Browse files

Merge pull request #1827 from wknet123/dev-revised

Updates for showing image ID and router tags.
parents b6c15ec6 fb79b4d7
......@@ -44,6 +44,7 @@ func initRouters() {
beego.Router("/harbor/projects/:id/replication", &controllers.IndexController{})
beego.Router("/harbor/projects/:id/member", &controllers.IndexController{})
beego.Router("/harbor/projects/:id/log", &controllers.IndexController{})
beego.Router("/harbor/tags/:id/*", &controllers.IndexController{})
beego.Router("/harbor/users", &controllers.IndexController{})
beego.Router("/harbor/logs", &controllers.IndexController{})
......
......@@ -10,7 +10,7 @@
top: 60px;
left: 0px;
padding-left: 36px;
padding-right: 24px;
padding-right: 36px;
}
.search-header {
......
......@@ -9,4 +9,8 @@
.start-content-padding {
padding: 0px !important;
background-color: white;
}
.content-area-override {
padding: 24px 36px !important;
}
\ No newline at end of file
......@@ -2,27 +2,27 @@
<global-message [isAppLevel]="true"></global-message>
<navigator (showAccountSettingsModal)="openModal($event)" (showPwdChangeModal)="openModal($event)"></navigator>
<div class="content-container">
<div class="content-area" [class.container-override]="showSearch" [class.start-content-padding]="shouldOverrideContent">
<global-message [isAppLevel]="false"></global-message>
<!-- Only appear when searching -->
<search-result></search-result>
<router-outlet></router-outlet>
</div>
<nav class="sidenav" *ngIf="isUserExisting">
<section class="sidenav-content">
<a routerLink="/harbor/projects" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.PROJECTS' | translate}}</a>
<a routerLink="/harbor/logs" routerLinkActive="active" class="nav-link" style="margin-top: 4px;">{{'SIDE_NAV.LOGS' | translate}}</a>
<section class="nav-group collapsible" *ngIf="isSystemAdmin" style="margin-top: 4px;">
<input id="tabsystem" type="checkbox">
<label for="tabsystem">{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}</label>
<ul class="nav-list">
<li><a class="nav-link" routerLink="/harbor/users" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</a></li>
<li><a class="nav-link" routerLink="/harbor/replications/endpoints" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a></li>
<li><a class="nav-link" routerLink="/harbor/configs" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}</a></li>
</ul>
</section>
</section>
</nav>
<div class="content-area" [class.container-override]="showSearch" [class.content-area-override]="!shouldOverrideContent" [class.start-content-padding]="shouldOverrideContent">
<global-message [isAppLevel]="false"></global-message>
<!-- Only appear when searching -->
<search-result></search-result>
<router-outlet></router-outlet>
</div>
<nav class="sidenav" style="padding: 12px 36px;" *ngIf="isUserExisting">
<section class="sidenav-content">
<a routerLink="/harbor/projects" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.PROJECTS' | translate}}</a>
<a routerLink="/harbor/logs" routerLinkActive="active" class="nav-link" style="margin-top: 4px;">{{'SIDE_NAV.LOGS' | translate}}</a>
<section class="nav-group collapsible" *ngIf="isSystemAdmin" style="margin-top: 4px;">
<input id="tabsystem" type="checkbox">
<label for="tabsystem">{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}</label>
<ul class="nav-list">
<li><a class="nav-link" routerLink="/harbor/users" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</a></li>
<li><a class="nav-link" routerLink="/harbor/replications/endpoints" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a></li>
<li><a class="nav-link" routerLink="/harbor/configs" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}</a></li>
</ul>
</section>
</section>
</nav>
</div>
</clr-main-container>
<account-settings-modal></account-settings-modal>
......
.custom-h2 {
margin-top: 0px !important;
}
.config-container {
margin-left: 24px;
}
\ No newline at end of file
<div class="config-container">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
<ul id="configTabs" class="nav" role="tablist">
......@@ -66,4 +67,5 @@
<button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn" [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
<span class="spinner spinner-inline" [hidden]="!testingInProgress"></span>
</div>
</div>
</div>
\ No newline at end of file
<div class="row" style="margin-right: 12px;">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
<div>
......
......@@ -2,12 +2,19 @@
<h3 class="modal-title">{{modalTitle}}</h3>
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
<div class="modal-body">
<div class="alert alert-danger" *ngIf="!editable">
<div class="alert-item">
<span class="alert-text">
{{'DESTINATION.CANNOT_EDIT' | translate}}
</span>
</div>
</div>
<form #targetForm="ngForm">
<section class="form-block">
<div class="form-group">
<label for="destination_name" class="col-md-4 form-group-label-override">{{ 'DESTINATION.NAME' | translate }}<span style="color: red">*</span></label>
<label class="col-md-8" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
<input type="text" id="destination_name" [disabled]="testOngoing" [(ngModel)]="target.name" name="targetName" size="20" #targetName="ngModel" value="" required>
<input type="text" id="destination_name" [disabled]="testOngoing || !editable" [(ngModel)]="target.name" name="targetName" size="20" #targetName="ngModel" value="" required>
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
{{ 'DESTINATION.NAME_IS_REQUIRED' | translate }}
</span>
......@@ -16,7 +23,7 @@
<div class="form-group">
<label for="destination_url" class="col-md-4 form-group-label-override">{{ 'DESTINATION.URL' | translate }}<span style="color: red">*</span></label>
<label class="col-md-8" for="destination_url" aria-haspopup="true" role="tooltip" [class.invalid]="targetEndpoint.errors && (targetEndpoint.dirty || targetEndpoint.touched)" [class.valid]="targetEndpoint.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
<input type="text" id="destination_url" [disabled]="testOngoing" [(ngModel)]="target.endpoint" size="20" name="endpointUrl" #targetEndpoint="ngModel" required>
<input type="text" id="destination_url" [disabled]="testOngoing || !editable" [(ngModel)]="target.endpoint" size="20" name="endpointUrl" #targetEndpoint="ngModel" required>
<span class="tooltip-content" *ngIf="targetEndpoint.errors && targetEndpoint.errors.required && (targetEndpoint.dirty || targetEndpoint.touched)">
{{ 'DESTINATION.URL_IS_REQUIRED' | translate }}
</span>
......@@ -24,11 +31,11 @@
</div>
<div class="form-group">
<label for="destination_username" class="col-md-4 form-group-label-override">{{ 'DESTINATION.USERNAME' | translate }}</label>
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [(ngModel)]="target.username" size="20" name="username" #username="ngModel">
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing || !editable" [(ngModel)]="target.username" size="20" name="username" #username="ngModel">
</div>
<div class="form-group">
<label for="destination_password" class="col-md-4 form-group-label-override">{{ 'DESTINATION.PASSWORD' | translate }}</label>
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [(ngModel)]="target.password" size="20" name="password" #password="ngModel">
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing || !editable" [(ngModel)]="target.password" size="20" name="password" #password="ngModel">
</div>
<div class="form-group">
<label for="spin" class="col-md-4"></label>
......@@ -41,6 +48,6 @@
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="testConnection()" [disabled]="testOngoing || targetEndpoint.errors">{{ 'DESTINATION.TEST_CONNECTION' | translate }}</button>
<button type="button" class="btn btn-outline" (click)="onCancel()" [disabled]="testOngoing">{{ 'BUTTON.CANCEL' | translate }}</button>
<button type="submit" class="btn btn-primary" [disabled]="!targetForm.form.valid" (click)="onSubmit()" [disabled]="testOngoing">{{ 'BUTTON.OK' | translate }}</button>
<button type="submit" class="btn btn-primary" (click)="onSubmit()" [disabled]="testOngoing || targetForm.form.invalid || !editable">{{ 'BUTTON.OK' | translate }}</button>
</div>
</clr-modal>
\ No newline at end of file
......@@ -20,6 +20,8 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
modalTitle: string;
createEditDestinationOpened: boolean;
editable: boolean;
testOngoing: boolean;
pingTestMessage: string;
pingStatus: boolean;
......@@ -49,10 +51,12 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
private messageHandlerService: MessageHandlerService,
private translateService: TranslateService) {}
openCreateEditTarget(targetId?: number) {
openCreateEditTarget(editable: boolean, targetId?: number) {
this.target = new Target();
this.createEditDestinationOpened = true;
this.editable = editable;
this.hasChanged = false;
this.pingTestMessage = '';
......
......@@ -87,13 +87,31 @@ export class DestinationComponent implements OnInit {
}
openModal() {
this.createEditDestinationComponent.openCreateEditTarget();
this.createEditDestinationComponent.openCreateEditTarget(true);
this.target = new Target();
}
editTarget(target: Target) {
if (target) {
this.createEditDestinationComponent.openCreateEditTarget(target.id);
let editable = true;
this.replicationService
.listTargetPolicies(target.id)
.subscribe(
policies=>{
if(policies && policies.length > 0) {
for(let i = 0; i < policies.length; i++){
let p = policies[i];
if(p.enabled === 1) {
editable = false;
break;
}
}
}
this.createEditDestinationComponent.openCreateEditTarget(editable, target.id);
},
error=>this.messageHandlerService.handleError(error)
);
}
}
......
......@@ -122,6 +122,14 @@ export class ReplicationService {
.catch(error=>Observable.throw(error));
}
listTargetPolicies(targetId: number): Observable<Policy[]> {
console.log('List target with policy.');
return this.http
.get(`/api/targets/${targetId}/policies`)
.map(response=>response.json() as Policy[])
.catch(error=>Observable.throw(error));
}
getTarget(targetId: number): Observable<Target> {
console.log('Get target by ID:' + targetId);
return this.http
......
......@@ -4,8 +4,6 @@
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]='r'>
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
<button class="action-item">{{'REPOSITORY.COPY_ID' | translate}}</button>
<button class="action-item">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-overflow>
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name || r.repository_name}}</a></clr-dg-cell>
......
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects', projectId, 'repository']">&lt; {{'REPOSITORY.REPOSITORIES' | translate}}</a>
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">&lt; {{'SEARCH.BACK' | translate}}</a>
<clr-modal [(clrModalOpen)]="showTagManifestOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
<div class="modal-body">
<div class="row col-md-12">
<textarea rows="3" (click)="selectAndCopy($event)">{{tagID}}</textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="showTagManifestOpened = false">{{'BUTTON.OK' | translate}}</button>
</div>
</clr-modal>
<h2 class="sub-header-title">{{repoName}} <span class="badge">{{tags ? tags.length : 0}}</span></h2>
<clr-datagrid>
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
......@@ -12,8 +25,10 @@
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.OS' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let t of tags" [clrDgItem]='t'>
<clr-dg-action-overflow *ngIf="hasProjectAdminRole">
<button class="action-item" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
<clr-dg-action-overflow>
<button class="action-item" (click)="showTagID('tag', t)">{{'REPOSITORY.COPY_ID' | translate}}</button>
<button class="action-item" (click)="showTagID('parent', t)">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
<button class="action-item" [hidden]="!hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-overflow>
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
......
......@@ -37,6 +37,14 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
hasSignedIn: boolean;
showTagManifestOpened: boolean;
manifestInfoTitle: string;
tagID: string;
staticBackdrop: boolean = true;
closable: boolean = false;
selectAll: boolean = false;
private subscription: Subscription;
constructor(
......@@ -123,6 +131,8 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
tag.dockerVersion = data['docker_version'];
tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag;
tag.os = data['os'];
tag.id = data['id'];
tag.parent = data['parent'];
this.tags.push(tag);
});
}
......@@ -149,4 +159,19 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
}
}
showTagID(type: string, tag: TagView) {
if(tag) {
if(type === 'tag') {
this.manifestInfoTitle = 'REPOSITORY.COPY_ID';
this.tagID = tag.id;
} else if(type === 'parent') {
this.manifestInfoTitle = 'REPOSITORY.COPY_PARENT_ID';
this.tagID = tag.parent;
}
this.showTagManifestOpened = true;
}
}
selectAndCopy($event) {
$event.target.select();
}
}
\ No newline at end of file
......@@ -7,4 +7,6 @@ export class TagView {
dockerVersion: string;
architecture: string;
os: string;
id: string;
parent: string;
}
\ No newline at end of file
......@@ -4,6 +4,13 @@
<div class="modal-body">
<form #policyForm="ngForm">
<section class="form-block">
<div class="alert alert-danger" *ngIf="readonly">
<div class="alert-item">
<span class="alert-text">
{{'REPLICATION.CANNOT_EDIT' | translate}}
</span>
</div>
</div>
<div class="form-group">
<label for="policy_name" class="col-md-4 form-group-label-override">{{'REPLICATION.NAME' | translate}}<span style="color: red">*</span></label>
<label for="policy_name" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="name.errors && (name.dirty || name.touched)" [class.valid]="name.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
......
......@@ -27,6 +27,7 @@ export const errorHandler = function (error: any): string {
case 404:
return "NOT_FOUND_ERROR";
case 412:
return "PRECONDITION_FAILED";
case 409:
return "CONFLICT_ERROR";
case 500:
......
......@@ -21,7 +21,6 @@
.action-panel-pos {
position: relative;
top: 6px;
padding-left: 12px;
margin-top: 12px;
}
......
<div style="margin-left: 24px;">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</h2>
<div class="action-panel-pos">
<span>
<button *ngIf="canCreateUser" type="submit" class="btn btn-primary custom-add-button" (click)="addNewUser()"><clr-icon shape="add"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
</span>
<grid-filter class="filter-pos" filterPlaceholder='{{"USER.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
<span class="refresh-btn" (click)="refreshUser()">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
</span>
<span>
<button *ngIf="canCreateUser" type="submit" class="btn btn-primary custom-add-button" (click)="addNewUser()"><clr-icon shape="add"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
</span>
<grid-filter class="filter-pos" filterPlaceholder='{{"USER.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
<span class="refresh-btn" (click)="refreshUser()">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
</span>
</div>
<div>
<clr-datagrid>
<clr-dg-column>{{'USER.COLUMN_NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_ADMIN' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_EMAIL' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_REG_NAME' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let user of users" [clrDgItem]="user">
<clr-dg-action-overflow [hidden]="isMySelf(user.user_id)">
<button class="action-item" (click)="changeAdminRole(user)">{{adminActions(user)}}</button>
<button class="action-item" (click)="deleteUser(user)">{{'USER.DEL_ACTION' | translate}}</button>
</clr-dg-action-overflow>
<clr-dg-cell>{{user.username}}</clr-dg-cell>
<clr-dg-cell>{{isSystemAdmin(user)}}</clr-dg-cell>
<clr-dg-cell>{{user.email}}</clr-dg-cell>
<clr-dg-cell>
{{user.creation_time}}
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{users.length}} {{'USER.ADD_ACTION' | translate}}</clr-dg-footer>
</clr-datagrid>
<clr-datagrid>
<clr-dg-column>{{'USER.COLUMN_NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_ADMIN' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_EMAIL' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_REG_NAME' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let user of users" [clrDgItem]="user">
<clr-dg-action-overflow [hidden]="isMySelf(user.user_id)">
<button class="action-item" (click)="changeAdminRole(user)">{{adminActions(user)}}</button>
<button class="action-item" (click)="deleteUser(user)">{{'USER.DEL_ACTION' | translate}}</button>
</clr-dg-action-overflow>
<clr-dg-cell>{{user.username}}</clr-dg-cell>
<clr-dg-cell>{{isSystemAdmin(user)}}</clr-dg-cell>
<clr-dg-cell>{{user.email}}</clr-dg-cell>
<clr-dg-cell>
{{user.creation_time}}
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{users.length}} {{'USER.ADD_ACTION' | translate}}</clr-dg-footer>
</clr-datagrid>
</div>
<new-user-modal (addNew)="addUserToList($event)"></new-user-modal>
</div>
</div>
\ No newline at end of file
......@@ -255,7 +255,8 @@
"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."
"TOGGLED_SUCCESS": "Toggled replication rule status successfully.",
"CANNOT_EDIT": "Replication rule cannot be changed while it is enabled."
},
"DESTINATION": {
"NEW_ENDPOINT": "New Endpoint",
......@@ -281,7 +282,8 @@
"CREATED_SUCCESS": "Created endpoint successfully.",
"UPDATED_SUCCESS": "Updated endpoint successfully.",
"DELETED_SUCCESS": "Deleted endpoint successfully.",
"DELETED_FAILED": "Deleted endpoint failed."
"DELETED_FAILED": "Deleted endpoint failed.",
"CANNOT_EDIT": "Endpoint cannot be changed while the replication rule is enabled."
},
"REPOSITORY": {
"COPY_ID": "Copy ID",
......@@ -312,7 +314,8 @@
"ITEMS": "item(s)",
"POP_REPOS": "Popular Repositories",
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
"DELETED_TAG_SUCCESS": "Deleted tag successfully."
"DELETED_TAG_SUCCESS": "Deleted tag successfully.",
"COPY": "Copy"
},
"ALERT": {
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?"
......@@ -421,6 +424,7 @@
"BAD_REQUEST_ERROR": "We are unable to perform your action because of a bad request",
"NOT_FOUND_ERROR": "Your request can not 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"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment