mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-23 07:11:36 +01:00
Merge pull request #13662 from AllForNothing/robot
Add system robot account UI
This commit is contained in:
commit
cf9b73d9a0
@ -47,6 +47,7 @@ import { HarborLibraryModule } from "../lib/harbor-library.module";
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { AllPipesModule } from './all-pipes/all-pipes.module';
|
||||
import { DistributionModule } from './distribution/distribution.module';
|
||||
import { SystemRobotAccountsModule } from './system-robot-accounts/system-robot-accounts.module';
|
||||
registerLocaleData(zh, 'zh-cn');
|
||||
registerLocaleData(es, 'es-es');
|
||||
registerLocaleData(localeFr, 'fr-fr');
|
||||
@ -88,6 +89,7 @@ export function getCurrentLanguage(translateService: TranslateService) {
|
||||
HarborLibraryModule,
|
||||
AllPipesModule,
|
||||
DistributionModule,
|
||||
SystemRobotAccountsModule
|
||||
],
|
||||
exports: [
|
||||
],
|
||||
|
@ -42,6 +42,10 @@
|
||||
<a clrVerticalNavLink routerLink="/harbor/users" routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLink="/harbor/robot-accounts" routerLinkActive="active">
|
||||
<clr-icon shape="robot-head" clrVerticalNavIcon></clr-icon>
|
||||
{{"SYSTEM_ROBOT.ROBOT_ACCOUNT_NAV" | translate}}
|
||||
</a>
|
||||
<a *ngIf='isLdapMode || isHttpAuthMode || isOidcMode' clrVerticalNavLink
|
||||
routerLink="/harbor/groups" routerLinkActive="active">
|
||||
|
@ -21,7 +21,6 @@ import { ConfigurationService } from "./config.service";
|
||||
import { ConfirmMessageHandler } from "./config.msg.utils";
|
||||
import { ConfigurationAuthComponent } from "./auth/config-auth.component";
|
||||
import { ConfigurationEmailComponent } from "./email/config-email.component";
|
||||
import { RobotApiRepository } from "../project/robot-account/robot.api.repository";
|
||||
import { ConfigurationScannerComponent } from "./scanner/config-scanner.component";
|
||||
import { NewScannerModalComponent } from "./scanner/new-scanner-modal/new-scanner-modal.component";
|
||||
import { NewScannerFormComponent } from "./scanner/new-scanner-form/new-scanner-form.component";
|
||||
@ -44,7 +43,6 @@ import { ScannerMetadataComponent } from "./scanner/scanner-metadata/scanner-met
|
||||
providers: [
|
||||
ConfigurationService,
|
||||
ConfirmMessageHandler,
|
||||
RobotApiRepository,
|
||||
ConfigScannerService,
|
||||
]
|
||||
})
|
||||
|
@ -18,6 +18,27 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { CookieModule } from 'ngx-cookie';
|
||||
import { MarkdownModule } from 'ngx-markdown';
|
||||
import { ClarityIconsApi } from '@clr/icons/clr-icons-api';
|
||||
|
||||
// ClarityIcons is publicly accessible from the browser's window object.
|
||||
declare const ClarityIcons: ClarityIconsApi;
|
||||
|
||||
// Add custom icons to ClarityIcons
|
||||
// Add robot head icon
|
||||
ClarityIcons.add({"robot-head": `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36">
|
||||
<defs><style>.cls-1{fill:none;}</style></defs><g id="Layer_2" data-name="Layer 2">
|
||||
<circle cx="12.62" cy="18.6" r="1.5"/><circle cx="23.5" cy="18.5" r="1.5"/>
|
||||
<path d="M22,28H14a1,1,0,0,1,0-2h8a1,1,0,0,1,0,2Z"/>
|
||||
<path d="M35,25.22a1,1,0,0,1-1-1V19.38a1,1,0,1,1,2,0v4.84A1,1,0,0,1,35,25.22Z"/>
|
||||
<path d="M1,25a1,1,0,0,1-1-1V19a1,1,0,0,1,2,0v5A1,1,0,0,1,1,25Z"/>
|
||||
<path d="M19,8.26A3.26,3.26,0,1,1,22.26,5,3.26,3.26,0,0,1,19,8.26Zm0-4.92A1.66,1.66,0,1,0,20.66,5,1.67,1.67,0,0,0,19,3.34Z"/>
|
||||
<path d="M29.1,10.49a1,1,0,0,0-.86-.49H20V7.58H18V12h9.67A19.51,19.51,0,0,1,30,21.42,
|
||||
19.06,19.06,0,0,1,27,32H9.05A19.06,19.06,0,0,1,6,21.42,
|
||||
19.51,19.51,0,0,1,8.33,12H16V10H7.76a1,1,0,0,0-.86.49A21.18,
|
||||
21.18,0,0,0,4,21.42,21,21,0,0,0,7.71,33.58a1,1,0,0,0,.81.42h19a1,1,0,0,0,
|
||||
.81-.42A21,21,0,0,0,32,21.42,21.18,21.18,0,0,0,29.1,10.49Z"/>
|
||||
<rect class="cls-1" width="36" height="36"/></g></svg>`});
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -67,6 +67,7 @@ import { DistributionInstancesComponent } from './distribution/distribution-inst
|
||||
import { PolicyComponent } from './project/p2p-provider/policy/policy.component';
|
||||
import { TaskListComponent } from './project/p2p-provider/task-list/task-list.component';
|
||||
import { P2pProviderComponent } from './project/p2p-provider/p2p-provider.component';
|
||||
import { SystemRobotAccountsComponent } from './system-robot-accounts/system-robot-accounts.component';
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
@ -112,6 +113,11 @@ const harborRoutes: Routes = [
|
||||
component: UserComponent,
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'robot-accounts',
|
||||
component: SystemRobotAccountsComponent,
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'groups',
|
||||
component: GroupComponent,
|
||||
|
@ -26,7 +26,6 @@ import { MemberComponent } from './member/member.component';
|
||||
import { AddMemberComponent } from './member/add-member/add-member.component';
|
||||
import { AddGroupComponent } from './member/add-group/add-group.component';
|
||||
import { MemberService } from './member/member.service';
|
||||
import { RobotService } from './robot-account/robot-account.service';
|
||||
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
|
||||
import { HelmChartModule } from './helm-chart/helm-chart.module';
|
||||
import { RobotAccountComponent } from './robot-account/robot-account.component';
|
||||
@ -78,7 +77,7 @@ import { P2pProviderComponent } from './p2p-provider/p2p-provider.component';
|
||||
HelmChartModule,
|
||||
SummaryModule,
|
||||
TagFeatureIntegrationModule,
|
||||
AllPipesModule
|
||||
AllPipesModule,
|
||||
],
|
||||
declarations: [
|
||||
ProjectComponent,
|
||||
@ -120,12 +119,11 @@ import { P2pProviderComponent } from './p2p-provider/p2p-provider.component';
|
||||
PolicyComponent,
|
||||
AddP2pPolicyComponent,
|
||||
TaskListComponent,
|
||||
P2pProviderComponent
|
||||
P2pProviderComponent,
|
||||
],
|
||||
exports: [ProjectComponent, ListProjectComponent],
|
||||
providers: [
|
||||
MemberService,
|
||||
RobotService,
|
||||
WebhookService,
|
||||
ConfigScannerService,
|
||||
ArtifactDefaultService,
|
||||
|
@ -1,163 +1,108 @@
|
||||
<clr-modal [(clrModalOpen)]="addRobotOpened"
|
||||
[clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<h3 class="modal-title">{{'ROBOT_ACCOUNT.CREAT_ROBOT_ACCOUNT' | translate}}</h3>
|
||||
<inline-alert #copyAlert class="modal-title"></inline-alert>
|
||||
<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">
|
||||
<form #robotForm="ngForm">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<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">
|
||||
<section class="form-block">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 permission permission-dark">
|
||||
<label class="col-md-3 required">
|
||||
{{'ROBOT_ACCOUNT.NAME' | translate}}
|
||||
</label>
|
||||
<!-- name -->
|
||||
<div class="clr-form-control">
|
||||
<label for="name" class="clr-control-label required">{{'P2P_PROVIDER.NAME' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="((name.dirty || name.touched) && name.invalid) || isNameExisting">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input"
|
||||
[disabled]="loading || isEditMode"
|
||||
type="text" id="name"
|
||||
[(ngModel)]="systemRobot.name"
|
||||
required
|
||||
pattern='[^" ~#$%]+'
|
||||
maxLengthExt="255"
|
||||
autocomplete="off"
|
||||
size="30" name="name" #name="ngModel" (input)="inputName()">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkNameOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="((name.dirty || name.touched) && name.invalid) || isNameExisting">
|
||||
<span *ngIf="!((name.dirty || name.touched) && name.invalid) && isNameExisting">{{'ROBOT_ACCOUNT.ACCOUNT_EXISTING' | translate}}</span>
|
||||
<span *ngIf="(name.dirty || name.touched) && name.invalid">{{ 'ROBOT_ACCOUNT.ROBOT_NAME' | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
<div class="clr-col padding-left-0">
|
||||
<div class="clr-control-container" [class.clr-error]="!isRobotNameValid">
|
||||
</div>
|
||||
<!-- expiration -->
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label required">{{"SYSTEM_ROBOT.EXPIRATION_TIME" | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{"SYSTEM_ROBOT.EXPIRATION_TIME_EXPLAIN" | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<div class="clr-control-container" [class.clr-error]="((expiration.dirty || expiration.touched) && expiration.invalid) || isExpirationInvalid()">
|
||||
<div class="input-width flex">
|
||||
<div class="clr-select-wrapper">
|
||||
<select [ngModelOptions]="{standalone: true}" (change)="changeExpirationType()" [(ngModel)]="expirationType" id="expiration-type" class="clr-select">
|
||||
<option value="days">{{"SYSTEM_ROBOT.EXPIRATION_DAYS" | translate}}</option>
|
||||
<option value="never">{{"SYSTEM_ROBOT.EXPIRATION_NEVER" | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input input-width" type="text"
|
||||
[(ngModel)]="robot.name"
|
||||
size="30"
|
||||
name="robot_name"
|
||||
id="robot_name"
|
||||
#robotName="ngModel"
|
||||
required
|
||||
pattern='[^" ~#$%]+'
|
||||
maxLengthExt="255"
|
||||
<input (input)="inputExpiration()" class="clr-input expiration-width" name="expiration" type="text"
|
||||
#expiration="ngModel"
|
||||
autocomplete="off"
|
||||
(keyup)='handleValidation()'>
|
||||
[(ngModel)]="systemRobot.duration" required
|
||||
pattern="^[\-1-9]{1}[0-9]*$" id="robotTokenExpiration" size="20"/>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isRobotNameValid" class="tooltip-content">
|
||||
{{ nameTooltipText | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
<clr-control-error *ngIf="((expiration.dirty || expiration.touched) && expiration.invalid)|| isExpirationInvalid()">
|
||||
{{"SYSTEM_ROBOT.EXPIRATION_REQUIRED" | translate}}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row mt-1">
|
||||
<div class="clr-col-4 permission permission-dark">
|
||||
<label class="col-md-3">{{'ROBOT_ACCOUNT.EXPIRES_AT' | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'ROBOT_ACCOUNT.EXPIRATION_TOOLTIP' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</div>
|
||||
<div class="clr-col padding-left-0 date">
|
||||
<input class="input-width-date" type="date" readonly clrDate name="expiresAt"
|
||||
[(clrDate)]="expiresDate" placeholder="{{expiresDatePlaceholder}}">
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox name="neverExpired" [(ngModel)]="isNeverExpired" (change)="switch()" />
|
||||
<label>{{"ROBOT_ACCOUNT.NEVER_EXPIRED" | translate}}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row mt-1">
|
||||
<div class="clr-col-4 permission permission-dark">
|
||||
<label class="col-md-3">{{'REPLICATION.DESCRIPTION' |translate}}</label>
|
||||
</div>
|
||||
<div class="clr-col padding-left-0">
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input input-width" type="text" size="255"
|
||||
[(ngModel)]="robot.description"
|
||||
name="robot_desc" id="robot_desc">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row mt-1">
|
||||
<div class="clr-col-4 permission permission-dark">
|
||||
<label class="col-md-3">
|
||||
{{'ROBOT_ACCOUNT.PERMISSIONS' | translate}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="clr-col padding-left-0">
|
||||
<table class="table table-noborder m-0 w-90">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="left">{{'ROBOT_ACCOUNT.PUSH' | translate}}</th>
|
||||
<th class="left">{{'ROBOT_ACCOUNT.PULL' | translate}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<span>{{'ROBOT_ACCOUNT.PERMISSIONS_ARTIFACT' | translate}}</span>
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'ROBOT_ACCOUNT.PULL_IS_MUST' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" name="image-permission-push"
|
||||
[(ngModel)]="imagePermissionPush" clrCheckbox>
|
||||
</td>
|
||||
<td class="clr-form-control-disabled">
|
||||
<input disabled type="checkbox" name="image-permission-pull"
|
||||
[(ngModel)]="imagePermissionPull" clrCheckbox>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="withHelmChart">
|
||||
<td class="left">{{'ROBOT_ACCOUNT.PERMISSIONS_HELMCHART' | translate}}</td>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
[(ngModel)]="robot.access.isPushChart"
|
||||
name="helm-permission" clrCheckbox>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
[(ngModel)]="robot.access.isPullChart"
|
||||
name="helm-permission" clrCheckbox>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- 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 clrPosition="bottom-left" *clrIfOpen>
|
||||
<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">
|
||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
<button type="button" [disabled]="shouldDisable" class="btn btn-primary"
|
||||
(click)="onSubmit()">{{'BUTTON.SAVE'
|
||||
| translate}}</button>
|
||||
<span>
|
||||
<button (click)="cancel()" id="system-robot-cancel" type="button" class="btn btn-outline">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
<button [disabled]="disabled()|| checkNameOnGoing" [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-modal [(clrModalOpen)]="copyToken" class="copy-token"
|
||||
[clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<div class="modal-title">
|
||||
<h3 class="modal-title">
|
||||
<clr-icon class="alert-icon success-icon" shape="check-circle" size="50"></clr-icon>
|
||||
{{ createSuccess | translate}}</h3>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<div class="alert-icon-wrapper">
|
||||
<clr-icon class="alert-icon" shape="info-circle"></clr-icon>
|
||||
</div>
|
||||
<span class="alert-text">{{'ROBOT_ACCOUNT.ALERT_TEXT' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<section class="form-block show-info">
|
||||
<div class="form-group robot-name">
|
||||
<label class="form-group-label-override">{{'ROBOT_ACCOUNT.NAME'
|
||||
| translate}}</label>
|
||||
<span>{{robotAccount}}</span>
|
||||
</div>
|
||||
<div class="form-group robot-token">
|
||||
<label class="form-group-label-override">{{'ROBOT_ACCOUNT.TOKEN' |
|
||||
translate}}</label>
|
||||
<hbr-copy-input (onCopySuccess)="onCpSuccess($event)"
|
||||
(onCopyError)="onCpError($event)" inputSize="50" headerTitle=""
|
||||
defaultValue="{{robotToken}}" class="copy-input"></hbr-copy-input>
|
||||
</div>
|
||||
<a [href]="downLoadHref" [download]="downLoadFileName"><button class="btn mr-0" (click)="closeModal()">{{'ROBOT_ACCOUNT.EXPORT_TO_FILE' | translate}}</button></a>
|
||||
</section>
|
||||
</div>
|
||||
</clr-modal>
|
@ -1,34 +1,3 @@
|
||||
.rule-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-width {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.copy-token {
|
||||
.success-icon {
|
||||
color: #318700;
|
||||
}
|
||||
.show-info {
|
||||
.robot-name {
|
||||
margin: 30px 0;
|
||||
|
||||
label {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
.robot-token {
|
||||
margin-bottom: 20px;
|
||||
label {
|
||||
margin-right: 24px;
|
||||
}
|
||||
.copy-input {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding-left-0 {
|
||||
padding-left: 0;
|
||||
}
|
||||
@ -52,4 +21,28 @@
|
||||
}
|
||||
.input-width-date {
|
||||
width: 265px;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.input-width {
|
||||
width: 232px;
|
||||
}
|
||||
.expiration-width {
|
||||
width: 80px;
|
||||
}
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
color: green;
|
||||
}
|
||||
.dropdown-per {
|
||||
margin-left: -12px;
|
||||
}
|
||||
.mt-description {
|
||||
width: 238px;
|
||||
}
|
||||
.mt-8px {
|
||||
margin-top: 8px !important;
|
||||
}
|
@ -1,55 +1,48 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { AddRobotComponent } from './add-robot.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RobotService } from "../robot-account.service";
|
||||
import { of } from "rxjs";
|
||||
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { AppConfigService } from "../../../services/app-config.service";
|
||||
import { ErrorHandler } from "../../../../lib/utils/error-handler";
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { RobotService } from "../../../../../ng-swagger-gen/services/robot.service";
|
||||
import { OperationService } from "../../../../lib/components/operation/operation.service";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
|
||||
describe('AddRobotComponent', () => {
|
||||
let component: AddRobotComponent;
|
||||
let fixture: ComponentFixture<AddRobotComponent>;
|
||||
let fakeRobotService = {
|
||||
listRobotAccount: function () {
|
||||
return of([{
|
||||
name: "robot$" + 1
|
||||
}, {
|
||||
name: "abc"
|
||||
}]);
|
||||
const fakedRobotService = {
|
||||
ListRobot() {
|
||||
return of([]).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
let fakeMessageHandlerService = {
|
||||
showSuccess: function() {}
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
getConfig: function() {
|
||||
return {
|
||||
with_chartmuseum: true
|
||||
};
|
||||
const fakedMessageHandlerService = {
|
||||
showSuccess() {
|
||||
},
|
||||
error() {
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AddRobotComponent],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
TranslateModule.forRoot(),
|
||||
FormsModule
|
||||
FormsModule,
|
||||
SharedModule
|
||||
],
|
||||
providers: [
|
||||
TranslateService,
|
||||
ErrorHandler,
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: RobotService, useValue: fakeRobotService }
|
||||
OperationService,
|
||||
{ provide: RobotService, useValue: fakedRobotService },
|
||||
{ provide: MessageHandlerService, useValue: fakedMessageHandlerService },
|
||||
],
|
||||
schemas: [
|
||||
NO_ERRORS_SCHEMA
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
@ -2,254 +2,278 @@ import {
|
||||
Component,
|
||||
OnInit,
|
||||
Input,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectorRef
|
||||
OnDestroy, Output, EventEmitter, ViewChild,
|
||||
} from "@angular/core";
|
||||
import { Robot } from "../robot";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { Subject } from "rxjs";
|
||||
import { debounceTime, finalize } from "rxjs/operators";
|
||||
import { RobotService } from "../robot-account.service";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { debounceTime, distinctUntilChanged, filter, finalize, switchMap } from "rxjs/operators";
|
||||
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
ExpirationType,
|
||||
FrontAccess, INITIAL_ACCESSES, PermissionsKinds
|
||||
} from "../../../system-robot-accounts/system-robot-util";
|
||||
import { Robot } from "../../../../../ng-swagger-gen/models/robot";
|
||||
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
|
||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||
import { AppConfigService } from "../../../services/app-config.service";
|
||||
import { ErrorHandler } from "../../../../lib/utils/error-handler";
|
||||
const ONE_THOUSAND: number = 1000;
|
||||
const NEVER_EXPIRED: number = -1;
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import { Subject, Subscription } from "rxjs";
|
||||
import { RobotService } from "../../../../../ng-swagger-gen/services/robot.service";
|
||||
import { OperationService } from "../../../../lib/components/operation/operation.service";
|
||||
import { clone } from "../../../../lib/utils/utils";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../../../lib/components/operation/operate";
|
||||
import { errorHandler } from "../../../../lib/utils/shared/shared.utils";
|
||||
import { Access } from "../../../../../ng-swagger-gen/models/access";
|
||||
|
||||
@Component({
|
||||
selector: "add-robot",
|
||||
templateUrl: "./add-robot.component.html",
|
||||
styleUrls: ["./add-robot.component.scss"]
|
||||
})
|
||||
export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
addRobotOpened: boolean;
|
||||
copyToken: boolean;
|
||||
robotToken: string;
|
||||
robotAccount: string;
|
||||
downLoadFileName: string = '';
|
||||
downLoadHref: SafeUrl = '';
|
||||
isSubmitOnGoing = false;
|
||||
closable: boolean = false;
|
||||
staticBackdrop: boolean = true;
|
||||
createSuccess: string;
|
||||
isRobotNameValid: boolean = true;
|
||||
checkOnGoing: boolean = false;
|
||||
robot: Robot = new Robot();
|
||||
robotNameChecker: Subject<string> = new Subject<string>();
|
||||
nameTooltipText = "ROBOT_ACCOUNT.ROBOT_NAME";
|
||||
robotForm: NgForm;
|
||||
imagePermissionPush: boolean = true;
|
||||
imagePermissionPull: boolean = true;
|
||||
withHelmChart: boolean;
|
||||
@Input() projectId: number;
|
||||
@Input() projectName: string;
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
@ViewChild("robotForm", {static: true}) currentForm: NgForm;
|
||||
@ViewChild("copyAlert") copyAlert: InlineAlertComponent;
|
||||
private _expiresDate: Date;
|
||||
isNeverExpired: boolean = false;
|
||||
expiresDatePlaceholder: string = ' ';
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
isEditMode: boolean = false;
|
||||
originalRobotForEdit: Robot;
|
||||
@Output()
|
||||
addSuccess: EventEmitter<Robot> = new EventEmitter<Robot>();
|
||||
addRobotOpened: boolean = false;
|
||||
systemRobot: 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;
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
constructor(
|
||||
private robotService: RobotService,
|
||||
private translate: TranslateService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private appConfigService: AppConfigService
|
||||
|
||||
private msgHandler: MessageHandlerService,
|
||||
private operationService: OperationService
|
||||
) {}
|
||||
ngOnInit(): void {
|
||||
this.withHelmChart = this.appConfigService.getConfig().with_chartmuseum;
|
||||
|
||||
this.robotNameChecker.pipe(debounceTime(800)).subscribe((name: string) => {
|
||||
let cont = this.currentForm.controls["robot_name"];
|
||||
if (cont) {
|
||||
this.isRobotNameValid = cont.valid;
|
||||
if (this.isRobotNameValid) {
|
||||
this.checkOnGoing = true;
|
||||
this.robotService
|
||||
.listRobotAccount(this.projectId)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.checkOnGoing = false;
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
if (response && response.length) {
|
||||
if (
|
||||
response.find(target => {
|
||||
return target.name === "robot$" + cont.value;
|
||||
})
|
||||
) {
|
||||
this.isRobotNameValid = false;
|
||||
this.nameTooltipText = "ROBOT_ACCOUNT.ACCOUNT_EXISTING";
|
||||
}
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.nameTooltipText = "ROBOT_ACCOUNT.ROBOT_NAME";
|
||||
}
|
||||
}
|
||||
});
|
||||
this.subscribeName();
|
||||
}
|
||||
|
||||
openAddRobotModal(): void {
|
||||
if (this.isSubmitOnGoing) {
|
||||
return;
|
||||
ngOnDestroy() {
|
||||
if (this._nameSubscription) {
|
||||
this._nameSubscription.unsubscribe();
|
||||
this._nameSubscription = null;
|
||||
}
|
||||
this.robot.name = "";
|
||||
this.robot.description = "";
|
||||
this.addRobotOpened = true;
|
||||
this.imagePermissionPush = true;
|
||||
this.imagePermissionPull = true;
|
||||
this.isRobotNameValid = true;
|
||||
this.robot = new Robot();
|
||||
this.nameTooltipText = "ROBOT_ACCOUNT.ROBOT_NAME";
|
||||
this.isNeverExpired = false;
|
||||
this.expiresDate = null;
|
||||
this.expiresDatePlaceholder = ' ';
|
||||
this.copyAlert.close();
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
subscribeName() {
|
||||
if (!this._nameSubscription) {
|
||||
this._nameSubscription = this._nameSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
if (this.isEditMode && this.originalRobotForEdit && this.originalRobotForEdit.name === name) {
|
||||
return false;
|
||||
}
|
||||
return name.length > 0;
|
||||
}),
|
||||
switchMap((name) => {
|
||||
this.isNameExisting = false;
|
||||
this.checkNameOnGoing = true;
|
||||
return this.robotService.ListRobot({
|
||||
q: encodeURIComponent(`Level=${PermissionsKinds.PROJECT},ProjectID=${this.projectId},name=${name}`)
|
||||
}).pipe(finalize(() => this.checkNameOnGoing = false));
|
||||
}))
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isNameExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
isExpirationInvalid(): boolean {
|
||||
return this.systemRobot.duration < -1;
|
||||
}
|
||||
inputExpiration() {
|
||||
if (+this.systemRobot.duration === -1) {
|
||||
this.expirationType = ExpirationType.NEVER;
|
||||
} else {
|
||||
this.expirationType = ExpirationType.DAYS;
|
||||
}
|
||||
}
|
||||
changeExpirationType() {
|
||||
if (this.expirationType === ExpirationType.DAYS) {
|
||||
this.systemRobot.duration = null;
|
||||
}
|
||||
if (this.expirationType === ExpirationType.NEVER) {
|
||||
this.systemRobot.duration = -1;
|
||||
}
|
||||
}
|
||||
inputName() {
|
||||
this._nameSubject.next(this.systemRobot.name);
|
||||
}
|
||||
cancel() {
|
||||
this.addRobotOpened = false;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.robotNameChecker.unsubscribe();
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.isSubmitOnGoing) {
|
||||
return;
|
||||
}
|
||||
// set value to robot.access.isPullImage and robot.access.isPushOrPullImage when submit
|
||||
if ( this.imagePermissionPush && this.imagePermissionPull) {
|
||||
this.robot.access.isPullImage = false;
|
||||
this.robot.access.isPushOrPullImage = true;
|
||||
} else {
|
||||
this.robot.access.isPullImage = true;
|
||||
this.robot.access.isPushOrPullImage = false;
|
||||
}
|
||||
if (this.isNeverExpired) {
|
||||
this.robot.expires_at = NEVER_EXPIRED;
|
||||
} else {
|
||||
if (this.expiresDate) {
|
||||
if (this.expiresDate <= new Date()) {
|
||||
this.copyAlert.showInlineError("ROBOT_ACCOUNT.INVALID_VALUE");
|
||||
return;
|
||||
} else {
|
||||
this.robot.expires_at = Math.floor(this.expiresDate.getTime() / ONE_THOUSAND);
|
||||
}
|
||||
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.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.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,
|
||||
});
|
||||
}
|
||||
open(isEditMode: boolean) {
|
||||
this.isEditMode = isEditMode;
|
||||
this.addRobotOpened = true;
|
||||
this.inlineAlertComponent.close();
|
||||
}
|
||||
disabled(): boolean {
|
||||
if (!this.isEditMode) {
|
||||
return !this.canAdd();
|
||||
}
|
||||
this.isSubmitOnGoing = true;
|
||||
this.robotService
|
||||
.addRobotAccount(
|
||||
this.projectId,
|
||||
this.robot,
|
||||
this.projectName
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.isSubmitOnGoing = false;
|
||||
this.robotToken = response.token;
|
||||
this.robotAccount = response.name;
|
||||
this.copyToken = true;
|
||||
this.create.emit(true);
|
||||
this.translate
|
||||
.get("ROBOT_ACCOUNT.CREATED_SUCCESS", { param: this.robotAccount })
|
||||
.subscribe((res: string) => {
|
||||
this.createSuccess = res;
|
||||
});
|
||||
this.addRobotOpened = false;
|
||||
// export to token file
|
||||
const downLoadUrl = `data:text/json;charset=utf-8, ${encodeURIComponent(JSON.stringify(response))}`;
|
||||
this.downLoadHref = this.sanitizer.bypassSecurityTrustUrl(downLoadUrl);
|
||||
this.downLoadFileName = `${response.name}.json`;
|
||||
},
|
||||
error => {
|
||||
this.isSubmitOnGoing = false;
|
||||
this.copyAlert.showInlineError(error);
|
||||
}
|
||||
);
|
||||
return !this.canEdit();
|
||||
}
|
||||
|
||||
isValid(): boolean {
|
||||
return (
|
||||
this.currentForm &&
|
||||
this.currentForm.valid &&
|
||||
!this.isSubmitOnGoing &&
|
||||
this.isRobotNameValid &&
|
||||
!this.checkOnGoing
|
||||
);
|
||||
}
|
||||
get shouldDisable(): boolean {
|
||||
if (this.robot && this.robot.access) {
|
||||
return (
|
||||
!this.isValid() ||
|
||||
(!this.robot.access.isPushOrPullImage && !this.robot.access.isPullImage
|
||||
&& !this.robot.access.isPullChart && !this.robot.access.isPushChart)
|
||||
);
|
||||
canAdd(): boolean {
|
||||
let flag = false;
|
||||
this.defaultAccesses.forEach( item => {
|
||||
if (item.checked) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
if (!flag) {
|
||||
return false;
|
||||
}
|
||||
return !this.robotForm.invalid;
|
||||
}
|
||||
|
||||
// Handle the form validation
|
||||
handleValidation(): void {
|
||||
let cont = this.currentForm.controls["robot_name"];
|
||||
if (cont) {
|
||||
this.robotNameChecker.next(cont.value);
|
||||
canEdit() {
|
||||
if (!this.canAdd()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onCpError($event: any): void {
|
||||
if (this.copyAlert) {
|
||||
this.copyAlert.showInlineError("PUSH_IMAGE.COPY_ERROR");
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if (this.systemRobot.duration != this.originalRobotForEdit.duration) {
|
||||
return true;
|
||||
}
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if (this.systemRobot.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;
|
||||
}
|
||||
|
||||
onCpSuccess($event: any): void {
|
||||
this.copyToken = false;
|
||||
this.translate
|
||||
.get("ROBOT_ACCOUNT.COPY_SUCCESS", { param: this.robotAccount })
|
||||
.subscribe((res: string) => {
|
||||
this.messageHandlerService.showSuccess(res);
|
||||
save() {
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
const robot: Robot = clone(this.systemRobot);
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
this.copyToken = false;
|
||||
}
|
||||
switch() {
|
||||
if (this.isNeverExpired) {
|
||||
this.expiresDate = null;
|
||||
this.translate.get('ROBOT_ACCOUNT.NEVER_EXPIRED').subscribe(value => {
|
||||
this.expiresDatePlaceholder = value;
|
||||
}
|
||||
});
|
||||
robot.permissions = [{
|
||||
namespace: this.projectName,
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
access: access
|
||||
}];
|
||||
if (this.isEditMode) {
|
||||
robot.disable = this.systemRobot.disable;
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = "SYSTEM_ROBOT.UPDATE_ROBOT";
|
||||
opeMessage.data.id = robot.id;
|
||||
opeMessage.state = OperationState.progressing;
|
||||
opeMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(opeMessage);
|
||||
this.robotService.UpdateRobot({
|
||||
robotId: this.originalRobotForEdit.id,
|
||||
robot
|
||||
}).subscribe( res => {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(null);
|
||||
this.addRobotOpened = false;
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
this.msgHandler.showSuccess("SYSTEM_ROBOT.UPDATE_ROBOT_SUCCESSFULLY");
|
||||
}, error => {
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||
this.inlineAlertComponent.showInlineError(error);
|
||||
});
|
||||
} else {
|
||||
this.expiresDatePlaceholder = ' ';
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = "SYSTEM_ROBOT.ADD_ROBOT";
|
||||
opeMessage.data.id = robot.id;
|
||||
opeMessage.state = OperationState.progressing;
|
||||
opeMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(opeMessage);
|
||||
this.robotService.CreateRobot({
|
||||
robot: robot
|
||||
}).subscribe( res => {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(res);
|
||||
this.addRobotOpened = false;
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
}, error => {
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
this.inlineAlertComponent.showInlineError(error);
|
||||
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
get expiresDate(): Date {
|
||||
return this._expiresDate;
|
||||
}
|
||||
set expiresDate(date: Date) {
|
||||
if (date) {
|
||||
this.isNeverExpired = false;
|
||||
}
|
||||
this._expiresDate = date;
|
||||
getAccessNum(access: FrontAccess[]): number {
|
||||
let count: number = 0;
|
||||
access.forEach(item => {
|
||||
if (item.checked) {
|
||||
count ++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@
|
||||
<div class="flex-xs-middle option-left">
|
||||
</div>
|
||||
<div class="flex-xs-middle option-right">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder='{{"ROBOT_ACCOUNT.FILTER_PLACEHOLDER" | translate}}'
|
||||
(filterEvt)="doSearch($event)" [currentValue]="searchRobot"></hbr-filter>
|
||||
<span class="refresh-btn" (click)="retrieve()">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder='{{"ROBOT_ACCOUNT.FILTER_PLACEHOLDER" | translate}}'></hbr-filter>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
@ -14,10 +13,10 @@
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-dg-action-bar>
|
||||
<button class="btn btn-secondary" [disabled]="!hasRobotCreatePermission" (click)="openAddRobotModal()">
|
||||
<button [disabled]="!hasRobotCreatePermission" [clrLoading]="addBtnState" class="btn btn-secondary" (click)="openNewRobotModal(false)">
|
||||
<span>
|
||||
<clr-icon shape="plus" size="16"></clr-icon> {{'ROBOT_ACCOUNT.NEW_ROBOT_ACCOUNT'
|
||||
| translate }}
|
||||
| translate }}
|
||||
</span>
|
||||
</button>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="btn
|
||||
@ -25,48 +24,86 @@
|
||||
<span>{{'MEMBER.ACTION' | translate}}<clr-icon shape="caret
|
||||
down"></clr-icon></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button clrDropdownItem [disabled]="!hasRobotUpdatePermission||!(selectedRow.length ==
|
||||
1)" (click)="changeAccountStatus(selectedRow)">
|
||||
<span *ngIf="selectedRow[0] && !selectedRow[0].disabled
|
||||
|| selectedRow.length!==1">{{'ROBOT_ACCOUNT.DISABLE_ACCOUNT'
|
||||
| translate}}</span>
|
||||
<span *ngIf="selectedRow.length == 1 && selectedRow[0]
|
||||
&& selectedRow[0].disabled">{{'ROBOT_ACCOUNT.ENABLE_ACCOUNT'
|
||||
| translate}}</span>
|
||||
<button [disabled]="!hasRobotReadPermission || !(selectedRows && selectedRows.length === 1) || !selectedRows[0].editable" clrDropdownItem (click)="openTokenModal()">
|
||||
<clr-icon shape="details" size="16"></clr-icon>
|
||||
<span id="system-robot-token">{{"SYSTEM_ROBOT.VIEW_SECRET" | translate}}</span>
|
||||
</button>
|
||||
<button [disabled]="!hasRobotUpdatePermission || !(selectedRows && selectedRows.length === 1) || !selectedRows[0].editable" clrDropdownItem (click)="openNewRobotModal(true)">
|
||||
<clr-icon shape="edit" size="16"></clr-icon>
|
||||
<span id="system-robot-edit">{{'BUTTON.EDIT' | translate}}</span>
|
||||
</button>
|
||||
<button *ngIf="selectedRows && selectedRows.length === 1 && selectedRows[0].disable" type="button" class="btn btn-secondary"
|
||||
(click)="disableOrEnable()"
|
||||
[disabled]="!hasRobotUpdatePermission ||!(selectedRows && selectedRows.length === 1 && selectedRows[0].disable) || !selectedRows[0].editable">
|
||||
<clr-icon size="16" shape="success-standard"></clr-icon>
|
||||
<span id="distribution-enable">{{'WEBHOOK.ENABLED_BUTTON' | translate}}</span>
|
||||
</button>
|
||||
<button *ngIf="!(selectedRows && selectedRows.length === 1 && selectedRows[0].disable)"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="disableOrEnable()"
|
||||
[disabled]="!hasRobotUpdatePermission ||!(selectedRows && selectedRows.length === 1 && !selectedRows[0].disable) || !selectedRows[0].editable">
|
||||
<clr-icon size="16" shape="ban"></clr-icon>
|
||||
<span id="distribution-disable">{{'WEBHOOK.DISABLED_BUTTON' | translate}}</span>
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button clrDropdownItem (click)="openDeleteRobotsDialog(selectedRow)" [disabled]="!hasRobotDeletePermission || !selectedRow.length">{{'ROBOT_ACCOUNT.DELETE'
|
||||
| translate}}</button>
|
||||
<button [disabled]="!hasRobotDeletePermission || !(selectedRows && selectedRows.length >=1)" clrDropdownItem (click)="openDeleteRobotsDialog()">
|
||||
<clr-icon shape="window-close" size="16"></clr-icon>
|
||||
<span id="system-robot-delete">{{'BUTTON.DELETE' | translate}}</span>
|
||||
</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</clr-dg-action-bar>
|
||||
<clr-datagrid [(clrDgSelected)]="selectedRow" [clrDgLoading]="loading">
|
||||
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRows">
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.ENABLED_STATE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{"SYSTEM_ROBOT.PERMISSION_COLUMN" | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.CREATETION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.EXPIRATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SYSTEM_ROBOT.EXPIRED_AT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{
|
||||
'SYSTEM_ROBOT.NOT_FOUND' | translate
|
||||
}}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let r of robots" [clrDgItem]="r">
|
||||
<clr-dg-cell>{{r.name}}</clr-dg-cell>
|
||||
<clr-dg-cell [ngSwitch]="r.disabled">
|
||||
<clr-dg-cell>
|
||||
{{r.name}}
|
||||
<span *ngIf="!r.editable" class="label label-warning ml-1">{{'SYSTEM_ROBOT.LEGACY' | translate}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell [ngSwitch]="r.disable">
|
||||
<clr-icon shape="check-circle" *ngSwitchCase="false" size="20" class="color-green"></clr-icon>
|
||||
<clr-icon shape="times-circle" *ngSwitchCase="true" size="16" class="color-red red-position"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.description}}</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>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.expires_at === -1?("ROBOT_ACCOUNT.NEVER_EXPIRED" | translate):(r.expires_at * 1000 | date: 'short')}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.description}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15">
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="currentPage" [clrDgTotalItems]="total">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span *ngIf="robots?.length">{{pagination.firstItem + 1}}
|
||||
<span *ngIf="total">{{pagination.firstItem + 1}}
|
||||
-
|
||||
{{pagination.lastItem +1 }} {{'ROBOT_ACCOUNT.OF' |
|
||||
translate}} </span>
|
||||
{{robots?.length}} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
|
||||
{{total}} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<add-robot [projectId]="projectId" [projectName]="projectName" (create)="createAccount($event)"></add-robot>
|
||||
</div>
|
||||
<add-robot [projectId]="projectId" [projectName]="projectName" (addSuccess)="addSuccess($event)"></add-robot>
|
||||
<view-token (refreshSuccess)="refresh()"></view-token>
|
@ -26,3 +26,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.permissions {
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.datagrid-host {
|
||||
position: inherit;
|
||||
}
|
@ -1,63 +1,135 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { of } from 'rxjs';
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { of, Subscription } from 'rxjs';
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { RobotService } from "./robot-account.service";
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { RobotAccountComponent } from './robot-account.component';
|
||||
import { UserPermissionService } from "../../../lib/services";
|
||||
import { ErrorHandler } from "../../../lib/utils/error-handler";
|
||||
import { OperationService } from "../../../lib/components/operation/operation.service";
|
||||
import { RobotService } from "../../../../ng-swagger-gen/services/robot.service";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { Robot } from "../../../../ng-swagger-gen/models/robot";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { Action, PermissionsKinds, Resource } from "../../system-robot-accounts/system-robot-util";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { HttpClientTestingModule } from "@angular/common/http/testing";
|
||||
|
||||
describe('RobotAccountComponent', () => {
|
||||
let component: RobotAccountComponent;
|
||||
let fixture: ComponentFixture<RobotAccountComponent>;
|
||||
let robotService = {
|
||||
listRobotAccount: function () {
|
||||
return of([]);
|
||||
const robot1: Robot = {
|
||||
id: 1,
|
||||
name: 'robot1',
|
||||
level: PermissionsKinds.PROJECT,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'tthf54hfth4545dfgd5g454grd54gd54g',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project1',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const robot2: Robot = {
|
||||
id: 2,
|
||||
name: 'robot2',
|
||||
level: PermissionsKinds.PROJECT,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'fsdf454654654fs6dfe',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project2',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const robot3: Robot = {
|
||||
id: 3,
|
||||
name: 'robot3',
|
||||
level: PermissionsKinds.PROJECT,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'fsdg48454fse84',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project3',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const mockUserPermissionService = {
|
||||
getPermission() {
|
||||
return of(true);
|
||||
}
|
||||
};
|
||||
let mockConfirmationDialogService = null;
|
||||
let mockUserPermissionService = {
|
||||
getPermission: function () {
|
||||
return 1;
|
||||
const fakedRobotService = {
|
||||
ListRobotResponse() {
|
||||
const res: HttpResponse<Array<Robot>> = new HttpResponse<Array<Robot>>({
|
||||
headers: new HttpHeaders({'x-total-count': '3'}),
|
||||
body: [robot1, robot2, robot3]
|
||||
});
|
||||
return of(res).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
let mockErrorHandler = {
|
||||
error: function () { }
|
||||
const fakedMessageHandlerService = {
|
||||
showSuccess() {
|
||||
},
|
||||
error() {
|
||||
}
|
||||
};
|
||||
let mockMessageHandlerService = null;
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
NO_ERRORS_SCHEMA
|
||||
],
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
TranslateModule.forRoot()
|
||||
TranslateModule.forRoot(),
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ActivatedRoute, useValue: {
|
||||
paramMap: of({ get: (key) => 'value' }),
|
||||
snapshot: {
|
||||
parent: {
|
||||
params: { id: 1 }
|
||||
params: { id: 1 },
|
||||
data: null
|
||||
},
|
||||
data: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
TranslateService,
|
||||
{ provide: RobotService, useValue: robotService },
|
||||
{ provide: ConfirmationDialogService, useClass: ConfirmationDialogService },
|
||||
{ provide: MessageHandlerService, useValue: fakedMessageHandlerService },
|
||||
ConfirmationDialogService,
|
||||
OperationService,
|
||||
{ provide: UserPermissionService, useValue: mockUserPermissionService },
|
||||
{ provide: ErrorHandler, useValue: mockErrorHandler },
|
||||
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
|
||||
OperationService
|
||||
{ provide: RobotService, useValue: fakedRobotService},
|
||||
],
|
||||
declarations: [RobotAccountComponent]
|
||||
}).compileComponents();
|
||||
@ -66,10 +138,17 @@ describe('RobotAccountComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RobotAccountComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.searchSub = new Subscription();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should render project robot list', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
await fixture.whenStable();
|
||||
const rows = fixture.nativeElement.querySelectorAll('clr-dg-row');
|
||||
expect(rows.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
@ -1,31 +1,30 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
ChangeDetectorRef
|
||||
} from "@angular/core";
|
||||
import { AddRobotComponent } from "./add-robot/add-robot.component";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Robot } from "./robot";
|
||||
import { Project } from "./../project";
|
||||
import { finalize, catchError, map } from "rxjs/operators";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Subscription, forkJoin, Observable, throwError as observableThrowError } from "rxjs";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { RobotService } from "./robot-account.service";
|
||||
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import {
|
||||
ConfirmationTargets,
|
||||
ConfirmationState,
|
||||
ConfirmationButtons
|
||||
} from "../../shared/shared.const";
|
||||
import { OperationService } from "../../../lib/components/operation/operation.service";
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ClrDatagridStateInterface, ClrLoadingState } from "@clr/angular";
|
||||
import { catchError, debounceTime, distinctUntilChanged, finalize, map, switchMap } from "rxjs/operators";
|
||||
import { forkJoin, Observable, of, Subscription } from "rxjs";
|
||||
import { UserPermissionService, USERSTATICPERMISSION } from "../../../lib/services";
|
||||
import { ErrorHandler } from "../../../lib/utils/error-handler";
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
PermissionsKinds
|
||||
} from "../../system-robot-accounts/system-robot-util";
|
||||
import { clone, DEFAULT_PAGE_SIZE } from "../../../lib/utils/utils";
|
||||
import { ViewTokenComponent } from "../../system-robot-accounts/view-token/view-token.component";
|
||||
import { FilterComponent } from "../../../lib/components/filter/filter.component";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { OperationService } from "../../../lib/components/operation/operation.service";
|
||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../shared/shared.const";
|
||||
import { RobotService } from "../../../../ng-swagger-gen/services/robot.service";
|
||||
import { Robot } from "../../../../ng-swagger-gen/models/robot";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Project } from "../../../../ng-swagger-gen/models/project";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { errorHandler } from "../../../lib/utils/shared/shared.utils";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../../lib/components/operation/operate";
|
||||
import { errorHandler as errorHandlerFn } from "../../../lib/utils/shared/shared.utils";
|
||||
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
|
||||
import { AddRobotComponent } from "./add-robot/add-robot.component";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
|
||||
@Component({
|
||||
selector: "app-robot-account",
|
||||
@ -33,197 +32,267 @@ import { errorHandler as errorHandlerFn } from "../../../lib/utils/shared/shared
|
||||
styleUrls: ["./robot-account.component.scss"]
|
||||
})
|
||||
export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
currentPage: number = 1;
|
||||
total: number = 0;
|
||||
robots: Robot[] = [];
|
||||
selectedRows: Robot[] = [];
|
||||
loading: boolean = true;
|
||||
addBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
@ViewChild(AddRobotComponent)
|
||||
addRobotComponent: AddRobotComponent;
|
||||
selectedRow: Robot[] = [];
|
||||
robotsCopy: Robot[] = [];
|
||||
loading = false;
|
||||
searchRobot: string;
|
||||
projectName: string;
|
||||
timerHandler: any;
|
||||
batchChangeInfos: {};
|
||||
isDisabled: boolean;
|
||||
isDisabledTip: string = "ROBOT_ACCOUNT.DISABLE_ACCOUNT";
|
||||
robots: Robot[];
|
||||
projectId: number;
|
||||
newRobotComponent: AddRobotComponent;
|
||||
@ViewChild(ViewTokenComponent)
|
||||
viewTokenComponent: ViewTokenComponent;
|
||||
@ViewChild(FilterComponent, {static: true})
|
||||
filterComponent: FilterComponent;
|
||||
searchSub: Subscription;
|
||||
searchKey: string;
|
||||
subscription: Subscription;
|
||||
hasRobotCreatePermission: boolean;
|
||||
hasRobotUpdatePermission: boolean;
|
||||
hasRobotDeletePermission: boolean;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private robotService: RobotService,
|
||||
private OperateDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService,
|
||||
private translate: TranslateService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private ref: ChangeDetectorRef,
|
||||
private messageHandlerService: MessageHandlerService
|
||||
hasRobotReadPermission: boolean;
|
||||
projectId: number;
|
||||
projectName: string;
|
||||
constructor(private robotService: RobotService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private operateDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private route: ActivatedRoute,
|
||||
private translate: TranslateService,
|
||||
private sanitizer: DomSanitizer,
|
||||
) {
|
||||
this.subscription = OperateDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (
|
||||
message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.ROBOT_ACCOUNT
|
||||
) {
|
||||
this.delRobots(message.data);
|
||||
this.subscription = operateDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (
|
||||
message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.ROBOT_ACCOUNT
|
||||
) {
|
||||
this.deleteRobots(message.data);
|
||||
}
|
||||
if ( message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.ROBOT_ACCOUNT_ENABLE_OR_DISABLE) {
|
||||
this.operateRobot();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
this.forceRefreshView(2000);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
ngOnInit() {
|
||||
this.projectId = +this.route.snapshot.parent.params["id"];
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
if (resolverData) {
|
||||
let project = <Project>resolverData["projectResolver"];
|
||||
this.projectName = project.name;
|
||||
}
|
||||
this.searchRobot = "";
|
||||
this.retrieve();
|
||||
this.getPermissionsList(this.projectId);
|
||||
this.getPermissionsList();
|
||||
if (!this.searchSub) {
|
||||
this.searchSub = this.filterComponent.filterTerms.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
switchMap(robotSearchName => {
|
||||
this.currentPage = 1;
|
||||
this.selectedRows = [];
|
||||
const queryParam: RobotService.ListRobotParams = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
};
|
||||
this.searchKey = robotSearchName;
|
||||
if (this.searchKey) {
|
||||
queryParam.q = encodeURIComponent(`Level=${PermissionsKinds.PROJECT},ProjectID=${this.projectId},name=~${this.searchKey}`);
|
||||
}
|
||||
this.loading = true;
|
||||
return this.robotService.ListRobotResponse(queryParam)
|
||||
.pipe(finalize(() => {
|
||||
this.loading = false;
|
||||
}));
|
||||
})).subscribe(response => {
|
||||
this.total = Number.parseInt(
|
||||
response.headers.get('x-total-count')
|
||||
);
|
||||
this.robots = response.body as Robot[];
|
||||
}, error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
getPermissionsList(projectId: number): void {
|
||||
getPermissionsList(): void {
|
||||
let permissionsList = [];
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.CREATE));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.UPDATE));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.DELETE));
|
||||
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.CREATE));
|
||||
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.UPDATE));
|
||||
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.DELETE));
|
||||
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.READ));
|
||||
|
||||
forkJoin(...permissionsList).subscribe(Rules => {
|
||||
this.hasRobotCreatePermission = Rules[0] as boolean;
|
||||
this.hasRobotUpdatePermission = Rules[1] as boolean;
|
||||
this.hasRobotDeletePermission = Rules[2] as boolean;
|
||||
|
||||
}, error => this.errorHandler.error(error));
|
||||
this.hasRobotReadPermission = Rules[3] as boolean;
|
||||
}, error => this.msgHandler.error(error));
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
if (this.timerHandler) {
|
||||
clearInterval(this.timerHandler);
|
||||
this.timerHandler = null;
|
||||
ngOnDestroy() {
|
||||
if (this.searchSub) {
|
||||
this.searchSub.unsubscribe();
|
||||
this.searchSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
openAddRobotModal(): void {
|
||||
this.addRobotComponent.openAddRobotModal();
|
||||
}
|
||||
|
||||
openDeleteRobotsDialog(robots: Robot[]) {
|
||||
let robotNames = robots.map(robot => robot.name).join(",");
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
"ROBOT_ACCOUNT.DELETION_TITLE",
|
||||
"ROBOT_ACCOUNT.DELETION_SUMMARY",
|
||||
robotNames,
|
||||
robots,
|
||||
ConfirmationTargets.ROBOT_ACCOUNT,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.OperateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
delRobots(robots: Robot[]): void {
|
||||
if (robots && robots.length < 1) {
|
||||
return;
|
||||
clrLoad(state?: ClrDatagridStateInterface) {
|
||||
if (state && state.page && state.page.size) {
|
||||
this.pageSize = state.page.size;
|
||||
}
|
||||
let robotsDelete$ = robots.map(robot => this.delOperate(robot));
|
||||
forkJoin(robotsDelete$)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.retrieve();
|
||||
this.selectedRow = [];
|
||||
})
|
||||
)
|
||||
.subscribe(() => { }
|
||||
, error => {
|
||||
this.errorHandler.error(error);
|
||||
this.selectedRows = [];
|
||||
const queryParam: RobotService.ListRobotParams = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
q: encodeURIComponent(`Level=${PermissionsKinds.PROJECT},ProjectID=${this.projectId}`)
|
||||
};
|
||||
if (this.searchKey) {
|
||||
queryParam.q += encodeURIComponent(`,name=~${this.searchKey}`);
|
||||
}
|
||||
this.loading = true;
|
||||
this.robotService.ListRobotResponse(queryParam)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.total = Number.parseInt(
|
||||
response.headers.get('x-total-count')
|
||||
);
|
||||
this.robots = response.body as Robot[];
|
||||
},
|
||||
err => {
|
||||
this.msgHandler.error(err);
|
||||
});
|
||||
}
|
||||
openNewRobotModal(isEditMode: boolean) {
|
||||
if (isEditMode) {
|
||||
this.newRobotComponent.resetForEdit(clone(this.selectedRows[0]));
|
||||
} else {
|
||||
this.newRobotComponent.reset();
|
||||
}
|
||||
}
|
||||
openTokenModal() {
|
||||
this.viewTokenComponent.open();
|
||||
this.viewTokenComponent.robot = clone(this.selectedRows[0]);
|
||||
}
|
||||
refresh() {
|
||||
this.currentPage = 1;
|
||||
this.selectedRows = [];
|
||||
this.clrLoad();
|
||||
}
|
||||
deleteRobots(robots: Robot[]) {
|
||||
let observableLists: Observable<any>[] = [];
|
||||
if (robots && robots.length) {
|
||||
robots.forEach(item => {
|
||||
observableLists.push(this.deleteRobot(item));
|
||||
});
|
||||
forkJoin(...observableLists).subscribe(resArr => {
|
||||
let error;
|
||||
if (resArr && resArr.length) {
|
||||
resArr.forEach(item => {
|
||||
if (item instanceof HttpErrorResponse) {
|
||||
error = errorHandler(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (error) {
|
||||
this.msgHandler.handleError(error);
|
||||
} else {
|
||||
this.msgHandler.showSuccess('SYSTEM_ROBOT.DELETE_ROBOT_SUCCESS');
|
||||
}
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
delOperate(robot: Robot) {
|
||||
// init operation info
|
||||
deleteRobot(robot: Robot): Observable<any> {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = "OPERATION.DELETE_ROBOT";
|
||||
operMessage.name = 'SYSTEM_ROBOT.DELETE_ROBOT';
|
||||
operMessage.data.id = robot.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
|
||||
return this.robotService
|
||||
.deleteRobotAccount(this.projectId, robot.id)
|
||||
.pipe(
|
||||
map(
|
||||
() => operateChanges(operMessage, OperationState.success),
|
||||
catchError(error => {
|
||||
const errorMsg = errorHandlerFn(error);
|
||||
this.translate.get(errorMsg).subscribe(res =>
|
||||
operateChanges(operMessage, OperationState.failure, res)
|
||||
);
|
||||
return observableThrowError(error);
|
||||
}
|
||||
)
|
||||
));
|
||||
return this.robotService.DeleteRobot({robotId: robot.id}).pipe(
|
||||
map(() => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
operateChanges(operMessage, OperationState.failure, message);
|
||||
return of(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
openDeleteRobotsDialog() {
|
||||
const robotNames = this.selectedRows.map(robot => robot.name).join(",");
|
||||
const deletionMessage = new ConfirmationMessage(
|
||||
"ROBOT_ACCOUNT.DELETION_TITLE",
|
||||
"ROBOT_ACCOUNT.DELETION_SUMMARY",
|
||||
robotNames,
|
||||
this.selectedRows,
|
||||
ConfirmationTargets.ROBOT_ACCOUNT,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.operateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
createAccount(created: boolean): void {
|
||||
if (created) {
|
||||
this.retrieve();
|
||||
disableOrEnable() {
|
||||
const title: string = this.selectedRows[0].disable ? "SYSTEM_ROBOT.ENABLE_TITLE" : "SYSTEM_ROBOT.DISABLE_TITLE";
|
||||
const summary: string = this.selectedRows[0].disable ? "SYSTEM_ROBOT.ENABLE_SUMMARY" : "SYSTEM_ROBOT.DISABLE_SUMMARY";
|
||||
const deletionMessage = new ConfirmationMessage(
|
||||
title,
|
||||
summary,
|
||||
this.selectedRows[0].name,
|
||||
this.selectedRows[0],
|
||||
ConfirmationTargets.ROBOT_ACCOUNT_ENABLE_OR_DISABLE,
|
||||
this.selectedRows[0].disable ? ConfirmationButtons.ENABLE_CANCEL : ConfirmationButtons.DISABLE_CANCEL
|
||||
);
|
||||
this.operateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
operateRobot() {
|
||||
const robot: Robot = clone(this.selectedRows[0]);
|
||||
const successMessage: string = robot.disable ? "SYSTEM_ROBOT.ENABLE_ROBOT_SUCCESSFULLY" : "SYSTEM_ROBOT.DISABLE_ROBOT_SUCCESSFULLY";
|
||||
robot.disable = !robot.disable;
|
||||
delete robot.secret;
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = robot.disable ? "SYSTEM_ROBOT.DISABLE_TITLE" : "SYSTEM_ROBOT.ENABLE_TITLE";
|
||||
opeMessage.data.id = robot.id;
|
||||
opeMessage.state = OperationState.progressing;
|
||||
opeMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(opeMessage);
|
||||
this.robotService.UpdateRobot({
|
||||
robot: robot,
|
||||
robotId: robot.id
|
||||
}).subscribe( res => {
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
this.msgHandler.showSuccess(successMessage);
|
||||
this.refresh();
|
||||
}, error => {
|
||||
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||
this.msgHandler.showSuccess(error);
|
||||
});
|
||||
}
|
||||
addSuccess(robot: Robot) {
|
||||
if (robot) {
|
||||
this.viewTokenComponent.open();
|
||||
this.viewTokenComponent.tokenModalOpened = false;
|
||||
this.viewTokenComponent.robot = clone(robot);
|
||||
this.viewTokenComponent.copyToken = true;
|
||||
this.translate
|
||||
.get("ROBOT_ACCOUNT.CREATED_SUCCESS", { param: robot.name })
|
||||
.subscribe((res: string) => {
|
||||
this.viewTokenComponent.createSuccess = res;
|
||||
});
|
||||
// export to token file
|
||||
const downLoadUrl = `data:text/json;charset=utf-8, ${encodeURIComponent(JSON.stringify(robot))}`;
|
||||
this.viewTokenComponent.downLoadHref = this.sanitizer.bypassSecurityTrustUrl(downLoadUrl);
|
||||
this.viewTokenComponent.downLoadFileName = `${robot.name}.json`;
|
||||
}
|
||||
}
|
||||
|
||||
forceRefreshView(duration: number): void {
|
||||
// Reset timer
|
||||
if (this.timerHandler) {
|
||||
clearInterval(this.timerHandler);
|
||||
}
|
||||
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => {
|
||||
if (this.timerHandler) {
|
||||
clearInterval(this.timerHandler);
|
||||
this.timerHandler = null;
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
doSearch(value: string): void {
|
||||
this.searchRobot = value;
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
retrieve(): void {
|
||||
this.loading = true;
|
||||
this.selectedRow = [];
|
||||
this.robotService
|
||||
.listRobotAccount(this.projectId)
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.robots = response.filter(x =>
|
||||
x.name.split('$')[1].includes(this.searchRobot)
|
||||
);
|
||||
this.robotsCopy = response.map(x => Object.assign({}, x));
|
||||
this.forceRefreshView(2000);
|
||||
},
|
||||
error => {
|
||||
this.messageHandlerService.handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
changeAccountStatus(robots: Robot): void {
|
||||
let id: number | string = robots[0].id;
|
||||
this.isDisabled = robots[0].disabled ? false : true;
|
||||
this.robotService
|
||||
.toggleDisabledAccount(this.projectId, id, this.isDisabled)
|
||||
.subscribe(response => {
|
||||
this.retrieve();
|
||||
});
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { RobotService } from './robot-account.service';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { RobotApiRepository } from "./robot.api.repository";
|
||||
|
||||
describe('RobotService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [RobotService, RobotApiRepository]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([RobotService], (service: RobotService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
@ -1,76 +0,0 @@
|
||||
import { throwError as observableThrowError, Observable } from "rxjs";
|
||||
|
||||
import { map, catchError } from "rxjs/operators";
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Injectable } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { RobotApiRepository } from "./robot.api.repository";
|
||||
import { Robot } from "./robot";
|
||||
@Injectable()
|
||||
export class RobotService {
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private robotApiRepository: RobotApiRepository
|
||||
) { }
|
||||
|
||||
/** addRobotAccount
|
||||
* projectId
|
||||
* robot: Robot
|
||||
* projectName
|
||||
*/
|
||||
public addRobotAccount(projectId: number, robot: Robot, projectName: string): Observable<any> {
|
||||
let access = [];
|
||||
if (robot.access.isPullImage) {
|
||||
access.push({ "resource": `/project/${projectId}/repository`, "action": "pull" });
|
||||
}
|
||||
if (robot.access.isPushOrPullImage) {
|
||||
access.push({ "resource": `/project/${projectId}/repository`, "action": "push" });
|
||||
}
|
||||
if (robot.access.isPullChart) {
|
||||
access.push({ "resource": `/project/${projectId}/helm-chart`, "action": "read" });
|
||||
}
|
||||
if (robot.access.isPushChart) {
|
||||
access.push({ "resource": `/project/${projectId}/helm-chart-version`, "action": "create" });
|
||||
}
|
||||
|
||||
let param = {
|
||||
name: robot.name,
|
||||
expires_at: +robot.expires_at,
|
||||
description: robot.description,
|
||||
access
|
||||
};
|
||||
|
||||
return this.robotApiRepository.postRobot(projectId, param);
|
||||
}
|
||||
|
||||
public deleteRobotAccount(projectId, id): Observable<any> {
|
||||
return this.robotApiRepository.deleteRobot(projectId, id);
|
||||
}
|
||||
|
||||
public listRobotAccount(projectId): Observable<any> {
|
||||
return this.robotApiRepository.listRobot(projectId);
|
||||
}
|
||||
|
||||
public getRobotAccount(projectId, id): Observable<any> {
|
||||
return this.robotApiRepository.getRobot(projectId, id);
|
||||
}
|
||||
|
||||
public toggleDisabledAccount(projectId, id, isDisabled): Observable<any> {
|
||||
let data = {
|
||||
Disabled: isDisabled
|
||||
};
|
||||
return this.robotApiRepository.toggleDisabledAccount(projectId, id, data);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { throwError as observableThrowError, Observable, pipe } from "rxjs";
|
||||
import { catchError, map } from "rxjs/operators";
|
||||
import { Robot } from './robot';
|
||||
import { CURRENT_BASE_HREF } from "../../../lib/utils/utils";
|
||||
|
||||
@Injectable()
|
||||
export class RobotApiRepository {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
public postRobot(projectId, param): Observable<any> {
|
||||
return this.http
|
||||
.post(`${ CURRENT_BASE_HREF }/projects/${projectId}/robots`, param)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public deleteRobot(projectId, id): Observable<any> {
|
||||
return this.http
|
||||
.delete(`${ CURRENT_BASE_HREF }/projects/${projectId}/robots/${id}`)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public listRobot(projectId): Observable<Robot[]> {
|
||||
return this.http
|
||||
.get(`${ CURRENT_BASE_HREF }/projects/${projectId}/robots`)
|
||||
.pipe(map(response => response as Robot[]))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public getRobot(projectId, id): Observable<Robot[]> {
|
||||
return this.http
|
||||
.get(`${ CURRENT_BASE_HREF }/projects/${projectId}/robots/${id}`)
|
||||
.pipe(map(response => response as Robot[]))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public toggleDisabledAccount(projectId, id, data): Observable<any> {
|
||||
return this.http
|
||||
.put(`${ CURRENT_BASE_HREF }/projects/${projectId}/robots/${id}`, data)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
export class Robot {
|
||||
project_id: number;
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
expires_at: number;
|
||||
disabled: boolean;
|
||||
creation_time?: Date;
|
||||
access: {
|
||||
isPullImage: boolean;
|
||||
isPushOrPullImage: boolean;
|
||||
isPushChart: boolean;
|
||||
isPullChart: boolean;
|
||||
};
|
||||
|
||||
|
||||
constructor () {
|
||||
this.access = <any>{};
|
||||
this.access.isPullImage = false;
|
||||
this.access.isPushOrPullImage = true;
|
||||
this.access.isPushChart = true;
|
||||
this.access.isPullChart = true;
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,8 @@ export const enum ConfirmationTargets {
|
||||
P2P_PROVIDER,
|
||||
P2P_PROVIDER_EXECUTE,
|
||||
P2P_PROVIDER_STOP,
|
||||
P2P_PROVIDER_DELETE
|
||||
P2P_PROVIDER_DELETE,
|
||||
ROBOT_ACCOUNT_ENABLE_OR_DISABLE
|
||||
}
|
||||
|
||||
export const enum ActionType {
|
||||
|
@ -47,6 +47,7 @@ import { IServiceConfig, SERVICE_CONFIG } from "../../lib/entities/service.confi
|
||||
import { ErrorHandler } from "../../lib/utils/error-handler";
|
||||
import { HarborLibraryModule } from "../../lib/harbor-library.module";
|
||||
import { CURRENT_BASE_HREF, V1_BASE_HREF } from "../../lib/utils/utils";
|
||||
import { ViewTokenComponent } from "../system-robot-accounts/view-token/view-token.component";
|
||||
|
||||
const uiLibConfig: IServiceConfig = {
|
||||
enablei18Support: true,
|
||||
@ -102,7 +103,8 @@ const uiLibConfig: IServiceConfig = {
|
||||
ListRepositoryROComponent,
|
||||
GaugeComponent,
|
||||
DateValidatorDirective,
|
||||
ListChartVersionRoComponent
|
||||
ListChartVersionRoComponent,
|
||||
ViewTokenComponent,
|
||||
],
|
||||
exports: [
|
||||
CoreModule,
|
||||
@ -124,7 +126,8 @@ const uiLibConfig: IServiceConfig = {
|
||||
DateValidatorDirective,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
ListChartVersionRoComponent
|
||||
ListChartVersionRoComponent,
|
||||
ViewTokenComponent,
|
||||
],
|
||||
providers: [
|
||||
SessionService,
|
||||
|
@ -0,0 +1,54 @@
|
||||
<clr-datagrid [clrDgPreserveSelection]="true" [(clrDgSelected)]="selectedRow">
|
||||
<clr-dg-action-bar>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<button [disabled]="coverAll" class="btn btn-secondary btn-sm" clrDropdownTrigger>
|
||||
{{"SYSTEM_ROBOT.RESET_PERMISSION" | translate}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
|
||||
<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>
|
||||
<button (click)="selectAllOrUnselectAll()" [disabled]="coverAll" class="btn btn-secondary btn-sm ml-1">
|
||||
<span *ngIf="showSelectAll">{{"SYSTEM_ROBOT.SELECT_ALL" | translate}}</span>
|
||||
<span *ngIf="!showSelectAll">{{"SYSTEM_ROBOT.UNSELECT_ALL" | translate}}</span>
|
||||
</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'" [(clrFilterValue)]="myNameFilterValue">{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="timeComparator">{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<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-cell>
|
||||
<a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.creation_time | date: '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 clrPosition="bottom-left" *clrIfOpen>
|
||||
<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>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[5,15,25]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span>{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span> {{projects?.length
|
||||
}} {{'PROJECT.ITEMS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
color: green;
|
||||
}
|
||||
.permissions {
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.datagrid-host {
|
||||
position: inherit;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ListAllProjectsComponent } from './list-all-projects.component';
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { clone } from "../../../lib/utils/utils";
|
||||
import { INITIAL_ACCESSES } from "../system-robot-util";
|
||||
import { Project } from "../../../../ng-swagger-gen/models/project";
|
||||
|
||||
describe('ListAllProjectsComponent', () => {
|
||||
let component: ListAllProjectsComponent;
|
||||
let fixture: ComponentFixture<ListAllProjectsComponent>;
|
||||
const project1: Project = {
|
||||
project_id: 1,
|
||||
name: 'project1'
|
||||
};
|
||||
const project2: Project = {
|
||||
project_id: 2,
|
||||
name: 'project2'
|
||||
};
|
||||
const project3: Project = {
|
||||
project_id: 3,
|
||||
name: 'project3'
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
RouterTestingModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
declarations: [ ListAllProjectsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListAllProjectsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.defaultAccesses = clone(INITIAL_ACCESSES);
|
||||
component.cachedAllProjects = [project1, project2, project3];
|
||||
component.init(false);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should render list', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const rows = fixture.nativeElement.querySelectorAll('clr-dg-row');
|
||||
expect(rows.length).toEqual(3);
|
||||
});
|
||||
});
|
@ -0,0 +1,110 @@
|
||||
import { Component, Input, OnInit, } from '@angular/core';
|
||||
import { Project } from "../../../../ng-swagger-gen/models/project";
|
||||
import { clone, CustomComparator } from "../../../lib/utils/utils";
|
||||
import { ClrDatagridComparatorInterface } from "@clr/angular";
|
||||
import { Router } from "@angular/router";
|
||||
import { Permission } from "../../../../ng-swagger-gen/models/permission";
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
FrontAccess,
|
||||
FrontProjectForAdd, INITIAL_ACCESSES,
|
||||
PermissionsKinds
|
||||
} from "../system-robot-util";
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-all-projects',
|
||||
templateUrl: './list-all-projects.component.html',
|
||||
styleUrls: ['./list-all-projects.component.scss']
|
||||
})
|
||||
export class ListAllProjectsComponent implements OnInit {
|
||||
cachedAllProjects: Project[];
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
permissionsForAdd: Permission[] = [];
|
||||
selectedRow: FrontProjectForAdd[] = [];
|
||||
timeComparator: ClrDatagridComparatorInterface<Project> = new CustomComparator<Project>("creation_time", "date");
|
||||
projects: FrontProjectForAdd[] = [];
|
||||
pageSize: number = 5;
|
||||
currentPage: number = 1;
|
||||
defaultAccesses: FrontAccess[] = [];
|
||||
@Input()
|
||||
coverAll: boolean = false;
|
||||
showSelectAll: boolean = true;
|
||||
myNameFilterValue: string;
|
||||
constructor(private router: Router) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
resetAccess(accesses: FrontAccess[]) {
|
||||
if (this.projects && this.projects.length) {
|
||||
this.projects.forEach(item => {
|
||||
item.permissions = [{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: item.name,
|
||||
access: 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;
|
||||
}
|
||||
goToLink(proId: number): void {
|
||||
this.router.navigate(["harbor", "projects", proId]);
|
||||
}
|
||||
selectAllOrUnselectAll() {
|
||||
if (this.showSelectAll) {
|
||||
if (this.myNameFilterValue) {
|
||||
this.projects.forEach(item => {
|
||||
let flag = false;
|
||||
if (item.name.indexOf(this.myNameFilterValue) !== -1) {
|
||||
this.selectedRow.forEach(item2 => {
|
||||
if (item2.name === item.name) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
if (!flag) {
|
||||
this.selectedRow.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.selectedRow = this.projects;
|
||||
}
|
||||
} else {
|
||||
this.selectedRow = [];
|
||||
}
|
||||
this.showSelectAll = !this.showSelectAll;
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
<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>
|
||||
<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">
|
||||
<section class="form-block">
|
||||
<!-- name -->
|
||||
<div class="clr-form-control">
|
||||
<label for="name" class="clr-control-label required">{{'P2P_PROVIDER.NAME' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="((name.dirty || name.touched) && name.invalid) || isNameExisting">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input input-width"
|
||||
[disabled]="loading || isEditMode"
|
||||
type="text" id="name"
|
||||
[(ngModel)]="systemRobot.name"
|
||||
required
|
||||
pattern='[^" ~#$%]+'
|
||||
maxLengthExt="255"
|
||||
autocomplete="off"
|
||||
size="30" name="name" #name="ngModel" (input)="inputName()">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkNameOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="((name.dirty || name.touched) && name.invalid) || isNameExisting">
|
||||
<span *ngIf="!((name.dirty || name.touched) && name.invalid) && isNameExisting">{{'ROBOT_ACCOUNT.ACCOUNT_EXISTING' | translate}}</span>
|
||||
<span *ngIf="(name.dirty || name.touched) && name.invalid">{{ 'ROBOT_ACCOUNT.ROBOT_NAME' | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- expiration -->
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label required">{{"SYSTEM_ROBOT.EXPIRATION_TIME" | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{"SYSTEM_ROBOT.EXPIRATION_TIME_EXPLAIN" | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<div class="clr-control-container input-width flex" [class.clr-error]="((expiration.dirty || expiration.touched) && expiration.invalid) || isExpirationInvalid()">
|
||||
<div class="clr-select-wrapper">
|
||||
<select [ngModelOptions]="{standalone: true}" (change)="changeExpirationType()" [(ngModel)]="expirationType" id="expiration-type" class="clr-select">
|
||||
<option value="default">{{systemExpirationDays}} {{"SYSTEM_ROBOT.EXPIRATION_DEFAULT" | translate}}</option>
|
||||
<option value="days">{{"SYSTEM_ROBOT.EXPIRATION_DAYS" | translate}}</option>
|
||||
<option value="never">{{"SYSTEM_ROBOT.EXPIRATION_NEVER" | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="clr-input-wrapper">
|
||||
<input (input)="inputExpiration()" [disabled]="loadingSystemConfig" class="clr-input expiration-width" name="expiration" type="text"
|
||||
#expiration="ngModel"
|
||||
autocomplete="off"
|
||||
[(ngModel)]="systemRobot.duration" required
|
||||
pattern="^[\-1-9]{1}[0-9]*$" id="robotTokenExpiration" size="20"/>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!loadingSystemConfig"></span>
|
||||
<clr-control-error *ngIf="((expiration.dirty || expiration.touched) && expiration.invalid)|| isExpirationInvalid()">
|
||||
{{"SYSTEM_ROBOT.EXPIRATION_REQUIRED" | translate}}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</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">{{"SYSTEM_ROBOT.COVER_ALL" | translate}}</label>
|
||||
<div class="clr-control-container padding-top-3 flex">
|
||||
<clr-checkbox-wrapper>
|
||||
<input
|
||||
clrCheckbox
|
||||
id="coverAll"
|
||||
name="coverAll"
|
||||
type="checkbox"
|
||||
[(ngModel)]="coverAll"
|
||||
/>
|
||||
<label>
|
||||
<clr-tooltip>
|
||||
<clr-icon class="opacity-07" clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{"SYSTEM_ROBOT.COVER_ALL_EXPLAIN" | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</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 clrPosition="bottom-left" *clrIfOpen>
|
||||
<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>
|
||||
<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" [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>
|
@ -0,0 +1,71 @@
|
||||
.rule-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-width {
|
||||
width: 350px;
|
||||
}
|
||||
.padding-left-0 {
|
||||
padding-left: 0;
|
||||
}
|
||||
.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.permission{
|
||||
padding-top: 0.1rem;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.padding-left-120{
|
||||
padding-left: 126px;
|
||||
}
|
||||
.w-90{
|
||||
width: 90%;
|
||||
}
|
||||
.date {
|
||||
margin-top: -0.9rem;
|
||||
}
|
||||
.input-width-date {
|
||||
width: 265px;
|
||||
}
|
||||
.mt-description {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
.width-table {
|
||||
width: 388px;
|
||||
}
|
||||
.all-projects {
|
||||
width: 100%;
|
||||
}
|
||||
.opacity-07 {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.modal-footer {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.message {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.expiration-width {
|
||||
width: 194px;
|
||||
}
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
color: green;
|
||||
}
|
||||
.dropdown-per {
|
||||
margin-top: -1px;
|
||||
}
|
||||
.mt-8px {
|
||||
margin-top: 8px !important;
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NewRobotComponent } from './new-robot.component';
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
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 { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { OperationService } from "../../../lib/components/operation/operation.service";
|
||||
import { RobotService } from "../../../../ng-swagger-gen/services/robot.service";
|
||||
import { of } from "rxjs";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { ConfigurationService } from "../../config/config.service";
|
||||
import { Configuration } from "../../../lib/components/config/config";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { clone } from "../../../lib/utils/utils";
|
||||
|
||||
describe('NewRobotComponent', () => {
|
||||
let component: NewRobotComponent;
|
||||
let fixture: ComponentFixture<NewRobotComponent>;
|
||||
const robot1: Robot = {
|
||||
id: 1,
|
||||
name: 'robot1',
|
||||
level: PermissionsKinds.SYSTEM,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'tthf54hfth4545dfgd5g454grd54gd54g',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project1',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const fakedRobotService = {
|
||||
ListRobot() {
|
||||
return of([]).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
const mockConfigurationService = {
|
||||
getConfiguration() {
|
||||
const config: Configuration = new Configuration();
|
||||
config.robot_token_duration = {
|
||||
value : 10000,
|
||||
editable: true
|
||||
};
|
||||
return of(config).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
const fakedMessageHandlerService = {
|
||||
showSuccess() {
|
||||
},
|
||||
error() {
|
||||
}
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
TranslateModule.forRoot(),
|
||||
FormsModule
|
||||
],
|
||||
declarations: [ NewRobotComponent ],
|
||||
providers: [
|
||||
OperationService,
|
||||
{ provide: MessageHandlerService, useValue: fakedMessageHandlerService },
|
||||
{ provide: RobotService, useValue: fakedRobotService},
|
||||
{ provide: ConfigurationService, useValue: mockConfigurationService},
|
||||
],
|
||||
schemas: [
|
||||
NO_ERRORS_SCHEMA
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewRobotComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show "name is required"', 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 = "";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('should be edit model', async () => {
|
||||
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();
|
||||
});
|
||||
});
|
@ -0,0 +1,423 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { ConfigurationService } from "../../config/config.service";
|
||||
import { Robot } from "../../../../ng-swagger-gen/models/robot";
|
||||
import { ListAllProjectsComponent } from "../list-all-projects/list-all-projects.component";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { debounceTime, distinctUntilChanged, filter, finalize, switchMap } from "rxjs/operators";
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
import { Access } from "../../../../ng-swagger-gen/models/access";
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP, ExpirationType,
|
||||
FrontAccess, INITIAL_ACCESSES,
|
||||
NAMESPACE_ALL_PROJECTS,
|
||||
PermissionsKinds,
|
||||
} from "../system-robot-util";
|
||||
import { clone } from "../../../lib/utils/utils";
|
||||
import { RobotService } from "../../../../ng-swagger-gen/services/robot.service";
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { Subject, Subscription } from "rxjs";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../../lib/components/operation/operate";
|
||||
import { OperationService } from "../../../lib/components/operation/operation.service";
|
||||
import { errorHandler } from "../../../lib/utils/shared/shared.utils";
|
||||
|
||||
const MINUETS_ONE_DAY: number = 60 * 24;
|
||||
|
||||
@Component({
|
||||
selector: 'new-robot',
|
||||
templateUrl: './new-robot.component.html',
|
||||
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 = {};
|
||||
expirationType: string = ExpirationType.DAYS;
|
||||
systemExpirationDays: number;
|
||||
coverAll: boolean = false;
|
||||
coverAllForEdit: boolean = false;
|
||||
|
||||
isNameExisting: boolean = false;
|
||||
loading: boolean = false;
|
||||
checkNameOnGoing: boolean = false;
|
||||
loadingSystemConfig: boolean = false;
|
||||
defaultAccesses: FrontAccess[] = [];
|
||||
defaultAccessesForEdit: FrontAccess[] = [];
|
||||
@ViewChild(ListAllProjectsComponent)
|
||||
listAllProjectsComponent: ListAllProjectsComponent;
|
||||
@ViewChild(InlineAlertComponent)
|
||||
inlineAlertComponent: InlineAlertComponent;
|
||||
@ViewChild('robotForm', { static: true }) robotForm: NgForm;
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
constructor( private configService: ConfigurationService,
|
||||
private robotService: RobotService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private operationService: OperationService) {
|
||||
}
|
||||
ngOnInit(): void {
|
||||
this.subscribeName();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this._nameSubscription) {
|
||||
this._nameSubscription.unsubscribe();
|
||||
this._nameSubscription = null;
|
||||
}
|
||||
}
|
||||
subscribeName() {
|
||||
if (!this._nameSubscription) {
|
||||
this._nameSubscription = this._nameSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
if (this.isEditMode && this.originalRobotForEdit && this.originalRobotForEdit.name === name) {
|
||||
return false;
|
||||
}
|
||||
return name.length > 0;
|
||||
}),
|
||||
switchMap((name) => {
|
||||
this.isNameExisting = false;
|
||||
this.checkNameOnGoing = true;
|
||||
return this.robotService.ListRobot({
|
||||
q: encodeURIComponent(`name=${name}`)
|
||||
}).pipe(finalize(() => this.checkNameOnGoing = false));
|
||||
}))
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isNameExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
isExpirationInvalid(): boolean {
|
||||
return this.systemRobot.duration < -1;
|
||||
}
|
||||
inputExpiration() {
|
||||
if (+this.systemRobot.duration === -1) {
|
||||
this.expirationType = ExpirationType.NEVER;
|
||||
} else {
|
||||
this.expirationType = ExpirationType.DAYS;
|
||||
}
|
||||
}
|
||||
changeExpirationType() {
|
||||
if (this.expirationType === ExpirationType.DEFAULT) {
|
||||
this.systemRobot.duration = this.systemExpirationDays;
|
||||
}
|
||||
if (this.expirationType === ExpirationType.DAYS) {
|
||||
this.systemRobot.duration = this.systemExpirationDays;
|
||||
}
|
||||
if (this.expirationType === ExpirationType.NEVER) {
|
||||
this.systemRobot.duration = -1;
|
||||
}
|
||||
}
|
||||
getSystemRobotExpiration() {
|
||||
this.loadingSystemConfig = true;
|
||||
this.configService.getConfiguration()
|
||||
.pipe(finalize(() => this.loadingSystemConfig = false))
|
||||
.subscribe(res => {
|
||||
if (res && res.robot_token_duration && res.robot_token_duration.value) {
|
||||
this.systemRobot.duration = Math.floor(res.robot_token_duration.value / MINUETS_ONE_DAY);
|
||||
this.systemExpirationDays = this.systemRobot.duration;
|
||||
}
|
||||
});
|
||||
}
|
||||
inputName() {
|
||||
this._nameSubject.next(this.systemRobot.name);
|
||||
}
|
||||
cancel() {
|
||||
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.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.expirationType =
|
||||
robot.duration === -1 ? ExpirationType.NEVER : ExpirationType.DAYS;
|
||||
if (robot && robot.permissions && robot.permissions.length) {
|
||||
this.coverAll = false;
|
||||
robot.permissions.forEach(item => {
|
||||
if (item.kind === PermissionsKinds.PROJECT
|
||||
&& 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.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.isEditMode = isEditMode;
|
||||
this.addRobotOpened = true;
|
||||
this.inlineAlertComponent.close();
|
||||
}
|
||||
disabled(): boolean {
|
||||
if (!this.isEditMode) {
|
||||
return !this.canAdd();
|
||||
}
|
||||
return !this.canEdit();
|
||||
}
|
||||
canAdd(): boolean {
|
||||
if (this.robotForm.invalid) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
canEdit() {
|
||||
if (!this.canAdd()) {
|
||||
return false;
|
||||
}
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if (this.systemRobot.duration != this.originalRobotForEdit.duration) {
|
||||
return true;
|
||||
}
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if (this.systemRobot.description != this.originalRobotForEdit.description) {
|
||||
return true;
|
||||
}
|
||||
if (this.coverAll !== this.coverAllForEdit) {
|
||||
return true;
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
let flag1 = true;
|
||||
this.systemRobot.permissions.forEach(item => {
|
||||
if (!map[item.namespace]) {
|
||||
flag1 = false;
|
||||
}
|
||||
});
|
||||
return !flag1;
|
||||
}
|
||||
save() {
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
const robot: Robot = clone(this.systemRobot);
|
||||
robot.disable = false;
|
||||
robot.level = PermissionsKinds.SYSTEM;
|
||||
robot.duration = +this.systemRobot.duration;
|
||||
robot.permissions = [];
|
||||
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
|
||||
});
|
||||
} 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.isEditMode) {
|
||||
robot.disable = this.systemRobot.disable;
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = "SYSTEM_ROBOT.UPDATE_ROBOT";
|
||||
opeMessage.data.id = robot.id;
|
||||
opeMessage.state = OperationState.progressing;
|
||||
opeMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(opeMessage);
|
||||
this.robotService.UpdateRobot({
|
||||
robotId: this.originalRobotForEdit.id,
|
||||
robot
|
||||
}).subscribe( res => {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(null);
|
||||
this.addRobotOpened = false;
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
this.msgHandler.showSuccess("SYSTEM_ROBOT.UPDATE_ROBOT_SUCCESSFULLY");
|
||||
}, error => {
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||
this.inlineAlertComponent.showInlineError(error);
|
||||
});
|
||||
} else {
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = "SYSTEM_ROBOT.ADD_ROBOT";
|
||||
opeMessage.data.id = robot.id;
|
||||
opeMessage.state = OperationState.progressing;
|
||||
opeMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(opeMessage);
|
||||
this.robotService.CreateRobot({
|
||||
robot: robot
|
||||
}).subscribe( res => {
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this.addSuccess.emit(res);
|
||||
this.addRobotOpened = false;
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
}, error => {
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
this.inlineAlertComponent.showInlineError(error);
|
||||
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getAccessNum(access: FrontAccess[]): number {
|
||||
let count: number = 0;
|
||||
access.forEach(item => {
|
||||
if (item.checked) {
|
||||
count ++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<clr-modal clrModalSize="lg" [(clrModalOpen)]="projectsModalOpened"
|
||||
[clrModalStaticBackdrop]="true" [clrModalClosable]="true">
|
||||
<h3 class="modal-title">
|
||||
<span>{{"SYSTEM_ROBOT.PROJECTS_MODAL_TITLE" | translate}}</span>
|
||||
<div class="nav-divider"></div>
|
||||
<span class="name">{{robotName}}</span>
|
||||
</h3>
|
||||
<div class="modal-body">
|
||||
<p class="mt-0">{{"SYSTEM_ROBOT.PROJECTS_MODAL_SUMMARY" | translate}}</p>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{"SYSTEM_ROBOT.PERMISSION_COLUMN" | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let p of permissions" [clrDgItem]="p">
|
||||
<clr-dg-cell>
|
||||
<a href="javascript:void(0)" (click)="goToLink(getProject(p)?.project_id)">{{p.namespace}}</a>
|
||||
</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>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{getProject(p)?.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,30]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span>{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span> {{permissions?.length
|
||||
}} {{'PROJECT.ITEMS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" (click)="close()" class="btn btn-primary">{{'BUTTON.CLOSE'
|
||||
| translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
@ -0,0 +1,23 @@
|
||||
.nav-divider {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
opacity: 0.15;
|
||||
content: '';
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.name {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.permissions {
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.datagrid-host {
|
||||
position: inherit;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ProjectsModalComponent } from './projects-modal.component';
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { Project } from "../../../../ng-swagger-gen/models/project";
|
||||
import { Permission } from "../../../../ng-swagger-gen/models/permission";
|
||||
import { Action, PermissionsKinds, Resource } from "../system-robot-util";
|
||||
|
||||
describe('ProjectsModalComponent', () => {
|
||||
let component: ProjectsModalComponent;
|
||||
let fixture: ComponentFixture<ProjectsModalComponent>;
|
||||
const project1: Project = {
|
||||
project_id: 1,
|
||||
name: 'project1'
|
||||
};
|
||||
const project2: Project = {
|
||||
project_id: 2,
|
||||
name: 'project2'
|
||||
};
|
||||
const permissions: Permission[] = [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: project1.name,
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: project2.name,
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
RouterTestingModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
declarations: [ ProjectsModalComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProjectsModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render list', async () => {
|
||||
component.projectsModalOpened = true;
|
||||
component.permissions = permissions;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const rows = fixture.nativeElement.querySelectorAll('clr-dg-row');
|
||||
expect(rows.length).toEqual(2);
|
||||
});
|
||||
});
|
@ -0,0 +1,38 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Permission } from "../../../../ng-swagger-gen/models/permission";
|
||||
import { Project } from "../../../../ng-swagger-gen/models/project";
|
||||
import { Router } from "@angular/router";
|
||||
import { ACTION_RESOURCE_I18N_MAP } from "../system-robot-util";
|
||||
|
||||
@Component({
|
||||
selector: 'app-projects-modal',
|
||||
templateUrl: './projects-modal.component.html',
|
||||
styleUrls: ['./projects-modal.component.scss']
|
||||
})
|
||||
export class ProjectsModalComponent implements OnInit {
|
||||
projectsModalOpened: boolean = false;
|
||||
robotName: string;
|
||||
cachedAllProjects: Project[];
|
||||
permissions: Permission[] = [];
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
constructor(private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
close() {
|
||||
this.projectsModalOpened = false;
|
||||
}
|
||||
getProject(p: Permission): Project {
|
||||
if (this.cachedAllProjects && this.cachedAllProjects.length) {
|
||||
for (let i = 0; i < this.cachedAllProjects.length; i++) {
|
||||
if (p.namespace === this.cachedAllProjects[i].name) {
|
||||
return this.cachedAllProjects[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
goToLink(proId: number): void {
|
||||
this.router.navigate(["harbor", "projects", proId]);
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
<h2 class="custom-h2">{{"SYSTEM_ROBOT.ROBOT_ACCOUNT_NAV" | translate}}</h2>
|
||||
<div class="row robot-space">
|
||||
<div>
|
||||
<div class="row flex-items-xs-between rightPos">
|
||||
<div class="flex-xs-middle option-left">
|
||||
</div>
|
||||
<div class="flex-xs-middle option-right">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder='{{"ROBOT_ACCOUNT.FILTER_PLACEHOLDER" | translate}}'></hbr-filter>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-dg-action-bar>
|
||||
<button [disabled]="loadingData" [clrLoading]="addBtnState" class="btn btn-secondary" (click)="openNewRobotModal(false)">
|
||||
<span>
|
||||
<clr-icon shape="plus" size="16"></clr-icon> {{'ROBOT_ACCOUNT.NEW_ROBOT_ACCOUNT'
|
||||
| translate }}
|
||||
</span>
|
||||
</button>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="btn
|
||||
btn-link" clrDropdownTrigger>
|
||||
<span>{{'MEMBER.ACTION' | translate}}<clr-icon shape="caret
|
||||
down"></clr-icon></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button [disabled]="loadingData || !(selectedRows && selectedRows.length === 1)" clrDropdownItem (click)="openTokenModal()">
|
||||
<clr-icon shape="details" size="16"></clr-icon>
|
||||
<span id="system-robot-token">{{"SYSTEM_ROBOT.VIEW_SECRET" | translate}}</span>
|
||||
</button>
|
||||
<button [disabled]="loadingData || !(selectedRows && selectedRows.length === 1)" clrDropdownItem (click)="openNewRobotModal(true)">
|
||||
<clr-icon shape="edit" size="16"></clr-icon>
|
||||
<span id="system-robot-edit">{{'BUTTON.EDIT' | translate}}</span>
|
||||
</button>
|
||||
<button *ngIf="selectedRows && selectedRows.length === 1 && selectedRows[0].disable" type="button" class="btn btn-secondary"
|
||||
(click)="disableOrEnable()"
|
||||
[disabled]="!(selectedRows && selectedRows.length === 1 && selectedRows[0].disable)">
|
||||
<clr-icon size="16" shape="success-standard"></clr-icon>
|
||||
<span id="distribution-enable">{{'WEBHOOK.ENABLED_BUTTON' | translate}}</span>
|
||||
</button>
|
||||
<button *ngIf="!(selectedRows && selectedRows.length === 1 && selectedRows[0].disable)"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="disableOrEnable()"
|
||||
[disabled]="!(selectedRows && selectedRows.length === 1 && !selectedRows[0].disable)">
|
||||
<clr-icon size="16" shape="ban"></clr-icon>
|
||||
<span id="distribution-disable">{{'WEBHOOK.DISABLED_BUTTON' | translate}}</span>
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button [disabled]="loadingData || !(selectedRows && selectedRows.length >=1)" clrDropdownItem (click)="openDeleteRobotsDialog()">
|
||||
<clr-icon shape="window-close" size="16"></clr-icon>
|
||||
<span id="system-robot-delete">{{'BUTTON.DELETE' | translate}}</span>
|
||||
</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</clr-dg-action-bar>
|
||||
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRows">
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.ENABLED_STATE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{"SYSTEM_ROBOT.PROJECTS" | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.CREATETION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SYSTEM_ROBOT.EXPIRES_AT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'ROBOT_ACCOUNT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{
|
||||
'SYSTEM_ROBOT.NOT_FOUND' | translate
|
||||
}}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let r of robots" [clrDgItem]="r">
|
||||
<clr-dg-cell>{{r.name}}</clr-dg-cell>
|
||||
<clr-dg-cell [ngSwitch]="r.disable">
|
||||
<clr-icon shape="check-circle" *ngSwitchCase="false" size="20" class="color-green"></clr-icon>
|
||||
<clr-icon shape="times-circle" *ngSwitchCase="true" size="16" class="color-red red-position"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="all-projects" *ngIf="r.permissionScope?.coverAll">
|
||||
<span>{{"SYSTEM_ROBOT.ALL_PROJECTS" | translate}}</span>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<button class="btn btn-link" clrDropdownTrigger>
|
||||
{{r.permissionScope?.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.permissionScope?.access">
|
||||
<span>{{i18nMap[item.action] | translate}} {{i18nMap[item.resource] | translate}}</span>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
<span *ngIf="!r.permissionScope || !r.permissionScope?.coverAll">
|
||||
<a href="javascript:void(0)" (click)="openProjectModal(getProjects(r), r.name)">
|
||||
{{getProjects(r)?.length?getProjects(r)?.length:""}} {{'SYSTEM_ROBOT.COVERED_PROJECTS' | translate}}
|
||||
</a>
|
||||
</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.expires_at === -1?("ROBOT_ACCOUNT.NEVER_EXPIRED" | translate):(r.expires_at * 1000 | date: 'short')}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{r.description}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="currentPage" [clrDgTotalItems]="total">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span *ngIf="total">{{pagination.firstItem + 1}}
|
||||
-
|
||||
{{pagination.lastItem +1 }} {{'ROBOT_ACCOUNT.OF' |
|
||||
translate}} </span>
|
||||
{{total}} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
<new-robot (addSuccess)="addSuccess($event)"></new-robot>
|
||||
<view-token (refreshSuccess)="refresh()"></view-token>
|
||||
<app-projects-modal></app-projects-modal>
|
||||
|
||||
|
@ -0,0 +1,35 @@
|
||||
.robot-space {
|
||||
margin-top: 28px;
|
||||
position: relative;
|
||||
|
||||
clr-icon.red-position {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.rightPos {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
|
||||
.option-left {
|
||||
padding-left: 16px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
|
||||
.refresh-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.all-projects {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { SystemRobotAccountsComponent } from './system-robot-accounts.component';
|
||||
import { RobotService } from "../../../ng-swagger-gen/services/robot.service";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { of, Subscription } from "rxjs";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { Robot } from "../../../ng-swagger-gen/models/robot";
|
||||
import { Action, PermissionsKinds, Resource } from "./system-robot-util";
|
||||
import { Project } from "../../../ng-swagger-gen/models/project";
|
||||
import { ProjectService } from "../../../ng-swagger-gen/services/project.service";
|
||||
import { MessageHandlerService } from "../shared/message-handler/message-handler.service";
|
||||
import { ConfirmationDialogService } from "../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { OperationService } from "../../lib/components/operation/operation.service";
|
||||
import { HttpClientTestingModule } from "@angular/common/http/testing";
|
||||
|
||||
describe('SystemRobotAccountsComponent', () => {
|
||||
let component: SystemRobotAccountsComponent;
|
||||
let fixture: ComponentFixture<SystemRobotAccountsComponent>;
|
||||
const project1: Project = {
|
||||
project_id: 1,
|
||||
name: 'project1'
|
||||
};
|
||||
const project2: Project = {
|
||||
project_id: 2,
|
||||
name: 'project2'
|
||||
};
|
||||
const project3: Project = {
|
||||
project_id: 3,
|
||||
name: 'project3'
|
||||
};
|
||||
const robot1: Robot = {
|
||||
id: 1,
|
||||
name: 'robot1',
|
||||
level: PermissionsKinds.SYSTEM,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'tthf54hfth4545dfgd5g454grd54gd54g',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project1',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const robot2: Robot = {
|
||||
id: 2,
|
||||
name: 'robot2',
|
||||
level: PermissionsKinds.SYSTEM,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'fsdf454654654fs6dfe',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project2',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const robot3: Robot = {
|
||||
id: 3,
|
||||
name: 'robot3',
|
||||
level: PermissionsKinds.SYSTEM,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'fsdg48454fse84',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project3',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const mockProjectService = {
|
||||
listProjectsResponse: () => {
|
||||
const res: HttpResponse<Array<Project>> = new HttpResponse<Array<Project>>({
|
||||
headers: new HttpHeaders({'x-total-count': '3'}),
|
||||
body: [project1, project2, project3]
|
||||
});
|
||||
return of(res).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
const fakedRobotService = {
|
||||
ListRobotResponse() {
|
||||
const res: HttpResponse<Array<Robot>> = new HttpResponse<Array<Robot>>({
|
||||
headers: new HttpHeaders({'x-total-count': '3'}),
|
||||
body: [robot1, robot2, robot3]
|
||||
});
|
||||
return of(res).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
const fakedMessageHandlerService = {
|
||||
showSuccess() {
|
||||
},
|
||||
error() {
|
||||
}
|
||||
};
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
TranslateModule.forRoot(),
|
||||
HttpClientTestingModule
|
||||
],
|
||||
declarations: [ SystemRobotAccountsComponent ],
|
||||
providers: [
|
||||
{ provide: MessageHandlerService, useValue: fakedMessageHandlerService },
|
||||
ConfirmationDialogService,
|
||||
OperationService,
|
||||
{ provide: RobotService, useValue: fakedRobotService},
|
||||
{ provide: ProjectService, useValue: mockProjectService},
|
||||
],
|
||||
schemas: [
|
||||
NO_ERRORS_SCHEMA
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SystemRobotAccountsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.searchSub = new Subscription();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should render robot list', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
await fixture.whenStable();
|
||||
const rows = fixture.nativeElement.querySelectorAll('clr-dg-row');
|
||||
expect(rows.length).toEqual(3);
|
||||
});
|
||||
});
|
@ -0,0 +1,367 @@
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { NewRobotComponent } from './new-robot/new-robot.component';
|
||||
import { ViewTokenComponent } from './view-token/view-token.component';
|
||||
import { RobotService } from "../../../ng-swagger-gen/services/robot.service";
|
||||
import { Robot } from "../../../ng-swagger-gen/models/robot";
|
||||
import { clone, DEFAULT_PAGE_SIZE } from "../../lib/utils/utils";
|
||||
import { ClrDatagridStateInterface, ClrLoadingState } from "@clr/angular";
|
||||
import { catchError, debounceTime, distinctUntilChanged, finalize, map, switchMap } from "rxjs/operators";
|
||||
import { MessageHandlerService } from "../shared/message-handler/message-handler.service";
|
||||
import {
|
||||
ACTION_RESOURCE_I18N_MAP,
|
||||
FrontRobot,
|
||||
NAMESPACE_ALL_PROJECTS,
|
||||
PermissionsKinds
|
||||
} from "./system-robot-util";
|
||||
import { ProjectsModalComponent } from "./projects-modal/projects-modal.component";
|
||||
import { Permission } from "../../../ng-swagger-gen/models/permission";
|
||||
import { forkJoin, Observable, of, Subscription } from "rxjs";
|
||||
import { FilterComponent } from "../../lib/components/filter/filter.component";
|
||||
import { ProjectService } from "../../../ng-swagger-gen/services/project.service";
|
||||
import { ConfirmationMessage } from "../shared/confirmation-dialog/confirmation-message";
|
||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../shared/shared.const";
|
||||
import { ConfirmationDialogService } from "../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { errorHandler } from "../../lib/utils/shared/shared.utils";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../lib/components/operation/operate";
|
||||
import { OperationService } from "../../lib/components/operation/operation.service";
|
||||
import { Observable as __Observable } from "rxjs/internal/Observable";
|
||||
import { Project } from "../../../ng-swagger-gen/models/project";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
const FIRST_PROJECTS_PAGE_SIZE: number = 100;
|
||||
@Component({
|
||||
selector: 'system-robot-accounts',
|
||||
templateUrl: './system-robot-accounts.component.html',
|
||||
styleUrls: ['./system-robot-accounts.component.scss']
|
||||
})
|
||||
export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||
i18nMap = ACTION_RESOURCE_I18N_MAP;
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
currentPage: number = 1;
|
||||
total: number = 0;
|
||||
robots: FrontRobot[] = [];
|
||||
selectedRows: FrontRobot[] = [];
|
||||
loading: boolean = true;
|
||||
loadingData: boolean = false;
|
||||
addBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
hasGetAllProjects: boolean = false;
|
||||
@ViewChild(NewRobotComponent)
|
||||
newRobotComponent: NewRobotComponent;
|
||||
@ViewChild(ViewTokenComponent)
|
||||
viewTokenComponent: ViewTokenComponent;
|
||||
@ViewChild(ProjectsModalComponent)
|
||||
projectsModalComponent: ProjectsModalComponent;
|
||||
@ViewChild(FilterComponent, {static: true})
|
||||
filterComponent: FilterComponent;
|
||||
searchSub: Subscription;
|
||||
searchKey: string;
|
||||
subscription: Subscription;
|
||||
constructor(private robotService: RobotService,
|
||||
private projectService: ProjectService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private operateDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
this.subscription = operateDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (
|
||||
message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.ROBOT_ACCOUNT
|
||||
) {
|
||||
this.deleteRobots(message.data);
|
||||
}
|
||||
if ( message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.ROBOT_ACCOUNT_ENABLE_OR_DISABLE) {
|
||||
this.operateRobot();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
ngOnInit() {
|
||||
this.loadDataFromBackend();
|
||||
if (!this.searchSub) {
|
||||
this.searchSub = this.filterComponent.filterTerms.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
switchMap(robotSearchName => {
|
||||
this.currentPage = 1;
|
||||
this.selectedRows = [];
|
||||
const queryParam: RobotService.ListRobotParams = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
};
|
||||
this.searchKey = robotSearchName;
|
||||
if (this.searchKey) {
|
||||
queryParam.q = encodeURIComponent(`name=~${this.searchKey}`);
|
||||
}
|
||||
this.loading = true;
|
||||
return this.robotService.ListRobotResponse(queryParam)
|
||||
.pipe(finalize(() => {
|
||||
this.loading = false;
|
||||
}));
|
||||
})).subscribe(response => {
|
||||
this.total = Number.parseInt(
|
||||
response.headers.get('x-total-count')
|
||||
);
|
||||
this.robots = response.body as Robot[];
|
||||
this.calculateProjects();
|
||||
}, error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.searchSub) {
|
||||
this.searchSub.unsubscribe();
|
||||
this.searchSub = null;
|
||||
}
|
||||
}
|
||||
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);
|
||||
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 = 1; 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) {
|
||||
let arr = [];
|
||||
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;
|
||||
}
|
||||
this.selectedRows = [];
|
||||
const queryParam: RobotService.ListRobotParams = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
};
|
||||
if (this.searchKey) {
|
||||
queryParam.q = encodeURIComponent(`name=~${this.searchKey}`);
|
||||
}
|
||||
this.loading = true;
|
||||
this.robotService.ListRobotResponse(queryParam)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.total = Number.parseInt(
|
||||
response.headers.get('x-total-count')
|
||||
);
|
||||
this.robots = response.body as Robot[];
|
||||
this.calculateProjects();
|
||||
},
|
||||
err => {
|
||||
this.msgHandler.error(err);
|
||||
});
|
||||
}
|
||||
openNewRobotModal(isEditMode: boolean) {
|
||||
if (isEditMode) {
|
||||
this.newRobotComponent.resetForEdit(clone(this.selectedRows[0]));
|
||||
} else {
|
||||
this.newRobotComponent.reset();
|
||||
}
|
||||
}
|
||||
openTokenModal() {
|
||||
this.viewTokenComponent.open();
|
||||
this.viewTokenComponent.robot = clone(this.selectedRows[0]);
|
||||
}
|
||||
calculateProjects() {
|
||||
if (this.robots && this.robots.length) {
|
||||
for (let i = 0 ; i < this.robots.length; i++) {
|
||||
if (this.robots[i] && this.robots[i].permissions && this.robots[i].permissions.length) {
|
||||
for (let j = 0 ; j < this.robots[i].permissions.length; j++) {
|
||||
if (this.robots[i].permissions[j].kind === PermissionsKinds.PROJECT
|
||||
&& this.robots[i].permissions[j].namespace === NAMESPACE_ALL_PROJECTS) {
|
||||
this.robots[i].permissionScope = {
|
||||
coverAll: true,
|
||||
access: this.robots[i].permissions[j].access
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getProjects(r: FrontRobot): Permission[] {
|
||||
const arr = [];
|
||||
if (r && r.permissions && r.permissions.length) {
|
||||
for (let i = 0 ; i < r.permissions.length; i++) {
|
||||
if (r.permissions[i].kind === PermissionsKinds.PROJECT
|
||||
) {
|
||||
arr.push(r.permissions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
openProjectModal(permissions: Permission[], robotName: string) {
|
||||
this.projectsModalComponent.projectsModalOpened = true;
|
||||
this.projectsModalComponent.robotName = robotName;
|
||||
this.projectsModalComponent.permissions = permissions;
|
||||
}
|
||||
refresh() {
|
||||
this.currentPage = 1;
|
||||
this.selectedRows = [];
|
||||
this.clrLoad();
|
||||
}
|
||||
deleteRobots(robots: Robot[]) {
|
||||
let observableLists: Observable<any>[] = [];
|
||||
if (robots && robots.length) {
|
||||
robots.forEach(item => {
|
||||
observableLists.push(this.deleteRobot(item));
|
||||
});
|
||||
forkJoin(...observableLists).subscribe(resArr => {
|
||||
let error;
|
||||
if (resArr && resArr.length) {
|
||||
resArr.forEach(item => {
|
||||
if (item instanceof HttpErrorResponse) {
|
||||
error = errorHandler(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (error) {
|
||||
this.msgHandler.handleError(error);
|
||||
} else {
|
||||
this.msgHandler.showSuccess('SYSTEM_ROBOT.DELETE_ROBOT_SUCCESS');
|
||||
}
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
deleteRobot(robot: Robot): Observable<any> {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'SYSTEM_ROBOT.DELETE_ROBOT';
|
||||
operMessage.data.id = robot.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
return this.robotService.DeleteRobot({robotId: robot.id}).pipe(
|
||||
map(() => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
operateChanges(operMessage, OperationState.failure, message);
|
||||
return of(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
openDeleteRobotsDialog() {
|
||||
const robotNames = this.selectedRows.map(robot => robot.name).join(",");
|
||||
const deletionMessage = new ConfirmationMessage(
|
||||
"ROBOT_ACCOUNT.DELETION_TITLE",
|
||||
"ROBOT_ACCOUNT.DELETION_SUMMARY",
|
||||
robotNames,
|
||||
this.selectedRows,
|
||||
ConfirmationTargets.ROBOT_ACCOUNT,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.operateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
disableOrEnable() {
|
||||
const title: string = this.selectedRows[0].disable ? "SYSTEM_ROBOT.ENABLE_TITLE" : "SYSTEM_ROBOT.DISABLE_TITLE";
|
||||
const summary: string = this.selectedRows[0].disable ? "SYSTEM_ROBOT.ENABLE_SUMMARY" : "SYSTEM_ROBOT.DISABLE_SUMMARY";
|
||||
const deletionMessage = new ConfirmationMessage(
|
||||
title,
|
||||
summary,
|
||||
this.selectedRows[0].name,
|
||||
this.selectedRows[0],
|
||||
ConfirmationTargets.ROBOT_ACCOUNT_ENABLE_OR_DISABLE,
|
||||
this.selectedRows[0].disable ? ConfirmationButtons.ENABLE_CANCEL : ConfirmationButtons.DISABLE_CANCEL
|
||||
);
|
||||
this.operateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
|
||||
operateRobot() {
|
||||
const robot: Robot = clone(this.selectedRows[0]);
|
||||
const successMessage: string = robot.disable ? "SYSTEM_ROBOT.ENABLE_ROBOT_SUCCESSFULLY" : "SYSTEM_ROBOT.DISABLE_ROBOT_SUCCESSFULLY";
|
||||
robot.disable = !robot.disable;
|
||||
delete robot.secret;
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = robot.disable ? "SYSTEM_ROBOT.DISABLE_TITLE" : "SYSTEM_ROBOT.ENABLE_TITLE";
|
||||
opeMessage.data.id = robot.id;
|
||||
opeMessage.state = OperationState.progressing;
|
||||
opeMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(opeMessage);
|
||||
this.robotService.UpdateRobot({
|
||||
robot: robot,
|
||||
robotId: robot.id
|
||||
}).subscribe( res => {
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
this.msgHandler.showSuccess(successMessage);
|
||||
this.refresh();
|
||||
}, error => {
|
||||
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||
this.msgHandler.showSuccess(error);
|
||||
});
|
||||
}
|
||||
addSuccess(robot: Robot) {
|
||||
if (robot) {
|
||||
this.viewTokenComponent.open();
|
||||
this.viewTokenComponent.tokenModalOpened = false;
|
||||
this.viewTokenComponent.robot = clone(robot);
|
||||
this.viewTokenComponent.copyToken = true;
|
||||
this.translate
|
||||
.get("ROBOT_ACCOUNT.CREATED_SUCCESS", { param: robot.name })
|
||||
.subscribe((res: string) => {
|
||||
this.viewTokenComponent.createSuccess = res;
|
||||
});
|
||||
// export to token file
|
||||
const downLoadUrl = `data:text/json;charset=utf-8, ${encodeURIComponent(JSON.stringify(robot))}`;
|
||||
this.viewTokenComponent.downLoadHref = this.sanitizer.bypassSecurityTrustUrl(downLoadUrl);
|
||||
this.viewTokenComponent.downLoadFileName = `${robot.name}.json`;
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SystemRobotAccountsComponent } from './system-robot-accounts.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { NewRobotComponent } from './new-robot/new-robot.component';
|
||||
import { ProjectModule } from '../project/project.module';
|
||||
import { ListAllProjectsComponent } from './list-all-projects/list-all-projects.component';
|
||||
import { ProjectsModalComponent } from './projects-modal/projects-modal.component';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SystemRobotAccountsComponent,
|
||||
NewRobotComponent,
|
||||
ListAllProjectsComponent,
|
||||
ProjectsModalComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
ProjectModule
|
||||
]
|
||||
})
|
||||
export class SystemRobotAccountsModule { }
|
@ -0,0 +1,83 @@
|
||||
import { Robot } from "../../../ng-swagger-gen/models/robot";
|
||||
import { Access } from "../../../ng-swagger-gen/models/access";
|
||||
import { Project } from "../../../ng-swagger-gen/models/project";
|
||||
|
||||
export interface FrontRobot extends Robot {
|
||||
permissionScope?: {
|
||||
coverAll?: boolean,
|
||||
access?: Array<Access>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FrontProjectForAdd extends Project {
|
||||
permissions?: Array<{
|
||||
kind?: string;
|
||||
namespace?: string;
|
||||
access?: Array<FrontAccess>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface FrontAccess extends Access {
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export enum PermissionsKinds {
|
||||
PROJECT = 'project',
|
||||
SYSTEM = 'system'
|
||||
}
|
||||
|
||||
export enum Resource {
|
||||
ARTIFACT = 'repository',
|
||||
HELM_CHART = 'helm-chart',
|
||||
HELM_CHART_VERSION = 'helm-chart-version'
|
||||
}
|
||||
|
||||
export enum Action {
|
||||
PUSH = 'push',
|
||||
PULL = 'pull',
|
||||
READ = 'read',
|
||||
CREATE = 'create'
|
||||
}
|
||||
|
||||
export const NAMESPACE_ALL_PROJECTS: string = '*';
|
||||
|
||||
export const INITIAL_ACCESSES: FrontAccess[] = [
|
||||
{
|
||||
"resource": "repository",
|
||||
"action": "push",
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"resource": "repository",
|
||||
"action": "pull",
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"resource": "helm-chart",
|
||||
"action": "read",
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"resource": "helm-chart-version",
|
||||
"action": "create",
|
||||
"checked": true
|
||||
}
|
||||
];
|
||||
|
||||
export const ACTION_RESOURCE_I18N_MAP = {
|
||||
'push': 'ROBOT_ACCOUNT.PUSH',
|
||||
'pull': 'ROBOT_ACCOUNT.PULL',
|
||||
'read': 'SYSTEM_ROBOT.READ',
|
||||
'create': 'SYSTEM_ROBOT.CREATE',
|
||||
'repository': 'SYSTEM_ROBOT.ARTIFACT',
|
||||
'helm-chart': 'SYSTEM_ROBOT.HELM',
|
||||
'helm-chart-version': 'SYSTEM_ROBOT.HELM_VERSION'
|
||||
};
|
||||
|
||||
export enum ExpirationType {
|
||||
DEFAULT= 'default',
|
||||
DAYS = 'days',
|
||||
NEVER = 'never'
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
<clr-modal [(clrModalOpen)]="tokenModalOpened" class="copy-token"
|
||||
[clrModalStaticBackdrop]="true" [clrModalClosable]="true">
|
||||
<div class="modal-title">
|
||||
<span>{{"SYSTEM_ROBOT.REFRESH_SECRET" | translate}}</span>
|
||||
<div class="nav-divider"></div>
|
||||
<span class="name">{{robot?.name}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<p class="mt-0">{{"SYSTEM_ROBOT.REFRESH_SECRET_SUMMARY" | translate}}</p>
|
||||
<section class="form-block show-info">
|
||||
<form #secretForm="ngForm" class="clr-form clr-form-horizontal">
|
||||
<div class="clr-form-control">
|
||||
<label for="new-token" class="clr-control-label">{{"SYSTEM_ROBOT.NEW_TOKEN" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="(newToken.dirty || newToken.touched) && newToken.invalid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input"
|
||||
type="password" id="new-token"
|
||||
name="newToken"
|
||||
autocomplete="off"
|
||||
#newToken = "ngModel"
|
||||
[(ngModel)]="newSecret"
|
||||
pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,128}$"
|
||||
placeholder="{{'SYSTEM_ROBOT.PLACEHOLDER'| translate}}">
|
||||
</div>
|
||||
<clr-control-error *ngIf="(newToken.dirty || newToken.touched) && newToken.invalid">
|
||||
{{'SYSTEM_ROBOT.SECRET' | translate}}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control">
|
||||
<label for="confirm-token" class="clr-control-label">{{"SYSTEM_ROBOT.CONFIRM_SECRET" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="notSame() || (confirmToken.dirty || confirmToken.touched) && confirmToken.invalid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input"
|
||||
type="password" id="confirm-token"
|
||||
name="confirmToken"
|
||||
autocomplete="off"
|
||||
#confirmToken = "ngModel"
|
||||
[(ngModel)]="confirmSecret"
|
||||
pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,128}$"
|
||||
placeholder="{{'SYSTEM_ROBOT.SECRET_AGAIN'| translate}}">
|
||||
</div>
|
||||
<clr-control-error *ngIf="notSame() || ((confirmToken.dirty || confirmToken.touched) && confirmToken.invalid)">
|
||||
<span *ngIf="!notSame()">{{'SYSTEM_ROBOT.SECRET' | translate}}</span>
|
||||
<span *ngIf="notSame()">{{'SYSTEM_ROBOT.INCONSISTENT' | translate}}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button (click)="cancel()" id="refresh-token-cancel" type="button" class="btn btn-outline">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
<button [disabled]="!canRefresh()" (click)="refreshToken()" [clrLoading]="btnState" id="refresh-token-refresh" type="button" class="btn btn-primary">{{"SYSTEM_ROBOT.REFRESH" | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<clr-modal clrModalSize="lg" [(clrModalOpen)]="copyToken" class="copy-token"
|
||||
[clrModalStaticBackdrop]="true" [clrModalClosable]="false">
|
||||
<div class="modal-title">
|
||||
<h3 class="modal-title">
|
||||
<clr-icon class="alert-icon success-icon" shape="check-circle" size="50"></clr-icon>
|
||||
{{ createSuccess | translate}}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info" role="alert">
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<div class="alert-icon-wrapper">
|
||||
<clr-icon class="alert-icon" shape="info-circle"></clr-icon>
|
||||
</div>
|
||||
<span class="alert-text">{{'ROBOT_ACCOUNT.ALERT_TEXT' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="form-block show-info">
|
||||
<div class="form-group robot-name">
|
||||
<label class="form-group-label-override">{{'ROBOT_ACCOUNT.NAME'
|
||||
| translate}}</label>
|
||||
<span>{{robot?.name}}</span>
|
||||
</div>
|
||||
<div class="form-group robot-token">
|
||||
<label class="form-group-label-override">{{'ROBOT_ACCOUNT.TOKEN' |
|
||||
translate}}</label>
|
||||
<hbr-copy-input (onCopySuccess)="onCpSuccess($event)"
|
||||
inputSize="50" headerTitle=""
|
||||
defaultValue="{{robot?.secret}}" class="copy-input"></hbr-copy-input>
|
||||
</div>
|
||||
<a [href]="downLoadHref" [download]="downLoadFileName"><button class="btn mr-0" (click)="closeModal()">{{'ROBOT_ACCOUNT.EXPORT_TO_FILE' | translate}}</button></a>
|
||||
</section>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,49 @@
|
||||
.copy-token {
|
||||
.success-icon {
|
||||
color: #318700;
|
||||
}
|
||||
.show-info {
|
||||
.robot-name {
|
||||
margin: 30px 0;
|
||||
|
||||
label {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
.robot-token {
|
||||
margin-bottom: 20px;
|
||||
label {
|
||||
margin-right: 24px;
|
||||
}
|
||||
.copy-input {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:host::ng-deep {
|
||||
hbr-copy-input {
|
||||
.command-input {
|
||||
width: 32rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-divider {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
opacity: 0.15;
|
||||
content: '';
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.name {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ViewTokenComponent } from './view-token.component';
|
||||
import { RobotService } from "../../../../ng-swagger-gen/services/robot.service";
|
||||
import { OperationService } from "../../../lib/components/operation/operation.service";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
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, PermissionsKinds, Resource } from "../system-robot-util";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { of } from "rxjs";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
describe('ViewTokenComponent', () => {
|
||||
let component: ViewTokenComponent;
|
||||
let fixture: ComponentFixture<ViewTokenComponent>;
|
||||
const robot1: Robot = {
|
||||
id: 1,
|
||||
name: 'robot1',
|
||||
level: PermissionsKinds.SYSTEM,
|
||||
disable: false,
|
||||
expires_at: (new Date().getTime() + 100000) % 1000,
|
||||
description: 'for test',
|
||||
secret: 'tthf54hfth4545dfgd5g454grd54gd54g',
|
||||
permissions: [
|
||||
{
|
||||
kind: PermissionsKinds.PROJECT,
|
||||
namespace: 'project1',
|
||||
access: [
|
||||
{
|
||||
resource: Resource.ARTIFACT,
|
||||
action: Action.PUSH
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const fakedMessageHandlerService = {
|
||||
showSuccess() {
|
||||
},
|
||||
error() {
|
||||
}
|
||||
};
|
||||
const fakedRobotService = {
|
||||
UpdateRobot() {
|
||||
return of(null).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
TranslateModule.forRoot(),
|
||||
FormsModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [ ViewTokenComponent ],
|
||||
providers: [
|
||||
{ provide: RobotService, useValue: fakedRobotService},
|
||||
OperationService,
|
||||
{ provide: MessageHandlerService, useValue: fakedMessageHandlerService },
|
||||
],
|
||||
schemas: [
|
||||
NO_ERRORS_SCHEMA
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ViewTokenComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should show invalid secret', async () => {
|
||||
await fixture.whenStable();
|
||||
component.tokenModalOpened = true;
|
||||
component.robot = robot1;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const newSecretInput: HTMLInputElement = fixture.nativeElement.querySelector('#new-token');
|
||||
newSecretInput.value = '123';
|
||||
newSecretInput.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const error = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
it('should show secrets inconsistent', async () => {
|
||||
await fixture.whenStable();
|
||||
component.tokenModalOpened = true;
|
||||
component.robot = robot1;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const newSecretInput: HTMLInputElement = fixture.nativeElement.querySelector('#new-token');
|
||||
newSecretInput.value = 'Harbor12345';
|
||||
newSecretInput.dispatchEvent(new Event('input'));
|
||||
const confirmSecretInput: HTMLInputElement = fixture.nativeElement.querySelector('#confirm-token');
|
||||
confirmSecretInput.value = 'Harbor123456';
|
||||
confirmSecretInput.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const error = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,122 @@
|
||||
import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { RobotService } from "../../../../ng-swagger-gen/services/robot.service";
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
import { Robot } from "../../../../ng-swagger-gen/models/robot";
|
||||
import { clone } from "../../../lib/utils/utils";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../../lib/components/operation/operate";
|
||||
import { OperationService } from "../../../lib/components/operation/operation.service";
|
||||
import { errorHandler } from "../../../lib/utils/shared/shared.utils";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { Observable } from "rxjs";
|
||||
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
@Component({
|
||||
selector: 'view-token',
|
||||
templateUrl: './view-token.component.html',
|
||||
styleUrls: ['./view-token.component.scss']
|
||||
})
|
||||
export class ViewTokenComponent implements OnInit {
|
||||
tokenModalOpened: boolean = false;
|
||||
robot: Robot;
|
||||
newSecret: string;
|
||||
confirmSecret: string;
|
||||
btnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
@ViewChild(InlineAlertComponent)
|
||||
inlineAlertComponent: InlineAlertComponent;
|
||||
@ViewChild('secretForm', { static: true }) secretForm: NgForm;
|
||||
@Output()
|
||||
refreshSuccess: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
copyToken: boolean = false;
|
||||
createSuccess: string;
|
||||
downLoadFileName: string = '';
|
||||
downLoadHref: SafeUrl = '';
|
||||
constructor(private robotService: RobotService,
|
||||
private operationService: OperationService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private translate: TranslateService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.tokenModalOpened = false;
|
||||
}
|
||||
open() {
|
||||
this.tokenModalOpened = true;
|
||||
this.inlineAlertComponent.close();
|
||||
this.copyToken = false;
|
||||
this.createSuccess = null;
|
||||
this.newSecret = null;
|
||||
this.confirmSecret = null;
|
||||
this.downLoadFileName = '';
|
||||
this.downLoadHref = '';
|
||||
this.secretForm.reset();
|
||||
}
|
||||
refreshToken() {
|
||||
this.btnState = ClrLoadingState.LOADING;
|
||||
const robot: Robot = clone(this.robot);
|
||||
const opeMessage = new OperateInfo();
|
||||
opeMessage.name = "SYSTEM_ROBOT.REFRESH_SECRET";
|
||||
opeMessage.data.id = robot.id;
|
||||
opeMessage.state = OperationState.progressing;
|
||||
opeMessage.data.name = robot.name;
|
||||
this.operationService.publishInfo(opeMessage);
|
||||
if (this.newSecret) {
|
||||
robot.secret = this.newSecret;
|
||||
}
|
||||
this.robotService.RefreshSec({
|
||||
robotId: robot.id,
|
||||
robotSec: {
|
||||
secret: robot.secret
|
||||
}
|
||||
}).subscribe(res => {
|
||||
this.btnState = ClrLoadingState.SUCCESS;
|
||||
operateChanges(opeMessage, OperationState.success);
|
||||
this.refreshSuccess.emit(true);
|
||||
this.cancel();
|
||||
if (res && res.secret) {
|
||||
this.robot.secret = res.secret;
|
||||
this.copyToken = true;
|
||||
this.createSuccess = 'SYSTEM_ROBOT.REFRESH_SECRET_SUCCESS';
|
||||
// export to token file
|
||||
const downLoadUrl = `data:text/json;charset=utf-8, ${encodeURIComponent(JSON.stringify(robot))}`;
|
||||
this.downLoadHref = this.sanitizer.bypassSecurityTrustUrl(downLoadUrl);
|
||||
this.downLoadFileName = `${robot.name}.json`;
|
||||
} else {
|
||||
this.msgHandler.showSuccess('SYSTEM_ROBOT.REFRESH_SECRET_SUCCESS');
|
||||
}
|
||||
}, error => {
|
||||
this.btnState = ClrLoadingState.ERROR;
|
||||
this.inlineAlertComponent.showInlineError(error);
|
||||
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||
});
|
||||
}
|
||||
canRefresh() {
|
||||
if (!this.newSecret && !this.confirmSecret) {
|
||||
return true;
|
||||
}
|
||||
return this.newSecret && this.confirmSecret && this.newSecret === this.confirmSecret && this.secretForm.valid;
|
||||
}
|
||||
onCpSuccess($event: any): void {
|
||||
this.copyToken = false;
|
||||
this.tokenModalOpened = false;
|
||||
this.translate
|
||||
.get("ROBOT_ACCOUNT.COPY_SUCCESS", { param: this.robot.name })
|
||||
.subscribe((res: string) => {
|
||||
this.msgHandler.showSuccess(res);
|
||||
});
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
this.copyToken = false;
|
||||
this.tokenModalOpened = false;
|
||||
}
|
||||
|
||||
notSame(): boolean {
|
||||
return this.secretForm.valid && this.newSecret && this.confirmSecret && this.newSecret !== this.confirmSecret;
|
||||
}
|
||||
}
|
@ -243,3 +243,7 @@ artifact-list-tab {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-divider {
|
||||
background-color: $nav-divider-bg-color;
|
||||
}
|
||||
|
@ -38,5 +38,6 @@ $harbor-icon-drop-shadow-x: 58px;
|
||||
$command-input-color: #eaedf0;
|
||||
$command-input-bg-color: #2e4757;
|
||||
$label-hover-bg-color: #28404d;
|
||||
$nav-divider-bg-color: #fafafa;
|
||||
|
||||
@import "./common.scss";
|
||||
|
@ -38,6 +38,7 @@ $hbr-result-tip-histogram-inner-bg-color: #fff;
|
||||
$harbor-icon-translate-x: 100%;
|
||||
$harbor-icon-drop-shadow-x: -56px;
|
||||
$command-input-color: none;
|
||||
$command-input-bg-color: none;
|
||||
$command-input-bg-color: #ededed;
|
||||
$label-hover-bg-color: #eee;
|
||||
$nav-divider-bg-color: #000;
|
||||
@import "./common.scss";
|
||||
|
@ -1592,5 +1592,67 @@
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "Einträge pro Seite"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "Read",
|
||||
"CREATE": "Create",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "Add Robot",
|
||||
"UPDATE_ROBOT": "Update Robot",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully",
|
||||
"PLACEHOLDER": "Input new secret",
|
||||
"SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.",
|
||||
"REFRESH_SECRET": "Refresh Secret",
|
||||
"REFRESH_SECRET_SUCCESS": "Refreshed secret successfully",
|
||||
"DELETE_ROBOT": "Delete Robot",
|
||||
"DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully",
|
||||
"ENABLE_TITLE": "Enable Robot",
|
||||
"ENABLE_SUMMARY": "Do you want to enable robot {{param}}?",
|
||||
"DISABLE_TITLE": "Disable Robot",
|
||||
"DISABLE_SUMMARY": "Do you want to disable robot {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully",
|
||||
"ROBOT_ACCOUNT": "Robot account",
|
||||
"PROJECTS": "Projects",
|
||||
"ALL_PROJECTS": "All projects with",
|
||||
"PERMISSIONS": "PERMISSION(S)",
|
||||
"REFRESH_SECRET_SUMMARY": "Refresh the secret for this robot account",
|
||||
"TOKEN": "Secret",
|
||||
"NEW_TOKEN": "New Secret",
|
||||
"REFRESH": "REFRESH",
|
||||
"PROJECTS_MODAL_TITLE": "Projects for Robot Account",
|
||||
"PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.",
|
||||
"CREATE_ROBOT": "Create System Robot Account",
|
||||
"CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EDIT_ROBOT": "Edit System Robot Account",
|
||||
"EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EXPIRATION_TIME": "Expiration time",
|
||||
"EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".",
|
||||
"EXPIRATION_DEFAULT": "days(default)",
|
||||
"EXPIRATION_DAYS": "Specify # of days",
|
||||
"EXPIRATION_NEVER": "Never",
|
||||
"EXPIRATION_REQUIRED": "Valid expiration time is required",
|
||||
"COVER_ALL": "Cover all projects",
|
||||
"COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects",
|
||||
"COVER_ALL_SUMMARY": "All current and future projects selected.",
|
||||
"RESET_PERMISSION": "RESET PERMISSIONS",
|
||||
"PERMISSION_COLUMN": "Permissions",
|
||||
"EXPIRES_AT": "Expires at",
|
||||
"VIEW_SECRET": "REFRESH SECRET",
|
||||
"LEGACY": "Legacy",
|
||||
"CREATE_PROJECT_ROBOT": "Create Robot Account",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project",
|
||||
"EDIT_PROJECT_ROBOT": "Edit Robot Account",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project",
|
||||
"NOT_FOUND": "We couldn't find any robots!",
|
||||
"SELECT_ALL": "SELECT ALL",
|
||||
"UNSELECT_ALL": "UNSELECT ALL",
|
||||
"ROBOT_ACCOUNT_NAV": "Robot Accounts",
|
||||
"COVERED_PROJECTS": "PROJECT(S)",
|
||||
"CONFIRM_SECRET": "Confirm Secret",
|
||||
"SECRET_AGAIN": "Input secret again",
|
||||
"INCONSISTENT": "Two secrets are inconsistent"
|
||||
}
|
||||
}
|
||||
|
@ -357,7 +357,7 @@
|
||||
"FILTER_PLACEHOLDER": "Filter Robot Accounts",
|
||||
"ROBOT_NAME": "Cannot contain special characters(~#$%) and maximum length should be 255 characters.",
|
||||
"ACCOUNT_EXISTING": "Robot Account is already exists.",
|
||||
"ALERT_TEXT": "This is the only time to copy your personal access token.You wont't have another opportunity",
|
||||
"ALERT_TEXT": "This is the only time to copy this secret.You won't have another opportunity",
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
@ -1592,5 +1592,67 @@
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "Page size"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "Read",
|
||||
"CREATE": "Create",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "Add Robot",
|
||||
"UPDATE_ROBOT": "Update Robot",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully",
|
||||
"PLACEHOLDER": "Input new secret",
|
||||
"SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.",
|
||||
"REFRESH_SECRET": "Refresh Secret",
|
||||
"REFRESH_SECRET_SUCCESS": "Refreshed secret successfully",
|
||||
"DELETE_ROBOT": "Delete Robot",
|
||||
"DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully",
|
||||
"ENABLE_TITLE": "Enable Robot",
|
||||
"ENABLE_SUMMARY": "Do you want to enable robot {{param}}?",
|
||||
"DISABLE_TITLE": "Disable Robot",
|
||||
"DISABLE_SUMMARY": "Do you want to disable robot {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully",
|
||||
"ROBOT_ACCOUNT": "Robot account",
|
||||
"PROJECTS": "Projects",
|
||||
"ALL_PROJECTS": "All projects with",
|
||||
"PERMISSIONS": "PERMISSION(S)",
|
||||
"REFRESH_SECRET_SUMMARY": "Refresh the secret for this robot account",
|
||||
"TOKEN": "Secret",
|
||||
"NEW_TOKEN": "New Secret",
|
||||
"REFRESH": "REFRESH",
|
||||
"PROJECTS_MODAL_TITLE": "Projects for Robot Account",
|
||||
"PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.",
|
||||
"CREATE_ROBOT": "Create System Robot Account",
|
||||
"CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EDIT_ROBOT": "Edit System Robot Account",
|
||||
"EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EXPIRATION_TIME": "Expiration time",
|
||||
"EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".",
|
||||
"EXPIRATION_DEFAULT": "days(default)",
|
||||
"EXPIRATION_DAYS": "Specify # of days",
|
||||
"EXPIRATION_NEVER": "Never",
|
||||
"EXPIRATION_REQUIRED": "Valid expiration time is required",
|
||||
"COVER_ALL": "Cover all projects",
|
||||
"COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects",
|
||||
"COVER_ALL_SUMMARY": "All current and future projects selected.",
|
||||
"RESET_PERMISSION": "RESET PERMISSIONS",
|
||||
"PERMISSION_COLUMN": "Permissions",
|
||||
"EXPIRES_AT": "Expires at",
|
||||
"VIEW_SECRET": "REFRESH SECRET",
|
||||
"LEGACY": "Legacy",
|
||||
"CREATE_PROJECT_ROBOT": "Create Robot Account",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project",
|
||||
"EDIT_PROJECT_ROBOT": "Edit Robot Account",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project",
|
||||
"NOT_FOUND": "We couldn't find any robots!",
|
||||
"SELECT_ALL": "SELECT ALL",
|
||||
"UNSELECT_ALL": "UNSELECT ALL",
|
||||
"ROBOT_ACCOUNT_NAV": "Robot Accounts",
|
||||
"COVERED_PROJECTS": "PROJECT(S)",
|
||||
"CONFIRM_SECRET": "Confirm Secret",
|
||||
"SECRET_AGAIN": "Input secret again",
|
||||
"INCONSISTENT": "Two secrets are inconsistent"
|
||||
}
|
||||
}
|
||||
|
@ -358,7 +358,7 @@
|
||||
"FILTER_PLACEHOLDER": "Filter Robot Accounts",
|
||||
"ROBOT_NAME": "Cannot contain special characters(~#$%) and maximum length should be 255 characters.",
|
||||
"ACCOUNT_EXISTING": "Robot Account is already exists.",
|
||||
"ALERT_TEXT": "This is the only time to copy your personal access token.You wont't have another opportunity",
|
||||
"ALERT_TEXT": "This is the only time to copy this secret.You won't have another opportunity",
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
@ -1590,5 +1590,67 @@
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "Page size"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "Read",
|
||||
"CREATE": "Create",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "Add Robot",
|
||||
"UPDATE_ROBOT": "Update Robot",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully",
|
||||
"PLACEHOLDER": "Input new secret",
|
||||
"SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.",
|
||||
"REFRESH_SECRET": "Refresh Secret",
|
||||
"REFRESH_SECRET_SUCCESS": "Refreshed secret successfully",
|
||||
"DELETE_ROBOT": "Delete Robot",
|
||||
"DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully",
|
||||
"ENABLE_TITLE": "Enable Robot",
|
||||
"ENABLE_SUMMARY": "Do you want to enable robot {{param}}?",
|
||||
"DISABLE_TITLE": "Disable Robot",
|
||||
"DISABLE_SUMMARY": "Do you want to disable robot {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully",
|
||||
"ROBOT_ACCOUNT": "Robot account",
|
||||
"PROJECTS": "Projects",
|
||||
"ALL_PROJECTS": "All projects with",
|
||||
"PERMISSIONS": "PERMISSION(S)",
|
||||
"REFRESH_SECRET_SUMMARY": "Refresh the secret for this robot account",
|
||||
"TOKEN": "Secret",
|
||||
"NEW_TOKEN": "New Secret",
|
||||
"REFRESH": "REFRESH",
|
||||
"PROJECTS_MODAL_TITLE": "Projects for Robot Account",
|
||||
"PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.",
|
||||
"CREATE_ROBOT": "Create System Robot Account",
|
||||
"CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EDIT_ROBOT": "Edit System Robot Account",
|
||||
"EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EXPIRATION_TIME": "Expiration time",
|
||||
"EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".",
|
||||
"EXPIRATION_DEFAULT": "days(default)",
|
||||
"EXPIRATION_DAYS": "Specify # of days",
|
||||
"EXPIRATION_NEVER": "Never",
|
||||
"EXPIRATION_REQUIRED": "Valid expiration time is required",
|
||||
"COVER_ALL": "Cover all projects",
|
||||
"COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects",
|
||||
"COVER_ALL_SUMMARY": "All current and future projects selected.",
|
||||
"RESET_PERMISSION": "RESET PERMISSIONS",
|
||||
"PERMISSION_COLUMN": "Permissions",
|
||||
"EXPIRES_AT": "Expires at",
|
||||
"VIEW_SECRET": "REFRESH SECRET",
|
||||
"LEGACY": "Legacy",
|
||||
"CREATE_PROJECT_ROBOT": "Create Robot Account",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project",
|
||||
"EDIT_PROJECT_ROBOT": "Edit Robot Account",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project",
|
||||
"NOT_FOUND": "We couldn't find any robots!",
|
||||
"SELECT_ALL": "SELECT ALL",
|
||||
"UNSELECT_ALL": "UNSELECT ALL",
|
||||
"ROBOT_ACCOUNT_NAV": "Robot Accounts",
|
||||
"COVERED_PROJECTS": "PROJECT(S)",
|
||||
"CONFIRM_SECRET": "Confirm Secret",
|
||||
"SECRET_AGAIN": "Input secret again",
|
||||
"INCONSISTENT": "Two secrets are inconsistent"
|
||||
}
|
||||
}
|
||||
|
@ -349,7 +349,7 @@
|
||||
"FILTER_PLACEHOLDER": "Filter Robot Accounts",
|
||||
"ROBOT_NAME": "ne peut pas contenir de caractères spéciaux(~#$%) et la longueur maximale devrait être de 255 caractères.",
|
||||
"ACCOUNT_EXISTING": "le robot est existe déjà.",
|
||||
"ALERT_TEXT": "This is the only time to copy your personal access token.You wont't have another opportunity",
|
||||
"ALERT_TEXT": "This is the only time to copy this secret.You won't have another opportunity",
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "confirmer l'enlèvement des comptes du robot ",
|
||||
@ -1560,5 +1560,67 @@
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "Page size"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "Read",
|
||||
"CREATE": "Create",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "Add Robot",
|
||||
"UPDATE_ROBOT": "Update Robot",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully",
|
||||
"PLACEHOLDER": "Input new secret",
|
||||
"SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.",
|
||||
"REFRESH_SECRET": "Refresh Secret",
|
||||
"REFRESH_SECRET_SUCCESS": "Refreshed secret successfully",
|
||||
"DELETE_ROBOT": "Delete Robot",
|
||||
"DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully",
|
||||
"ENABLE_TITLE": "Enable Robot",
|
||||
"ENABLE_SUMMARY": "Do you want to enable robot {{param}}?",
|
||||
"DISABLE_TITLE": "Disable Robot",
|
||||
"DISABLE_SUMMARY": "Do you want to disable robot {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully",
|
||||
"ROBOT_ACCOUNT": "Robot account",
|
||||
"PROJECTS": "Projects",
|
||||
"ALL_PROJECTS": "All projects with",
|
||||
"PERMISSIONS": "PERMISSION(S)",
|
||||
"REFRESH_SECRET_SUMMARY": "Refresh the secret for this robot account",
|
||||
"TOKEN": "Secret",
|
||||
"NEW_TOKEN": "New Secret",
|
||||
"REFRESH": "REFRESH",
|
||||
"PROJECTS_MODAL_TITLE": "Projects for Robot Account",
|
||||
"PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.",
|
||||
"CREATE_ROBOT": "Create System Robot Account",
|
||||
"CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EDIT_ROBOT": "Edit System Robot Account",
|
||||
"EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EXPIRATION_TIME": "Expiration time",
|
||||
"EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".",
|
||||
"EXPIRATION_DEFAULT": "days(default)",
|
||||
"EXPIRATION_DAYS": "Specify # of days",
|
||||
"EXPIRATION_NEVER": "Never",
|
||||
"EXPIRATION_REQUIRED": "Valid expiration time is required",
|
||||
"COVER_ALL": "Cover all projects",
|
||||
"COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects",
|
||||
"COVER_ALL_SUMMARY": "All current and future projects selected.",
|
||||
"RESET_PERMISSION": "RESET PERMISSIONS",
|
||||
"PERMISSION_COLUMN": "Permissions",
|
||||
"EXPIRES_AT": "Expires at",
|
||||
"VIEW_SECRET": "REFRESH SECRET",
|
||||
"LEGACY": "Legacy",
|
||||
"CREATE_PROJECT_ROBOT": "Create Robot Account",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project",
|
||||
"EDIT_PROJECT_ROBOT": "Edit Robot Account",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project",
|
||||
"NOT_FOUND": "We couldn't find any robots!",
|
||||
"SELECT_ALL": "SELECT ALL",
|
||||
"UNSELECT_ALL": "UNSELECT ALL",
|
||||
"ROBOT_ACCOUNT_NAV": "Robot Accounts",
|
||||
"COVERED_PROJECTS": "PROJECT(S)",
|
||||
"CONFIRM_SECRET": "Confirm Secret",
|
||||
"SECRET_AGAIN": "Input secret again",
|
||||
"INCONSISTENT": "Two secrets are inconsistent"
|
||||
}
|
||||
}
|
||||
|
@ -1588,6 +1588,68 @@
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "Page size"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "Read",
|
||||
"CREATE": "Create",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "Add Robot",
|
||||
"UPDATE_ROBOT": "Update Robot",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully",
|
||||
"PLACEHOLDER": "Input new secret",
|
||||
"SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.",
|
||||
"REFRESH_SECRET": "Refresh Secret",
|
||||
"REFRESH_SECRET_SUCCESS": "Refreshed secret successfully",
|
||||
"DELETE_ROBOT": "Delete Robot",
|
||||
"DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully",
|
||||
"ENABLE_TITLE": "Enable Robot",
|
||||
"ENABLE_SUMMARY": "Do you want to enable robot {{param}}?",
|
||||
"DISABLE_TITLE": "Disable Robot",
|
||||
"DISABLE_SUMMARY": "Do you want to disable robot {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully",
|
||||
"ROBOT_ACCOUNT": "Robot account",
|
||||
"PROJECTS": "Projects",
|
||||
"ALL_PROJECTS": "All projects with",
|
||||
"PERMISSIONS": "PERMISSION(S)",
|
||||
"REFRESH_SECRET_SUMMARY": "Refresh the secret for this robot account",
|
||||
"TOKEN": "Secret",
|
||||
"NEW_TOKEN": "New Secret",
|
||||
"REFRESH": "REFRESH",
|
||||
"PROJECTS_MODAL_TITLE": "Projects for Robot Account",
|
||||
"PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.",
|
||||
"CREATE_ROBOT": "Create System Robot Account",
|
||||
"CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EDIT_ROBOT": "Edit System Robot Account",
|
||||
"EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EXPIRATION_TIME": "Expiration time",
|
||||
"EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".",
|
||||
"EXPIRATION_DEFAULT": "days(default)",
|
||||
"EXPIRATION_DAYS": "Specify # of days",
|
||||
"EXPIRATION_NEVER": "Never",
|
||||
"EXPIRATION_REQUIRED": "Valid expiration time is required",
|
||||
"COVER_ALL": "Cover all projects",
|
||||
"COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects",
|
||||
"COVER_ALL_SUMMARY": "All current and future projects selected.",
|
||||
"RESET_PERMISSION": "RESET PERMISSIONS",
|
||||
"PERMISSION_COLUMN": "Permissions",
|
||||
"EXPIRES_AT": "Expires at",
|
||||
"VIEW_SECRET": "REFRESH SECRET",
|
||||
"LEGACY": "Legacy",
|
||||
"CREATE_PROJECT_ROBOT": "Create Robot Account",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project",
|
||||
"EDIT_PROJECT_ROBOT": "Edit Robot Account",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project",
|
||||
"NOT_FOUND": "We couldn't find any robots!",
|
||||
"SELECT_ALL": "SELECT ALL",
|
||||
"UNSELECT_ALL": "UNSELECT ALL",
|
||||
"ROBOT_ACCOUNT_NAV": "Robot Accounts",
|
||||
"COVERED_PROJECTS": "PROJECT(S)",
|
||||
"CONFIRM_SECRET": "Confirm Secret",
|
||||
"SECRET_AGAIN": "Input secret again",
|
||||
"INCONSISTENT": "Two secrets are inconsistent"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1592,5 +1592,67 @@
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "Page size"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "Read",
|
||||
"CREATE": "Create",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "Add Robot",
|
||||
"UPDATE_ROBOT": "Update Robot",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully",
|
||||
"PLACEHOLDER": "Input new secret",
|
||||
"SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.",
|
||||
"REFRESH_SECRET": "Refresh Secret",
|
||||
"REFRESH_SECRET_SUCCESS": "Refreshed secret successfully",
|
||||
"DELETE_ROBOT": "Delete Robot",
|
||||
"DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully",
|
||||
"ENABLE_TITLE": "Enable Robot",
|
||||
"ENABLE_SUMMARY": "Do you want to enable robot {{param}}?",
|
||||
"DISABLE_TITLE": "Disable Robot",
|
||||
"DISABLE_SUMMARY": "Do you want to disable robot {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully",
|
||||
"ROBOT_ACCOUNT": "Robot account",
|
||||
"PROJECTS": "Projects",
|
||||
"ALL_PROJECTS": "All projects with",
|
||||
"PERMISSIONS": "PERMISSION(S)",
|
||||
"REFRESH_SECRET_SUMMARY": "Refresh the secret for this robot account",
|
||||
"TOKEN": "Secret",
|
||||
"NEW_TOKEN": "New Secret",
|
||||
"REFRESH": "REFRESH",
|
||||
"PROJECTS_MODAL_TITLE": "Projects for Robot Account",
|
||||
"PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.",
|
||||
"CREATE_ROBOT": "Create System Robot Account",
|
||||
"CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EDIT_ROBOT": "Edit System Robot Account",
|
||||
"EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EXPIRATION_TIME": "Expiration time",
|
||||
"EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".",
|
||||
"EXPIRATION_DEFAULT": "days(default)",
|
||||
"EXPIRATION_DAYS": "Specify # of days",
|
||||
"EXPIRATION_NEVER": "Never",
|
||||
"EXPIRATION_REQUIRED": "Valid expiration time is required",
|
||||
"COVER_ALL": "Cover all projects",
|
||||
"COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects",
|
||||
"COVER_ALL_SUMMARY": "All current and future projects selected.",
|
||||
"RESET_PERMISSION": "RESET PERMISSIONS",
|
||||
"PERMISSION_COLUMN": "Permissions",
|
||||
"EXPIRES_AT": "Expires at",
|
||||
"VIEW_SECRET": "REFRESH SECRET",
|
||||
"LEGACY": "Legacy",
|
||||
"CREATE_PROJECT_ROBOT": "Create Robot Account",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project",
|
||||
"EDIT_PROJECT_ROBOT": "Edit Robot Account",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project",
|
||||
"NOT_FOUND": "We couldn't find any robots!",
|
||||
"SELECT_ALL": "SELECT ALL",
|
||||
"UNSELECT_ALL": "UNSELECT ALL",
|
||||
"ROBOT_ACCOUNT_NAV": "Robot Accounts",
|
||||
"COVERED_PROJECTS": "PROJECT(S)",
|
||||
"CONFIRM_SECRET": "Confirm Secret",
|
||||
"SECRET_AGAIN": "Input secret again",
|
||||
"INCONSISTENT": "Two secrets are inconsistent"
|
||||
}
|
||||
}
|
||||
|
@ -356,7 +356,7 @@
|
||||
"FILTER_PLACEHOLDER": "过滤机器人账户",
|
||||
"ROBOT_NAME": "不能包含特殊字符(~#$%)且长度不能超过255。",
|
||||
"ACCOUNT_EXISTING": "机器人账户已经存在。",
|
||||
"ALERT_TEXT": "这是唯一一次复制您的个人访问令牌的机会",
|
||||
"ALERT_TEXT": "这是唯一一次复制当前令牌的机会",
|
||||
"CREATED_SUCCESS": "创建账户 '{{param}}' 成功。",
|
||||
"COPY_SUCCESS": "成功复制 '{{param}}' 的令牌",
|
||||
"DELETION_TITLE": "删除账户确认",
|
||||
@ -1589,5 +1589,67 @@
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "页面大小"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "读取",
|
||||
"CREATE": "创建",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "添加机器人",
|
||||
"UPDATE_ROBOT": "更新机器人",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "更新机器人成功",
|
||||
"PLACEHOLDER": "请输入新令牌",
|
||||
"SECRET": "令牌长度在8到20之间且需包含至少一个大写字符,一个小写字符和一个数字。",
|
||||
"REFRESH_SECRET": "刷新令牌",
|
||||
"REFRESH_SECRET_SUCCESS": "刷新令牌成功",
|
||||
"DELETE_ROBOT": "删除机器人",
|
||||
"DELETE_ROBOT_SUCCESS": "删除机器人成功",
|
||||
"ENABLE_TITLE": "启用机器人",
|
||||
"ENABLE_SUMMARY": "您想启用机器人 {{param}}?",
|
||||
"DISABLE_TITLE": "禁用机器人",
|
||||
"DISABLE_SUMMARY": "你想禁用机器人 {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "启用机器人成功",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "禁用机器人成功",
|
||||
"ROBOT_ACCOUNT": "机器人账户",
|
||||
"PROJECTS": "覆盖项目数",
|
||||
"ALL_PROJECTS": "全部项目且具有",
|
||||
"PERMISSIONS": "项权限",
|
||||
"REFRESH_SECRET_SUMMARY": "刷新当前机器人账户令牌",
|
||||
"TOKEN": "令牌",
|
||||
"NEW_TOKEN": "新令牌",
|
||||
"REFRESH": "刷新",
|
||||
"PROJECTS_MODAL_TITLE": "当前机器人账户覆盖的项目",
|
||||
"PROJECTS_MODAL_SUMMARY": "以下为当前机器人账户覆盖的项目列表",
|
||||
"CREATE_ROBOT": "创建系统级机器人账户",
|
||||
"CREATE_ROBOT_SUMMARY": "创建机器人账户。您可以选中\"覆盖全部项目\"项来覆盖当前所有项目以及未来新增的项目",
|
||||
"EDIT_ROBOT": "编辑系统级机器人账户",
|
||||
"EDIT_ROBOT_SUMMARY": "编辑机器人账户。您可以选中\"覆盖全部项目\"项来覆盖当前所有项目以及未来新增的项目",
|
||||
"EXPIRATION_TIME": "过期时间",
|
||||
"EXPIRATION_TIME_EXPLAIN": "当前机器人账户的令牌有效时间(单位为天且起始点为创建时间)。如想设置为永不过期, 请输入\"-1\"。",
|
||||
"EXPIRATION_DEFAULT": "天(默认)",
|
||||
"EXPIRATION_DAYS": "指定天数",
|
||||
"EXPIRATION_NEVER": "永不过期",
|
||||
"EXPIRATION_REQUIRED": "请输入有效的过期时间",
|
||||
"COVER_ALL": "覆盖全部项目",
|
||||
"COVER_ALL_EXPLAIN": "选中此项来覆盖当前所有项目以及未来新增的项目",
|
||||
"COVER_ALL_SUMMARY": "已选中当前所有项目以及未来新增的项目",
|
||||
"RESET_PERMISSION": "重置初始权限",
|
||||
"PERMISSION_COLUMN": "权限",
|
||||
"EXPIRES_AT": "有效期至",
|
||||
"VIEW_SECRET": "刷新令牌",
|
||||
"LEGACY": "旧",
|
||||
"CREATE_PROJECT_ROBOT": "创建机器人账号",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "为当前项目创建机器人账号",
|
||||
"EDIT_PROJECT_ROBOT": "编辑机器人账号",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "为当前项目编辑机器人账号",
|
||||
"NOT_FOUND": "未发现任何机器人账户!",
|
||||
"SELECT_ALL": "全选",
|
||||
"UNSELECT_ALL": "全不选",
|
||||
"ROBOT_ACCOUNT_NAV": "机器人账户",
|
||||
"COVERED_PROJECTS": "个项目",
|
||||
"CONFIRM_SECRET": "确认令牌",
|
||||
"SECRET_AGAIN": "请再次输入令牌",
|
||||
"INCONSISTENT": "两次输入不一致"
|
||||
}
|
||||
}
|
||||
|
@ -1575,6 +1575,68 @@
|
||||
"NEED_HELP": "Please ask your system admin to add a provider first"
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PAGE_SIZE": "Page size"
|
||||
"PAGE_SIZE": "Page size"
|
||||
},
|
||||
"SYSTEM_ROBOT": {
|
||||
"READ": "Read",
|
||||
"CREATE": "Create",
|
||||
"ARTIFACT": "Artifact",
|
||||
"HELM": "Helm Chart",
|
||||
"HELM_VERSION": "Helm Chart Version",
|
||||
"ADD_ROBOT": "Add Robot",
|
||||
"UPDATE_ROBOT": "Update Robot",
|
||||
"UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully",
|
||||
"PLACEHOLDER": "Input new secret",
|
||||
"SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.",
|
||||
"REFRESH_SECRET": "Refresh Secret",
|
||||
"REFRESH_SECRET_SUCCESS": "Refreshed secret successfully",
|
||||
"DELETE_ROBOT": "Delete Robot",
|
||||
"DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully",
|
||||
"ENABLE_TITLE": "Enable Robot",
|
||||
"ENABLE_SUMMARY": "Do you want to enable robot {{param}}?",
|
||||
"DISABLE_TITLE": "Disable Robot",
|
||||
"DISABLE_SUMMARY": "Do you want to disable robot {{param}}?",
|
||||
"ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully",
|
||||
"DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully",
|
||||
"ROBOT_ACCOUNT": "Robot account",
|
||||
"PROJECTS": "Projects",
|
||||
"ALL_PROJECTS": "All projects with",
|
||||
"PERMISSIONS": "PERMISSION(S)",
|
||||
"REFRESH_SECRET_SUMMARY": "Refresh the secret for this robot account",
|
||||
"TOKEN": "Secret",
|
||||
"NEW_TOKEN": "New Secret",
|
||||
"REFRESH": "REFRESH",
|
||||
"PROJECTS_MODAL_TITLE": "Projects for Robot Account",
|
||||
"PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.",
|
||||
"CREATE_ROBOT": "Create System Robot Account",
|
||||
"CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EDIT_ROBOT": "Edit System Robot Account",
|
||||
"EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects",
|
||||
"EXPIRATION_TIME": "Expiration time",
|
||||
"EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".",
|
||||
"EXPIRATION_DEFAULT": "days(default)",
|
||||
"EXPIRATION_DAYS": "Specify # of days",
|
||||
"EXPIRATION_NEVER": "Never",
|
||||
"EXPIRATION_REQUIRED": "Valid expiration time is required",
|
||||
"COVER_ALL": "Cover all projects",
|
||||
"COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects",
|
||||
"COVER_ALL_SUMMARY": "All current and future projects selected.",
|
||||
"RESET_PERMISSION": "RESET PERMISSIONS",
|
||||
"PERMISSION_COLUMN": "Permissions",
|
||||
"EXPIRES_AT": "Expires at",
|
||||
"VIEW_SECRET": "REFRESH SECRET",
|
||||
"LEGACY": "Legacy",
|
||||
"CREATE_PROJECT_ROBOT": "Create Robot Account",
|
||||
"CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project",
|
||||
"EDIT_PROJECT_ROBOT": "Edit Robot Account",
|
||||
"EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project",
|
||||
"NOT_FOUND": "We couldn't find any robots!",
|
||||
"SELECT_ALL": "SELECT ALL",
|
||||
"UNSELECT_ALL": "UNSELECT ALL",
|
||||
"ROBOT_ACCOUNT_NAV": "Robot Accounts",
|
||||
"COVERED_PROJECTS": "PROJECT(S)",
|
||||
"CONFIRM_SECRET": "Confirm Secret",
|
||||
"SECRET_AGAIN": "Input secret again",
|
||||
"INCONSISTENT": "Two secrets are inconsistent"
|
||||
}
|
||||
}
|
||||
|
@ -14,4 +14,4 @@
|
||||
<clr-icon shape="copy" [class.is-success]="isCopied" [class.is-error]="hasCopyError" class="info-tips-icon" size="24" [ngxClipboard]="inputTarget1" (cbOnSuccess)="onSuccess($event)" (cbOnError)="onError($event)"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user