mirror of
https://github.com/goharbor/harbor.git
synced 2025-03-25 21:10:58 +01:00
Merge pull request #3934 from pengpengshui/batchDelection
Modify filters' position and modify member module switch members' ap…
This commit is contained in:
commit
7831a01f26
src/ui_ng
lib/src
confirmation-dialog
endpoint
inline-alert
list-replication-rule
replication
repository-listview
repository-listview.component.css.tsrepository-listview.component.html.tsrepository-listview.component.ts
repository-stackview
repository
tag
src
app
global-message
project
replication/replication-rule/list-project-model
repository/tag-repository
shared/confirmation-dialog
user
i18n/lang
tests/resources/Harbor-Pages
@ -22,8 +22,9 @@ export const CONFIRMATION_DIALOG_STYLE: string = `
|
||||
padding: 20px; list-style-type: none;
|
||||
}
|
||||
.batchInfoUl li {line-height: 24px;border-bottom: 1px solid #e8e8e8;}
|
||||
.batchInfoUl li span:first-child {padding-right: 20px; width: 210px; display: inline-block; color:#666;}
|
||||
.batchInfoUl li span:last-child {width: 260px; display: inline-block; color:#666;}
|
||||
.batchInfoUl li span:first-child {padding-right: 20px; width: 240px; display: inline-block; color:#666;
|
||||
text-overflow: ellipsis; overflow: hidden; vertical-align: middle;}
|
||||
.batchInfoUl li span:last-child {width: 230px; display: inline-block; color:#666;}
|
||||
.batchInfoUl li span i {display: inline-block; line-height: 1.2em; font-size: 0.8em; color: #999;}
|
||||
.batchInfoUl li span a{cursor: pointer; text-decoration: underline;}
|
||||
`;
|
@ -12,4 +12,10 @@ export const ENDPOINT_STYLE: string = `
|
||||
.refresh-btn:hover {
|
||||
color: #007CBB;
|
||||
}
|
||||
.rightPos{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
height: 24px;}
|
||||
`;
|
@ -1,8 +1,8 @@
|
||||
export const ENDPOINT_TEMPLATE: string = `
|
||||
<div style="margin-top: -24px;">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between" style="height: 24px; float:right;">
|
||||
<div>
|
||||
<div class="row" style="position:relative;">
|
||||
<div>
|
||||
<div class="row flex-items-xs-between rightPos">
|
||||
<div class="flex-items-xs-middle option-right">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_TARGETS_PLACEHOLDER" | translate}}' (filter)="doSearchTargets($event)" [currentValue]="targetName"></hbr-filter>
|
||||
<span class="refresh-btn" (click)="refreshTargets()">
|
||||
@ -25,7 +25,7 @@ export const ENDPOINT_TEMPLATE: string = `
|
||||
<clr-dg-column [clrDgField]="'insecure'">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'DESTINATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
|
||||
<clr-dg-row *ngFor="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>
|
||||
|
@ -102,6 +102,7 @@ export class EndpointComponent implements OnInit, OnDestroy {
|
||||
|
||||
retrieve(): void {
|
||||
this.loading = true;
|
||||
this.selectedRow = [];
|
||||
toPromise<Endpoint[]>(this.endpointService
|
||||
.getEndpoints(this.targetName))
|
||||
.then(
|
||||
|
@ -7,4 +7,11 @@ export const INLINE_ALERT_STYLE: string = `
|
||||
padding: 0px !important;
|
||||
min-width: 30px !important;
|
||||
}
|
||||
.alert-item {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
:host >>> .alert-icon-wrapper{
|
||||
display: inline;
|
||||
}
|
||||
`;
|
@ -1,7 +1,7 @@
|
||||
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||
<div>
|
||||
<div style="padding-bottom: 15px;">
|
||||
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" (clrDgSingleSelectedChange)="selectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<clr-dg-action-bar style="height:24px;">
|
||||
<div class="btn-group" *ngIf="opereateAvailable">
|
||||
<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>
|
||||
|
@ -28,4 +28,4 @@ export const REPLICATION_STYLE: string = `
|
||||
margin-top: 5px;
|
||||
z-index: 100;
|
||||
height: 32px;
|
||||
}`;
|
||||
}`;
|
||||
|
@ -1,5 +1,5 @@
|
||||
export const REPLICATION_TEMPLATE: string = `
|
||||
<div class="row" style="position:relative">
|
||||
<div class="row" style="position:relative;">
|
||||
<div>
|
||||
<div class="row flex-items-xs-between rightPos">
|
||||
<div class="flex-xs-middle option-right">
|
||||
@ -13,7 +13,6 @@ export const REPLICATION_TEMPLATE: string = `
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<hbr-list-replication-rule #listReplicationRule [readonly]="readonly" [projectId]="projectId" (replicateManual)=replicateManualRule($event) (selectOne)="selectOneRule($event)" (openNewRule)="openModal()" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
|
||||
</div>
|
||||
<br> <br>
|
||||
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between" style="height:60px;">
|
||||
<h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
|
||||
|
@ -1,2 +1,8 @@
|
||||
export const REPOSITORY_LISTVIEW_STYLE = `
|
||||
.rightPos{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
`;
|
@ -1,8 +1,8 @@
|
||||
export const REPOSITORY_LISTVIEW_TEMPLATE = `
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-right option-right">
|
||||
<div class="row" style="position:relative;">
|
||||
<div>
|
||||
<div class="row flex-items-xs-right option-right rightPos">
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-push-image-button style="display: inline-block;" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter>
|
||||
@ -21,7 +21,7 @@ export const REPOSITORY_LISTVIEW_TEMPLATE = `
|
||||
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let r of repositories" [clrDgItem]="r">
|
||||
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]="r">
|
||||
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
||||
|
@ -283,6 +283,7 @@ export class RepositoryListviewComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
clrLoad(state: State): void {
|
||||
this.selectedRow = [];
|
||||
//Keep it for future filtering and sorting
|
||||
this.currentState = state;
|
||||
|
||||
|
@ -31,10 +31,17 @@ export const REPOSITORY_STACKVIEW_STYLES: string = `
|
||||
:host >>> .datagrid .datagrid-placeholder-container {
|
||||
display: none;
|
||||
}
|
||||
:host >>> .datagrid-overlay-wrapper{margin-top:24px;}
|
||||
|
||||
.db-status-warning {
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
display: inline-block;
|
||||
}
|
||||
.rightPos{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
`;
|
||||
|
@ -1,8 +1,8 @@
|
||||
export const REPOSITORY_STACKVIEW_TEMPLATE: string = `
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" style="height: 32px;">
|
||||
<div class="row flex-items-xs-right option-right">
|
||||
<div class="row" style="margin-top: 20px; position: relative;">
|
||||
<div>
|
||||
<div class="row flex-items-xs-right rightPos">
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-push-image-button style="display: inline-block;" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter>
|
||||
|
@ -51,13 +51,12 @@ export class RepositoryComponent implements OnInit {
|
||||
@Input() repoName: string;
|
||||
@Input() hasSignedIn: boolean;
|
||||
@Input() hasProjectAdminRole: boolean;
|
||||
|
||||
@Input() withNotary: boolean;
|
||||
@Input() withClair: boolean;
|
||||
@Output() tagClickEvent = new EventEmitter<TagClickEvent>();
|
||||
@Output() backEvt: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
onGoing = false;
|
||||
withNotary = false;
|
||||
withClair = true;
|
||||
editing = false;
|
||||
inProgress = true;
|
||||
currentTabID = 'repo-image';
|
||||
|
@ -25,10 +25,6 @@ export const TAG_STYLE = `
|
||||
display: block; height: 0;
|
||||
}
|
||||
|
||||
:host >>> .datagrid {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:host >>> .datagrid-placeholder {
|
||||
display: none;
|
||||
}
|
||||
@ -48,5 +44,10 @@ export const TAG_STYLE = `
|
||||
:host >>> .datagrid clr-dg-column {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.rightPos{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
`;
|
@ -12,9 +12,9 @@ export const TAG_TEMPLATE = `
|
||||
<button type="button" class="btn btn-primary" [ngxClipboard]="digestTarget" (cbOnSuccess)="onSuccess($event)" (cbOnError)="onError($event)">{{'BUTTON.COPY' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-right option-right">
|
||||
<div class="row" style="position:relative;">
|
||||
<div>
|
||||
<div class="row flex-items-xs-right rightPos">
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filter)="doSearchTagNames($event)" [currentValue]="lastFilteredTagName"></hbr-filter>
|
||||
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
|
||||
@ -23,24 +23,24 @@ export const TAG_TEMPLATE = `
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
|
||||
<clr-dg-action-bar style="margin-bottom: 0;">
|
||||
<clr-dg-action-bar>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="canScanNow(selectedRow)" [disabled]="!(selectedRow.length==1)" (click)="scanNow(selectedRow)">{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!canScanNow(selectedRow)" [disabled]="!(selectedRow.length==1)" (click)="scanNow(selectedRow)">{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length==1)" (click)="showDigestId(selectedRow)" >{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasProjectAdminRole" (click)="deleteTags(selectedRow)" [disabled]="!selectedRow.length">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column style="min-width: 160px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 160px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 90px;" [clrDgField]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="min-width: 120px; max-width:220px;">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 140px;" *ngIf="withClair">{{'REPOSITORY.VULNERABILITY' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 80px;" *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 130px;">{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="min-width: 130px;">{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 160px;"[clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 80px;" [clrDgField]="'docker_version'" *ngIf="!withClair">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'TGA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-cell class="truncated" style="min-width: 160px;" [ngSwitch]="withClair">
|
||||
<clr-dg-row *ngFor="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-cell class="truncated" style="width: 160px;" [ngSwitch]="withClair">
|
||||
<a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)" title="{{t.name}}">{{t.name}}</a>
|
||||
<span *ngSwitchDefault>{{t.name}}</span>
|
||||
</clr-dg-cell>
|
||||
@ -59,7 +59,7 @@ export const TAG_TEMPLATE = `
|
||||
<span class="tooltip-content">{{'REPOSITORY.NOTARY_IS_UNDETERMINED' | translate}}</span>
|
||||
</a>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" style="width: 130px;" title="{{t.author}}">{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" style="min-width: 130px;" title="{{t.author}}">{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 160px;">{{t.created | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.docker_version}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
|
@ -152,6 +152,7 @@ export class TagComponent implements OnInit {
|
||||
}
|
||||
|
||||
clrLoad(state: State): void {
|
||||
this.selectedRow = [];
|
||||
// Keep it for future filtering and sorting
|
||||
this.currentState = state;
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.10.17",
|
||||
"clarity-ui": "^0.10.17",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.6.9",
|
||||
"harbor-ui": "0.6.13",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -4,4 +4,11 @@
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
.alert-item {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
:host >>> .alert-icon-wrapper{
|
||||
display: inline;
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
<clr-dg-column *ngIf="showRoleInfo" [clrDgSortBy]="roleComparator">{{'PROJECT.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="repoCountComparator">{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="timeComparator">{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{ (p.metadata.public === 'true' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="showRoleInfo">{{roleInfo[p.current_user_role_id] | translate}}</clr-dg-cell>
|
||||
|
@ -141,6 +141,8 @@ export class ListProjectComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
clrLoad(state: State) {
|
||||
this.selectedRow = [];
|
||||
|
||||
//Keep state for future filtering and sorting
|
||||
this.currentState = state;
|
||||
|
||||
|
@ -38,6 +38,8 @@ import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import {User} from "../../../user/user";
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {Project} from "../../project";
|
||||
|
||||
@Component({
|
||||
selector: 'add-member',
|
||||
@ -79,54 +81,63 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
private userService: UserService,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private translateService: TranslateService,
|
||||
private route: ActivatedRoute,
|
||||
private ref: ChangeDetectorRef) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
let hasProjectAdminRole: boolean;
|
||||
if (resolverData) {
|
||||
hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
}
|
||||
if (hasProjectAdminRole) {
|
||||
this.userService.getUsers()
|
||||
.then(users => {
|
||||
this.userLists = users;
|
||||
});
|
||||
|
||||
this.nameChecker
|
||||
.debounceTime(500)
|
||||
.distinctUntilChanged()
|
||||
.subscribe((name: string) => {
|
||||
let cont = this.currentForm.controls['member_name'];
|
||||
if (cont) {
|
||||
this.isMemberNameValid = cont.valid;
|
||||
if (cont.valid) {
|
||||
this.checkOnGoing = true;
|
||||
this.memberService
|
||||
.listMembers(this.projectId, cont.value).toPromise()
|
||||
.then((members: Member[]) => {
|
||||
if (members.filter(m => { return m.username === cont.value }).length > 0) {
|
||||
this.isMemberNameValid = false;
|
||||
this.memberTooltip = 'MEMBER.USERNAME_ALREADY_EXISTS';
|
||||
}
|
||||
this.checkOnGoing = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
//username autocomplete
|
||||
if (this.userLists.length) {
|
||||
this.selectUserName = [];
|
||||
this.userLists.filter(data => {
|
||||
if (data.username.startsWith(cont.value)) {
|
||||
if (this.selectUserName.length < 10) {
|
||||
this.selectUserName.push(data.username);
|
||||
.debounceTime(500)
|
||||
.distinctUntilChanged()
|
||||
.subscribe((name: string) => {
|
||||
let cont = this.currentForm.controls['member_name'];
|
||||
if (cont) {
|
||||
this.isMemberNameValid = cont.valid;
|
||||
if (cont.valid) {
|
||||
this.checkOnGoing = true;
|
||||
this.memberService
|
||||
.listMembers(this.projectId, cont.value).toPromise()
|
||||
.then((members: Member[]) => {
|
||||
if (members.filter(m => { return m.username === cont.value }).length > 0) {
|
||||
this.isMemberNameValid = false;
|
||||
this.memberTooltip = 'MEMBER.USERNAME_ALREADY_EXISTS';
|
||||
}
|
||||
this.checkOnGoing = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
//username autocomplete
|
||||
if (this.userLists && this.userLists.length) {
|
||||
this.selectUserName = [];
|
||||
this.userLists.filter(data => {
|
||||
if (data.username.startsWith(cont.value)) {
|
||||
if (this.selectUserName.length < 10) {
|
||||
this.selectUserName.push(data.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 100);
|
||||
}, 1000);
|
||||
});
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 100);
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
}
|
||||
} else {
|
||||
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -16,4 +16,10 @@
|
||||
:host >>> .btn-group-overflow .dropdown-toggle {
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.rightPos{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" style="top: 8px;">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="row" style="margin-top: 24px;position: relative;">
|
||||
<div>
|
||||
<div class="row flex-items-xs-between rightPos">
|
||||
<div class="flex-xs-middle option-left" style="position: relative; top: 10px;">
|
||||
</div>
|
||||
<div class="flex-xs-middle option-right">
|
||||
@ -15,7 +15,7 @@
|
||||
<clr-datagrid [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="SelectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<div class="btn-group">
|
||||
<clr-button-group [clrMenuPosition]="'bottom-right'">
|
||||
<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>
|
||||
@ -26,7 +26,7 @@
|
||||
</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 *clrDgItems="let m of members" [clrDgItem]="m">
|
||||
<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-row>
|
||||
|
@ -60,7 +60,10 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
searchMember: string;
|
||||
selectedRow: Member[] = [];
|
||||
selectedRow: Member[] = []
|
||||
roleNum: number;
|
||||
isDelete: boolean =false;
|
||||
isChangeRole: boolean =false;
|
||||
batchDelectionInfos: BatchInfo[] = [];
|
||||
|
||||
constructor(
|
||||
@ -69,15 +72,20 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
private memberService: MemberService,
|
||||
private translate: TranslateService,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private OperateDialogService: ConfirmationDialogService,
|
||||
private session: SessionService,
|
||||
private ref: ChangeDetectorRef) {
|
||||
|
||||
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
this.delSub = OperateDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.PROJECT_MEMBER) {
|
||||
this.deleteMem(message.data);
|
||||
if (this.isDelete) {
|
||||
this.deleteMem(message.data);
|
||||
}
|
||||
if (this.isChangeRole) {
|
||||
this.changeOpe(message.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
||||
@ -85,6 +93,7 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
retrieve(projectId: number, username: string) {
|
||||
this.selectedRow = [];
|
||||
this.memberService
|
||||
.listMembers(projectId, username)
|
||||
.subscribe(
|
||||
@ -127,27 +136,75 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
changeRole(m: Member[], roleId: number) {
|
||||
if (m) {
|
||||
let promiseList: any[] = [];
|
||||
if (m && m.length) {
|
||||
this.isDelete = false;
|
||||
this.isChangeRole = true;
|
||||
this.roleNum = roleId;
|
||||
let nameArr: string[] = [];
|
||||
this.batchDelectionInfos = [];
|
||||
m.forEach(data => {
|
||||
if (!(data.user_id === this.currentUser.user_id || !this.hasProjectAdminRole)) {
|
||||
promiseList.push(this.memberService.changeMemberRole(this.projectId, data.user_id, roleId));
|
||||
}
|
||||
})
|
||||
Promise.all(promiseList).then(num => {
|
||||
if (num.length === promiseList.length) {
|
||||
this.messageHandlerService.showSuccess('MEMBER.SWITCHED_SUCCESS');
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.messageHandlerService.handleError(error);
|
||||
}
|
||||
nameArr.push(data.username);
|
||||
let initBatchMessage = new BatchInfo();
|
||||
initBatchMessage.name = data.username;
|
||||
this.batchDelectionInfos.push(initBatchMessage);
|
||||
});
|
||||
this.OperateDialogService.addBatchInfoList(this.batchDelectionInfos);
|
||||
|
||||
let switchMessage = new ConfirmationMessage(
|
||||
'MEMBER.SWITCH_TITLE',
|
||||
'MEMBER.SWITCH_SUMMARY',
|
||||
nameArr.join(','),
|
||||
m,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
}
|
||||
this.OperateDialogService.openComfirmDialog(switchMessage);
|
||||
}
|
||||
}
|
||||
|
||||
changeOpe(members: Member[]) {
|
||||
if (members && members.length) {
|
||||
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);
|
||||
});
|
||||
}else {
|
||||
promiseList.push(this.changeOperate(this.projectId, member.user_id, this.roleNum, member.username));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Promise.all(promiseList).then(num => {
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
changeOperate(projectId: number, memberId: number, roleId: number, username: string) {
|
||||
let findedList = this.batchDelectionInfos.find(data => data.name === username);
|
||||
return this.memberService
|
||||
.changeMemberRole(projectId, memberId, roleId)
|
||||
.then(
|
||||
response => {
|
||||
this.translate.get('BATCH.SWITCH_SUCCESS').subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.translate.get('BATCH.SWITCH_FAILURE').subscribe(res => {
|
||||
findedList = BathInfoChanges(findedList, res, false, true);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
deleteMembers(m: Member[]) {
|
||||
this.isDelete = true;
|
||||
this.isChangeRole = false;
|
||||
let nameArr: string[] = [];
|
||||
this.batchDelectionInfos = [];
|
||||
if (m && m.length) {
|
||||
@ -157,17 +214,17 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
initBatchMessage.name = data.username;
|
||||
this.batchDelectionInfos.push(initBatchMessage);
|
||||
});
|
||||
this.deletionDialogService.addBatchInfoList(this.batchDelectionInfos);
|
||||
this.OperateDialogService.addBatchInfoList(this.batchDelectionInfos);
|
||||
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
'PROJECT.DELETION_TITLE',
|
||||
'PROJECT.DELETION_SUMMARY',
|
||||
'MEMBER.DELETION_TITLE',
|
||||
'MEMBER.DELETION_SUMMARY',
|
||||
nameArr.join(','),
|
||||
m,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
this.OperateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,5 +18,5 @@
|
||||
color: #007CBB;
|
||||
}
|
||||
.rightPos {
|
||||
position: absolute; right: 20px; margin-top: 16px; height:32px;
|
||||
position: absolute; right: 20px; margin-top: 5px; height:32px; z-index: 100;
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedProject">
|
||||
<clr-dg-row *clrDgItems="let project of projects; let i = index" [clrDgItem]="project">
|
||||
<clr-dg-row *ngFor="let project of projects; let i = index" [clrDgItem]="project">
|
||||
<clr-dg-cell>{{project.name}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
|
@ -87,6 +87,7 @@ export class ListProjectModelComponent {
|
||||
|
||||
|
||||
clrLoad(state: State) {
|
||||
this.selectedProject = null;
|
||||
//Keep state for future filtering and sorting
|
||||
this.currentState = state;
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
<div>
|
||||
<hbr-repository (tagClickEvent)="watchTagClickEvt($event)" (backEvt)="goBack($event)" [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId"></hbr-repository>
|
||||
<hbr-repository (tagClickEvent)="watchTagClickEvt($event)" (backEvt)="goBack($event)" [repoName]="repoName" [withClair]="withClair" [withNotary]="withNotary" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId"></hbr-repository>
|
||||
</div>
|
@ -21,7 +21,8 @@
|
||||
padding: 20px; list-style-type: none;
|
||||
}
|
||||
.batchInfoUl li {line-height: 24px;border-bottom: 1px solid #e8e8e8;}
|
||||
.batchInfoUl li span:first-child {padding-right: 20px; width: 210px; display: inline-block; color:#666;}
|
||||
.batchInfoUl li span:last-child {width: 260px; display: inline-block; color:#666;}
|
||||
.batchInfoUl li span:first-child {padding-right: 20px; width: 240px; display: inline-block; color:#666;
|
||||
text-overflow: ellipsis; overflow: hidden; vertical-align: middle;}
|
||||
.batchInfoUl li span:last-child {width: 230px; display: inline-block; color:#666;}
|
||||
.batchInfoUl li span i {display: inline-block; line-height: 1.2em; font-size: 0.8em; color: #999;}
|
||||
.batchInfoUl li span a{cursor: pointer; text-decoration: underline;}
|
||||
|
@ -29,7 +29,7 @@
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="2">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-danger" (click)="delete()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
|
||||
<button type="button" class="btn btn-danger" (click)="delete()" [hidden]="isDelete">{{isSwitch? 'BUTTON.SWITCH':'BUTTON.DELETE' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="3">
|
||||
|
@ -45,6 +45,13 @@ export class ConfirmationDialogComponent implements OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
get isSwitch(): boolean {
|
||||
if (this.dialogTitle && (this.dialogTitle.includes('SWITCH') || this.dialogTitle.includes('switch'))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
colorChange(list: BatchInfo) {
|
||||
if (!list.loading && !list.errorState) {
|
||||
return 'green';
|
||||
|
@ -21,7 +21,7 @@
|
||||
<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 *clrDgItems="let user of users" [clrDgItem]="user">
|
||||
<clr-dg-row *ngFor="let user of users" [clrDgItem]="user">
|
||||
<clr-dg-cell>{{user.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{isSystemAdmin(user)}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{user.email}}</clr-dg-cell>
|
||||
|
@ -317,6 +317,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
|
||||
//Data loading
|
||||
load(state: any): void {
|
||||
this.selectedRow = [];
|
||||
if (state && state.page) {
|
||||
if (this.originalUsers) {
|
||||
this.originalUsers.then(users => {
|
||||
|
@ -33,11 +33,14 @@
|
||||
"NO": "NO",
|
||||
"NEGATIVE": "NEGATIVE",
|
||||
"COPY": "COPY",
|
||||
"EDIT": "EDIT"
|
||||
"EDIT": "EDIT",
|
||||
"SWITCH": "SWITCH"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Deleted successfully",
|
||||
"DELETED_FAILURE": "Deleted failed"
|
||||
"DELETED_FAILURE": "Deleted failed",
|
||||
"SWITCH_SUCCESS": "Switch successfully",
|
||||
"SWITCH_FAILURE": "Switch failed"
|
||||
},
|
||||
"TOOLTIP": {
|
||||
"EMAIL": "Email should be a valid email address like name@example.com.",
|
||||
@ -203,12 +206,15 @@
|
||||
"USERNAME_ALREADY_EXISTS": "Username already exists.",
|
||||
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
||||
"FILTER_PLACEHOLDER": "Filter Members",
|
||||
"DELETION_TITLE": "Confirm project member deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete project member {{param}}?",
|
||||
"DELETION_TITLE": "Confirm project members deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete project members {{param}}?",
|
||||
"ADDED_SUCCESS": "Added member successfully.",
|
||||
"DELETED_SUCCESS": "Deleted members successfully.",
|
||||
"SWITCHED_SUCCESS": "Switched members role successfully.",
|
||||
"OF": "of"
|
||||
"DELETED_SUCCESS": "Deleted member successfully.",
|
||||
"SWITCHED_SUCCESS": "Switched member role successfully.",
|
||||
"OF": "of",
|
||||
"SWITCH_TITLE": "Confirm project members switch",
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?"
|
||||
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
|
@ -33,7 +33,14 @@
|
||||
"NO": "NO",
|
||||
"NEGATIVE": "NEGATIVO",
|
||||
"COPY": "COPY",
|
||||
"EDIT": "EDITAR"
|
||||
"EDIT": "EDITAR",
|
||||
"SWITCH": "SWITCH"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Deleted successfully",
|
||||
"DELETED_FAILURE": "Deleted failed",
|
||||
"SWITCH_SUCCESS": "Switch successfully",
|
||||
"SWITCH_FAILURE": "Switch failed"
|
||||
},
|
||||
"TOOLTIP": {
|
||||
"EMAIL": "El email debe ser una dirección válida como nombre@ejemplo.com.",
|
||||
@ -204,7 +211,9 @@
|
||||
"ADDED_SUCCESS": "Miembro añadido satisfactoriamente.",
|
||||
"DELETED_SUCCESS": "Miembro eliminado satisfactoriamente",
|
||||
"SWITCHED_SUCCESS": "Rol del miembro cambiado satisfactoriamente.",
|
||||
"OF": "of"
|
||||
"OF": "of",
|
||||
"SWITCH_TITLE": "Confirm project members switch",
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Nombre de usuario",
|
||||
|
@ -33,7 +33,14 @@
|
||||
"NO": "否",
|
||||
"NEGATIVE": "否",
|
||||
"COPY": "拷贝",
|
||||
"EDIT": "编辑"
|
||||
"EDIT": "编辑",
|
||||
"SWITCH": "切换"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "删除成功",
|
||||
"DELETED_FAILURE": "删除失败",
|
||||
"SWITCH_SUCCESS": "切换成功",
|
||||
"SWITCH_FAILURE": "切换失败"
|
||||
},
|
||||
"TOOLTIP": {
|
||||
"EMAIL": "请使用正确的邮箱地址,比如name@example.com。",
|
||||
@ -204,7 +211,9 @@
|
||||
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?",
|
||||
"ADDED_SUCCESS": "成功新增成员。",
|
||||
"DELETED_SUCCESS": "成功删除成员。",
|
||||
"SWITCHED_SUCCESS": "切换角色成功。"
|
||||
"SWITCHED_SUCCESS": "切换角色成功。",
|
||||
"SWITCH_TITLE": "切换项目成员确认",
|
||||
"SWITCH_SUMMARY": "你确认切换项目成员 {{param}}??"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "用户名",
|
||||
|
@ -69,7 +69,11 @@ Change Project Member Role
|
||||
#change role
|
||||
Click Element //button[@class='btn dropdown-toggle']
|
||||
Click Element //button[contains(.,'${role}')]
|
||||
#Click Element xpath=//project-detail//clr-dg-action-overflow//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}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user