mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 10:45:45 +01:00
commit
6f85741143
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "harbor-ui",
|
||||
"version": "0.6.25",
|
||||
"version": "0.6.30",
|
||||
"description": "Harbor shared UI components based on Clarity and Angular4",
|
||||
"scripts": {
|
||||
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "harbor-ui",
|
||||
"version": "0.6.25",
|
||||
"version": "0.6.30",
|
||||
"description": "Harbor shared UI components based on Clarity and Angular4",
|
||||
"author": "VMware",
|
||||
"module": "index.js",
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const ENDPOINT_TEMPLATE: string = `
|
||||
export const ENDPOINT_TEMPLATE = `
|
||||
<div>
|
||||
<div class="row" style="position:relative;">
|
||||
<div>
|
||||
@ -13,12 +13,10 @@ export const ENDPOINT_TEMPLATE: string = `
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="openModal()">{{'DESTINATION.NEW_ENDPOINT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length ===1)" (click)="editTargets(selectedRow)" >{{'DESTINATION.TITLE_EDIT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow.length" (click)="deleteTargets(selectedRow)">{{'DESTINATION.DELETE' | translate}}</button>
|
||||
</div>
|
||||
<clr-dg-action-bar>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon> {{'DESTINATION.NEW_ENDPOINT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length ===1)" (click)="editTargets(selectedRow)" ><clr-icon shape="pencil" size="16"></clr-icon> {{'DESTINATION.TITLE_EDIT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow.length" (click)="deleteTargets(selectedRow)"><clr-icon shape="times" size="16"></clr-icon> {{'DESTINATION.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'endpoint'">{{'DESTINATION.URL' | translate}}</clr-dg-column>
|
||||
@ -28,15 +26,15 @@ export const ENDPOINT_TEMPLATE: string = `
|
||||
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
|
||||
<clr-dg-cell>{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
{{!t.insecure}}
|
||||
<clr-dg-cell>
|
||||
{{!t.insecure}}
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
|
||||
{{pagination.totalItems}} {{'DESTINATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
|
||||
{{pagination.totalItems}} {{'DESTINATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
|
@ -1,13 +1,11 @@
|
||||
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||
<div style="padding-bottom: 15px;">
|
||||
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" [clDgRowSelection]="true">
|
||||
<clr-dg-action-bar style="height:24px;">
|
||||
<div class="btn-group" *ngIf="opereateAvailable || isSystemAdmin">
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="openModal()">{{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="editRule(selectedRow)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)">{{'REPLICATION.REPLICATE' | translate}}</button>
|
||||
</div>
|
||||
<clr-dg-action-bar style="height:24px;" *ngIf="opereateAvailable || isSystemAdmin">
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon> {{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="editRule(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon> {{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon> {{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon> {{'REPLICATION.REPLICATE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'projects'" *ngIf="!projectScope">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
|
@ -13,9 +13,7 @@ export const REPOSITORY_LISTVIEW_TEMPLATE = `
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteRepos(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteRepos(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon> {{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||
|
@ -24,7 +24,7 @@ export const REPOSITORY_TEMPLATE = `
|
||||
<section id="info" role="tabpanel" aria-labelledby="repo-info" [hidden]='!isCurrentTabContent("info")'>
|
||||
<form #repoInfoForm="ngForm">
|
||||
<div id="info-edit-button">
|
||||
<button class="btn btn-sm" [disabled]="editing || !hasProjectAdminRole " (click)="editInfo()" >{{'BUTTON.EDIT' | translate}}</button>
|
||||
<button class="btn btn-sm" [disabled]="editing || !hasProjectAdminRole " (click)="editInfo()" ><clr-icon shape="pencil" size="16"></clr-icon> {{'BUTTON.EDIT' | translate}}</button>
|
||||
</div>
|
||||
<div *ngIf="!editing">
|
||||
<div *ngIf="!hasInfo()" class="no-info-div">
|
||||
|
@ -44,7 +44,7 @@ export const TAG_TEMPLATE = `
|
||||
<a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)" title="{{t.name}}">{{t.name}}</a>
|
||||
<span *ngSwitchDefault>{{t.name}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 90px;">{{t.size}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 90px;">{{sizeTransform(t.size)}}</clr-dg-cell>
|
||||
<clr-dg-cell style="min-width: 120px; max-width:220px;" class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">
|
||||
<hbr-copy-input #copyInput (onCopyError)="onCpError($event)" iconMode="true" defaultValue="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}"></hbr-copy-input>
|
||||
</clr-dg-cell>
|
||||
|
@ -21,25 +21,25 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
} from "@angular/core";
|
||||
|
||||
import { TagService, VulnerabilitySeverity, RequestQueryParams } from '../service/index';
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { ChannelService } from '../channel/index';
|
||||
import { TagService, VulnerabilitySeverity, RequestQueryParams } from "../service/index";
|
||||
import { ErrorHandler } from "../error-handler/error-handler";
|
||||
import { ChannelService } from "../channel/index";
|
||||
import {
|
||||
ConfirmationTargets,
|
||||
ConfirmationState,
|
||||
ConfirmationButtons
|
||||
} from '../shared/shared.const';
|
||||
} from "../shared/shared.const";
|
||||
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
|
||||
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
|
||||
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
|
||||
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
|
||||
|
||||
import { Tag, TagClickEvent } from '../service/interface';
|
||||
import { Tag, TagClickEvent } from "../service/interface";
|
||||
|
||||
import { TAG_TEMPLATE } from './tag.component.html';
|
||||
import { TAG_STYLE } from './tag.component.css';
|
||||
import { TAG_TEMPLATE } from "./tag.component.html";
|
||||
import { TAG_STYLE } from "./tag.component.css";
|
||||
|
||||
import {
|
||||
toPromise,
|
||||
@ -49,17 +49,17 @@ import {
|
||||
doSorting,
|
||||
VULNERABILITY_SCAN_STATUS,
|
||||
DEFAULT_PAGE_SIZE
|
||||
} from '../utils';
|
||||
} from "../utils";
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
import { State, Comparator } from 'clarity-angular';
|
||||
import {CopyInputComponent} from '../push-image/copy-input.component';
|
||||
import { State, Comparator } from "clarity-angular";
|
||||
import {CopyInputComponent} from "../push-image/copy-input.component";
|
||||
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message";
|
||||
import {Observable} from "rxjs/Observable";
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-tag',
|
||||
selector: "hbr-tag",
|
||||
template: TAG_TEMPLATE,
|
||||
styles: [TAG_STYLE],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
@ -87,22 +87,22 @@ export class TagComponent implements OnInit {
|
||||
showTagManifestOpened: boolean;
|
||||
manifestInfoTitle: string;
|
||||
digestId: string;
|
||||
staticBackdrop: boolean = true;
|
||||
closable: boolean = false;
|
||||
staticBackdrop = true;
|
||||
closable = false;
|
||||
lastFilteredTagName: string;
|
||||
batchDelectionInfos: BatchInfo[] = [];
|
||||
|
||||
createdComparator: Comparator<Tag> = new CustomComparator<Tag>('created', 'date');
|
||||
createdComparator: Comparator<Tag> = new CustomComparator<Tag>("created", "date");
|
||||
|
||||
loading: boolean = false;
|
||||
copyFailed: boolean = false;
|
||||
loading = false;
|
||||
copyFailed = false;
|
||||
selectedRow: Tag[] = [];
|
||||
|
||||
@ViewChild('confirmationDialog')
|
||||
@ViewChild("confirmationDialog")
|
||||
confirmationDialog: ConfirmationDialogComponent;
|
||||
|
||||
@ViewChild('digestTarget') textInput: ElementRef;
|
||||
@ViewChild('copyInput') copyInput: CopyInputComponent;
|
||||
@ViewChild("digestTarget") textInput: ElementRef;
|
||||
@ViewChild("copyInput") copyInput: CopyInputComponent;
|
||||
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
currentPage = 1;
|
||||
@ -119,16 +119,16 @@ export class TagComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.projectId) {
|
||||
this.errorHandler.error('Project ID cannot be unset.');
|
||||
this.errorHandler.error("Project ID cannot be unset.");
|
||||
return;
|
||||
}
|
||||
if (!this.repoName) {
|
||||
this.errorHandler.error('Repo name cannot be unset.');
|
||||
this.errorHandler.error("Repo name cannot be unset.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.retrieve();
|
||||
this.lastFilteredTagName = '';
|
||||
this.lastFilteredTagName = "";
|
||||
}
|
||||
|
||||
selectedChange(): void {
|
||||
@ -147,7 +147,7 @@ export class TagComponent implements OnInit {
|
||||
st.page.size = this.pageSize;
|
||||
st.page.from = 0;
|
||||
st.page.to = this.pageSize - 1;
|
||||
st.filters = [{property: 'name', value: this.lastFilteredTagName}];
|
||||
st.filters = [{property: "name", value: this.lastFilteredTagName}];
|
||||
this.clrLoad(st);
|
||||
}
|
||||
|
||||
@ -161,8 +161,8 @@ export class TagComponent implements OnInit {
|
||||
|
||||
// Pagination
|
||||
let params: RequestQueryParams = new RequestQueryParams();
|
||||
params.set('page', '' + pageNumber);
|
||||
params.set('page_size', '' + this.pageSize);
|
||||
params.set("page", "" + pageNumber);
|
||||
params.set("page_size", "" + this.pageSize);
|
||||
|
||||
this.loading = true;
|
||||
|
||||
@ -188,7 +188,7 @@ export class TagComponent implements OnInit {
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.doSearchTagNames('');
|
||||
this.doSearchTagNames("");
|
||||
}
|
||||
|
||||
|
||||
@ -217,9 +217,6 @@ export class TagComponent implements OnInit {
|
||||
if (t.signature !== null) {
|
||||
signatures.push(t.name);
|
||||
}
|
||||
|
||||
// size
|
||||
t.size = this.sizeTransform(t.size);
|
||||
});
|
||||
this.tags = items;
|
||||
let signedName: {[key: string]: string[]} = {};
|
||||
@ -241,13 +238,13 @@ export class TagComponent implements OnInit {
|
||||
sizeTransform(tagSize: string): string {
|
||||
let size: number = Number.parseInt(tagSize);
|
||||
if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) {
|
||||
return (size / Math.pow(1024, 1)).toFixed(2) + 'KB';
|
||||
return (size / Math.pow(1024, 1)).toFixed(2) + "KB";
|
||||
} else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) {
|
||||
return (size / Math.pow(1024, 2)).toFixed(2) + 'MB';
|
||||
return (size / Math.pow(1024, 2)).toFixed(2) + "MB";
|
||||
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
|
||||
return (size / Math.pow(1024, 3)).toFixed(2) + 'MB';
|
||||
return (size / Math.pow(1024, 3)).toFixed(2) + "MB";
|
||||
} else {
|
||||
return size + 'B';
|
||||
return size + "B";
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,10 +260,10 @@ export class TagComponent implements OnInit {
|
||||
});
|
||||
|
||||
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
|
||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG';
|
||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG';
|
||||
titleKey = "REPOSITORY.DELETION_TITLE_TAG";
|
||||
summaryKey = "REPOSITORY.DELETION_SUMMARY_TAG";
|
||||
buttons = ConfirmationButtons.DELETE_CANCEL;
|
||||
content = tagNames.join(' , ');
|
||||
content = tagNames.join(" , ");
|
||||
let message = new ConfirmationMessage(
|
||||
titleKey,
|
||||
summaryKey,
|
||||
@ -300,9 +297,9 @@ export class TagComponent implements OnInit {
|
||||
delOperate(signature: any, name: string) {
|
||||
let findedList = this.batchDelectionInfos.find(data => data.name === name);
|
||||
if (signature) {
|
||||
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
|
||||
this.translateService.get('REPOSITORY.DELETION_SUMMARY_TAG_DENIED')).subscribe(res => {
|
||||
let wrongInfo: string = res[1] + 'notary -s https://' + this.registryUrl + ':4443 -d ~/.docker/trust remove -p ' + this.registryUrl + '/' + this.repoName + ' ' + name;
|
||||
Observable.forkJoin(this.translateService.get("BATCH.DELETED_FAILURE"),
|
||||
this.translateService.get("REPOSITORY.DELETION_SUMMARY_TAG_DENIED")).subscribe(res => {
|
||||
let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl + ":4443 -d ~/.docker/trust remove -p " + this.registryUrl + "/" + this.repoName + " " + name;
|
||||
findedList = BathInfoChanges(findedList, res[0], false, true, wrongInfo);
|
||||
});
|
||||
} else {
|
||||
@ -310,12 +307,12 @@ export class TagComponent implements OnInit {
|
||||
.deleteTag(this.repoName, name))
|
||||
.then(
|
||||
response => {
|
||||
this.translateService.get('BATCH.DELETED_SUCCESS')
|
||||
this.translateService.get("BATCH.DELETED_SUCCESS")
|
||||
.subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res);
|
||||
});
|
||||
}).catch(error => {
|
||||
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
|
||||
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res, false, true);
|
||||
});
|
||||
});
|
||||
@ -324,7 +321,7 @@ export class TagComponent implements OnInit {
|
||||
|
||||
showDigestId(tag: Tag[]) {
|
||||
if (tag && (tag.length === 1)) {
|
||||
this.manifestInfoTitle = 'REPOSITORY.COPY_DIGEST_ID';
|
||||
this.manifestInfoTitle = "REPOSITORY.COPY_DIGEST_ID";
|
||||
this.digestId = tag[0].digest;
|
||||
this.showTagManifestOpened = true;
|
||||
this.copyFailed = false;
|
||||
@ -388,7 +385,7 @@ export class TagComponent implements OnInit {
|
||||
if (t && t.length) {
|
||||
t.forEach((data: any) => {
|
||||
let tagId = data.name;
|
||||
this.channel.publishScanEvent(this.repoName + '/' + tagId);
|
||||
this.channel.publishScanEvent(this.repoName + "/" + tagId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.10.17",
|
||||
"clarity-ui": "^0.10.17",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.6.29",
|
||||
"harbor-ui": "0.6.30",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -11,38 +11,35 @@
|
||||
// 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, ViewChild, AfterViewChecked } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Component, OnInit, ViewChild, AfterViewChecked } from "@angular/core";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { Router, NavigationExtras } from "@angular/router";
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||
import { SessionUser } from "../../shared/session-user";
|
||||
import { SessionService } from "../../shared/session.service";
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { SearchTriggerService } from "../../base/global-search/search-trigger.service";
|
||||
import { CommonRoutes } from "../../shared/shared.const";
|
||||
|
||||
@Component({
|
||||
selector: "account-settings-modal",
|
||||
templateUrl: "account-settings-modal.component.html",
|
||||
styleUrls: ['../../common.css']
|
||||
styleUrls: ["../../common.css"]
|
||||
})
|
||||
|
||||
export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
opened: boolean = false;
|
||||
staticBackdrop: boolean = true;
|
||||
opened = false;
|
||||
staticBackdrop = true;
|
||||
account: SessionUser;
|
||||
error: any = null;
|
||||
originalStaticData: SessionUser;
|
||||
emailTooltip: string = 'TOOLTIP.EMAIL';
|
||||
private validationStateMap: any = {
|
||||
"account_settings_email": true,
|
||||
"account_settings_full_name": true
|
||||
};
|
||||
emailTooltip = "TOOLTIP.EMAIL";
|
||||
mailAlreadyChecked = {};
|
||||
|
||||
isOnCalling: boolean = false;
|
||||
formValueChanged: boolean = false;
|
||||
checkOnGoing: boolean = false;
|
||||
|
||||
RenameOnGoing: boolean = false;
|
||||
isOnCalling = false;
|
||||
formValueChanged = false;
|
||||
checkOnGoing = false;
|
||||
RenameOnGoing = false;
|
||||
|
||||
accountFormRef: NgForm;
|
||||
@ViewChild("accountSettingsFrom") accountForm: NgForm;
|
||||
@ -51,10 +48,18 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private msgHandler: MessageHandlerService) { }
|
||||
private msgHandler: MessageHandlerService,
|
||||
private router: Router,
|
||||
private searchTrigger: SearchTriggerService
|
||||
) { }
|
||||
|
||||
|
||||
private validationStateMap: any = {
|
||||
"account_settings_email": true,
|
||||
"account_settings_full_name": true
|
||||
};
|
||||
ngOnInit(): void {
|
||||
//Value copy
|
||||
// Value copy
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
}
|
||||
|
||||
@ -64,11 +69,11 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
|
||||
handleValidation(key: string, flag: boolean): void {
|
||||
if (flag) {
|
||||
//Checking
|
||||
// Checking
|
||||
let cont = this.accountForm.controls[key];
|
||||
if (cont) {
|
||||
this.validationStateMap[key] = cont.valid;
|
||||
//Check email existing from backend
|
||||
// Check email existing from backend
|
||||
if (cont.valid && key === "account_settings_email") {
|
||||
if (this.formValueChanged && this.account.email != this.originalStaticData.email) {
|
||||
if (this.mailAlreadyChecked[this.account.email]) {
|
||||
@ -79,7 +84,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
return;
|
||||
}
|
||||
|
||||
//Mail changed
|
||||
// Mail changed
|
||||
this.checkOnGoing = true;
|
||||
this.session.checkUserExisting("email", this.account.email)
|
||||
.then((res: boolean) => {
|
||||
@ -90,17 +95,17 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
}
|
||||
this.mailAlreadyChecked[this.account.email] = {
|
||||
result: res
|
||||
}; //Tag it checked
|
||||
}; // Tag it checked
|
||||
})
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
this.validationStateMap[key] = false;//Not valid @ backend
|
||||
this.validationStateMap[key] = false; // Not valid @ backend
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Reset
|
||||
// Reset
|
||||
this.validationStateMap[key] = true;
|
||||
this.emailTooltip = "TOOLTIP.EMAIL";
|
||||
}
|
||||
@ -124,7 +129,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
return this.accountForm &&
|
||||
this.accountForm.valid &&
|
||||
this.error === null &&
|
||||
this.validationStateMap["account_settings_email"]; //backend check is valid as well
|
||||
this.validationStateMap["account_settings_email"]; // backend check is valid as well
|
||||
}
|
||||
|
||||
public get showProgress(): boolean {
|
||||
@ -136,13 +141,13 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
}
|
||||
|
||||
public get renamable(): boolean {
|
||||
return this.account && this.account.has_admin_role && this.account.username === 'admin' && this.account.user_id === 1;
|
||||
return this.account && this.account.has_admin_role && this.account.username === "admin" && this.account.user_id === 1;
|
||||
}
|
||||
|
||||
openRenameAlert(): void {
|
||||
this.RenameOnGoing = true;
|
||||
this.inlineAlert.showInlineConfirmation({
|
||||
message: 'PROFILE.RENAME_CONFIRM_INFO'
|
||||
message: "PROFILE.RENAME_CONFIRM_INFO"
|
||||
});
|
||||
}
|
||||
|
||||
@ -150,7 +155,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
if (this.renamable) {
|
||||
this.session.renameAdmin(this.account)
|
||||
.then(() => {
|
||||
this.msgHandler.showSuccess('PROFILE.RENAME_SUCCESS');
|
||||
this.msgHandler.showSuccess("PROFILE.RENAME_SUCCESS");
|
||||
})
|
||||
.catch(error => {
|
||||
this.msgHandler.handleError(error);
|
||||
@ -173,19 +178,31 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
}
|
||||
}
|
||||
|
||||
// Log out system
|
||||
logOut(): void {
|
||||
// Naviagte to the sign in route
|
||||
// Appending 'signout' means destroy session cache
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: { "signout": true }
|
||||
};
|
||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
|
||||
// Confirm search result panel is close
|
||||
this.searchTrigger.closeSearch(true);
|
||||
}
|
||||
|
||||
open() {
|
||||
//Keep the initial data for future diff
|
||||
// Keep the initial data for future diff
|
||||
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
this.formValueChanged = false;
|
||||
|
||||
//Confirm inline alert is closed
|
||||
// Confirm inline alert is closed
|
||||
this.inlineAlert.close();
|
||||
|
||||
//Clear check history
|
||||
// Clear check history
|
||||
this.mailAlreadyChecked = {};
|
||||
|
||||
//Reset validation status
|
||||
// Reset validation status
|
||||
this.validationStateMap = {
|
||||
"account_settings_email": true,
|
||||
"account_settings_full_name": true
|
||||
@ -195,11 +212,14 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.RenameOnGoing) {
|
||||
this.RenameOnGoing = false;
|
||||
}
|
||||
if (this.formValueChanged) {
|
||||
if (!this.isUserDataChange()) {
|
||||
this.opened = false;
|
||||
} else {
|
||||
//Need user confirmation
|
||||
// Need user confirmation
|
||||
this.inlineAlert.showInlineConfirmation({
|
||||
message: "ALERT.FORM_CHANGE_CONFIRMATION"
|
||||
});
|
||||
@ -214,7 +234,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
return;
|
||||
}
|
||||
|
||||
//Double confirm session is valid
|
||||
// Double confirm session is valid
|
||||
let cUser = this.session.getCurrentUser();
|
||||
if (!cUser) {
|
||||
return;
|
||||
@ -249,9 +269,9 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
if (this.RenameOnGoing) {
|
||||
this.confirmRename();
|
||||
this.RenameOnGoing = false;
|
||||
this.logOut();
|
||||
}
|
||||
this.inlineAlert.close();
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
}
|
@ -14,25 +14,6 @@
|
||||
import { ClairDBStatus } from 'harbor-ui';
|
||||
|
||||
export class AppConfig {
|
||||
constructor() {
|
||||
//Set default value
|
||||
this.with_notary = false;
|
||||
this.with_admiral = false;
|
||||
this.with_clair = false;
|
||||
this.admiral_endpoint = "";
|
||||
this.auth_mode = "db_auth";
|
||||
this.registry_url = "";
|
||||
this.project_creation_restriction = "everyone";
|
||||
this.self_registration = true;
|
||||
this.has_ca_root = false;
|
||||
this.harbor_version = "1.2.0";//default
|
||||
this.clair_vulnerability_status = {
|
||||
overall_last_update: 0,
|
||||
details: []
|
||||
};
|
||||
this.next_scan_all = 0;
|
||||
}
|
||||
|
||||
with_notary: boolean;
|
||||
with_admiral: boolean;
|
||||
with_clair: boolean;
|
||||
@ -45,4 +26,25 @@ export class AppConfig {
|
||||
harbor_version: string;
|
||||
clair_vulnerability_status?: ClairDBStatus;
|
||||
next_scan_all: number;
|
||||
registry_storage_provider_name: string;
|
||||
|
||||
constructor() {
|
||||
// Set default value
|
||||
this.with_notary = false;
|
||||
this.with_admiral = false;
|
||||
this.with_clair = false;
|
||||
this.admiral_endpoint = "";
|
||||
this.auth_mode = "db_auth";
|
||||
this.registry_url = "";
|
||||
this.project_creation_restriction = "everyone";
|
||||
this.self_registration = true;
|
||||
this.has_ca_root = false;
|
||||
this.harbor_version = "1.2.0";
|
||||
this.clair_vulnerability_status = {
|
||||
overall_last_update: 0,
|
||||
details: []
|
||||
};
|
||||
this.next_scan_all = 0;
|
||||
this.registry_storage_provider_name = "";
|
||||
}
|
||||
}
|
@ -107,15 +107,15 @@ export class NavigatorComponent implements OnInit {
|
||||
public get canChangePassword(): boolean {
|
||||
let user = this.session.getCurrentUser();
|
||||
let config = this.appConfigService.getConfig();
|
||||
|
||||
return user && ((config && !(config.auth_mode === 'ldap_auth' || config.auth_mode === 'uaa_auth')) || (user.user_id === 1 && user.username === 'admin'));
|
||||
|
||||
return user && ((config && !(config.auth_mode === "ldap_auth" || config.auth_mode === "uaa_auth")) || (user.user_id === 1 && user.username === "admin"));
|
||||
}
|
||||
|
||||
matchLang(lang: string): boolean {
|
||||
return lang.trim() === this.selectedLang;
|
||||
}
|
||||
|
||||
//Open the account setting dialog
|
||||
// Open the account setting dialog
|
||||
openAccountSettingsModal(): void {
|
||||
this.showAccountSettingsModal.emit({
|
||||
modalName: modalEvents.USER_PROFILE,
|
||||
@ -123,7 +123,7 @@ export class NavigatorComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
//Open change password dialog
|
||||
// Open change password dialog
|
||||
openChangePwdModal(): void {
|
||||
this.showPwdChangeModal.emit({
|
||||
modalName: modalEvents.CHANGE_PWD,
|
||||
|
@ -1,6 +1,6 @@
|
||||
<clr-modal [(clrModalOpen)]="createProjectOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<div class="modal-body" style="height: 16.8em; overflow-y: hidden;">
|
||||
<form #projectForm="ngForm">
|
||||
<section class="form-block">
|
||||
|
@ -17,35 +17,34 @@ import {
|
||||
Output,
|
||||
ViewChild,
|
||||
AfterViewChecked,
|
||||
HostBinding,
|
||||
OnInit,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { Response } from '@angular/http';
|
||||
import { NgForm } from '@angular/forms';
|
||||
} from "@angular/core";
|
||||
import { Response } from "@angular/http";
|
||||
import { NgForm } from "@angular/forms";
|
||||
|
||||
import { Project } from '../project';
|
||||
import { ProjectService } from '../project.service';
|
||||
import { Project } from "../project";
|
||||
import { ProjectService } from "../project.service";
|
||||
|
||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import { Subject } from "rxjs/Subject";
|
||||
import "rxjs/add/operator/debounceTime";
|
||||
import "rxjs/add/operator/distinctUntilChanged";
|
||||
|
||||
@Component({
|
||||
selector: 'create-project',
|
||||
templateUrl: 'create-project.component.html',
|
||||
styleUrls: ['create-project.css']
|
||||
selector: "create-project",
|
||||
templateUrl: "create-project.component.html",
|
||||
styleUrls: ["create-project.css"]
|
||||
})
|
||||
export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
|
||||
projectForm: NgForm;
|
||||
|
||||
@ViewChild('projectForm')
|
||||
@ViewChild("projectForm")
|
||||
currentForm: NgForm;
|
||||
|
||||
project: Project = new Project();
|
||||
@ -54,14 +53,14 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr
|
||||
createProjectOpened: boolean;
|
||||
|
||||
hasChanged: boolean;
|
||||
isSubmitOnGoing:boolean=false;
|
||||
isSubmitOnGoing = false;
|
||||
|
||||
staticBackdrop: boolean = true;
|
||||
closable: boolean = false;
|
||||
staticBackdrop = true;
|
||||
closable = false;
|
||||
|
||||
isNameValid: boolean = true;
|
||||
nameTooltipText: string = 'PROJECT.NAME_TOOLTIP';
|
||||
checkOnGoing: boolean = false;
|
||||
isNameValid = true;
|
||||
nameTooltipText = "PROJECT.NAME_TOOLTIP";
|
||||
checkOnGoing = false;
|
||||
proNameChecker: Subject<string> = new Subject<string>();
|
||||
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
@ -71,7 +70,6 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr
|
||||
constructor(private projectService: ProjectService,
|
||||
private translateService: TranslateService,
|
||||
private messageHandlerService: MessageHandlerService) { }
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.proNameChecker
|
||||
@ -82,20 +80,20 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr
|
||||
if (cont && this.hasChanged) {
|
||||
this.isNameValid = cont.valid;
|
||||
if (this.isNameValid) {
|
||||
//Check exiting from backend
|
||||
// Check exiting from backend
|
||||
this.projectService
|
||||
.checkProjectExists(cont.value).toPromise()
|
||||
.then(() => {
|
||||
//Project existing
|
||||
// Project existing
|
||||
this.isNameValid = false;
|
||||
this.nameTooltipText = 'PROJECT.NAME_ALREADY_EXISTS';
|
||||
this.nameTooltipText = "PROJECT.NAME_ALREADY_EXISTS";
|
||||
this.checkOnGoing = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
} else {
|
||||
this.nameTooltipText = 'PROJECT.NAME_TOOLTIP';
|
||||
this.nameTooltipText = "PROJECT.NAME_TOOLTIP";
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -106,54 +104,44 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.isSubmitOnGoing){
|
||||
if (this.isSubmitOnGoing) {
|
||||
return ;
|
||||
}
|
||||
|
||||
this.isSubmitOnGoing=true;
|
||||
this.isSubmitOnGoing = true;
|
||||
this.projectService
|
||||
.createProject(this.project.name, this.project.metadata)
|
||||
.subscribe(
|
||||
status => {
|
||||
this.isSubmitOnGoing=false;
|
||||
this.isSubmitOnGoing = false;
|
||||
|
||||
this.create.emit(true);
|
||||
this.messageHandlerService.showSuccess('PROJECT.CREATED_SUCCESS');
|
||||
this.messageHandlerService.showSuccess("PROJECT.CREATED_SUCCESS");
|
||||
this.createProjectOpened = false;
|
||||
},
|
||||
error => {
|
||||
this.isSubmitOnGoing=false;
|
||||
this.isSubmitOnGoing = false;
|
||||
|
||||
let errorMessage: string;
|
||||
if (error instanceof Response) {
|
||||
switch (error.status) {
|
||||
case 409:
|
||||
this.translateService.get('PROJECT.NAME_ALREADY_EXISTS').subscribe(res => errorMessage = res);
|
||||
this.translateService.get("PROJECT.NAME_ALREADY_EXISTS").subscribe(res => errorMessage = res);
|
||||
break;
|
||||
case 400:
|
||||
this.translateService.get('PROJECT.NAME_IS_ILLEGAL').subscribe(res => errorMessage = res);
|
||||
this.translateService.get("PROJECT.NAME_IS_ILLEGAL").subscribe(res => errorMessage = res);
|
||||
break;
|
||||
default:
|
||||
this.translateService.get('PROJECT.UNKNOWN_ERROR').subscribe(res => errorMessage = res);
|
||||
}
|
||||
if (this.messageHandlerService.isAppLevel(error)) {
|
||||
this.messageHandlerService.handleError(error);
|
||||
this.createProjectOpened = false;
|
||||
} else {
|
||||
this.inlineAlert.showInlineError(errorMessage);
|
||||
this.translateService.get("PROJECT.UNKNOWN_ERROR").subscribe(res => errorMessage = res);
|
||||
}
|
||||
this.messageHandlerService.handleError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
if (this.hasChanged) {
|
||||
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' });
|
||||
} else {
|
||||
this.createProjectOpened = false;
|
||||
this.projectForm.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
@ -181,21 +169,15 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr
|
||||
this.createProjectOpened = true;
|
||||
}
|
||||
|
||||
confirmCancel(event: boolean): void {
|
||||
this.createProjectOpened = false;
|
||||
this.inlineAlert.close();
|
||||
this.projectForm.reset();
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this.currentForm &&
|
||||
this.currentForm.valid &&
|
||||
!this.isSubmitOnGoing&&
|
||||
!this.isSubmitOnGoing &&
|
||||
this.isNameValid &&
|
||||
!this.checkOnGoing;
|
||||
}
|
||||
|
||||
//Handle the form validation
|
||||
// Handle the form validation
|
||||
handleValidation(): void {
|
||||
let cont = this.currentForm.controls["create_project_name"];
|
||||
if (cont) {
|
||||
|
@ -1,9 +1,7 @@
|
||||
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addNewProject()" *ngIf="projectCreationRestriction">{{'PROJECT.NEW_PROJECT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length && (isSystemAdmin || canDelete))" (click)="deleteProjects(selectedRow)" >{{'PROJECT.DELETE' | translate}}</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addNewProject()" *ngIf="projectCreationRestriction"><clr-icon shape="plus" size="16"></clr-icon> {{'PROJECT.NEW_PROJECT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length && (isSystemAdmin || canDelete))" (click)="deleteProjects(selectedRow)" ><clr-icon shape="times" size="16"></clr-icon> {{'PROJECT.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="accessLevelComparator">{{'PROJECT.ACCESS_LEVEL' | translate}}</clr-dg-column>
|
||||
|
@ -14,42 +14,41 @@
|
||||
import {
|
||||
Component,
|
||||
Output,
|
||||
Input,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy, EventEmitter
|
||||
} from '@angular/core';
|
||||
import { Router, NavigationExtras } from '@angular/router';
|
||||
import { Project } from '../project';
|
||||
import { ProjectService } from '../project.service';
|
||||
} from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Project } from "../project";
|
||||
import { ProjectService } from "../project.service";
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
|
||||
import { ProjectTypes, RoleInfo } from '../../shared/shared.const';
|
||||
import { CustomComparator, doFiltering, doSorting, calculatePage } from '../../shared/shared.utils';
|
||||
import { SessionService } from "../../shared/session.service";
|
||||
import { SearchTriggerService } from "../../base/global-search/search-trigger.service";
|
||||
import { RoleInfo } from "../../shared/shared.const";
|
||||
import { CustomComparator, doFiltering, doSorting, calculatePage } from "../../shared/shared.utils";
|
||||
|
||||
import { Comparator, State } from 'clarity-angular';
|
||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||
import { StatisticHandler } from '../../shared/statictics/statistic-handler.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../../shared/shared.const';
|
||||
import { Comparator, State } from "clarity-angular";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { StatisticHandler } from "../../shared/statictics/statistic-handler.service";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from "../../shared/shared.const";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {BatchInfo, BathInfoChanges} from "../../shared/confirmation-dialog/confirmation-batch-message";
|
||||
import {Observable} from "rxjs/Observable";
|
||||
import {AppConfigService} from "../../app-config.service";
|
||||
|
||||
@Component({
|
||||
selector: 'list-project',
|
||||
templateUrl: 'list-project.component.html',
|
||||
selector: "list-project",
|
||||
templateUrl: "list-project.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ListProjectComponent implements OnDestroy {
|
||||
loading: boolean = true;
|
||||
loading = true;
|
||||
projects: Project[] = [];
|
||||
filteredType: number = 0;//All projects
|
||||
searchKeyword: string = "";
|
||||
filteredType = 0; // All projects
|
||||
searchKeyword = "";
|
||||
selectedRow: Project[] = [];
|
||||
batchDelectionInfos: BatchInfo[] = [];
|
||||
|
||||
@ -60,9 +59,9 @@ export class ListProjectComponent implements OnDestroy {
|
||||
timeComparator: Comparator<Project> = new CustomComparator<Project>("creation_time", "date");
|
||||
accessLevelComparator: Comparator<Project> = new CustomComparator<Project>("public", "number");
|
||||
roleComparator: Comparator<Project> = new CustomComparator<Project>("current_user_role_id", "number");
|
||||
currentPage: number = 1;
|
||||
totalCount: number = 0;
|
||||
pageSize: number = 15;
|
||||
currentPage = 1;
|
||||
totalCount = 0;
|
||||
pageSize = 15;
|
||||
currentState: State;
|
||||
subscription: Subscription;
|
||||
|
||||
@ -97,9 +96,9 @@ export class ListProjectComponent implements OnDestroy {
|
||||
let account = this.session.getCurrentUser();
|
||||
if (account) {
|
||||
switch (this.appConfigService.getConfig().project_creation_restriction) {
|
||||
case 'adminonly':
|
||||
case "adminonly":
|
||||
return (account.has_admin_role === 1);
|
||||
case 'everyone':
|
||||
case "everyone":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -131,7 +130,7 @@ export class ListProjectComponent implements OnDestroy {
|
||||
goToLink(proId: number): void {
|
||||
this.searchTrigger.closeSearch(true);
|
||||
|
||||
let linkUrl = ['harbor', 'projects', proId, 'repositories'];
|
||||
let linkUrl = ["harbor", "projects", proId, "repositories"];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
|
||||
@ -143,7 +142,7 @@ export class ListProjectComponent implements OnDestroy {
|
||||
clrLoad(state: State) {
|
||||
this.selectedRow = [];
|
||||
|
||||
//Keep state for future filtering and sorting
|
||||
// Keep state for future filtering and sorting
|
||||
this.currentState = state;
|
||||
|
||||
let pageNumber: number = calculatePage(state);
|
||||
@ -157,7 +156,7 @@ export class ListProjectComponent implements OnDestroy {
|
||||
}
|
||||
this.proService.listProjects(this.searchKeyword, passInFilteredType, pageNumber, this.pageSize).toPromise()
|
||||
.then(response => {
|
||||
//Get total count
|
||||
// Get total count
|
||||
if (response.headers) {
|
||||
let xHeader: string = response.headers.get("X-Total-Count");
|
||||
if (xHeader) {
|
||||
@ -166,7 +165,7 @@ export class ListProjectComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
this.projects = response.json() as Project[];
|
||||
//Do customising filtering and sorting
|
||||
// Do customising filtering and sorting
|
||||
this.projects = doFiltering<Project>(this.projects, state);
|
||||
this.projects = doSorting<Project>(this.projects, state);
|
||||
|
||||
@ -177,7 +176,7 @@ export class ListProjectComponent implements OnDestroy {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
|
||||
//Force refresh view
|
||||
// Force refresh view
|
||||
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 5000);
|
||||
}
|
||||
@ -190,12 +189,12 @@ export class ListProjectComponent implements OnDestroy {
|
||||
|
||||
toggleProject(p: Project) {
|
||||
if (p) {
|
||||
p.metadata.public === 'true' ? p.metadata.public = 'false' : p.metadata.public = 'true';
|
||||
p.metadata.public === "true" ? p.metadata.public = "false" : p.metadata.public = "true";
|
||||
this.proService
|
||||
.toggleProjectPublic(p.project_id, p.metadata.public)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.msgHandler.showSuccess('PROJECT.TOGGLED_SUCCESS');
|
||||
this.msgHandler.showSuccess("PROJECT.TOGGLED_SUCCESS");
|
||||
let pp: Project = this.projects.find((item: Project) => item.project_id === p.project_id);
|
||||
if (pp) {
|
||||
pp.metadata.public = p.metadata.public;
|
||||
@ -205,7 +204,7 @@ export class ListProjectComponent implements OnDestroy {
|
||||
error => this.msgHandler.handleError(error)
|
||||
);
|
||||
|
||||
//Force refresh view
|
||||
// Force refresh view
|
||||
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
}
|
||||
@ -222,21 +221,13 @@ export class ListProjectComponent implements OnDestroy {
|
||||
this.batchDelectionInfos.push(initBatchMessage);
|
||||
});
|
||||
this.deletionDialogService.addBatchInfoList(this.batchDelectionInfos);
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
'PROJECT.DELETION_TITLE',
|
||||
'PROJECT.DELETION_SUMMARY',
|
||||
nameArr.join(','),
|
||||
p,
|
||||
ConfirmationTargets.PROJECT,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
this.delProjects(p);
|
||||
}
|
||||
}
|
||||
delProjects(datas: Project[]) {
|
||||
delProjects(projects: Project[]) {
|
||||
let observableLists: any[] = [];
|
||||
if (datas && datas.length) {
|
||||
datas.forEach(data => {
|
||||
if (projects && projects.length) {
|
||||
projects.forEach(data => {
|
||||
observableLists.push(this.delOperate(data.project_id, data.name));
|
||||
});
|
||||
Promise.all(observableLists).then(item => {
|
||||
@ -252,23 +243,23 @@ export class ListProjectComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
delOperate(id: number, name: string) {
|
||||
delOperate(id: number, name: string) {
|
||||
let findedList = this.batchDelectionInfos.find(list => list.name === name);
|
||||
return this.proService.deleteProject(id)
|
||||
.then(
|
||||
() => {
|
||||
this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => {
|
||||
this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
if (error && error.status === 412) {
|
||||
Observable.forkJoin(this.translate.get('BATCH.DELETED_FAILURE'),
|
||||
this.translate.get('PROJECT.FAILED_TO_DELETE_PROJECT')).subscribe(res => {
|
||||
Observable.forkJoin(this.translate.get("BATCH.DELETED_FAILURE"),
|
||||
this.translate.get("PROJECT.FAILED_TO_DELETE_PROJECT")).subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
|
||||
});
|
||||
} else {
|
||||
this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => {
|
||||
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res, false, true);
|
||||
});
|
||||
}
|
||||
@ -318,7 +309,7 @@ export class ListProjectComponent implements OnDestroy {
|
||||
let targetPageNumber: number = this.currentPage;
|
||||
|
||||
if (this.currentPage > totalPages) {
|
||||
targetPageNumber = totalPages;//Should == currentPage -1
|
||||
targetPageNumber = totalPages; // Should == currentPage -1
|
||||
}
|
||||
|
||||
let st: State = this.currentState;
|
||||
|
@ -12,23 +12,31 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-dg-action-bar>
|
||||
<button class="btn btn-sm btn-secondary" (click)="openAddMemberModal()" [disabled]="!hasProjectAdminRole">
|
||||
<span><clr-icon shape="plus" size="16"></clr-icon> {{'MEMBER.NEW_MEMBER' | translate }}</span>
|
||||
</button>
|
||||
<clr-dropdown id='member-action' [clrCloseMenuOnItemClick]="false" class="btn btn-sm btn-secondary" clrDropdownTrigger>
|
||||
<span>{{'MEMBER.ACTION' | translate}}<clr-icon shape="caret down"></clr-icon></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button class="btn btn-sm btn-secondary" (click)="changeRole(selectedRow, 1)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||
<button class="btn btn-sm btn-secondary" (click)="changeRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button class="btn btn-sm btn-secondary" (click)="changeRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'MEMBER.GUEST' | translate}}</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<button class="btn btn-sm btn-secondary" (click)="deleteMembers(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">
|
||||
<span><clr-icon shape="times" size="16"></clr-icon> {{'MEMBER.REMOVE' | translate}}</span>
|
||||
</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-datagrid [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="SelectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<div class="btn-group">
|
||||
<clr-button-group [clrMenuPosition]="'bottom-right'" >
|
||||
<clr-button class="btn btn-sm btn-secondary" (click)="openAddMemberModal()" [disabled]="!hasProjectAdminRole">{{'MEMBER.NEW_MEMBER' | translate }}</clr-button>
|
||||
<clr-button class="btn btn-sm btn-secondary" (click)="deleteMembers(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'MEMBER.DELETE' | translate}}</clr-button>
|
||||
<clr-button class="btn btn-sm btn-secondary" [clrInMenu]="true" (click)="changeRole(selectedRow, 1)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'MEMBER.PROJECT_ADMIN' | translate}}</clr-button>
|
||||
<clr-button class="btn btn-sm btn-secondary" [clrInMenu]="true" (click)="changeRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'MEMBER.DEVELOPER' | translate}}</clr-button>
|
||||
<clr-button class="btn btn-sm btn-secondary" [clrInMenu]="true" (click)="changeRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasProjectAdminRole)">{{'MEMBER.GUEST' | translate}}</clr-button>
|
||||
</clr-button-group>
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let m of members" [clrDgItem]="m">
|
||||
<clr-dg-cell>{{m.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{roleInfo[m.role_id] | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span *ngIf="ChangeRoleOngoing(m.username)" class="spinner spinner-inline"> Loading... </span>
|
||||
<span *ngIf="!ChangeRoleOngoing(m.username)">{{roleInfo[m.role_id] | translate}}</span>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'MEMBER.OF' | translate}} </span>
|
||||
|
@ -11,39 +11,37 @@
|
||||
// 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, ViewChild, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { Response } from '@angular/http';
|
||||
import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { Member } from './member';
|
||||
import { MemberService } from './member.service';
|
||||
import { SessionUser } from "../../shared/session-user";
|
||||
import { Member } from "./member";
|
||||
import { MemberService } from "./member.service";
|
||||
|
||||
import { AddMemberComponent } from './add-member/add-member.component';
|
||||
import { AddMemberComponent } from "./add-member/add-member.component";
|
||||
|
||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../../shared/shared.const';
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from "../../shared/shared.const";
|
||||
|
||||
import { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
|
||||
import { SessionService } from "../../shared/session.service";
|
||||
|
||||
import { RoleInfo } from '../../shared/shared.const';
|
||||
import { RoleInfo } from "../../shared/shared.const";
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import "rxjs/add/operator/switchMap";
|
||||
import "rxjs/add/operator/catch";
|
||||
import "rxjs/add/operator/map";
|
||||
import "rxjs/add/observable/throw";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
|
||||
import { Project } from '../../project/project';
|
||||
import { Project } from "../../project/project";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {BatchInfo, BathInfoChanges} from "../../shared/confirmation-dialog/confirmation-batch-message";
|
||||
|
||||
@Component({
|
||||
templateUrl: 'member.component.html',
|
||||
styleUrls: ['./member.component.css'],
|
||||
templateUrl: "member.component.html",
|
||||
styleUrls: ["./member.component.css"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MemberComponent implements OnInit, OnDestroy {
|
||||
@ -60,22 +58,22 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
searchMember: string;
|
||||
selectedRow: Member[] = []
|
||||
selectedRow: Member[] = [];
|
||||
roleNum: number;
|
||||
isDelete: boolean =false;
|
||||
isChangeRole: boolean =false;
|
||||
batchDelectionInfos: BatchInfo[] = [];
|
||||
isDelete = false;
|
||||
isChangeRole = false;
|
||||
batchActionInfos: BatchInfo[] = [];
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private memberService: MemberService,
|
||||
private memberService: MemberService,
|
||||
private translate: TranslateService,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private OperateDialogService: ConfirmationDialogService,
|
||||
private session: SessionService,
|
||||
private ref: ChangeDetectorRef) {
|
||||
|
||||
|
||||
this.delSub = OperateDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
@ -88,8 +86,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
});
|
||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
let hnd = setInterval(() => ref.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 1000);
|
||||
}
|
||||
|
||||
retrieve(projectId: number, username: string) {
|
||||
@ -99,11 +97,11 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.members = response;
|
||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 1000);
|
||||
},
|
||||
error => {
|
||||
this.router.navigate(['/harbor', 'projects']);
|
||||
this.router.navigate(["/harbor", "projects"]);
|
||||
this.messageHandlerService.handleError(error);
|
||||
});
|
||||
}
|
||||
@ -115,15 +113,15 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
//Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
//Get current user from registered resolver.
|
||||
// Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params["id"];
|
||||
// Get current user from registered resolver.
|
||||
this.currentUser = this.session.getCurrentUser();
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
if(resolverData) {
|
||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
if (resolverData) {
|
||||
this.hasProjectAdminRole = (<Project>resolverData["projectResolver"]).has_project_admin_role;
|
||||
}
|
||||
this.retrieve(this.projectId, '');
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
|
||||
openAddMemberModal() {
|
||||
@ -131,8 +129,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
addedMember($event: any) {
|
||||
this.searchMember = '';
|
||||
this.retrieve(this.projectId, '');
|
||||
this.searchMember = "";
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
|
||||
changeRole(m: Member[], roleId: number) {
|
||||
@ -141,24 +139,16 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
this.isChangeRole = true;
|
||||
this.roleNum = roleId;
|
||||
let nameArr: string[] = [];
|
||||
this.batchDelectionInfos = [];
|
||||
this.batchActionInfos = [];
|
||||
m.forEach(data => {
|
||||
nameArr.push(data.username);
|
||||
let initBatchMessage = new BatchInfo();
|
||||
initBatchMessage.name = data.username;
|
||||
this.batchDelectionInfos.push(initBatchMessage);
|
||||
this.batchActionInfos.push(initBatchMessage);
|
||||
});
|
||||
this.OperateDialogService.addBatchInfoList(this.batchDelectionInfos);
|
||||
this.OperateDialogService.addBatchInfoList(this.batchActionInfos);
|
||||
|
||||
let switchMessage = new ConfirmationMessage(
|
||||
'MEMBER.SWITCH_TITLE',
|
||||
'MEMBER.SWITCH_SUMMARY',
|
||||
nameArr.join(','),
|
||||
m,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.SWITCH_CANCEL
|
||||
);
|
||||
this.OperateDialogService.openComfirmDialog(switchMessage);
|
||||
this.changeOpe(m);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,64 +157,66 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
let promiseList: any[] = [];
|
||||
members.forEach(member => {
|
||||
if (member.user_id === this.currentUser.user_id) {
|
||||
let findedList = this.batchDelectionInfos.find(data => data.name === member.username);
|
||||
this.translate.get('BATCH.SWITCH_FAILURE').subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res, false, true);
|
||||
let foundMember = this.batchActionInfos.find(batchInfo => batchInfo.name === member.username);
|
||||
this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => {
|
||||
this.messageHandlerService.handleError(res + ": " + foundMember.name);
|
||||
foundMember = BathInfoChanges(foundMember, res, false, true);
|
||||
});
|
||||
}else {
|
||||
} else {
|
||||
promiseList.push(this.changeOperate(this.projectId, member.user_id, this.roleNum, member.username));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Promise.all(promiseList).then(num => {
|
||||
this.retrieve(this.projectId, '');
|
||||
this.retrieve(this.projectId, "");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
changeOperate(projectId: number, memberId: number, roleId: number, username: string) {
|
||||
let findedList = this.batchDelectionInfos.find(data => data.name === username);
|
||||
let foundMember = this.batchActionInfos.find(batchInfo => batchInfo.name === username);
|
||||
return this.memberService
|
||||
.changeMemberRole(projectId, memberId, roleId)
|
||||
.then(
|
||||
response => {
|
||||
this.translate.get('BATCH.SWITCH_SUCCESS').subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res);
|
||||
this.translate.get("BATCH.SWITCH_SUCCESS").subscribe(res => {
|
||||
foundMember = BathInfoChanges(foundMember, res);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.translate.get('BATCH.SWITCH_FAILURE').subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res, false, true);
|
||||
this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => {
|
||||
this.messageHandlerService.handleError(res + ": " + username);
|
||||
foundMember = BathInfoChanges(foundMember, res, false, true);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ChangeRoleOngoing(username: string) {
|
||||
if (this.batchActionInfos) {
|
||||
let memberActionInfo = this.batchActionInfos.find(batchInfo => batchInfo.name === username);
|
||||
return memberActionInfo && memberActionInfo.status === "pending";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
deleteMembers(m: Member[]) {
|
||||
this.isDelete = true;
|
||||
this.isChangeRole = false;
|
||||
let nameArr: string[] = [];
|
||||
this.batchDelectionInfos = [];
|
||||
this.batchActionInfos = [];
|
||||
if (m && m.length) {
|
||||
m.forEach(data => {
|
||||
nameArr.push(data.username);
|
||||
let initBatchMessage = new BatchInfo ();
|
||||
initBatchMessage.name = data.username;
|
||||
this.batchDelectionInfos.push(initBatchMessage);
|
||||
this.batchActionInfos.push(initBatchMessage);
|
||||
});
|
||||
this.OperateDialogService.addBatchInfoList(this.batchDelectionInfos);
|
||||
this.OperateDialogService.addBatchInfoList(this.batchActionInfos);
|
||||
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
'MEMBER.DELETION_TITLE',
|
||||
'MEMBER.DELETION_SUMMARY',
|
||||
nameArr.join(','),
|
||||
m,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.OperateDialogService.openComfirmDialog(deletionMessage);
|
||||
this.deleteMem(m);
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,8 +225,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
let promiseLists: any[] = [];
|
||||
members.forEach(member => {
|
||||
if (member.user_id === this.currentUser.user_id) {
|
||||
let findedList = this.batchDelectionInfos.find(data => data.name === member.username);
|
||||
this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => {
|
||||
let findedList = this.batchActionInfos.find(data => data.name === member.username);
|
||||
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res, false, true);
|
||||
});
|
||||
}else {
|
||||
@ -245,23 +237,23 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
|
||||
Promise.all(promiseLists).then(item => {
|
||||
this.selectedRow = [];
|
||||
this.retrieve(this.projectId, '');
|
||||
this.retrieve(this.projectId, "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
delOperate(projectId: number, memberId: number, username: string) {
|
||||
let findedList = this.batchDelectionInfos.find(data => data.name === username);
|
||||
let findedList = this.batchActionInfos.find(data => data.name === username);
|
||||
return this.memberService
|
||||
.deleteMember(projectId, memberId)
|
||||
.then(
|
||||
response => {
|
||||
this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => {
|
||||
this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => {
|
||||
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res, false, true);
|
||||
});
|
||||
}
|
||||
@ -269,7 +261,7 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
SelectedChange(): void {
|
||||
//this.forceRefreshView(5000);
|
||||
// this.forceRefreshView(5000);
|
||||
}
|
||||
|
||||
doSearch(searchMember: string) {
|
||||
@ -278,6 +270,6 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.retrieve(this.projectId, '');
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
}
|
@ -10,10 +10,10 @@
|
||||
<div class="option-right">
|
||||
<div class="select" style="float: left; left:-6px; top:8px;">
|
||||
<select (change)="doFilterProjects()" [(ngModel)]="selecteType">
|
||||
<option value="0" [selected]="currentFilteredType === 0">{{projectTypes[0] | translate}}</option>
|
||||
<option value="1">{{projectTypes[1] | translate}}</option>
|
||||
<option value="2">{{projectTypes[2] | translate}}</option>
|
||||
</select>
|
||||
<option value="0" [selected]="currentFilteredType === 0">{{projectTypes[0] | translate}}</option>
|
||||
<option value="1">{{projectTypes[1] | translate}}</option>
|
||||
<option value="2">{{projectTypes[2] | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProjects($event)" [currentValue]="projectName"></hbr-filter>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
|
@ -10,10 +10,10 @@ export class BatchInfo {
|
||||
errorState: boolean;
|
||||
errorInfo: string;
|
||||
constructor() {
|
||||
this.status = 'pending';
|
||||
this.status = "pending";
|
||||
this.loading = false;
|
||||
this.errorState = false;
|
||||
this.errorInfo = '';
|
||||
this.errorInfo = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ export class ConfirmationDialogComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
operate(): void {
|
||||
if(!this.message){//Inproper condition
|
||||
if (!this.message) {// Improper condition
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
@ -11,22 +11,24 @@
|
||||
// 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, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Component, Input, OnInit, OnDestroy } from "@angular/core";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
|
||||
import { StatisticsService } from './statistics.service';
|
||||
import { Statistics } from './statistics';
|
||||
import { StatisticsService } from "./statistics.service";
|
||||
import { Statistics } from "./statistics";
|
||||
|
||||
import { SessionService } from '../session.service';
|
||||
import { Volumes } from './volumes';
|
||||
import { SessionService } from "../session.service";
|
||||
import { Volumes } from "./volumes";
|
||||
|
||||
import { MessageHandlerService } from "../message-handler/message-handler.service";
|
||||
import { StatisticHandler } from "./statistic-handler.service";
|
||||
import { AppConfigService } from "./../../app-config.service";
|
||||
|
||||
import { MessageHandlerService } from '../message-handler/message-handler.service';
|
||||
import { StatisticHandler } from './statistic-handler.service';
|
||||
|
||||
@Component({
|
||||
selector: 'statistics-panel',
|
||||
selector: "statistics-panel",
|
||||
templateUrl: "statistics-panel.component.html",
|
||||
styleUrls: ['statistics.component.css'],
|
||||
styleUrls: ["statistics.component.css"],
|
||||
providers: [StatisticsService]
|
||||
})
|
||||
|
||||
@ -36,16 +38,17 @@ export class StatisticsPanelComponent implements OnInit, OnDestroy {
|
||||
volumesInfo: Volumes = new Volumes();
|
||||
refreshSub: Subscription;
|
||||
small: number;
|
||||
|
||||
|
||||
constructor(
|
||||
private statistics: StatisticsService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private session: SessionService,
|
||||
private appConfigService: AppConfigService,
|
||||
private statisticHandler: StatisticHandler) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
//Refresh
|
||||
// Refresh
|
||||
this.refreshSub = this.statisticHandler.refreshChan$.subscribe(clear => {
|
||||
this.getStatistics();
|
||||
});
|
||||
@ -95,7 +98,8 @@ export class StatisticsPanelComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public get isValidStorage(): boolean {
|
||||
return this.volumesInfo.storage.total != 0;
|
||||
return this.volumesInfo.storage.total !== 0 &&
|
||||
this.appConfigService.getConfig().registry_storage_provider_name === "filesystem";
|
||||
}
|
||||
|
||||
getGBFromBytes(bytes: number): number {
|
||||
|
@ -11,11 +11,9 @@
|
||||
<div>
|
||||
<clr-datagrid (clrDgRefresh)="load($event)" [clrDgLoading]="inProgress" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="SelectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addNewUser()">{{'USER.ADD_ACTION' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!ifSameRole" (click)="changeAdminRole()" >{{ISADMNISTRATOR | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteUsers(selectedRow)" [disabled]="!selectedRow.length">{{'USER.DEL_ACTION' | translate}}</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addNewUser()"><clr-icon shape="plus" size="16"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" id="set-admin" [disabled]="!ifSameRole" (click)="changeAdminRole()" ><clr-icon shape="wrench" size="16"></clr-icon> {{ISADMNISTRATOR | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteUsers(selectedRow)" [disabled]="!selectedRow.length"><clr-icon shape="times" size="16"></clr-icon> {{'USER.DEL_ACTION' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column>{{'USER.COLUMN_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'USER.COLUMN_ADMIN' | translate}}</clr-dg-column>
|
||||
|
@ -119,8 +119,8 @@
|
||||
},
|
||||
"USER": {
|
||||
"ADD_ACTION": "New User",
|
||||
"ENABLE_ADMIN_ACTION": "Set as Administrator",
|
||||
"DISABLE_ADMIN_ACTION": "Revoke Administrator",
|
||||
"ENABLE_ADMIN_ACTION": "SET AS ADMIN",
|
||||
"DISABLE_ADMIN_ACTION": "REVOKE ADMIN",
|
||||
"DEL_ACTION": "Delete",
|
||||
"FILTER_PLACEHOLDER": "Filter users",
|
||||
"COLUMN_NAME": "Name",
|
||||
@ -219,7 +219,9 @@
|
||||
"SWITCHED_SUCCESS": "Switched member role successfully.",
|
||||
"OF": "of",
|
||||
"SWITCH_TITLE": "Confirm project members switch",
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?"
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?",
|
||||
"ACTION": "SET ROLE",
|
||||
"REMOVE": "REMOVE"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
|
@ -219,7 +219,9 @@
|
||||
"SWITCHED_SUCCESS": "Rol del miembro cambiado satisfactoriamente.",
|
||||
"OF": "of",
|
||||
"SWITCH_TITLE": "Confirm project members switch",
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?"
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?",
|
||||
"ACTION": "SET ROLE",
|
||||
"REMOVE": "REMOVE"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Nombre de usuario",
|
||||
|
@ -219,7 +219,9 @@
|
||||
"SWITCHED_SUCCESS": "切换角色成功。",
|
||||
"OF": "共计",
|
||||
"SWITCH_TITLE": "切换项目成员确认",
|
||||
"SWITCH_SUMMARY": "你确认切换项目成员 {{param}}??"
|
||||
"SWITCH_SUMMARY": "你确认切换项目成员 {{param}}??",
|
||||
"ACTION": "设置角色",
|
||||
"REMOVE": "移除成员"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "用户名",
|
||||
|
@ -28,7 +28,7 @@ Assign User Admin
|
||||
#select checkbox
|
||||
Click Element //clr-dg-row[contains(.,"${user}")]//label
|
||||
#click assign admin
|
||||
Click Element //button[contains(.,'Set as')]
|
||||
Click Element //*[@id="set-admin"]
|
||||
Sleep 1
|
||||
|
||||
Switch to User Tag
|
||||
|
@ -67,13 +67,8 @@ Change Project Member Role
|
||||
Click Element xpath=//project-detail//clr-dg-row[contains(.,'${user}')]//label
|
||||
Sleep 1
|
||||
#change role
|
||||
Click Element //button[@class='btn dropdown-toggle']
|
||||
Click Element //*[@id="member-action"]
|
||||
Click Element //button[contains(.,'${role}')]
|
||||
sleep 1
|
||||
Click Element xpath=//clr-modal//button[contains(.,'SWITCH')]
|
||||
sleep 1
|
||||
Click Element xpath=//clr-modal//button[contains(.,'CLOSE')]
|
||||
|
||||
Sleep 2
|
||||
Wait Until Page Contains ${role}
|
||||
|
||||
@ -112,10 +107,7 @@ Delete Project Member
|
||||
[arguments] ${member}
|
||||
Click Element xpath=//clr-dg-row[contains(.,'${member}')]//input/../label
|
||||
Click Element xpath=${project_member_delete_button_xpath}
|
||||
Sleep 1
|
||||
Click Element xpath=${project_member_delete_confirmation_xpath}
|
||||
Sleep 1
|
||||
Click Element xpath=//button[contains(.,'CLOSE')]
|
||||
Sleep 2
|
||||
|
||||
User Should Be Owner Of Project
|
||||
[Arguments] ${user} ${pwd} ${project}
|
||||
|
@ -27,5 +27,4 @@ ${project_member_add_confirmation_ok_xpath} //project-detail//add-member//butto
|
||||
${project_member_search_button_xpath2} //button[contains(.,'New')]
|
||||
${project_member_add_button_xpath2} //project-detail//add-member//button[2]
|
||||
${project_member_guest_radio_checkbox} //project-detail//form//input[@id='checkrads_guest']
|
||||
${project_member_delete_button_xpath} //button[contains(.,"Delete")]
|
||||
${project_member_delete_confirmation_xpath} //confiramtion-dialog//button[2]
|
||||
${project_member_delete_button_xpath} //button[contains(.,"Delete")]
|
@ -82,8 +82,6 @@ Make Project Private
|
||||
Go Into Project ${project name}
|
||||
Sleep 1
|
||||
Click Element xpath=//project-detail//a[contains(.,'Configuration')]
|
||||
#Click element xpath=//project//list-project//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow
|
||||
#Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Private")]
|
||||
Checkbox Should Be Selected xpath=//input[@name='public']
|
||||
Click Element //clr-checkbox[@name='public']//label
|
||||
Click Element //button[contains(.,'SAVE')]
|
||||
@ -93,15 +91,12 @@ Make Project Public
|
||||
Go Into Project ${project name}
|
||||
Sleep 1
|
||||
Click Element xpath=//project-detail//a[contains(.,'Configuration')]
|
||||
#Click element xpath=//project//list-project//clr-dg-row-master[contains(.,'${projectname}')]//clr-dg-action-overflow
|
||||
#Click element xpath=//project//list-project//clr-dg-action-overflow//button[contains(.,"Make Public")]
|
||||
Checkbox Should Not Be Selected xpath=//input[@name='public']
|
||||
Click Element //clr-checkbox[@name='public']//label
|
||||
Click Element //button[contains(.,'SAVE')]
|
||||
|
||||
Delete Repo
|
||||
[Arguments] ${projectname}
|
||||
#Click Element xpath=//project-detail//clr-dg-row-master[contains(.,"${projectname}")]//clr-dg-action-overflow
|
||||
Click Element xpath=//clr-dg-row[contains(.,"${projectname}")]//clr-checkbox//label
|
||||
Sleep 1
|
||||
Click Element xpath=//button[contains(.,"Delete")]
|
||||
@ -113,16 +108,10 @@ Delete Repo
|
||||
Delete Project
|
||||
[Arguments] ${projectname}
|
||||
Sleep 1
|
||||
#Click Element //list-project//clr-dg-row-master[contains(.,'${projname}')]//clr-dg-action-overflow
|
||||
#Click Element //list-project//clr-dg-row-master[contains(.,'${projname}')]//clr-dg-action-overflow//button[contains(.,'Delete')]
|
||||
#click delete button to confirm
|
||||
Click Element xpath=//clr-dg-row[contains(.,"${projectname}")]//clr-checkbox//label
|
||||
Sleep 1
|
||||
Click Element xpath=//button[contains(.,"Delete")]
|
||||
Sleep 1
|
||||
Click Element xpath=//clr-modal//button[2]
|
||||
Sleep 1
|
||||
Click Element xpath=//button[contains(.,"CLOSE")]
|
||||
Sleep 2
|
||||
|
||||
Project Should Not Be Deleted
|
||||
[Arguments] ${projname}
|
||||
@ -205,8 +194,6 @@ Expand Repo
|
||||
|
||||
Scan Repo
|
||||
[Arguments] ${tagname}
|
||||
#Click Element //hbr-tag//clr-dg-row-master[contains(.,'${tagname}')]//clr-dg-action-overflow
|
||||
#Click Element //hbr-tag//clr-dg-row-master[contains(.,'${tagname}')]//clr-dg-action-overflow//button[contains(.,'Scan')]
|
||||
#select one tag
|
||||
Click Element //clr-dg-row[contains(.,"${tagname}")]//label
|
||||
Click Element //button[contains(.,'Scan')]
|
||||
|
@ -52,5 +52,13 @@ Multi-delete Object
|
||||
Click Element //clr-modal//button[contains(.,'DELETE')]
|
||||
Sleep 3
|
||||
|
||||
Multi-delete Object Without Confirmation
|
||||
[Arguments] @{obj}
|
||||
:For ${obj} in @{obj}
|
||||
\ Click Element //clr-dg-row[contains(.,'${obj}')]//label
|
||||
Sleep 1
|
||||
Click Element //button[contains(.,'Delete')]
|
||||
Sleep 3
|
||||
|
||||
Select All On Current Page Object
|
||||
Click Element //div[@class='datagrid-head']//label
|
||||
|
@ -149,10 +149,10 @@ Test Case - Project Level Policy Public
|
||||
Goto Project Config
|
||||
Click Project Public
|
||||
Save Project Config
|
||||
#verify
|
||||
# Verify
|
||||
Public Should Be Selected
|
||||
Back To Projects
|
||||
#project${d} default should be private
|
||||
# Project${d} default should be private
|
||||
Project Should Be Public project${d}
|
||||
Close Browser
|
||||
|
||||
@ -166,13 +166,13 @@ Test Case - Project Level Policy Content Trust
|
||||
Goto Project Config
|
||||
Click Content Trust
|
||||
Save Project Config
|
||||
#verify
|
||||
# Verify
|
||||
Content Trust Should Be Selected
|
||||
Cannot Pull Unsigned Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} hello-world:latest
|
||||
Close Browser
|
||||
|
||||
Test Case - Edit Project Creation
|
||||
# create normal user and login
|
||||
# Create normal user and login
|
||||
Init Chrome Driver
|
||||
${d}= Get Current Date result_format=%m%s
|
||||
Create An New User url=${HARBOR_URL} username=tester${d} email=tester${d}@vmware.com realname=harbortest newPassword=Test1@34 comment=harbortest
|
||||
@ -206,7 +206,7 @@ Test Case - Edit Self-Registration
|
||||
Self Reg Should Be Disabled
|
||||
Sleep 1
|
||||
|
||||
#restore setting
|
||||
# Restore setting
|
||||
Enable Self Reg
|
||||
Close Browser
|
||||
|
||||
@ -259,7 +259,7 @@ Test Case - Scan A Tag In The Repo
|
||||
Go Into Repo project${d}/hello-world
|
||||
Scan Repo latest
|
||||
Summary Chart Should Display latest
|
||||
#Edit Repo Info
|
||||
# Edit Repo Info
|
||||
Close Browser
|
||||
|
||||
Test Case - Manage Project Member
|
||||
@ -307,9 +307,8 @@ Test Case - Delete Multi Project
|
||||
Create An New Project projectb${d}
|
||||
Push Image ${ip} test${d} Test1@34 projecta${d} hello-world
|
||||
Filter Object project
|
||||
Multi-delete Object projecta projectb
|
||||
#verify delete project with image should not be deleted directly
|
||||
Partly Success
|
||||
Multi-delete Object Without Confirmation projecta projectb
|
||||
# Verify delete project with image should not be deleted directly
|
||||
Page Should Contain projecta${d}
|
||||
Page Should Not Contain projectb${d}
|
||||
Close Browser
|
||||
@ -327,11 +326,11 @@ Test Case - Delete Multi User
|
||||
Switch To User Tag
|
||||
Filter Object delete
|
||||
Multi-delete Object deletea deleteb deletec
|
||||
#assert delete
|
||||
#Delete Success comment temp wait for fixing
|
||||
# Assert delete
|
||||
# Delete Success comment temp wait for fixing
|
||||
Click Element //clr-modal//button[contains(.,'CLOSE')]
|
||||
Sleep 1
|
||||
#filter object delete
|
||||
# Filter object delete
|
||||
Page Should Not Contain deletea
|
||||
Close Browser
|
||||
|
||||
@ -345,7 +344,7 @@ Test Case - Delete Multi Repo
|
||||
Sleep 2
|
||||
Go Into Project project${d}
|
||||
Multi-delete Object hello-world busybox
|
||||
#verify
|
||||
# Verify
|
||||
Delete Success
|
||||
Close Browser
|
||||
|
||||
@ -360,7 +359,7 @@ Test Case - Delete Multi Tag
|
||||
Go Into Project project${d}
|
||||
Go Into Repo hello-world
|
||||
Multi-delete object latest v1
|
||||
#verify
|
||||
# Verify
|
||||
Delete Success
|
||||
Close Browser
|
||||
|
||||
@ -377,8 +376,7 @@ Test Case - Delete Multi Member
|
||||
Switch To Member
|
||||
Add Guest Member to project testa${d}
|
||||
Add Guest Member to project testb${d}
|
||||
Multi-delete Object testa${d} testb${d}
|
||||
Delete Success
|
||||
Multi-delete Object Without Confirmation testa${d} testb${d}
|
||||
Page Should Not Contain testa${d}
|
||||
Close Browser
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user