mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
Add full permissions for the robot account (#19507)
1.Fixes #19353 Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
5c02fd807e
commit
b7116fff0f
@ -1,53 +1,22 @@
|
||||
<clr-datagrid [clrDgPreserveSelection]="true" [(clrDgSelected)]="selectedRow">
|
||||
<clr-dg-action-bar>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<button
|
||||
[disabled]="coverAll"
|
||||
class="btn btn-secondary btn-sm"
|
||||
clrDropdownTrigger>
|
||||
<clr-datagrid
|
||||
[clrDgPreserveSelection]="true"
|
||||
[(clrDgSelected)]="selectedRow"
|
||||
[clrDgLoading]="loadingData"
|
||||
class="datagrid-compact">
|
||||
<clr-dg-action-bar class="action-bar">
|
||||
<robot-permissions-panel
|
||||
[mode]="PermissionSelectPanelModes.DROPDOWN"
|
||||
[(permissionsModel)]="initialAccess"
|
||||
(permissionsModelChange)="resetAccess(initialAccess)"
|
||||
[candidatePermissions]="robotMetadata?.project">
|
||||
<button class="btn btn-secondary btn-sm m-0">
|
||||
{{ 'SYSTEM_ROBOT.RESET_PERMISSION' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
<clr-icon size="12" shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu
|
||||
[style.height.px]="230"
|
||||
clrPosition="bottom-left"
|
||||
*clrIfOpen>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-link btn-sm select-all-for-dropdown ml-20px"
|
||||
(click)="
|
||||
selectAllPermissionOrUnselectAll(defaultAccesses);
|
||||
resetAccess(defaultAccesses)
|
||||
">
|
||||
<span *ngIf="isSelectAll(defaultAccesses)">{{
|
||||
'SYSTEM_ROBOT.SELECT_ALL' | translate
|
||||
}}</span>
|
||||
<span *ngIf="!isSelectAll(defaultAccesses)">{{
|
||||
'SYSTEM_ROBOT.UNSELECT_ALL' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
clrDropdownItem
|
||||
*ngFor="let item of defaultAccesses"
|
||||
(click)="chooseDefaultAccess(item)">
|
||||
<clr-icon
|
||||
class="check"
|
||||
shape="check"
|
||||
[style.visibility]="
|
||||
item.checked ? 'visible' : 'hidden'
|
||||
"></clr-icon>
|
||||
<span
|
||||
>{{ i18nMap[item.action] | translate }}
|
||||
{{ i18nMap[item.resource] | translate }}</span
|
||||
>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</robot-permissions-panel>
|
||||
<button
|
||||
(click)="selectAllOrUnselectAll()"
|
||||
[disabled]="coverAll"
|
||||
class="btn btn-secondary btn-sm ml-1">
|
||||
class="btn btn-secondary btn-sm m-0 ml-1">
|
||||
<span *ngIf="showSelectAll">{{
|
||||
'SYSTEM_ROBOT.SELECT_ALL' | translate
|
||||
}}</span>
|
||||
@ -67,9 +36,7 @@
|
||||
<clr-dg-column>{{
|
||||
'SYSTEM_ROBOT.PERMISSION_COLUMN' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-row
|
||||
*clrDgItems="let p of projects; let projectIndex = index"
|
||||
[clrDgItem]="p">
|
||||
<clr-dg-row *clrDgItems="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-cell>
|
||||
<a href="javascript:void(0)" [routerLink]="getLink(p.project_id)">{{
|
||||
p.name
|
||||
@ -79,62 +46,17 @@
|
||||
p.creation_time | harborDatetime : 'short'
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="permissions">
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<button
|
||||
[disabled]="coverAll"
|
||||
class="btn btn-link"
|
||||
clrDropdownTrigger>
|
||||
{{ getPermissions(p.permissions[0].access) }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu
|
||||
[style.height.px]="140"
|
||||
clrPosition="bottom-left"
|
||||
*clrIfOpen>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-link btn-sm select-all-for-dropdown ml-20px"
|
||||
(click)="
|
||||
selectAllPermissionOrUnselectAll(
|
||||
p.permissions[0].access
|
||||
)
|
||||
">
|
||||
<span
|
||||
*ngIf="isSelectAll(p.permissions[0].access)"
|
||||
>{{
|
||||
'SYSTEM_ROBOT.SELECT_ALL' | translate
|
||||
}}</span
|
||||
>
|
||||
<span
|
||||
*ngIf="
|
||||
!isSelectAll(p.permissions[0].access)
|
||||
"
|
||||
>{{
|
||||
'SYSTEM_ROBOT.UNSELECT_ALL' | translate
|
||||
}}</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
clrDropdownItem
|
||||
*ngFor="let item of p.permissions[0].access"
|
||||
(click)="chooseAccess(item)">
|
||||
<clr-icon
|
||||
class="check"
|
||||
shape="check"
|
||||
[style.visibility]="
|
||||
item.checked ? 'visible' : 'hidden'
|
||||
"></clr-icon>
|
||||
<span
|
||||
>{{ i18nMap[item.action] | translate }}
|
||||
{{ i18nMap[item.resource] | translate }}</span
|
||||
>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
<robot-permissions-panel
|
||||
[usedInDatagrid]="true"
|
||||
[mode]="PermissionSelectPanelModes.DROPDOWN"
|
||||
[(permissionsModel)]="selectedProjectPermissionMap[p.name]"
|
||||
[candidatePermissions]="robotMetadata?.project">
|
||||
<button class="btn btn-link btn-sm m-0 p-0">
|
||||
{{ selectedProjectPermissionMap[p.name]?.length || 0 }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
</robot-permissions-panel>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
@ -142,7 +64,7 @@
|
||||
#pagination
|
||||
[(clrDgPage)]="currentPage"
|
||||
[clrDgPageSize]="pageSize">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[5, 15, 25]">{{
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[5, 10]">{{
|
||||
'PAGINATION.PAGE_SIZE' | translate
|
||||
}}</clr-dg-page-size>
|
||||
<span
|
||||
|
@ -1,10 +1,9 @@
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
color: green;
|
||||
.ml-20px {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.permissions {
|
||||
height: 16px;
|
||||
.action-bar {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@ -13,16 +12,12 @@
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
overflow-y: auto;
|
||||
:host::ng-deep.datagrid-spinner {
|
||||
top: 5rem !important;
|
||||
width: 62% !important;
|
||||
height: 32% !important;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
min-height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ml-20px {
|
||||
margin-left: 20px;
|
||||
:host::ng-deep.spinner {
|
||||
left: 14rem !important;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ListAllProjectsComponent } from './list-all-projects.component';
|
||||
import { clone } from '../../../../shared/units/utils';
|
||||
import { INITIAL_ACCESSES } from '../system-robot-util';
|
||||
import { Project } from '../../../../../../ng-swagger-gen/models/project';
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
|
||||
@ -30,9 +28,6 @@ describe('ListAllProjectsComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListAllProjectsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
component.cachedAllProjects = [project1, project2, project3];
|
||||
component.init(false);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@ -40,6 +35,7 @@ describe('ListAllProjectsComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should render list', async () => {
|
||||
component.projects = [project1, project2, project3];
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const rows = fixture.nativeElement.querySelectorAll('clr-dg-row');
|
||||
|
@ -1,86 +1,60 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||
import { Project } from '../../../../../../ng-swagger-gen/models/project';
|
||||
import { clone, CustomComparator } from '../../../../shared/units/utils';
|
||||
import { ClrDatagridComparatorInterface } from '@clr/angular';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
FrontAccess,
|
||||
FrontProjectForAdd,
|
||||
INITIAL_ACCESSES,
|
||||
PermissionsKinds,
|
||||
} from '../system-robot-util';
|
||||
import { FrontAccess } from '../system-robot-util';
|
||||
import { Access } from '../../../../../../ng-swagger-gen/models/access';
|
||||
import { forkJoin, Observable } from 'rxjs';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { ProjectService } from '../../../../../../ng-swagger-gen/services/project.service';
|
||||
import { PermissionSelectPanelModes } from '../../../../shared/components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
import { RobotPermission } from '../../../../../../ng-swagger-gen/models/robot-permission';
|
||||
import { Permissions } from '../../../../../../ng-swagger-gen/models/permissions';
|
||||
|
||||
const FIRST_PROJECTS_PAGE_SIZE: number = 100;
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-all-projects',
|
||||
templateUrl: './list-all-projects.component.html',
|
||||
styleUrls: ['./list-all-projects.component.scss'],
|
||||
})
|
||||
export class ListAllProjectsComponent {
|
||||
cachedAllProjects: Project[];
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
permissionsForAdd: RobotPermission[] = [];
|
||||
selectedRow: FrontProjectForAdd[] = [];
|
||||
export class ListAllProjectsComponent implements OnInit {
|
||||
selectedRow: Project[] = [];
|
||||
selectedRowForEdit: Project[] = [];
|
||||
timeComparator: ClrDatagridComparatorInterface<Project> =
|
||||
new CustomComparator<Project>('creation_time', 'date');
|
||||
projects: FrontProjectForAdd[] = [];
|
||||
projects: Project[] = [];
|
||||
pageSize: number = 5;
|
||||
currentPage: number = 1;
|
||||
defaultAccesses: FrontAccess[] = [];
|
||||
@Input()
|
||||
coverAll: boolean = false;
|
||||
showSelectAll: boolean = true;
|
||||
myNameFilterValue: string;
|
||||
constructor(private router: Router) {}
|
||||
|
||||
init(isEdit: boolean) {
|
||||
this.pageSize = 5;
|
||||
this.currentPage = 1;
|
||||
this.showSelectAll = true;
|
||||
this.myNameFilterValue = null;
|
||||
if (isEdit) {
|
||||
this.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
this.defaultAccesses.forEach(item => (item.checked = false));
|
||||
} else {
|
||||
this.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
}
|
||||
if (this.cachedAllProjects && this.cachedAllProjects.length) {
|
||||
this.projects = clone(this.cachedAllProjects);
|
||||
this.resetAccess(this.defaultAccesses);
|
||||
} else {
|
||||
this.projects = [];
|
||||
}
|
||||
@Input()
|
||||
robotMetadata: Permissions;
|
||||
|
||||
initialAccess: Access[] = [];
|
||||
selectedProjectPermissionMap: { [key: string]: Access[] } = {};
|
||||
selectedProjectPermissionMapForEdit: { [key: string]: Access[] } = {};
|
||||
loadingData: boolean = true;
|
||||
@Input()
|
||||
initDataForEdit: RobotPermission[];
|
||||
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private cdf: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadDataFromBackend();
|
||||
}
|
||||
resetAccess(accesses: FrontAccess[]) {
|
||||
if (this.projects && this.projects.length) {
|
||||
this.projects.forEach(item => {
|
||||
item.permissions = [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: item.name,
|
||||
access: clone(accesses),
|
||||
},
|
||||
];
|
||||
this.selectedProjectPermissionMap[item.name] = clone(accesses);
|
||||
});
|
||||
}
|
||||
}
|
||||
chooseAccess(access: FrontAccess) {
|
||||
access.checked = !access.checked;
|
||||
}
|
||||
chooseDefaultAccess(access: FrontAccess) {
|
||||
access.checked = !access.checked;
|
||||
this.resetAccess(this.defaultAccesses);
|
||||
}
|
||||
getPermissions(access: FrontAccess[]): number {
|
||||
let count: number = 0;
|
||||
access.forEach(item => {
|
||||
if (item.checked) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
getLink(proId: number): string {
|
||||
return `/harbor/projects/${proId}`;
|
||||
}
|
||||
@ -108,26 +82,87 @@ export class ListAllProjectsComponent {
|
||||
}
|
||||
this.showSelectAll = !this.showSelectAll;
|
||||
}
|
||||
isSelectAll(permissions: FrontAccess[]): boolean {
|
||||
if (permissions?.length) {
|
||||
return (
|
||||
permissions.filter(item => item.checked).length <
|
||||
permissions.length / 2
|
||||
);
|
||||
}
|
||||
return false;
|
||||
|
||||
loadDataFromBackend() {
|
||||
this.loadingData = true;
|
||||
this.projectService
|
||||
.listProjectsResponse({
|
||||
withDetail: false,
|
||||
page: 1,
|
||||
pageSize: FIRST_PROJECTS_PAGE_SIZE,
|
||||
})
|
||||
.subscribe({
|
||||
next: result => {
|
||||
// Get total count
|
||||
if (result.headers) {
|
||||
const xHeader: string =
|
||||
result.headers.get('X-Total-Count');
|
||||
const totalCount = parseInt(xHeader, 0);
|
||||
let arr = result.body || [];
|
||||
if (totalCount <= FIRST_PROJECTS_PAGE_SIZE) {
|
||||
// already gotten all projects
|
||||
this.projects = result.body;
|
||||
this.initDataForEditMode();
|
||||
this.loadingData = false;
|
||||
this.cdf.detectChanges();
|
||||
} else {
|
||||
// get all the projects in specified times
|
||||
const times: number = Math.ceil(
|
||||
totalCount / FIRST_PROJECTS_PAGE_SIZE
|
||||
);
|
||||
const observableList: Observable<Project[]>[] = [];
|
||||
for (let i = 2; i <= times; i++) {
|
||||
observableList.push(
|
||||
this.projectService.listProjects({
|
||||
withDetail: false,
|
||||
page: i,
|
||||
pageSize: FIRST_PROJECTS_PAGE_SIZE,
|
||||
})
|
||||
);
|
||||
}
|
||||
forkJoin(observableList)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loadingData = false;
|
||||
})
|
||||
)
|
||||
.subscribe(res => {
|
||||
if (res && res.length) {
|
||||
res.forEach(item => {
|
||||
arr = arr.concat(item);
|
||||
});
|
||||
this.projects = arr;
|
||||
this.initDataForEditMode();
|
||||
this.cdf.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
error: error => {
|
||||
this.loadingData = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
selectAllPermissionOrUnselectAll(permissions: FrontAccess[]) {
|
||||
if (permissions?.length) {
|
||||
if (this.isSelectAll(permissions)) {
|
||||
permissions.forEach(item => {
|
||||
item.checked = true;
|
||||
initDataForEditMode() {
|
||||
if (this.initDataForEdit?.length) {
|
||||
this.selectedRow = [];
|
||||
this.projects.forEach((pro, index) => {
|
||||
this.initDataForEdit.forEach(item => {
|
||||
if (pro.name === item.namespace) {
|
||||
item.access.forEach(acc => {
|
||||
this.selectedProjectPermissionMap[pro.name] =
|
||||
item.access;
|
||||
});
|
||||
this.selectedRow.push(pro);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
permissions.forEach(item => {
|
||||
item.checked = false;
|
||||
});
|
||||
}
|
||||
this.selectedProjectPermissionMapForEdit = clone(
|
||||
this.selectedProjectPermissionMap
|
||||
);
|
||||
this.selectedRowForEdit = clone(this.selectedRow);
|
||||
});
|
||||
}
|
||||
}
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
||||
|
@ -1,25 +1,42 @@
|
||||
<clr-modal
|
||||
clrModalSize="lg"
|
||||
[(clrModalOpen)]="addRobotOpened"
|
||||
[clrModalStaticBackdrop]="true"
|
||||
[clrModalClosable]="true">
|
||||
<h3 *ngIf="!isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.CREATE_ROBOT' | translate }}
|
||||
</h3>
|
||||
<h3 *ngIf="isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.EDIT_ROBOT' | translate }}
|
||||
</h3>
|
||||
<div class="modal-body">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<clr-wizard
|
||||
#wizard
|
||||
[(clrWizardOpen)]="addRobotOpened"
|
||||
(clrWizardOnCancel)="cancel()">
|
||||
<clr-wizard-title>
|
||||
<h3 *ngIf="!isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.CREATE_ROBOT' | translate }}
|
||||
</h3>
|
||||
<h3 *ngIf="isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.EDIT_ROBOT' | translate }}
|
||||
</h3>
|
||||
<p *ngIf="!isEditMode" class="mt-0">
|
||||
{{ 'SYSTEM_ROBOT.CREATE_ROBOT_SUMMARY' | translate }}
|
||||
</p>
|
||||
<p *ngIf="isEditMode" class="mt-0">
|
||||
{{ 'SYSTEM_ROBOT.EDIT_ROBOT_SUMMARY' | translate }}
|
||||
</p>
|
||||
<form #robotForm="ngForm" class="clr-form clr-form-horizontal mt-1">
|
||||
</p></clr-wizard-title
|
||||
>
|
||||
<clr-wizard-button [type]="'cancel'">{{
|
||||
'BUTTON.CANCEL' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-button [type]="'previous'">{{
|
||||
'ROBOT_ACCOUNT.BACK' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-button [type]="'next'">{{
|
||||
'ROBOT_ACCOUNT.NEXT' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-button [clrLoading]="saveBtnState" [type]="'finish'">{{
|
||||
'ROBOT_ACCOUNT.FINISH' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-page
|
||||
[clrWizardPageNextDisabled]="
|
||||
!robotForm.valid || checkNameOnGoing || isNameExisting
|
||||
">
|
||||
<ng-template clrPageTitle>{{
|
||||
'ROBOT_ACCOUNT.BASIC_INFO' | translate
|
||||
}}</ng-template>
|
||||
<form #robotForm="ngForm" class="clr-form clr-form-horizontal">
|
||||
<section class="form-block">
|
||||
<!-- name -->
|
||||
<div class="clr-form-control">
|
||||
<label for="name" class="clr-control-label required"
|
||||
>{{ 'P2P_PROVIDER.NAME' | translate }}
|
||||
@ -95,8 +112,17 @@
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- expiration -->
|
||||
<div class="clr-form-control">
|
||||
<clr-textarea-container class="mt-2">
|
||||
<label>{{ 'DISTRIBUTION.DESCRIPTION' | translate }}</label>
|
||||
<textarea
|
||||
class="input-width"
|
||||
clrTextarea
|
||||
type="text"
|
||||
id="description"
|
||||
name="description"
|
||||
[(ngModel)]="systemRobot.description"></textarea>
|
||||
</clr-textarea-container>
|
||||
<div class="clr-form-control mt-2">
|
||||
<label class="clr-control-label required"
|
||||
>{{ 'SYSTEM_ROBOT.EXPIRATION_TIME' | translate }}
|
||||
<clr-tooltip>
|
||||
@ -216,22 +242,41 @@
|
||||
</clr-control-helper>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 3. description -->
|
||||
<clr-textarea-container class="mt-description">
|
||||
<label>{{ 'DISTRIBUTION.DESCRIPTION' | translate }}</label>
|
||||
<textarea
|
||||
class="input-width"
|
||||
clrTextarea
|
||||
type="text"
|
||||
id="description"
|
||||
name="description"
|
||||
[(ngModel)]="systemRobot.description"></textarea>
|
||||
</clr-textarea-container>
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label mt-8px">{{
|
||||
</section>
|
||||
</form>
|
||||
</clr-wizard-page>
|
||||
<clr-wizard-page class="pb-0">
|
||||
<ng-template clrPageTitle>{{
|
||||
'ROBOT_ACCOUNT.SELECT_SYSTEM_PERMISSIONS' | translate
|
||||
}}</ng-template>
|
||||
<form class="clr-form clr-form-horizontal">
|
||||
<section class="form-block">
|
||||
<robot-permissions-panel
|
||||
[mode]="PermissionSelectPanelModes.NORMAL"
|
||||
[(permissionsModel)]="permissionForSystem.access"
|
||||
[candidatePermissions]="robotMetadata?.system">
|
||||
</robot-permissions-panel>
|
||||
</section>
|
||||
</form>
|
||||
</clr-wizard-page>
|
||||
|
||||
<clr-wizard-page
|
||||
class="pb-0"
|
||||
(clrWizardPageOnLoad)="clrWizardPageOnLoad()"
|
||||
(clrWizardPageOnCommit)="save()"
|
||||
[clrWizardPagePreventDefaultNext]="true"
|
||||
[clrWizardPageNextDisabled]="disabled()">
|
||||
<ng-template clrPageTitle>{{
|
||||
'ROBOT_ACCOUNT.SELECT_PROJECT_PERMISSIONS' | translate
|
||||
}}</ng-template>
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<form class="clr-form clr-form-horizontal pb-0 pt-0">
|
||||
<section class="form-block">
|
||||
<div class="clr-form-control mt-1">
|
||||
<label class="clr-control-label">{{
|
||||
'SYSTEM_ROBOT.COVER_ALL' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container padding-top-3 flex">
|
||||
<div class="clr-control-container">
|
||||
<clr-checkbox-wrapper>
|
||||
<input
|
||||
clrCheckbox
|
||||
@ -247,7 +292,7 @@
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrPosition="bottom-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
@ -258,102 +303,26 @@
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
<clr-dropdown
|
||||
[style.visibility]="coverAll ? 'visible' : 'hidden'"
|
||||
class="dropdown-per"
|
||||
[clrCloseMenuOnItemClick]="false">
|
||||
<button class="btn btn-link" clrDropdownTrigger>
|
||||
{{ getPermissions() }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu
|
||||
class="dropdown-menu"
|
||||
[style.height.px]="230"
|
||||
clrPosition="bottom-left"
|
||||
*clrIfOpen>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-link btn-sm select-all-for-dropdown ml-20px"
|
||||
(click)="
|
||||
selectAllOrUnselectAll(
|
||||
defaultAccesses
|
||||
)
|
||||
">
|
||||
<span
|
||||
*ngIf="isSelectAll(defaultAccesses)"
|
||||
>{{
|
||||
'SYSTEM_ROBOT.SELECT_ALL'
|
||||
| translate
|
||||
}}</span
|
||||
>
|
||||
<span
|
||||
*ngIf="
|
||||
!isSelectAll(defaultAccesses)
|
||||
"
|
||||
>{{
|
||||
'SYSTEM_ROBOT.UNSELECT_ALL'
|
||||
| translate
|
||||
}}</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
clrDropdownItem
|
||||
*ngFor="let item of defaultAccesses"
|
||||
(click)="chooseAccess(item)">
|
||||
<clr-icon
|
||||
class="check"
|
||||
shape="check"
|
||||
[style.visibility]="
|
||||
item.checked ? 'visible' : 'hidden'
|
||||
"></clr-icon>
|
||||
<span
|
||||
>{{ i18nMap[item.action] | translate }}
|
||||
{{
|
||||
i18nMap[item.resource] | translate
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control mt-0" *ngIf="coverAll">
|
||||
<robot-permissions-panel
|
||||
[mode]="PermissionSelectPanelModes.NORMAL"
|
||||
[(permissionsModel)]="permissionForCoverAll.access"
|
||||
[candidatePermissions]="robotMetadata?.project">
|
||||
</robot-permissions-panel>
|
||||
</div>
|
||||
<div
|
||||
class="clr-form-control mt-1"
|
||||
*ngIf="showPage3 && !coverAll">
|
||||
<app-list-all-projects
|
||||
[initDataForEdit]="
|
||||
isEditMode ? systemRobot.permissions : null
|
||||
"
|
||||
[robotMetadata]="robotMetadata"
|
||||
class="all-projects"></app-list-all-projects>
|
||||
</div>
|
||||
</section>
|
||||
<div
|
||||
class="clr-form-control"
|
||||
[class.clr-form-control-disabled]="coverAll">
|
||||
<app-list-all-projects
|
||||
[coverAll]="coverAll"
|
||||
[class.disabled]="coverAll"
|
||||
class="all-projects"></app-list-all-projects>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span
|
||||
class="message"
|
||||
[style.visibility]="coverAll ? 'visible' : 'hidden'"
|
||||
>{{ 'SYSTEM_ROBOT.COVER_ALL_SUMMARY' | translate }}</span
|
||||
>
|
||||
<span>
|
||||
<button
|
||||
(click)="cancel()"
|
||||
id="system-robot-cancel"
|
||||
type="button"
|
||||
class="btn btn-outline">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
[disabled]="disabled() || checkNameOnGoing || isNameExisting"
|
||||
[clrLoading]="saveBtnState"
|
||||
(click)="save()"
|
||||
id="system-robot-save"
|
||||
type="button"
|
||||
class="btn btn-primary">
|
||||
<span *ngIf="isEditMode">{{ 'BUTTON.SAVE' | translate }}</span>
|
||||
<span *ngIf="!isEditMode">{{ 'BUTTON.ADD' | translate }}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</clr-wizard-page>
|
||||
</clr-wizard>
|
||||
|
@ -14,11 +14,6 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.permission{
|
||||
padding-top: 0.1rem;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.padding-left-120{
|
||||
padding-left: 126px;
|
||||
}
|
||||
@ -35,10 +30,6 @@
|
||||
width: 265px;
|
||||
}
|
||||
|
||||
.mt-description {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.width-table {
|
||||
width: 388px;
|
||||
}
|
||||
@ -74,33 +65,16 @@
|
||||
width: 194px;
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
color: green;
|
||||
}
|
||||
|
||||
.dropdown-per {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.mt-8px {
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
/* stylelint-disable */
|
||||
.showWarning {
|
||||
color: #b3a000;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
min-height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ml-20px {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
:host::ng-deep.modal-dialog {
|
||||
width: 48rem;
|
||||
}
|
||||
|
@ -5,12 +5,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { Robot } from '../../../../../../ng-swagger-gen/models/robot';
|
||||
import {
|
||||
Action,
|
||||
INITIAL_ACCESSES,
|
||||
PermissionsKinds,
|
||||
Resource,
|
||||
} from '../system-robot-util';
|
||||
import { Action, PermissionsKinds, Resource } from '../system-robot-util';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { OperationService } from '../../../../shared/components/operation/operation.service';
|
||||
import { RobotService } from '../../../../../../ng-swagger-gen/services/robot.service';
|
||||
@ -19,7 +14,6 @@ import { delay } from 'rxjs/operators';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { Configuration } from '../../config/config';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { clone } from '../../../../shared/units/utils';
|
||||
|
||||
describe('NewRobotComponent', () => {
|
||||
let component: NewRobotComponent;
|
||||
@ -103,7 +97,6 @@ describe('NewRobotComponent', () => {
|
||||
fixture.autoDetectChanges();
|
||||
component.isEditMode = false;
|
||||
component.addRobotOpened = true;
|
||||
component.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
await fixture.whenStable();
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
nameInput.value = '';
|
||||
@ -117,28 +110,9 @@ describe('NewRobotComponent', () => {
|
||||
fixture.autoDetectChanges();
|
||||
component.isEditMode = true;
|
||||
component.addRobotOpened = true;
|
||||
component.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
component.systemRobot = robot1;
|
||||
await fixture.whenStable();
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
expect(nameInput.value).toEqual('robot1');
|
||||
});
|
||||
it('should be valid', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component.isEditMode = false;
|
||||
component.addRobotOpened = true;
|
||||
component.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
await fixture.whenStable();
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
nameInput.value = 'test';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
const expiration = fixture.nativeElement.querySelector(
|
||||
'#robotTokenExpiration'
|
||||
);
|
||||
expiration.value = 10;
|
||||
expiration.dispatchEvent(new Event('input'));
|
||||
component.coverAll = true;
|
||||
await fixture.whenStable();
|
||||
expect(component.disabled()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
@ -17,19 +18,22 @@ import {
|
||||
finalize,
|
||||
switchMap,
|
||||
} from 'rxjs/operators';
|
||||
import { Access } from '../../../../../../ng-swagger-gen/models/access';
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
ExpirationType,
|
||||
FrontAccess,
|
||||
INITIAL_ACCESSES,
|
||||
getSystemAccess,
|
||||
NAMESPACE_ALL_PROJECTS,
|
||||
NAMESPACE_SYSTEM,
|
||||
NEW_EMPTY_ROBOT,
|
||||
onlyHasPushPermission,
|
||||
PermissionsKinds,
|
||||
} from '../system-robot-util';
|
||||
import { clone } from '../../../../shared/units/utils';
|
||||
import {
|
||||
clone,
|
||||
isSameArrayValue,
|
||||
isSameObject,
|
||||
} from '../../../../shared/units/utils';
|
||||
import { RobotService } from '../../../../../../ng-swagger-gen/services/robot.service';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
import { ClrLoadingState, ClrWizard } from '@clr/angular';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import {
|
||||
@ -40,6 +44,9 @@ import {
|
||||
import { OperationService } from '../../../../shared/components/operation/operation.service';
|
||||
import { InlineAlertComponent } from '../../../../shared/components/inline-alert/inline-alert.component';
|
||||
import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
import { RobotPermission } from '../../../../../../ng-swagger-gen/models/robot-permission';
|
||||
import { PermissionSelectPanelModes } from '../../../../shared/components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
import { Permissions } from '../../../../../../ng-swagger-gen/models/permissions';
|
||||
|
||||
const MINI_SECONDS_ONE_DAY: number = 60 * 24 * 60 * 1000;
|
||||
|
||||
@ -49,13 +56,12 @@ const MINI_SECONDS_ONE_DAY: number = 60 * 24 * 60 * 1000;
|
||||
styleUrls: ['./new-robot.component.scss'],
|
||||
})
|
||||
export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
isEditMode: boolean = false;
|
||||
originalRobotForEdit: Robot;
|
||||
@Output()
|
||||
addSuccess: EventEmitter<Robot> = new EventEmitter<Robot>();
|
||||
addRobotOpened: boolean = false;
|
||||
systemRobot: Robot = {};
|
||||
systemRobot: Robot = clone(NEW_EMPTY_ROBOT);
|
||||
expirationType: string = ExpirationType.DAYS;
|
||||
systemExpirationDays: number;
|
||||
coverAll: boolean = false;
|
||||
@ -65,8 +71,6 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
loading: boolean = false;
|
||||
checkNameOnGoing: boolean = false;
|
||||
loadingSystemConfig: boolean = false;
|
||||
defaultAccesses: FrontAccess[] = [];
|
||||
defaultAccessesForEdit: FrontAccess[] = [];
|
||||
@ViewChild(ListAllProjectsComponent)
|
||||
listAllProjectsComponent: ListAllProjectsComponent;
|
||||
@ViewChild(InlineAlertComponent)
|
||||
@ -75,6 +79,27 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
|
||||
@Input()
|
||||
robotMetadata: Permissions;
|
||||
|
||||
permissionForCoverAll: RobotPermission = {
|
||||
access: [],
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: NAMESPACE_ALL_PROJECTS,
|
||||
};
|
||||
|
||||
permissionForCoverAllForEdit: RobotPermission;
|
||||
|
||||
permissionForSystem: RobotPermission = {
|
||||
access: [],
|
||||
kind: PermissionsKinds.SYSTEM,
|
||||
namespace: NAMESPACE_SYSTEM,
|
||||
};
|
||||
|
||||
permissionForSystemForEdit: RobotPermission;
|
||||
showPage3: boolean = false;
|
||||
@ViewChild('wizard') wizard: ClrWizard;
|
||||
constructor(
|
||||
private configService: ConfigurationService,
|
||||
private robotService: RobotService,
|
||||
@ -166,36 +191,42 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
this._nameSubject.next(this.systemRobot.name);
|
||||
}
|
||||
cancel() {
|
||||
this.wizard.reset();
|
||||
this.reset();
|
||||
this.addRobotOpened = false;
|
||||
}
|
||||
getPermissions(): number {
|
||||
let count: number = 0;
|
||||
this.defaultAccesses.forEach(item => {
|
||||
if (item.checked) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
chooseAccess(access: FrontAccess) {
|
||||
access.checked = !access.checked;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.open(false);
|
||||
this.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
this.listAllProjectsComponent.init(false);
|
||||
this.listAllProjectsComponent.selectedRow = [];
|
||||
this.systemRobot = {};
|
||||
this.systemRobot = clone(NEW_EMPTY_ROBOT);
|
||||
this.permissionForCoverAll = {
|
||||
access: [],
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: NAMESPACE_ALL_PROJECTS,
|
||||
};
|
||||
this.permissionForSystem = {
|
||||
access: [],
|
||||
kind: PermissionsKinds.SYSTEM,
|
||||
namespace: NAMESPACE_SYSTEM,
|
||||
};
|
||||
this.coverAll = false;
|
||||
this.showPage3 = false;
|
||||
this.robotForm.reset();
|
||||
this.expirationType = ExpirationType.DAYS;
|
||||
this.getSystemRobotExpiration();
|
||||
}
|
||||
resetForEdit(robot: Robot) {
|
||||
this.open(true);
|
||||
this.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
this.defaultAccesses.forEach(item => (item.checked = false));
|
||||
this.originalRobotForEdit = clone(robot);
|
||||
this.systemRobot = robot;
|
||||
this.systemRobot = clone(robot);
|
||||
this.permissionForSystem = {
|
||||
access: getSystemAccess(robot),
|
||||
kind: PermissionsKinds.SYSTEM,
|
||||
namespace: NAMESPACE_SYSTEM,
|
||||
};
|
||||
|
||||
this.permissionForSystemForEdit = clone(this.permissionForSystem);
|
||||
|
||||
this.expirationType =
|
||||
robot.duration === -1 ? ExpirationType.NEVER : ExpirationType.DAYS;
|
||||
if (robot && robot.permissions && robot.permissions.length) {
|
||||
@ -206,67 +237,17 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
item.namespace === NAMESPACE_ALL_PROJECTS
|
||||
) {
|
||||
this.coverAll = true;
|
||||
if (item && item.access) {
|
||||
item.access.forEach(item2 => {
|
||||
this.defaultAccesses.forEach(item3 => {
|
||||
if (
|
||||
item3.resource === item2.resource &&
|
||||
item3.action === item2.action
|
||||
) {
|
||||
item3.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
this.defaultAccessesForEdit = clone(
|
||||
this.defaultAccesses
|
||||
);
|
||||
}
|
||||
this.permissionForCoverAll = clone(item);
|
||||
this.permissionForCoverAllForEdit = clone(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!this.coverAll) {
|
||||
this.defaultAccesses.forEach(item => (item.checked = true));
|
||||
}
|
||||
this.robotForm.reset({
|
||||
name: this.systemRobot.name,
|
||||
expiration: this.systemRobot.duration,
|
||||
description: this.systemRobot.description,
|
||||
coverAll: this.coverAll,
|
||||
});
|
||||
this.coverAllForEdit = this.coverAll;
|
||||
this.listAllProjectsComponent.init(true);
|
||||
this.listAllProjectsComponent.selectedRow = [];
|
||||
const map = {};
|
||||
this.listAllProjectsComponent.projects.forEach((pro, index) => {
|
||||
if (this.systemRobot && this.systemRobot.permissions) {
|
||||
this.systemRobot.permissions.forEach(item => {
|
||||
if (pro.name === item.namespace) {
|
||||
item.access.forEach(acc => {
|
||||
pro.permissions[0].access.forEach(item3 => {
|
||||
if (
|
||||
item3.resource === acc.resource &&
|
||||
item3.action === acc.action
|
||||
) {
|
||||
item3.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
map[index] = true;
|
||||
this.listAllProjectsComponent.selectedRow.push(pro);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.listAllProjectsComponent.defaultAccesses.forEach(
|
||||
item => (item.checked = true)
|
||||
);
|
||||
this.listAllProjectsComponent.projects.forEach((pro, index) => {
|
||||
if (!map[index]) {
|
||||
pro.permissions[0].access.forEach(item => {
|
||||
item.checked = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
open(isEditMode: boolean) {
|
||||
this.isNameExisting = false;
|
||||
@ -286,46 +267,32 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
if (this.coverAll) {
|
||||
let flag = false;
|
||||
this.defaultAccesses.forEach(item => {
|
||||
if (item.checked) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
if (flag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
!this.listAllProjectsComponent ||
|
||||
!this.listAllProjectsComponent.selectedRow ||
|
||||
!this.listAllProjectsComponent.selectedRow.length
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
for (
|
||||
let i = 0;
|
||||
i < this.listAllProjectsComponent.selectedRow.length;
|
||||
i++
|
||||
) {
|
||||
let flag = false;
|
||||
for (
|
||||
let j = 0;
|
||||
j <
|
||||
this.listAllProjectsComponent.selectedRow[i].permissions[0]
|
||||
.access.length;
|
||||
j++
|
||||
) {
|
||||
if (
|
||||
this.listAllProjectsComponent.selectedRow[i].permissions[0]
|
||||
.access[j].checked
|
||||
) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
if (!this.permissionForCoverAll.access?.length) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!this.permissionForSystem?.access?.length &&
|
||||
!this.listAllProjectsComponent?.selectedRow?.length
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.listAllProjectsComponent?.selectedRow?.length) {
|
||||
for (
|
||||
let i = 0;
|
||||
i < this.listAllProjectsComponent?.selectedRow?.length;
|
||||
i++
|
||||
) {
|
||||
if (
|
||||
!this.listAllProjectsComponent
|
||||
?.selectedProjectPermissionMap[
|
||||
this.listAllProjectsComponent?.selectedRow[i].name
|
||||
]?.length
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -344,79 +311,61 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.coverAll !== this.coverAllForEdit) {
|
||||
if (this.coverAll) {
|
||||
let flag = false;
|
||||
this.defaultAccesses.forEach(item => {
|
||||
if (item.checked) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
if (!flag) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (this.coverAll === this.coverAllForEdit) {
|
||||
if (this.coverAll) {
|
||||
let flag = true;
|
||||
this.defaultAccessesForEdit.forEach(item => {
|
||||
this.defaultAccesses.forEach(item2 => {
|
||||
if (
|
||||
item.resource === item2.resource &&
|
||||
item.action === item2.action &&
|
||||
item.checked !== item2.checked
|
||||
) {
|
||||
flag = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
return !flag;
|
||||
}
|
||||
}
|
||||
if (
|
||||
this.systemRobot.permissions.length !==
|
||||
this.listAllProjectsComponent.selectedRow.length
|
||||
!isSameObject(
|
||||
this.permissionForSystem,
|
||||
this.permissionForSystemForEdit
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const map = {};
|
||||
let accessFlag = true;
|
||||
this.listAllProjectsComponent.selectedRow.forEach(item => {
|
||||
this.systemRobot.permissions.forEach(item2 => {
|
||||
if (item.name === item2.namespace) {
|
||||
map[item.name] = true;
|
||||
if (
|
||||
item2.access.length !==
|
||||
this.getAccessNum(item.permissions[0].access)
|
||||
) {
|
||||
accessFlag = false;
|
||||
}
|
||||
item2.access.forEach(arr => {
|
||||
item.permissions[0].access.forEach(arr2 => {
|
||||
if (
|
||||
arr.resource === arr2.resource &&
|
||||
arr.action === arr2.action &&
|
||||
!arr2.checked
|
||||
) {
|
||||
accessFlag = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!accessFlag) {
|
||||
if (this.coverAll !== this.coverAllForEdit) {
|
||||
return true;
|
||||
}
|
||||
let flag1 = true;
|
||||
this.systemRobot.permissions.forEach(item => {
|
||||
if (!map[item.namespace]) {
|
||||
flag1 = false;
|
||||
if (this.coverAll) {
|
||||
if (
|
||||
!isSameObject(
|
||||
this.permissionForCoverAll,
|
||||
this.permissionForCoverAllForEdit
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return !flag1;
|
||||
}
|
||||
if (this.listAllProjectsComponent) {
|
||||
if (
|
||||
!isSameArrayValue(
|
||||
this.listAllProjectsComponent.selectedRow,
|
||||
this.listAllProjectsComponent.selectedRowForEdit
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
for (
|
||||
let i = 0;
|
||||
i < this.listAllProjectsComponent.selectedRow.length;
|
||||
i++
|
||||
) {
|
||||
if (
|
||||
!isSameArrayValue(
|
||||
this.listAllProjectsComponent
|
||||
.selectedProjectPermissionMap[
|
||||
this.listAllProjectsComponent.selectedRow[i]
|
||||
.name
|
||||
],
|
||||
this.listAllProjectsComponent
|
||||
.selectedProjectPermissionMapForEdit[
|
||||
this.listAllProjectsComponent.selectedRow[i]
|
||||
.name
|
||||
]
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
save() {
|
||||
const robot: Robot = clone(this.systemRobot);
|
||||
@ -424,37 +373,27 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
robot.level = PermissionsKinds.SYSTEM;
|
||||
robot.duration = +this.systemRobot.duration;
|
||||
robot.permissions = [];
|
||||
if (this.permissionForSystem?.access?.length) {
|
||||
robot.permissions.push(this.permissionForSystem);
|
||||
}
|
||||
if (this.coverAll) {
|
||||
const access: Access[] = [];
|
||||
this.defaultAccesses.forEach(item => {
|
||||
if (item.checked) {
|
||||
access.push({
|
||||
resource: item.resource,
|
||||
action: item.action,
|
||||
});
|
||||
}
|
||||
});
|
||||
robot.permissions.push({
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: NAMESPACE_ALL_PROJECTS,
|
||||
access: access,
|
||||
});
|
||||
if (this.permissionForCoverAll?.access?.length) {
|
||||
robot.permissions.push(this.permissionForCoverAll);
|
||||
}
|
||||
} else {
|
||||
this.listAllProjectsComponent.selectedRow.forEach(item => {
|
||||
const access: Access[] = [];
|
||||
item.permissions[0].access.forEach(item2 => {
|
||||
if (item2.checked) {
|
||||
access.push({
|
||||
resource: item2.resource,
|
||||
action: item2.action,
|
||||
});
|
||||
}
|
||||
});
|
||||
robot.permissions.push({
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: item.name,
|
||||
access: access,
|
||||
});
|
||||
if (
|
||||
this.listAllProjectsComponent.selectedProjectPermissionMap[
|
||||
item.name
|
||||
]?.length
|
||||
) {
|
||||
robot.permissions.push({
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: item.name,
|
||||
access: this.listAllProjectsComponent
|
||||
.selectedProjectPermissionMap[item.name],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// Push permission must work with pull permission
|
||||
@ -486,7 +425,7 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
res => {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(null);
|
||||
this.addRobotOpened = false;
|
||||
this.cancel();
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
this.msgHandler.showSuccess(
|
||||
'SYSTEM_ROBOT.UPDATE_ROBOT_SUCCESSFULLY'
|
||||
@ -517,7 +456,7 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
res => {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(res);
|
||||
this.addRobotOpened = false;
|
||||
this.cancel();
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
},
|
||||
error => {
|
||||
@ -533,15 +472,6 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
getAccessNum(access: FrontAccess[]): number {
|
||||
let count: number = 0;
|
||||
access.forEach(item => {
|
||||
if (item.checked) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
calculateExpiresAt(): Date {
|
||||
if (
|
||||
this.systemRobot &&
|
||||
@ -559,26 +489,9 @@ export class NewRobotComponent implements OnInit, OnDestroy {
|
||||
return new Date() >= this.calculateExpiresAt();
|
||||
}
|
||||
|
||||
isSelectAll(permissions: FrontAccess[]): boolean {
|
||||
if (permissions?.length) {
|
||||
return (
|
||||
permissions.filter(item => item.checked).length <
|
||||
permissions.length / 2
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
selectAllOrUnselectAll(permissions: FrontAccess[]) {
|
||||
if (permissions?.length) {
|
||||
if (this.isSelectAll(permissions)) {
|
||||
permissions.forEach(item => {
|
||||
item.checked = true;
|
||||
});
|
||||
} else {
|
||||
permissions.forEach(item => {
|
||||
item.checked = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
clrWizardPageOnLoad() {
|
||||
this.showPage3 = true;
|
||||
}
|
||||
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
||||
|
@ -12,7 +12,9 @@
|
||||
<p class="mt-0">
|
||||
{{ 'SYSTEM_ROBOT.PROJECTS_MODAL_SUMMARY' | translate }}
|
||||
</p>
|
||||
<clr-datagrid>
|
||||
<clr-datagrid
|
||||
(clrDgRefresh)="clrDgRefresh($event)"
|
||||
[clrDgLoading]="loading">
|
||||
<clr-dg-column>{{ 'PROJECT.NAME' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'SYSTEM_ROBOT.PERMISSION_COLUMN' | translate
|
||||
@ -29,36 +31,26 @@
|
||||
>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="permissions">
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<button class="btn btn-link" clrDropdownTrigger>
|
||||
{{ p.access?.length }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu
|
||||
clrPosition="bottom-left"
|
||||
*clrIfOpen>
|
||||
<div
|
||||
clrDropdownItem
|
||||
*ngFor="let item of p.access">
|
||||
<span
|
||||
>{{ i18nMap[item.action] | translate }}
|
||||
{{
|
||||
i18nMap[item.resource] | translate
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
<robot-permissions-panel
|
||||
[mode]="PermissionSelectPanelModes.MODAL"
|
||||
[permissionsModel]="p.access"
|
||||
[candidatePermissions]="p.access">
|
||||
<button class="btn btn-link btn-sm m-0" modal>
|
||||
{{ p.access?.length }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon
|
||||
class="icon"
|
||||
size="12"
|
||||
shape="caret down"></clr-icon>
|
||||
</button>
|
||||
</robot-permissions-panel>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
getProject(p)?.creation_time | harborDatetime : 'short'
|
||||
}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[10, 20, 30]">{{
|
||||
'PAGINATION.PAGE_SIZE' | translate
|
||||
}}</clr-dg-page-size>
|
||||
|
@ -14,13 +14,3 @@
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.permissions {
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.datagrid-host {
|
||||
position: inherit;
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Project } from '../../../../../../ng-swagger-gen/models/project';
|
||||
import { Router } from '@angular/router';
|
||||
import { ACTION_RESOURCE_I18N_MAP } from '../system-robot-util';
|
||||
import { PermissionsKinds } from '../system-robot-util';
|
||||
import { RobotPermission } from '../../../../../../ng-swagger-gen/models/robot-permission';
|
||||
import { PermissionSelectPanelModes } from '../../../../shared/components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
import { ProjectService } from '../../../../../../ng-swagger-gen/services/project.service';
|
||||
import { ClrDatagridStateInterface } from '@clr/angular';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-projects-modal',
|
||||
@ -14,12 +18,52 @@ export class ProjectsModalComponent {
|
||||
robotName: string;
|
||||
cachedAllProjects: Project[];
|
||||
permissions: RobotPermission[] = [];
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
constructor(private router: Router) {}
|
||||
pageSize: number = 10;
|
||||
loading: boolean = false;
|
||||
constructor(
|
||||
private router: Router,
|
||||
private projectService: ProjectService
|
||||
) {}
|
||||
|
||||
close() {
|
||||
this.projectsModalOpened = false;
|
||||
}
|
||||
clrDgRefresh(state?: ClrDatagridStateInterface) {
|
||||
if (this.permissions.length) {
|
||||
if (state) {
|
||||
this.pageSize = state.page.size;
|
||||
this.getProjectFromBackend(
|
||||
this.permissions.slice(state.page.from, state.page.to + 1)
|
||||
);
|
||||
} else {
|
||||
this.getProjectFromBackend(
|
||||
this.permissions.slice(0, this.pageSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
getProjectFromBackend(permissions: RobotPermission[]) {
|
||||
const projectNames: string[] = [];
|
||||
permissions?.forEach(item => {
|
||||
if (item?.kind === PermissionsKinds.PROJECT) {
|
||||
projectNames.push(item?.namespace);
|
||||
}
|
||||
});
|
||||
this.loading = true;
|
||||
this.projectService
|
||||
.listProjects({
|
||||
withDetail: false,
|
||||
page: 1,
|
||||
pageSize: permissions?.length,
|
||||
q: encodeURIComponent(`name={${projectNames.join(' ')}}`),
|
||||
})
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe(res => {
|
||||
if (res?.length) {
|
||||
this.cachedAllProjects = res;
|
||||
}
|
||||
});
|
||||
}
|
||||
getProject(p: RobotPermission): Project {
|
||||
if (this.cachedAllProjects && this.cachedAllProjects.length) {
|
||||
for (let i = 0; i < this.cachedAllProjects.length; i++) {
|
||||
@ -33,4 +77,6 @@ export class ProjectsModalComponent {
|
||||
goToLink(proId: number): void {
|
||||
this.router.navigate(['harbor', 'projects', proId]);
|
||||
}
|
||||
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-dg-action-bar>
|
||||
<button
|
||||
[disabled]="loadingData"
|
||||
[disabled]="loadingData || loadingMetadata"
|
||||
[clrLoading]="addBtnState"
|
||||
class="btn btn-secondary"
|
||||
(click)="openNewRobotModal(false)">
|
||||
@ -136,6 +136,7 @@
|
||||
<clr-dg-column>{{
|
||||
'ROBOT_ACCOUNT.ENABLED_STATE' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>System Permissions</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'SYSTEM_ROBOT.PROJECTS' | translate
|
||||
}}</clr-dg-column>
|
||||
@ -165,6 +166,25 @@
|
||||
size="16"
|
||||
class="color-red red-position"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span *ngIf="!getSystemAccess(r)?.length">
|
||||
{{ 'SCHEDULE.NONE' | translate }}
|
||||
</span>
|
||||
<robot-permissions-panel
|
||||
*ngIf="getSystemAccess(r)?.length"
|
||||
[mode]="PermissionSelectPanelModes.MODAL"
|
||||
[permissionsModel]="getSystemAccess(r)"
|
||||
[candidatePermissions]="getSystemAccess(r)">
|
||||
<button class="btn btn-link btn-sm m-0 p-0" modal>
|
||||
{{ getSystemAccess(r)?.length }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon
|
||||
class="icon"
|
||||
size="12"
|
||||
shape="caret down"></clr-icon>
|
||||
</button>
|
||||
</robot-permissions-panel>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div
|
||||
class="all-projects"
|
||||
@ -172,29 +192,20 @@
|
||||
<span>{{
|
||||
'SYSTEM_ROBOT.ALL_PROJECTS' | translate
|
||||
}}</span>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<button class="btn btn-link" clrDropdownTrigger>
|
||||
|
||||
<robot-permissions-panel
|
||||
[mode]="PermissionSelectPanelModes.MODAL"
|
||||
[permissionsModel]="r.permissionScope?.access"
|
||||
[candidatePermissions]="r.permissionScope?.access">
|
||||
<button class="btn btn-link btn-sm m-0" modal>
|
||||
{{ r.permissionScope?.access?.length }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
<clr-icon
|
||||
class="icon"
|
||||
size="12"
|
||||
shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu
|
||||
clrPosition="bottom-left"
|
||||
*clrIfOpen>
|
||||
<div
|
||||
clrDropdownItem
|
||||
*ngFor="
|
||||
let item of r.permissionScope?.access
|
||||
">
|
||||
<span
|
||||
>{{ i18nMap[item.action] | translate }}
|
||||
{{
|
||||
i18nMap[item.resource] | translate
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</robot-permissions-panel>
|
||||
</div>
|
||||
<span
|
||||
*ngIf="
|
||||
@ -208,7 +219,7 @@
|
||||
{{ 'SYSTEM_ROBOT.COVERED_PROJECTS' | translate }}
|
||||
</a>
|
||||
<span *ngIf="!getProjects(r)?.length">
|
||||
0 {{ 'SYSTEM_ROBOT.COVERED_PROJECTS' | translate }}
|
||||
{{ 'SCHEDULE.NONE' | translate }}
|
||||
</span>
|
||||
</span>
|
||||
</clr-dg-cell>
|
||||
@ -243,6 +254,8 @@
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
<new-robot (addSuccess)="addSuccess($event)"></new-robot>
|
||||
<new-robot
|
||||
(addSuccess)="addSuccess($event)"
|
||||
[robotMetadata]="robotMetadata"></new-robot>
|
||||
<view-token (refreshSuccess)="refresh()"></view-token>
|
||||
<app-projects-modal></app-projects-modal>
|
||||
|
@ -33,3 +33,9 @@
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
|
||||
.icon {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
|
@ -21,15 +21,15 @@ import {
|
||||
} from 'rxjs/operators';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
FrontRobot,
|
||||
getSystemAccess,
|
||||
NAMESPACE_ALL_PROJECTS,
|
||||
NEW_EMPTY_ROBOT,
|
||||
PermissionsKinds,
|
||||
} from './system-robot-util';
|
||||
import { ProjectsModalComponent } from './projects-modal/projects-modal.component';
|
||||
import { forkJoin, Observable, of, Subscription } from 'rxjs';
|
||||
import { FilterComponent } from '../../../shared/components/filter/filter.component';
|
||||
import { ProjectService } from '../../../../../ng-swagger-gen/services/project.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import {
|
||||
operateChanges,
|
||||
@ -37,7 +37,6 @@ import {
|
||||
OperationState,
|
||||
} from '../../../shared/components/operation/operate';
|
||||
import { OperationService } from '../../../shared/components/operation/operation.service';
|
||||
import { Project } from '../../../../../ng-swagger-gen/models/project';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfirmationDialogService } from '../../global-confirmation-dialog/confirmation-dialog.service';
|
||||
@ -50,8 +49,10 @@ import { errorHandler } from '../../../shared/units/shared.utils';
|
||||
import { ConfirmationMessage } from '../../global-confirmation-dialog/confirmation-message';
|
||||
import { RobotPermission } from '../../../../../ng-swagger-gen/models/robot-permission';
|
||||
import { SysteminfoService } from '../../../../../ng-swagger-gen/services/systeminfo.service';
|
||||
|
||||
const FIRST_PROJECTS_PAGE_SIZE: number = 100;
|
||||
import { Access } from '../../../../../ng-swagger-gen/models/access';
|
||||
import { PermissionSelectPanelModes } from '../../../shared/components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
import { PermissionsService } from '../../../../../ng-swagger-gen/services/permissions.service';
|
||||
import { Permissions } from '../../../../../ng-swagger-gen/models/permissions';
|
||||
|
||||
@Component({
|
||||
selector: 'system-robot-accounts',
|
||||
@ -59,7 +60,6 @@ const FIRST_PROJECTS_PAGE_SIZE: number = 100;
|
||||
styleUrls: ['./system-robot-accounts.component.scss'],
|
||||
})
|
||||
export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
pageSize: number = getPageSizeFromLocalStorage(
|
||||
PageSizeMapKeys.SYSTEM_ROBOT_COMPONENT
|
||||
);
|
||||
@ -82,19 +82,22 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
searchKey: string;
|
||||
subscription: Subscription;
|
||||
deltaTime: number; // the different between server time and local time
|
||||
|
||||
robotMetadata: Permissions;
|
||||
loadingMetadata: boolean = false;
|
||||
constructor(
|
||||
private robotService: RobotService,
|
||||
private projectService: ProjectService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private operateDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private translate: TranslateService,
|
||||
private systemInfoService: SysteminfoService
|
||||
private systemInfoService: SysteminfoService,
|
||||
private permissionService: PermissionsService
|
||||
) {}
|
||||
ngOnInit() {
|
||||
this.getRobotPermissions();
|
||||
this.getCurrenTime();
|
||||
this.loadDataFromBackend();
|
||||
if (!this.searchSub) {
|
||||
this.searchSub = this.filterComponent.filterTerms
|
||||
.pipe(
|
||||
@ -169,6 +172,17 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
this.subscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
getRobotPermissions() {
|
||||
this.loadingData = true;
|
||||
this.permissionService
|
||||
.getPermissions()
|
||||
.pipe(finalize(() => (this.loadingData = false)))
|
||||
.subscribe(res => {
|
||||
this.robotMetadata = res;
|
||||
});
|
||||
}
|
||||
|
||||
getCurrenTime() {
|
||||
this.systemInfoService.getSystemInfo().subscribe(res => {
|
||||
if (res?.current_time) {
|
||||
@ -178,89 +192,7 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
}
|
||||
loadDataFromBackend() {
|
||||
this.loadingData = true;
|
||||
this.addBtnState = ClrLoadingState.LOADING;
|
||||
this.projectService
|
||||
.listProjectsResponse({
|
||||
withDetail: false,
|
||||
page: 1,
|
||||
pageSize: FIRST_PROJECTS_PAGE_SIZE,
|
||||
})
|
||||
.subscribe(
|
||||
result => {
|
||||
// Get total count
|
||||
if (result.headers) {
|
||||
const xHeader: string =
|
||||
result.headers.get('X-Total-Count');
|
||||
const totalCount = parseInt(xHeader, 0);
|
||||
let arr = result.body || [];
|
||||
if (totalCount <= FIRST_PROJECTS_PAGE_SIZE) {
|
||||
// already gotten all projects
|
||||
if (
|
||||
this.newRobotComponent &&
|
||||
this.newRobotComponent.listAllProjectsComponent
|
||||
) {
|
||||
this.newRobotComponent.listAllProjectsComponent.cachedAllProjects =
|
||||
result.body;
|
||||
}
|
||||
if (this.projectsModalComponent) {
|
||||
this.projectsModalComponent.cachedAllProjects =
|
||||
result.body;
|
||||
}
|
||||
this.loadingData = false;
|
||||
this.addBtnState = ClrLoadingState.ERROR;
|
||||
} else {
|
||||
// get all the projects in specified times
|
||||
const times: number = Math.ceil(
|
||||
totalCount / FIRST_PROJECTS_PAGE_SIZE
|
||||
);
|
||||
const observableList: Observable<Project[]>[] = [];
|
||||
for (let i = 2; i <= times; i++) {
|
||||
observableList.push(
|
||||
this.projectService.listProjects({
|
||||
withDetail: false,
|
||||
page: i,
|
||||
pageSize: FIRST_PROJECTS_PAGE_SIZE,
|
||||
})
|
||||
);
|
||||
}
|
||||
forkJoin(observableList)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loadingData = false;
|
||||
this.addBtnState =
|
||||
ClrLoadingState.ERROR;
|
||||
})
|
||||
)
|
||||
.subscribe(res => {
|
||||
if (res && res.length) {
|
||||
res.forEach(item => {
|
||||
arr = arr.concat(item);
|
||||
});
|
||||
if (
|
||||
this.newRobotComponent &&
|
||||
this.newRobotComponent
|
||||
.listAllProjectsComponent
|
||||
) {
|
||||
this.newRobotComponent.listAllProjectsComponent.cachedAllProjects =
|
||||
arr;
|
||||
}
|
||||
if (this.projectsModalComponent) {
|
||||
this.projectsModalComponent.cachedAllProjects =
|
||||
arr;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.loadingData = false;
|
||||
this.addBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
clrLoad(state?: ClrDatagridStateInterface) {
|
||||
if (state && state.page && state.page.size) {
|
||||
this.pageSize = state.page.size;
|
||||
@ -337,7 +269,7 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
}
|
||||
getProjects(r: FrontRobot): RobotPermission[] {
|
||||
getProjects(r: Robot): RobotPermission[] {
|
||||
const arr = [];
|
||||
if (r && r.permissions && r.permissions.length) {
|
||||
for (let i = 0; i < r.permissions.length; i++) {
|
||||
@ -352,6 +284,7 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
this.projectsModalComponent.projectsModalOpened = true;
|
||||
this.projectsModalComponent.robotName = robotName;
|
||||
this.projectsModalComponent.permissions = permissions;
|
||||
this.projectsModalComponent.clrDgRefresh();
|
||||
}
|
||||
refresh() {
|
||||
this.currentPage = 1;
|
||||
@ -492,4 +425,11 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
getSystemAccess(r: Robot): Access[] {
|
||||
return getSystemAccess(r);
|
||||
}
|
||||
|
||||
protected readonly NEW_EMPTY_ROBOT = NEW_EMPTY_ROBOT;
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Robot } from '../../../../../ng-swagger-gen/models/robot';
|
||||
import { Access } from '../../../../../ng-swagger-gen/models/access';
|
||||
import { Project } from '../../../../../ng-swagger-gen/models/project';
|
||||
import { RobotPermission } from '../../../../../ng-swagger-gen/models/robot-permission';
|
||||
import { Permission } from '../../../../../ng-swagger-gen/models/permission';
|
||||
|
||||
export interface FrontRobot extends Robot {
|
||||
permissionScope?: {
|
||||
@ -9,14 +10,6 @@ export interface FrontRobot extends Robot {
|
||||
};
|
||||
}
|
||||
|
||||
export interface FrontProjectForAdd extends Project {
|
||||
permissions?: Array<{
|
||||
kind?: string;
|
||||
namespace?: string;
|
||||
access?: Array<FrontAccess>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface FrontAccess extends Access {
|
||||
checked?: boolean;
|
||||
}
|
||||
@ -43,78 +36,7 @@ export enum Action {
|
||||
|
||||
export const NAMESPACE_ALL_PROJECTS: string = '*';
|
||||
|
||||
export const INITIAL_ACCESSES: FrontAccess[] = [
|
||||
{
|
||||
resource: 'repository',
|
||||
action: 'list',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'repository',
|
||||
action: 'pull',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'repository',
|
||||
action: 'push',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'repository',
|
||||
action: 'delete',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'artifact',
|
||||
action: 'read',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'artifact',
|
||||
action: 'list',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'artifact',
|
||||
action: 'delete',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'artifact-label',
|
||||
action: 'create',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'artifact-label',
|
||||
action: 'delete',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'tag',
|
||||
action: 'create',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'tag',
|
||||
action: 'delete',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'tag',
|
||||
action: 'list',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'scan',
|
||||
action: 'create',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
resource: 'scan',
|
||||
action: 'stop',
|
||||
checked: true,
|
||||
},
|
||||
];
|
||||
export const NAMESPACE_SYSTEM: string = '/';
|
||||
|
||||
export const ACTION_RESOURCE_I18N_MAP = {
|
||||
push: 'SYSTEM_ROBOT.PUSH_AND_PULL', // push permission contains pull permission
|
||||
@ -122,16 +44,45 @@ export const ACTION_RESOURCE_I18N_MAP = {
|
||||
read: 'SYSTEM_ROBOT.READ',
|
||||
create: 'SYSTEM_ROBOT.CREATE',
|
||||
delete: 'SYSTEM_ROBOT.DELETE',
|
||||
repository: 'SYSTEM_ROBOT.REPOSITORY',
|
||||
artifact: 'SYSTEM_ROBOT.ARTIFACT',
|
||||
tag: 'REPLICATION.TAG',
|
||||
'artifact-label': 'SYSTEM_ROBOT.ARTIFACT_LABEL',
|
||||
scan: 'SYSTEM_ROBOT.SCAN',
|
||||
'scanner-pull': 'SYSTEM_ROBOT.SCANNER_PULL',
|
||||
stop: 'SYSTEM_ROBOT.STOP',
|
||||
list: 'SYSTEM_ROBOT.LIST',
|
||||
update: 'ROBOT_ACCOUNT.UPDATE',
|
||||
'audit-log': 'ROBOT_ACCOUNT.AUDIT_LOG',
|
||||
'preheat-instance': 'ROBOT_ACCOUNT.PREHEAT_INSTANCE',
|
||||
project: 'ROBOT_ACCOUNT.PROJECT',
|
||||
'replication-policy': 'ROBOT_ACCOUNT.REPLICATION_POLICY',
|
||||
replication: 'ROBOT_ACCOUNT.REPLICATION',
|
||||
'replication-adapter': 'ROBOT_ACCOUNT.REPLICATION_ADAPTER',
|
||||
registry: 'ROBOT_ACCOUNT.REGISTRY',
|
||||
'scan-all': 'ROBOT_ACCOUNT.SCAN_ALL',
|
||||
'system-volumes': 'ROBOT_ACCOUNT.SYSTEM_VOLUMES',
|
||||
'garbage-collection': 'ROBOT_ACCOUNT.GARBAGE_COLLECTION',
|
||||
'purge-audit': 'ROBOT_ACCOUNT.PURGE_AUDIT',
|
||||
'jobservice-monitor': 'ROBOT_ACCOUNT.JOBSERVICE_MONITOR',
|
||||
'tag-retention': 'ROBOT_ACCOUNT.TAG_RETENTION',
|
||||
scanner: 'ROBOT_ACCOUNT.SCANNER',
|
||||
label: 'ROBOT_ACCOUNT.LABEL',
|
||||
'export-cve': 'ROBOT_ACCOUNT.EXPORT_CVE',
|
||||
'security-hub': 'ROBOT_ACCOUNT.SECURITY_HUB',
|
||||
catalog: 'ROBOT_ACCOUNT.CATALOG',
|
||||
metadata: 'ROBOT_ACCOUNT.METADATA',
|
||||
repository: 'ROBOT_ACCOUNT.REPOSITORY',
|
||||
artifact: 'ROBOT_ACCOUNT.ARTIFACT',
|
||||
tag: 'ROBOT_ACCOUNT.TAG',
|
||||
accessory: 'ROBOT_ACCOUNT.ACCESSORY',
|
||||
'artifact-addition': 'ROBOT_ACCOUNT.ARTIFACT_ADDITION',
|
||||
'artifact-label': 'ROBOT_ACCOUNT.ARTIFACT_LABEL',
|
||||
'preheat-policy': 'ROBOT_ACCOUNT.PREHEAT_POLICY',
|
||||
'immutable-tag': 'ROBOT_ACCOUNT.IMMUTABLE_TAG',
|
||||
log: 'ROBOT_ACCOUNT.LOG',
|
||||
'notification-policy': 'ROBOT_ACCOUNT.NOTIFICATION_POLICY',
|
||||
};
|
||||
|
||||
export function convertKey(key: string) {
|
||||
return ACTION_RESOURCE_I18N_MAP[key] ? ACTION_RESOURCE_I18N_MAP[key] : key;
|
||||
}
|
||||
|
||||
export enum ExpirationType {
|
||||
DEFAULT = 'default',
|
||||
DAYS = 'days',
|
||||
@ -168,3 +119,66 @@ export enum RobotTimeRemainColor {
|
||||
WARNING = 'yellow',
|
||||
EXPIRED = 'red',
|
||||
}
|
||||
|
||||
export function isCandidate(
|
||||
candidatePermissions: Permission[],
|
||||
permission: Access
|
||||
): boolean {
|
||||
if (candidatePermissions?.length) {
|
||||
for (let i = 0; i < candidatePermissions.length; i++) {
|
||||
if (
|
||||
candidatePermissions[i].resource === permission.resource &&
|
||||
candidatePermissions[i].action === permission.action
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasPermission(
|
||||
permissions: Access[],
|
||||
permission: Access
|
||||
): boolean {
|
||||
if (permissions?.length) {
|
||||
for (let i = 0; i < permissions.length; i++) {
|
||||
if (
|
||||
permissions[i].resource === permission.resource &&
|
||||
permissions[i].action === permission.action
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const NEW_EMPTY_ROBOT: Robot = {
|
||||
permissions: [
|
||||
{
|
||||
access: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function getSystemAccess(r: Robot): Access[] {
|
||||
let systemPermissions: RobotPermission[] = [];
|
||||
if (r?.permissions?.length) {
|
||||
systemPermissions = r.permissions.filter(
|
||||
item => item.kind === PermissionsKinds.SYSTEM
|
||||
);
|
||||
}
|
||||
if (systemPermissions?.length) {
|
||||
const map = {};
|
||||
systemPermissions.forEach(p => {
|
||||
if (p?.access?.length) {
|
||||
p.access.forEach(item => {
|
||||
map[`${item.resource}@${item.action}`] = item;
|
||||
});
|
||||
}
|
||||
});
|
||||
return Object.values(map);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
@ -1,25 +1,45 @@
|
||||
<clr-modal
|
||||
clrModalSize="md"
|
||||
[(clrModalOpen)]="addRobotOpened"
|
||||
[clrModalStaticBackdrop]="true"
|
||||
[clrModalClosable]="true">
|
||||
<h3 *ngIf="!isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.CREATE_PROJECT_ROBOT' | translate }}
|
||||
</h3>
|
||||
<h3 *ngIf="isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.EDIT_PROJECT_ROBOT' | translate }}
|
||||
</h3>
|
||||
<div class="modal-body">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<clr-wizard
|
||||
#wizard
|
||||
[(clrWizardOpen)]="addRobotOpened"
|
||||
[clrWizardSize]="'lg'"
|
||||
(clrWizardOnCancel)="cancel()">
|
||||
<clr-wizard-title
|
||||
><h3 *ngIf="!isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.CREATE_PROJECT_ROBOT' | translate }}
|
||||
</h3>
|
||||
<h3 *ngIf="isEditMode" class="modal-title">
|
||||
{{ 'SYSTEM_ROBOT.EDIT_PROJECT_ROBOT' | translate }}
|
||||
</h3>
|
||||
<p *ngIf="!isEditMode" class="mt-0">
|
||||
{{ 'SYSTEM_ROBOT.CREATE_PROJECT_ROBOT_SUMMARY' | translate }}
|
||||
</p>
|
||||
<p *ngIf="isEditMode" class="mt-0">
|
||||
{{ 'SYSTEM_ROBOT.EDIT_PROJECT_ROBOT_SUMMARY' | translate }}
|
||||
</p>
|
||||
<form #robotForm="ngForm" class="clr-form clr-form-horizontal mt-1">
|
||||
</p></clr-wizard-title
|
||||
>
|
||||
<clr-wizard-button [type]="'cancel'">{{
|
||||
'BUTTON.CANCEL' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-button [type]="'previous'">{{
|
||||
'ROBOT_ACCOUNT.BACK' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-button [type]="'next'">{{
|
||||
'ROBOT_ACCOUNT.NEXT' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-button [clrLoading]="saveBtnState" [type]="'finish'">{{
|
||||
'ROBOT_ACCOUNT.FINISH' | translate
|
||||
}}</clr-wizard-button>
|
||||
<clr-wizard-page
|
||||
[clrWizardPageNextDisabled]="
|
||||
!robotBasicForm.valid || checkNameOnGoing || isNameExisting
|
||||
">
|
||||
<ng-template clrPageTitle>{{
|
||||
'ROBOT_ACCOUNT.BASIC_INFO' | translate
|
||||
}}</ng-template>
|
||||
<form
|
||||
#robotBasicForm="ngForm"
|
||||
class="clr-form clr-form-horizontal mt-1">
|
||||
<section class="form-block">
|
||||
<!-- name -->
|
||||
<div class="clr-form-control">
|
||||
<label for="name" class="clr-control-label required"
|
||||
>{{ 'P2P_PROVIDER.NAME' | translate }}
|
||||
@ -47,11 +67,11 @@
|
||||
">
|
||||
<div class="clr-input-wrapper">
|
||||
<input
|
||||
class="clr-input"
|
||||
class="clr-input input-width"
|
||||
[disabled]="loading || isEditMode"
|
||||
type="text"
|
||||
id="name"
|
||||
[(ngModel)]="systemRobot.name"
|
||||
[(ngModel)]="robot.name"
|
||||
required
|
||||
pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$"
|
||||
maxLengthExt="255"
|
||||
@ -95,8 +115,17 @@
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- expiration -->
|
||||
<div class="clr-form-control">
|
||||
<clr-textarea-container class="mt-2">
|
||||
<label>{{ 'DISTRIBUTION.DESCRIPTION' | translate }}</label>
|
||||
<textarea
|
||||
class="input-width"
|
||||
clrTextarea
|
||||
type="text"
|
||||
id="description"
|
||||
name="description"
|
||||
[(ngModel)]="robot.description"></textarea>
|
||||
</clr-textarea-container>
|
||||
<div class="clr-form-control mt-2">
|
||||
<label class="clr-control-label required"
|
||||
>{{ 'SYSTEM_ROBOT.EXPIRATION_TIME' | translate }}
|
||||
<clr-tooltip>
|
||||
@ -144,15 +173,15 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="clr-input-wrapper">
|
||||
<div class="clr-input-wrapper expiration">
|
||||
<input
|
||||
(input)="inputExpiration()"
|
||||
class="clr-input expiration-width"
|
||||
class="clr-input"
|
||||
name="expiration"
|
||||
type="text"
|
||||
#expiration="ngModel"
|
||||
autocomplete="off"
|
||||
[(ngModel)]="systemRobot.duration"
|
||||
[(ngModel)]="robot.duration"
|
||||
required
|
||||
pattern="^[\-1-9]{1}[0-9]*$"
|
||||
id="robotTokenExpiration"
|
||||
@ -165,7 +194,7 @@
|
||||
<clr-control-helper
|
||||
*ngIf="
|
||||
(isEditMode &&
|
||||
systemRobot?.duration > 0 &&
|
||||
robot?.duration > 0 &&
|
||||
!(
|
||||
(expiration.dirty ||
|
||||
expiration.touched) &&
|
||||
@ -201,104 +230,26 @@
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 3. description -->
|
||||
<clr-textarea-container>
|
||||
<label>{{ 'DISTRIBUTION.DESCRIPTION' | translate }}</label>
|
||||
<textarea
|
||||
class="mt-description"
|
||||
clrTextarea
|
||||
type="text"
|
||||
id="description"
|
||||
name="description"
|
||||
[(ngModel)]="systemRobot.description"></textarea>
|
||||
</clr-textarea-container>
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label mt-8px">{{
|
||||
'SYSTEM_ROBOT.PERMISSION_COLUMN' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container">
|
||||
<clr-dropdown
|
||||
class="dropdown-per"
|
||||
[clrCloseMenuOnItemClick]="false">
|
||||
<button class="btn btn-link" clrDropdownTrigger>
|
||||
{{ getPermissions() }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu
|
||||
class="overflow-y-scroll"
|
||||
[style.height.px]="230"
|
||||
clrPosition="bottom-left"
|
||||
*clrIfOpen>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-link btn-sm select-all-for-dropdown ml-20px"
|
||||
(click)="
|
||||
selectAllOrUnselectAll(
|
||||
defaultAccesses
|
||||
)
|
||||
">
|
||||
<span
|
||||
*ngIf="isSelectAll(defaultAccesses)"
|
||||
>{{
|
||||
'SYSTEM_ROBOT.SELECT_ALL'
|
||||
| translate
|
||||
}}</span
|
||||
>
|
||||
<span
|
||||
*ngIf="
|
||||
!isSelectAll(defaultAccesses)
|
||||
"
|
||||
>{{
|
||||
'SYSTEM_ROBOT.UNSELECT_ALL'
|
||||
| translate
|
||||
}}</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
clrDropdownItem
|
||||
*ngFor="let item of defaultAccesses"
|
||||
(click)="chooseAccess(item)">
|
||||
<clr-icon
|
||||
class="check"
|
||||
shape="check"
|
||||
[style.visibility]="
|
||||
item.checked ? 'visible' : 'hidden'
|
||||
"></clr-icon>
|
||||
<span
|
||||
>{{ i18nMap[item.action] | translate }}
|
||||
{{
|
||||
i18nMap[item.resource] | translate
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span>
|
||||
<button
|
||||
(click)="cancel()"
|
||||
id="system-robot-cancel"
|
||||
type="button"
|
||||
class="btn btn-outline">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
[disabled]="disabled() || checkNameOnGoing || isNameExisting"
|
||||
[clrLoading]="saveBtnState"
|
||||
(click)="save()"
|
||||
id="system-robot-save"
|
||||
type="button"
|
||||
class="btn btn-primary">
|
||||
<span *ngIf="isEditMode">{{ 'BUTTON.SAVE' | translate }}</span>
|
||||
<span *ngIf="!isEditMode">{{ 'BUTTON.ADD' | translate }}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</clr-wizard-page>
|
||||
|
||||
<clr-wizard-page
|
||||
(clrWizardPageOnCommit)="save()"
|
||||
[clrWizardPagePreventDefaultNext]="true"
|
||||
[clrWizardPageNextDisabled]="disabled()">
|
||||
<ng-template clrPageTitle>{{
|
||||
'ROBOT_ACCOUNT.SELECT_PERMISSIONS' | translate
|
||||
}}</ng-template>
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<form class="clr-form clr-form-horizontal mt-1">
|
||||
<section class="form-block">
|
||||
<robot-permissions-panel
|
||||
[mode]="PermissionSelectPanelModes.NORMAL"
|
||||
[(permissionsModel)]="robot.permissions[0].access"
|
||||
[candidatePermissions]="robotMetadata?.project">
|
||||
</robot-permissions-panel>
|
||||
</section>
|
||||
</form>
|
||||
</clr-wizard-page>
|
||||
</clr-wizard>
|
||||
|
@ -34,44 +34,26 @@
|
||||
}
|
||||
|
||||
.input-width {
|
||||
width: 232px;
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.expiration-width {
|
||||
width: 80px;
|
||||
.expiration {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
color: green;
|
||||
}
|
||||
|
||||
.dropdown-per {
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.mt-description {
|
||||
width: 238px;
|
||||
}
|
||||
|
||||
.mt-8px {
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
/* stylelint-disable */
|
||||
.showWarning {
|
||||
color: #b3a000;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
min-height: 20px;
|
||||
display: flex;
|
||||
.icon {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overflow-y-scroll {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ml-20px {
|
||||
margin-left: 20px;
|
||||
:host::ng-deep.modal-dialog {
|
||||
width: 46.2rem;
|
||||
}
|
||||
|
@ -16,30 +16,30 @@ import {
|
||||
} from 'rxjs/operators';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
ExpirationType,
|
||||
FrontAccess,
|
||||
INITIAL_ACCESSES,
|
||||
NEW_EMPTY_ROBOT,
|
||||
onlyHasPushPermission,
|
||||
PermissionsKinds,
|
||||
} from '../../../left-side-nav/system-robot-accounts/system-robot-util';
|
||||
import { Robot } from '../../../../../../ng-swagger-gen/models/robot';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
import { ClrLoadingState, ClrWizard } from '@clr/angular';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { RobotService } from '../../../../../../ng-swagger-gen/services/robot.service';
|
||||
import { OperationService } from '../../../../shared/components/operation/operation.service';
|
||||
import { clone } from '../../../../shared/units/utils';
|
||||
import { clone, isSameArrayValue } from '../../../../shared/units/utils';
|
||||
import {
|
||||
operateChanges,
|
||||
OperateInfo,
|
||||
OperationState,
|
||||
} from '../../../../shared/components/operation/operate';
|
||||
import { Access } from '../../../../../../ng-swagger-gen/models/access';
|
||||
import { InlineAlertComponent } from '../../../../shared/components/inline-alert/inline-alert.component';
|
||||
import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
import { PermissionSelectPanelModes } from '../../../../shared/components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
import { Permissions } from '../../../../../../ng-swagger-gen/models/permissions';
|
||||
|
||||
const MINI_SECONDS_ONE_DAY: number = 60 * 24 * 60 * 1000;
|
||||
|
||||
@Component({
|
||||
selector: 'add-robot',
|
||||
templateUrl: './add-robot.component.html',
|
||||
@ -48,25 +48,27 @@ const MINI_SECONDS_ONE_DAY: number = 60 * 24 * 60 * 1000;
|
||||
export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
@Input() projectId: number;
|
||||
@Input() projectName: string;
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
isEditMode: boolean = false;
|
||||
originalRobotForEdit: Robot;
|
||||
@Output()
|
||||
addSuccess: EventEmitter<Robot> = new EventEmitter<Robot>();
|
||||
addRobotOpened: boolean = false;
|
||||
systemRobot: Robot = {};
|
||||
robot: Robot = clone(NEW_EMPTY_ROBOT);
|
||||
expirationType: string = ExpirationType.DAYS;
|
||||
isNameExisting: boolean = false;
|
||||
loading: boolean = false;
|
||||
checkNameOnGoing: boolean = false;
|
||||
defaultAccesses: FrontAccess[] = [];
|
||||
defaultAccessesForEdit: FrontAccess[] = [];
|
||||
@ViewChild(InlineAlertComponent)
|
||||
inlineAlertComponent: InlineAlertComponent;
|
||||
@ViewChild('robotForm', { static: true }) robotForm: NgForm;
|
||||
@ViewChild('robotBasicForm', { static: true }) robotBasicForm: NgForm;
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
|
||||
@Input()
|
||||
robotMetadata: Permissions;
|
||||
|
||||
@ViewChild('wizard') wizard: ClrWizard;
|
||||
constructor(
|
||||
private robotService: RobotService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
@ -119,10 +121,10 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
isExpirationInvalid(): boolean {
|
||||
return this.systemRobot.duration < -1;
|
||||
return this.robot.duration < -1;
|
||||
}
|
||||
inputExpiration() {
|
||||
if (+this.systemRobot.duration === -1) {
|
||||
if (+this.robot.duration === -1) {
|
||||
this.expirationType = ExpirationType.NEVER;
|
||||
} else {
|
||||
this.expirationType = ExpirationType.DAYS;
|
||||
@ -130,60 +132,38 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
changeExpirationType() {
|
||||
if (this.expirationType === ExpirationType.DAYS) {
|
||||
this.systemRobot.duration = null;
|
||||
this.robot.duration = null;
|
||||
}
|
||||
if (this.expirationType === ExpirationType.NEVER) {
|
||||
this.systemRobot.duration = -1;
|
||||
this.robot.duration = -1;
|
||||
}
|
||||
}
|
||||
inputName() {
|
||||
this._nameSubject.next(this.systemRobot.name);
|
||||
this._nameSubject.next(this.robot.name);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.wizard.reset();
|
||||
this.reset();
|
||||
this.addRobotOpened = false;
|
||||
}
|
||||
getPermissions(): number {
|
||||
let count: number = 0;
|
||||
this.defaultAccesses.forEach(item => {
|
||||
if (item.checked) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
chooseAccess(access: FrontAccess) {
|
||||
access.checked = !access.checked;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.open(false);
|
||||
this.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
this.systemRobot = {};
|
||||
this.robotForm.reset();
|
||||
this.robot = clone(NEW_EMPTY_ROBOT);
|
||||
this.robotBasicForm.reset();
|
||||
this.expirationType = ExpirationType.DAYS;
|
||||
}
|
||||
resetForEdit(robot: Robot) {
|
||||
this.open(true);
|
||||
this.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
this.defaultAccesses.forEach(item => (item.checked = false));
|
||||
this.originalRobotForEdit = clone(robot);
|
||||
this.systemRobot = robot;
|
||||
this.robot = clone(robot);
|
||||
this.expirationType =
|
||||
robot.duration === -1 ? ExpirationType.NEVER : ExpirationType.DAYS;
|
||||
this.defaultAccesses.forEach(item => {
|
||||
this.systemRobot.permissions[0].access.forEach(item2 => {
|
||||
if (
|
||||
item.resource === item2.resource &&
|
||||
item.action === item2.action
|
||||
) {
|
||||
item.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
this.defaultAccessesForEdit = clone(this.defaultAccesses);
|
||||
this.robotForm.reset({
|
||||
name: this.systemRobot.name,
|
||||
expiration: this.systemRobot.duration,
|
||||
description: this.systemRobot.description,
|
||||
this.robotBasicForm.reset({
|
||||
name: this.robot.name,
|
||||
expiration: this.robot.duration,
|
||||
description: this.robot.description,
|
||||
});
|
||||
}
|
||||
open(isEditMode: boolean) {
|
||||
@ -200,75 +180,37 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
return !this.canEdit();
|
||||
}
|
||||
canAdd(): boolean {
|
||||
let flag = false;
|
||||
this.defaultAccesses.forEach(item => {
|
||||
if (item.checked) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
if (!flag) {
|
||||
return false;
|
||||
}
|
||||
return !this.robotForm.invalid;
|
||||
return (
|
||||
this.robot?.permissions[0]?.access?.length > 0 &&
|
||||
!this.robotBasicForm.invalid
|
||||
);
|
||||
}
|
||||
canEdit() {
|
||||
if (!this.canAdd()) {
|
||||
return false;
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (this.systemRobot.duration != this.originalRobotForEdit.duration) {
|
||||
if (this.robot.duration != this.originalRobotForEdit.duration) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (
|
||||
this.systemRobot.description !=
|
||||
this.originalRobotForEdit.description
|
||||
) {
|
||||
if (this.robot.description != this.originalRobotForEdit.description) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
this.getAccessNum(this.defaultAccesses) !==
|
||||
this.getAccessNum(this.defaultAccessesForEdit)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
let flag = true;
|
||||
this.defaultAccessesForEdit.forEach(item => {
|
||||
this.defaultAccesses.forEach(item2 => {
|
||||
if (
|
||||
item.resource === item2.resource &&
|
||||
item.action === item2.action &&
|
||||
item.checked !== item2.checked
|
||||
) {
|
||||
flag = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
return !flag;
|
||||
return !isSameArrayValue(
|
||||
this.robot.permissions[0].access,
|
||||
this.originalRobotForEdit.permissions[0].access
|
||||
);
|
||||
}
|
||||
save() {
|
||||
const robot: Robot = clone(this.systemRobot);
|
||||
const robot: Robot = clone(this.robot);
|
||||
robot.disable = false;
|
||||
robot.level = PermissionsKinds.PROJECT;
|
||||
robot.duration = +this.systemRobot.duration;
|
||||
const access: Access[] = [];
|
||||
this.defaultAccesses.forEach(item => {
|
||||
if (item.checked) {
|
||||
access.push({
|
||||
resource: item.resource,
|
||||
action: item.action,
|
||||
});
|
||||
}
|
||||
});
|
||||
robot.permissions = [
|
||||
{
|
||||
namespace: this.projectName,
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
access: access,
|
||||
},
|
||||
];
|
||||
robot.duration = +this.robot.duration;
|
||||
robot.permissions[0].kind = PermissionsKinds.PROJECT;
|
||||
robot.permissions[0].namespace = this.projectName;
|
||||
// Push permission must work with pull permission
|
||||
if (onlyHasPushPermission(access)) {
|
||||
if (onlyHasPushPermission(robot.permissions[0].access)) {
|
||||
this.inlineAlertComponent.showInlineError(
|
||||
'SYSTEM_ROBOT.PUSH_PERMISSION_TOOLTIP'
|
||||
);
|
||||
@ -276,7 +218,7 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
if (this.isEditMode) {
|
||||
robot.disable = this.systemRobot.disable;
|
||||
robot.disable = this.robot.disable;
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = 'SYSTEM_ROBOT.UPDATE_ROBOT';
|
||||
opeMessage.data.id = robot.id;
|
||||
@ -292,7 +234,7 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
res => {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(null);
|
||||
this.addRobotOpened = false;
|
||||
this.cancel();
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
this.msgHandler.showSuccess(
|
||||
'SYSTEM_ROBOT.UPDATE_ROBOT_SUCCESSFULLY'
|
||||
@ -324,7 +266,7 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(res);
|
||||
this.addRobotOpened = false;
|
||||
this.cancel();
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
},
|
||||
error => {
|
||||
@ -339,24 +281,12 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
}
|
||||
getAccessNum(access: FrontAccess[]): number {
|
||||
let count: number = 0;
|
||||
access.forEach(item => {
|
||||
if (item.checked) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
calculateExpiresAt(): Date {
|
||||
if (
|
||||
this.systemRobot &&
|
||||
this.systemRobot.creation_time &&
|
||||
this.systemRobot.duration > 0
|
||||
) {
|
||||
if (this.robot && this.robot.creation_time && this.robot.duration > 0) {
|
||||
return new Date(
|
||||
new Date(this.systemRobot.creation_time).getTime() +
|
||||
this.systemRobot.duration * MINI_SECONDS_ONE_DAY
|
||||
new Date(this.robot.creation_time).getTime() +
|
||||
this.robot.duration * MINI_SECONDS_ONE_DAY
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@ -364,26 +294,6 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
shouldShowWarning(): boolean {
|
||||
return new Date() >= this.calculateExpiresAt();
|
||||
}
|
||||
isSelectAll(permissions: FrontAccess[]): boolean {
|
||||
if (permissions?.length) {
|
||||
return (
|
||||
permissions.filter(item => item.checked).length <
|
||||
permissions.length / 2
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
selectAllOrUnselectAll(permissions: FrontAccess[]) {
|
||||
if (permissions?.length) {
|
||||
if (this.isSelectAll(permissions)) {
|
||||
permissions.forEach(item => {
|
||||
item.checked = true;
|
||||
});
|
||||
} else {
|
||||
permissions.forEach(item => {
|
||||
item.checked = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-dg-action-bar>
|
||||
<button
|
||||
[disabled]="!hasRobotCreatePermission"
|
||||
[disabled]="!hasRobotCreatePermission || loadingMetadata"
|
||||
[clrLoading]="addBtnState"
|
||||
class="btn btn-secondary"
|
||||
(click)="openNewRobotModal(false)">
|
||||
@ -178,33 +178,19 @@
|
||||
class="color-red red-position"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="permissions">
|
||||
<clr-dropdown
|
||||
[clrCloseMenuOnItemClick]="false"
|
||||
*ngIf="r.permissions[0]?.access?.length">
|
||||
<button class="btn btn-link" clrDropdownTrigger>
|
||||
{{ r.permissions[0]?.access?.length }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu
|
||||
clrPosition="bottom-left"
|
||||
*clrIfOpen>
|
||||
<div
|
||||
clrDropdownItem
|
||||
*ngFor="
|
||||
let item of r.permissions[0]?.access
|
||||
">
|
||||
<span
|
||||
>{{ i18nMap[item.action] | translate }}
|
||||
{{
|
||||
i18nMap[item.resource] | translate
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
<robot-permissions-panel
|
||||
[mode]="PermissionSelectPanelModes.MODAL"
|
||||
[permissionsModel]="r.permissions[0].access"
|
||||
[candidatePermissions]="r.permissions[0].access">
|
||||
<button class="btn btn-link btn-sm m-0 p-0" modal>
|
||||
{{ r.permissions[0]?.access?.length }}
|
||||
{{ 'SYSTEM_ROBOT.PERMISSIONS' | translate }}
|
||||
<clr-icon
|
||||
class="icon"
|
||||
size="12"
|
||||
shape="caret down"></clr-icon>
|
||||
</button>
|
||||
</robot-permissions-panel>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
r.creation_time | harborDatetime : 'short'
|
||||
@ -238,6 +224,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<add-robot
|
||||
[robotMetadata]="robotMetadata"
|
||||
[projectId]="projectId"
|
||||
[projectName]="projectName"
|
||||
(addSuccess)="addSuccess($event)"></add-robot>
|
||||
|
@ -27,12 +27,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.permissions {
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.datagrid-host {
|
||||
position: inherit;
|
||||
.icon {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { of, Subscription } from 'rxjs';
|
||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { RobotAccountComponent } from './robot-account.component';
|
||||
import { UserPermissionService } from '../../../shared/services';
|
||||
|
@ -13,10 +13,7 @@ import {
|
||||
UserPermissionService,
|
||||
USERSTATICPERMISSION,
|
||||
} from '../../../shared/services';
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
PermissionsKinds,
|
||||
} from '../../left-side-nav/system-robot-accounts/system-robot-util';
|
||||
import { PermissionsKinds } from '../../left-side-nav/system-robot-accounts/system-robot-util';
|
||||
import {
|
||||
clone,
|
||||
getPageSizeFromLocalStorage,
|
||||
@ -50,6 +47,9 @@ import {
|
||||
import { errorHandler } from '../../../shared/units/shared.utils';
|
||||
import { ConfirmationMessage } from '../../global-confirmation-dialog/confirmation-message';
|
||||
import { SysteminfoService } from '../../../../../ng-swagger-gen/services/systeminfo.service';
|
||||
import { PermissionSelectPanelModes } from '../../../shared/components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
import { PermissionsService } from '../../../../../ng-swagger-gen/services/permissions.service';
|
||||
import { Permissions } from '../../../../../ng-swagger-gen/models/permissions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-robot-account',
|
||||
@ -57,7 +57,6 @@ import { SysteminfoService } from '../../../../../ng-swagger-gen/services/system
|
||||
styleUrls: ['./robot-account.component.scss'],
|
||||
})
|
||||
export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
pageSize: number = getPageSizeFromLocalStorage(
|
||||
PageSizeMapKeys.PROJECT_ROBOT_COMPONENT
|
||||
);
|
||||
@ -83,6 +82,9 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
projectId: number;
|
||||
projectName: string;
|
||||
deltaTime: number; // the different between server time and local time
|
||||
|
||||
loadingMetadata: boolean = false;
|
||||
robotMetadata: Permissions;
|
||||
constructor(
|
||||
private robotService: RobotService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
@ -92,7 +94,8 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
private route: ActivatedRoute,
|
||||
private translate: TranslateService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private systemInfoService: SysteminfoService
|
||||
private systemInfoService: SysteminfoService,
|
||||
private permissionService: PermissionsService
|
||||
) {}
|
||||
ngOnInit() {
|
||||
this.getCurrenTime();
|
||||
@ -171,6 +174,17 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getRobotPermissions() {
|
||||
this.loadingMetadata = true;
|
||||
this.permissionService
|
||||
.getPermissions()
|
||||
.pipe(finalize(() => (this.loadingMetadata = false)))
|
||||
.subscribe(res => {
|
||||
this.robotMetadata = res;
|
||||
});
|
||||
}
|
||||
|
||||
getCurrenTime() {
|
||||
this.systemInfoService.getSystemInfo().subscribe(res => {
|
||||
if (res?.current_time) {
|
||||
@ -214,6 +228,9 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
forkJoin(...permissionsList).subscribe(
|
||||
Rules => {
|
||||
this.hasRobotCreatePermission = Rules[0] as boolean;
|
||||
if (this.hasRobotCreatePermission) {
|
||||
this.getRobotPermissions();
|
||||
}
|
||||
this.hasRobotUpdatePermission = Rules[1] as boolean;
|
||||
this.hasRobotDeletePermission = Rules[2] as boolean;
|
||||
this.hasRobotReadPermission = Rules[3] as boolean;
|
||||
@ -418,4 +435,5 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RemainingTimeComponent } from './remaining-time.component';
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Project } from '../../../../../ng-swagger-gen/models/project';
|
||||
import { RobotTimeRemainColor } from '../../../base/left-side-nav/system-robot-accounts/system-robot-util';
|
||||
import { SharedTestingModule } from '../../shared.module';
|
||||
|
||||
|
@ -0,0 +1,136 @@
|
||||
<ng-container *ngIf="mode === PermissionSelectPanelModes.MODAL">
|
||||
<div (click)="modalOpen = true">
|
||||
<ng-content select="[modal]"></ng-content>
|
||||
</div>
|
||||
<clr-modal
|
||||
[clrModalSize]="'lg'"
|
||||
[(clrModalOpen)]="modalOpen"
|
||||
[clrModalStaticBackdrop]="false">
|
||||
<div class="modal-body">
|
||||
<table class="table table-compact mt-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="left">
|
||||
{{ 'AUDIT_LOG.RESOURCE' | translate }}
|
||||
</th>
|
||||
<th class="left" *ngFor="let item of candidateActions">
|
||||
{{ convertKey(item) | translate }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let resource of candidateResources">
|
||||
<td class="left td">
|
||||
{{ convertKey(resource) | translate }}
|
||||
</td>
|
||||
<td class="td" *ngFor="let action of candidateActions">
|
||||
<input
|
||||
*ngIf="isCandidate(resource, action)"
|
||||
type="checkbox"
|
||||
clrCheckbox
|
||||
[disabled]="true"
|
||||
[ngModel]="getCheckBoxValue(resource, action)"
|
||||
(ngModelChange)="
|
||||
setCheckBoxValue(resource, action, $event)
|
||||
" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="mode === PermissionSelectPanelModes.DROPDOWN">
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<span #dropdown class="trigger" clrDropdownTrigger>
|
||||
<ng-content></ng-content>
|
||||
</span>
|
||||
<clr-dropdown-menu
|
||||
class="dropdown-menu p-1"
|
||||
clrPosition="{{ dropdownPosition }}"
|
||||
*clrIfOpen>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-link btn-sm select-all-for-dropdown p-0"
|
||||
(click)="selectAllOrUnselectAll()">
|
||||
<span *ngIf="!isAllSelected()">{{
|
||||
'SYSTEM_ROBOT.SELECT_ALL' | translate
|
||||
}}</span>
|
||||
<span *ngIf="isAllSelected()">{{
|
||||
'SYSTEM_ROBOT.UNSELECT_ALL' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<table class="table table-compact mt-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="left">
|
||||
{{ 'AUDIT_LOG.RESOURCE' | translate }}
|
||||
</th>
|
||||
<th class="left" *ngFor="let item of candidateActions">
|
||||
{{ convertKey(item) | translate }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let resource of candidateResources">
|
||||
<td class="left td">
|
||||
{{ convertKey(resource) | translate }}
|
||||
</td>
|
||||
<td class="td" *ngFor="let action of candidateActions">
|
||||
<input
|
||||
*ngIf="isCandidate(resource, action)"
|
||||
type="checkbox"
|
||||
clrCheckbox
|
||||
[ngModel]="getCheckBoxValue(resource, action)"
|
||||
(ngModelChange)="
|
||||
setCheckBoxValue(resource, action, $event)
|
||||
" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="mode === PermissionSelectPanelModes.NORMAL">
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-outline btn-sm"
|
||||
(click)="selectAllOrUnselectAll()">
|
||||
<span *ngIf="!isAllSelected()">{{
|
||||
'SYSTEM_ROBOT.SELECT_ALL' | translate
|
||||
}}</span>
|
||||
<span *ngIf="isAllSelected()">{{
|
||||
'SYSTEM_ROBOT.UNSELECT_ALL' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<table class="table table-compact mt-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="left">{{ 'AUDIT_LOG.RESOURCE' | translate }}</th>
|
||||
<th class="left" *ngFor="let item of candidateActions">
|
||||
{{ convertKey(item) | translate }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let resource of candidateResources">
|
||||
<td class="left td">{{ convertKey(resource) | translate }}</td>
|
||||
<td class="td" *ngFor="let action of candidateActions">
|
||||
<input
|
||||
*ngIf="isCandidate(resource, action)"
|
||||
type="checkbox"
|
||||
clrCheckbox
|
||||
[ngModel]="getCheckBoxValue(resource, action)"
|
||||
(ngModelChange)="
|
||||
setCheckBoxValue(resource, action, $event)
|
||||
" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
@ -0,0 +1,19 @@
|
||||
.td {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
:host::ng-deep.trigger clr-icon {
|
||||
padding: 0;
|
||||
position: unset !important;
|
||||
}
|
||||
|
||||
.select-all-for-dropdown {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { SharedTestingModule } from '../../shared.module';
|
||||
import {
|
||||
PermissionSelectPanelModes,
|
||||
RobotPermissionsPanelComponent,
|
||||
} from './robot-permissions-panel.component';
|
||||
|
||||
describe('RobotPermissionsPanelComponent', () => {
|
||||
let component: TestHostComponent;
|
||||
let fixture: ComponentFixture<TestHostComponent>;
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [TestHostComponent, RobotPermissionsPanelComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestHostComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render right mode', async () => {
|
||||
component.robotPermissionsPanelComponent.modalOpen = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const table = fixture.nativeElement.querySelector('table');
|
||||
expect(table).toBeTruthy();
|
||||
component.mode = PermissionSelectPanelModes.DROPDOWN;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const clrDropdown = fixture.nativeElement.querySelector('clr-dropdown');
|
||||
expect(clrDropdown).toBeTruthy();
|
||||
component.mode = PermissionSelectPanelModes.MODAL;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const modal = fixture.nativeElement.querySelector('clr-modal');
|
||||
expect(modal).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
// mock a TestHostComponent for RobotPermissionsPanelComponent
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *ngIf="mode === PermissionSelectPanelModes.MODAL">
|
||||
<robot-permissions-panel [mode]="mode">
|
||||
<div>modal</div>
|
||||
</robot-permissions-panel>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mode === PermissionSelectPanelModes.DROPDOWN">
|
||||
<robot-permissions-panel [mode]="mode">
|
||||
<div>dropDown</div>
|
||||
</robot-permissions-panel>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mode === PermissionSelectPanelModes.NORMAL">
|
||||
<robot-permissions-panel [mode]="mode"> </robot-permissions-panel>
|
||||
</ng-container>
|
||||
`,
|
||||
})
|
||||
class TestHostComponent {
|
||||
@ViewChild(RobotPermissionsPanelComponent)
|
||||
robotPermissionsPanelComponent: RobotPermissionsPanelComponent;
|
||||
mode = PermissionSelectPanelModes.NORMAL;
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
convertKey,
|
||||
hasPermission,
|
||||
isCandidate,
|
||||
} from '../../../base/left-side-nav/system-robot-accounts/system-robot-util';
|
||||
import { Access } from '../../../../../ng-swagger-gen/models/access';
|
||||
import { Permission } from '../../../../../ng-swagger-gen/models/permission';
|
||||
|
||||
enum Position {
|
||||
UP = 'left-bottom',
|
||||
DOWN = 'left-top',
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'robot-permissions-panel',
|
||||
templateUrl: './robot-permissions-panel.component.html',
|
||||
styleUrls: ['./robot-permissions-panel.component.scss'],
|
||||
})
|
||||
export class RobotPermissionsPanelComponent
|
||||
implements AfterViewInit, OnChanges
|
||||
{
|
||||
modalOpen: boolean = false;
|
||||
|
||||
@Input()
|
||||
mode: PermissionSelectPanelModes = PermissionSelectPanelModes.NORMAL;
|
||||
|
||||
@Input()
|
||||
dropdownPosition: string = 'bottom-left';
|
||||
|
||||
@Input()
|
||||
usedInDatagrid: boolean = false;
|
||||
|
||||
@Input()
|
||||
candidatePermissions: Permission[] = [];
|
||||
|
||||
candidateActions: string[] = [];
|
||||
candidateResources: string[] = [];
|
||||
|
||||
@Input()
|
||||
permissionsModel!: Access[];
|
||||
@Output()
|
||||
permissionsModelChange = new EventEmitter<Access[]>();
|
||||
|
||||
@ViewChild('dropdown')
|
||||
clrDropdown: ElementRef;
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.clrDropdown && this.usedInDatagrid) {
|
||||
if (
|
||||
this.clrDropdown.nativeElement.getBoundingClientRect().y <
|
||||
488
|
||||
) {
|
||||
this.dropdownPosition = Position.DOWN;
|
||||
} else {
|
||||
this.dropdownPosition = Position.UP;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes && changes['candidatePermissions']) {
|
||||
this.initCandidates();
|
||||
}
|
||||
}
|
||||
|
||||
initCandidates() {
|
||||
this.candidateActions = [];
|
||||
this.candidateResources = [];
|
||||
this.candidatePermissions?.forEach(item => {
|
||||
if (this.candidateResources.indexOf(item?.resource) === -1) {
|
||||
this.candidateResources.push(item?.resource);
|
||||
}
|
||||
if (this.candidateActions.indexOf(item?.action) === -1) {
|
||||
this.candidateActions.push(item?.action);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isCandidate(resource: string, action: string): boolean {
|
||||
return isCandidate(this.candidatePermissions, { resource, action });
|
||||
}
|
||||
|
||||
getCheckBoxValue(resource: string, action: string): boolean {
|
||||
return hasPermission(this.permissionsModel, { resource, action });
|
||||
}
|
||||
|
||||
setCheckBoxValue(resource: string, action: string, value: boolean) {
|
||||
if (value) {
|
||||
if (!this.permissionsModel) {
|
||||
this.permissionsModel = [];
|
||||
}
|
||||
this.permissionsModel.push({ resource, action });
|
||||
} else {
|
||||
this.permissionsModel = this.permissionsModel.filter(item => {
|
||||
return item.resource !== resource || item.action !== action;
|
||||
});
|
||||
}
|
||||
this.permissionsModelChange.emit(this.permissionsModel);
|
||||
}
|
||||
|
||||
isAllSelected(): boolean {
|
||||
let flag: boolean = true;
|
||||
this.candidateActions.forEach(action => {
|
||||
this.candidateResources.forEach(resource => {
|
||||
if (
|
||||
this.isCandidate(resource, action) &&
|
||||
!hasPermission(this.permissionsModel, { resource, action })
|
||||
) {
|
||||
flag = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
return flag;
|
||||
}
|
||||
|
||||
selectAllOrUnselectAll() {
|
||||
if (this.isAllSelected()) {
|
||||
this.permissionsModel = [];
|
||||
} else {
|
||||
this.permissionsModel = [];
|
||||
this.candidateActions.forEach(action => {
|
||||
this.candidateResources.forEach(resource => {
|
||||
if (this.isCandidate(resource, action)) {
|
||||
this.permissionsModel.push({ resource, action });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
this.permissionsModelChange.emit(this.permissionsModel);
|
||||
}
|
||||
|
||||
convertKey(key: string): string {
|
||||
return convertKey(key);
|
||||
}
|
||||
protected readonly PermissionSelectPanelModes = PermissionSelectPanelModes;
|
||||
}
|
||||
|
||||
export enum PermissionSelectPanelModes {
|
||||
DROPDOWN,
|
||||
MODAL,
|
||||
NORMAL,
|
||||
}
|
@ -91,6 +91,7 @@ import {
|
||||
} from 'echarts/components';
|
||||
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { RobotPermissionsPanelComponent } from './components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
|
||||
// register necessary components
|
||||
echarts.use([
|
||||
@ -175,6 +176,7 @@ ClarityIcons.add({
|
||||
RemainingTimeComponent,
|
||||
LabelSelectorComponent,
|
||||
AppLevelAlertsComponent,
|
||||
RobotPermissionsPanelComponent,
|
||||
],
|
||||
exports: [
|
||||
TranslateModule,
|
||||
@ -217,6 +219,7 @@ ClarityIcons.add({
|
||||
RemainingTimeComponent,
|
||||
LabelSelectorComponent,
|
||||
AppLevelAlertsComponent,
|
||||
RobotPermissionsPanelComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService },
|
||||
|
@ -378,7 +378,45 @@
|
||||
"INVALID_VALUE": "Der Wert der Ablaufzeit ist ungültig",
|
||||
"NEVER_EXPIRED": "Läuft nie ab",
|
||||
"NAME_PREFIX": "Prefix für den Namen der Robot-Zugänge",
|
||||
"NAME_PREFIX_REQUIRED": "Es ist ein Prefix für den Robot-Zugang erforderlich"
|
||||
"NAME_PREFIX_REQUIRED": "Es ist ein Prefix für den Robot-Zugang erforderlich",
|
||||
"UPDATE": "Update",
|
||||
"AUDIT_LOG": "Audit Log",
|
||||
"PREHEAT_INSTANCE": "Preheat Instance",
|
||||
"PROJECT": "Project",
|
||||
"REPLICATION_POLICY": "Replication Policy",
|
||||
"REPLICATION": "Replication",
|
||||
"REPLICATION_ADAPTER": "Replication Adapter",
|
||||
"REGISTRY": "Registry",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SYSTEM_VOLUMES": "System Volumes",
|
||||
"GARBAGE_COLLECTION": "Garbage Collection",
|
||||
"PURGE_AUDIT": "Purge Audit",
|
||||
"JOBSERVICE_MONITOR": "Job Service Monitor",
|
||||
"TAG_RETENTION": "Tag Retention",
|
||||
"SCANNER": "Scanner",
|
||||
"LABEL": "Label",
|
||||
"EXPORT_CVE": "Export CVE",
|
||||
"SECURITY_HUB": "Security Hub",
|
||||
"CATALOG": "Catalog",
|
||||
"METADATA": "Project Metadata",
|
||||
"REPOSITORY": "Repository",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "Scan",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "Accessory",
|
||||
"ARTIFACT_ADDITION": "Artifact Addition",
|
||||
"ARTIFACT_LABEL": "Artifact Label",
|
||||
"PREHEAT_POLICY": "Preheat Policy",
|
||||
"IMMUTABLE_TAG": "Immutable Tag",
|
||||
"LOG": "Log",
|
||||
"NOTIFICATION_POLICY": "Notification Policy",
|
||||
"BACK": "Back",
|
||||
"NEXT": "Next",
|
||||
"FINISH": "Finish",
|
||||
"BASIC_INFO": "Basic Information",
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
|
@ -378,7 +378,45 @@
|
||||
"INVALID_VALUE": "The value of the expiration time is invalid",
|
||||
"NEVER_EXPIRED": "Never Expires",
|
||||
"NAME_PREFIX": "Robot Name Prefix",
|
||||
"NAME_PREFIX_REQUIRED": "Robot name prefix is required"
|
||||
"NAME_PREFIX_REQUIRED": "Robot name prefix is required",
|
||||
"UPDATE": "Update",
|
||||
"AUDIT_LOG": "Audit Log",
|
||||
"PREHEAT_INSTANCE": "Preheat Instance",
|
||||
"PROJECT": "Project",
|
||||
"REPLICATION_POLICY": "Replication Policy",
|
||||
"REPLICATION": "Replication",
|
||||
"REPLICATION_ADAPTER": "Replication Adapter",
|
||||
"REGISTRY": "Registry",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SYSTEM_VOLUMES": "System Volumes",
|
||||
"GARBAGE_COLLECTION": "Garbage Collection",
|
||||
"PURGE_AUDIT": "Purge Audit",
|
||||
"JOBSERVICE_MONITOR": "Job Service Monitor",
|
||||
"TAG_RETENTION": "Tag Retention",
|
||||
"SCANNER": "Scanner",
|
||||
"LABEL": "Label",
|
||||
"EXPORT_CVE": "Export CVE",
|
||||
"SECURITY_HUB": "Security Hub",
|
||||
"CATALOG": "Catalog",
|
||||
"METADATA": "Project Metadata",
|
||||
"REPOSITORY": "Repository",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "Scan",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "Accessory",
|
||||
"ARTIFACT_ADDITION": "Artifact Addition",
|
||||
"ARTIFACT_LABEL": "Artifact Label",
|
||||
"PREHEAT_POLICY": "Preheat Policy",
|
||||
"IMMUTABLE_TAG": "Immutable Tag",
|
||||
"LOG": "Log",
|
||||
"NOTIFICATION_POLICY": "Notification Policy",
|
||||
"BACK": "Back",
|
||||
"NEXT": "Next",
|
||||
"FINISH": "Finish",
|
||||
"BASIC_INFO": "Basic Information",
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
|
@ -379,7 +379,45 @@
|
||||
"INVALID_VALUE": "The value of the expiration time is invalid",
|
||||
"NEVER_EXPIRED": "Never Expired",
|
||||
"NAME_PREFIX": "Robot Name Prefix",
|
||||
"NAME_PREFIX_REQUIRED": "Robot name prefix is required"
|
||||
"NAME_PREFIX_REQUIRED": "Robot name prefix is required",
|
||||
"UPDATE": "Update",
|
||||
"AUDIT_LOG": "Audit Log",
|
||||
"PREHEAT_INSTANCE": "Preheat Instance",
|
||||
"PROJECT": "Project",
|
||||
"REPLICATION_POLICY": "Replication Policy",
|
||||
"REPLICATION": "Replication",
|
||||
"REPLICATION_ADAPTER": "Replication Adapter",
|
||||
"REGISTRY": "Registry",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SYSTEM_VOLUMES": "System Volumes",
|
||||
"GARBAGE_COLLECTION": "Garbage Collection",
|
||||
"PURGE_AUDIT": "Purge Audit",
|
||||
"JOBSERVICE_MONITOR": "Job Service Monitor",
|
||||
"TAG_RETENTION": "Tag Retention",
|
||||
"SCANNER": "Scanner",
|
||||
"LABEL": "Label",
|
||||
"EXPORT_CVE": "Export CVE",
|
||||
"SECURITY_HUB": "Security Hub",
|
||||
"CATALOG": "Catalog",
|
||||
"METADATA": "Project Metadata",
|
||||
"REPOSITORY": "Repository",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "Scan",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "Accessory",
|
||||
"ARTIFACT_ADDITION": "Artifact Addition",
|
||||
"ARTIFACT_LABEL": "Artifact Label",
|
||||
"PREHEAT_POLICY": "Preheat Policy",
|
||||
"IMMUTABLE_TAG": "Immutable Tag",
|
||||
"LOG": "Log",
|
||||
"NOTIFICATION_POLICY": "Notification Policy",
|
||||
"BACK": "Back",
|
||||
"NEXT": "Next",
|
||||
"FINISH": "Finish",
|
||||
"BASIC_INFO": "Basic Information",
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
|
@ -370,7 +370,45 @@
|
||||
"INVALID_VALUE": "La valeur de la date d'expiration est invalide",
|
||||
"NEVER_EXPIRED": "Ne jamais expirer",
|
||||
"NAME_PREFIX": "Préfixe du nom du compte robot",
|
||||
"NAME_PREFIX_REQUIRED": "Le préfixe du nom du compte robot est obligatoire"
|
||||
"NAME_PREFIX_REQUIRED": "Le préfixe du nom du compte robot est obligatoire",
|
||||
"UPDATE": "Update",
|
||||
"AUDIT_LOG": "Audit Log",
|
||||
"PREHEAT_INSTANCE": "Preheat Instance",
|
||||
"PROJECT": "Project",
|
||||
"REPLICATION_POLICY": "Replication Policy",
|
||||
"REPLICATION": "Replication",
|
||||
"REPLICATION_ADAPTER": "Replication Adapter",
|
||||
"REGISTRY": "Registry",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SYSTEM_VOLUMES": "System Volumes",
|
||||
"GARBAGE_COLLECTION": "Garbage Collection",
|
||||
"PURGE_AUDIT": "Purge Audit",
|
||||
"JOBSERVICE_MONITOR": "Job Service Monitor",
|
||||
"TAG_RETENTION": "Tag Retention",
|
||||
"SCANNER": "Scanner",
|
||||
"LABEL": "Label",
|
||||
"EXPORT_CVE": "Export CVE",
|
||||
"SECURITY_HUB": "Security Hub",
|
||||
"CATALOG": "Catalog",
|
||||
"METADATA": "Project Metadata",
|
||||
"REPOSITORY": "Repository",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "Scan",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "Accessory",
|
||||
"ARTIFACT_ADDITION": "Artifact Addition",
|
||||
"ARTIFACT_LABEL": "Artifact Label",
|
||||
"PREHEAT_POLICY": "Preheat Policy",
|
||||
"IMMUTABLE_TAG": "Immutable Tag",
|
||||
"LOG": "Log",
|
||||
"NOTIFICATION_POLICY": "Notification Policy",
|
||||
"BACK": "Back",
|
||||
"NEXT": "Next",
|
||||
"FINISH": "Finish",
|
||||
"BASIC_INFO": "Basic Information",
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "Éditer",
|
||||
|
@ -376,7 +376,45 @@
|
||||
"INVALID_VALUE": "Valor do tempo de expiração inválido.",
|
||||
"NEVER_EXPIRED": "Não expirar nunca",
|
||||
"NAME_PREFIX": "Prefixo para contas de automação",
|
||||
"NAME_PREFIX_REQUIRED": "Prefixo é obrigatório."
|
||||
"NAME_PREFIX_REQUIRED": "Prefixo é obrigatório.",
|
||||
"UPDATE": "Update",
|
||||
"AUDIT_LOG": "Audit Log",
|
||||
"PREHEAT_INSTANCE": "Preheat Instance",
|
||||
"PROJECT": "Project",
|
||||
"REPLICATION_POLICY": "Replication Policy",
|
||||
"REPLICATION": "Replication",
|
||||
"REPLICATION_ADAPTER": "Replication Adapter",
|
||||
"REGISTRY": "Registry",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SYSTEM_VOLUMES": "System Volumes",
|
||||
"GARBAGE_COLLECTION": "Garbage Collection",
|
||||
"PURGE_AUDIT": "Purge Audit",
|
||||
"JOBSERVICE_MONITOR": "Job Service Monitor",
|
||||
"TAG_RETENTION": "Tag Retention",
|
||||
"SCANNER": "Scanner",
|
||||
"LABEL": "Label",
|
||||
"EXPORT_CVE": "Export CVE",
|
||||
"SECURITY_HUB": "Security Hub",
|
||||
"CATALOG": "Catalog",
|
||||
"METADATA": "Project Metadata",
|
||||
"REPOSITORY": "Repository",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "Scan",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "Accessory",
|
||||
"ARTIFACT_ADDITION": "Artifact Addition",
|
||||
"ARTIFACT_LABEL": "Artifact Label",
|
||||
"PREHEAT_POLICY": "Preheat Policy",
|
||||
"IMMUTABLE_TAG": "Immutable Tag",
|
||||
"LOG": "Log",
|
||||
"NOTIFICATION_POLICY": "Notification Policy",
|
||||
"BACK": "Back",
|
||||
"NEXT": "Next",
|
||||
"FINISH": "Finish",
|
||||
"BASIC_INFO": "Basic Information",
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Grupo",
|
||||
|
@ -378,7 +378,45 @@
|
||||
"INVALID_VALUE": "The value of the expiration time is invalid",
|
||||
"NEVER_EXPIRED": "Never Expired",
|
||||
"NAME_PREFIX": "Robot Name Prefix",
|
||||
"NAME_PREFIX_REQUIRED": "Robot name prefix is required"
|
||||
"NAME_PREFIX_REQUIRED": "Robot name prefix is required",
|
||||
"UPDATE": "Update",
|
||||
"AUDIT_LOG": "Audit Log",
|
||||
"PREHEAT_INSTANCE": "Preheat Instance",
|
||||
"PROJECT": "Project",
|
||||
"REPLICATION_POLICY": "Replication Policy",
|
||||
"REPLICATION": "Replication",
|
||||
"REPLICATION_ADAPTER": "Replication Adapter",
|
||||
"REGISTRY": "Registry",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SYSTEM_VOLUMES": "System Volumes",
|
||||
"GARBAGE_COLLECTION": "Garbage Collection",
|
||||
"PURGE_AUDIT": "Purge Audit",
|
||||
"JOBSERVICE_MONITOR": "Job Service Monitor",
|
||||
"TAG_RETENTION": "Tag Retention",
|
||||
"SCANNER": "Scanner",
|
||||
"LABEL": "Label",
|
||||
"EXPORT_CVE": "Export CVE",
|
||||
"SECURITY_HUB": "Security Hub",
|
||||
"CATALOG": "Catalog",
|
||||
"METADATA": "Project Metadata",
|
||||
"REPOSITORY": "Repository",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "Scan",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "Accessory",
|
||||
"ARTIFACT_ADDITION": "Artifact Addition",
|
||||
"ARTIFACT_LABEL": "Artifact Label",
|
||||
"PREHEAT_POLICY": "Preheat Policy",
|
||||
"IMMUTABLE_TAG": "Immutable Tag",
|
||||
"LOG": "Log",
|
||||
"NOTIFICATION_POLICY": "Notification Policy",
|
||||
"BACK": "Back",
|
||||
"NEXT": "Next",
|
||||
"FINISH": "Finish",
|
||||
"BASIC_INFO": "Basic Information",
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "DÜZENLE",
|
||||
|
@ -377,7 +377,45 @@
|
||||
"INVALID_VALUE": "无效的过期日期",
|
||||
"NEVER_EXPIRED": "永不过期",
|
||||
"NAME_PREFIX": "机器人账户名称前缀",
|
||||
"NAME_PREFIX_REQUIRED": "机器人账户名称前缀为必填项"
|
||||
"NAME_PREFIX_REQUIRED": "机器人账户名称前缀为必填项",
|
||||
"UPDATE": "更新",
|
||||
"AUDIT_LOG": "审核日志",
|
||||
"PREHEAT_INSTANCE": "预热实例",
|
||||
"PROJECT": "项目",
|
||||
"REPLICATION_POLICY": "镜像复制策略",
|
||||
"REPLICATION": "镜像复制",
|
||||
"REPLICATION_ADAPTER": "镜像复制适配器",
|
||||
"REGISTRY": "镜像库",
|
||||
"SCAN_ALL": "扫描全部",
|
||||
"SYSTEM_VOLUMES": "系统卷",
|
||||
"GARBAGE_COLLECTION": "垃圾回收",
|
||||
"PURGE_AUDIT": "日志清除",
|
||||
"JOBSERVICE_MONITOR": "任务监视器",
|
||||
"TAG_RETENTION": "Tag 保留",
|
||||
"SCANNER": "扫描器",
|
||||
"LABEL": "标签",
|
||||
"EXPORT_CVE": "导出 CVE",
|
||||
"SECURITY_HUB": "安全中心",
|
||||
"CATALOG": "镜像目录",
|
||||
"METADATA": "项目元数据",
|
||||
"REPOSITORY": "仓库",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "扫描",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "附件",
|
||||
"ARTIFACT_ADDITION": "Artifact 额外信息",
|
||||
"ARTIFACT_LABEL": "Artifact 标签",
|
||||
"PREHEAT_POLICY": "预热策略",
|
||||
"IMMUTABLE_TAG": "不可变 Tag",
|
||||
"LOG": "日志",
|
||||
"NOTIFICATION_POLICY": "Webhook 策略",
|
||||
"BACK": "上一步",
|
||||
"NEXT": "下一步",
|
||||
"FINISH": "完成",
|
||||
"BASIC_INFO": "基本信息",
|
||||
"SELECT_PERMISSIONS": "选择权限",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "选择系统权限",
|
||||
"SELECT_PROJECT_PERMISSIONS": "选择项目权限"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "编辑",
|
||||
|
@ -377,7 +377,45 @@
|
||||
"INVALID_VALUE": "無效的過期日期",
|
||||
"NEVER_EXPIRED": "永不過期",
|
||||
"NAME_PREFIX": "機器人名稱前綴",
|
||||
"NAME_PREFIX_REQUIRED": "機器人名稱前綴為必填項目"
|
||||
"NAME_PREFIX_REQUIRED": "機器人名稱前綴為必填項目",
|
||||
"UPDATE": "Update",
|
||||
"AUDIT_LOG": "Audit Log",
|
||||
"PREHEAT_INSTANCE": "Preheat Instance",
|
||||
"PROJECT": "Project",
|
||||
"REPLICATION_POLICY": "Replication Policy",
|
||||
"REPLICATION": "Replication",
|
||||
"REPLICATION_ADAPTER": "Replication Adapter",
|
||||
"REGISTRY": "Registry",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SYSTEM_VOLUMES": "System Volumes",
|
||||
"GARBAGE_COLLECTION": "Garbage Collection",
|
||||
"PURGE_AUDIT": "Purge Audit",
|
||||
"JOBSERVICE_MONITOR": "Job Service Monitor",
|
||||
"TAG_RETENTION": "Tag Retention",
|
||||
"SCANNER": "Scanner",
|
||||
"LABEL": "Label",
|
||||
"EXPORT_CVE": "Export CVE",
|
||||
"SECURITY_HUB": "Security Hub",
|
||||
"CATALOG": "Catalog",
|
||||
"METADATA": "Project Metadata",
|
||||
"REPOSITORY": "Repository",
|
||||
"ARTIFACT": "Artifact",
|
||||
"SCAN": "Scan",
|
||||
"TAG": "Tag",
|
||||
"ACCESSORY": "Accessory",
|
||||
"ARTIFACT_ADDITION": "Artifact Addition",
|
||||
"ARTIFACT_LABEL": "Artifact Label",
|
||||
"PREHEAT_POLICY": "Preheat Policy",
|
||||
"IMMUTABLE_TAG": "Immutable Tag",
|
||||
"LOG": "Log",
|
||||
"NOTIFICATION_POLICY": "Notification Policy",
|
||||
"BACK": "Back",
|
||||
"NEXT": "Next",
|
||||
"FINISH": "Finish",
|
||||
"BASIC_INFO": "Basic Information",
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "編輯",
|
||||
|
Loading…
Reference in New Issue
Block a user