mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 20:26:13 +01:00
Support Robot account in Harbor
Signed-off-by: FangyuanCheng <fangyuanc@vmware.com>
This commit is contained in:
parent
daf81e2413
commit
404ee307f3
@ -12,7 +12,7 @@
|
||||
@include text-overflow;
|
||||
}
|
||||
|
||||
@mixin grid-left-top-pos{
|
||||
@mixin grid-right-top-pos{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
|
@ -12,7 +12,7 @@ $size60:60px;
|
||||
.toolbar {
|
||||
overflow: hidden;
|
||||
.rightPos {
|
||||
@include grid-left-top-pos;
|
||||
@include grid-right-top-pos;
|
||||
margin-top: 20px;
|
||||
.filter-divider {
|
||||
display: inline-block;
|
||||
|
@ -16,7 +16,7 @@
|
||||
.toolbar {
|
||||
overflow: hidden;
|
||||
.rightPos {
|
||||
@include grid-left-top-pos;
|
||||
@include grid-right-top-pos;
|
||||
.filter-divider {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
|
@ -1,7 +1,7 @@
|
||||
@import '../mixin';
|
||||
|
||||
.rightPos{
|
||||
@include grid-left-top-pos;
|
||||
@include grid-right-top-pos;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
@ -33,6 +33,7 @@ export const enum ConfirmationTargets {
|
||||
PROJECT,
|
||||
PROJECT_MEMBER,
|
||||
USER,
|
||||
ROBOT_ACCOUNT,
|
||||
POLICY,
|
||||
TOGGLE_CONFIRM,
|
||||
TARGET,
|
||||
|
@ -11,27 +11,24 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreModule } from '../core/core.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { ConfigurationComponent } from './config.component';
|
||||
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 { GcComponent } from './gc/gc.component';
|
||||
import { GcRepoService } from './gc/gc.service';
|
||||
import { GcApiRepository } from './gc/gc.api.repository';
|
||||
import { GcViewModelFactory } from './gc/gc.viewmodel.factory';
|
||||
import { GcUtility } from './gc/gc.utility';
|
||||
import { NgModule } from "@angular/core";
|
||||
import { CoreModule } from "../core/core.module";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
import { ConfigurationComponent } from "./config.component";
|
||||
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 { GcComponent } from "./gc/gc.component";
|
||||
import { GcRepoService } from "./gc/gc.service";
|
||||
import { GcApiRepository } from "./gc/gc.api.repository";
|
||||
import { RobotApiRepository } from "../project/robot-account/robot.api.repository";
|
||||
import { GcViewModelFactory } from "./gc/gc.viewmodel.factory";
|
||||
import { GcUtility } from "./gc/gc.utility";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreModule,
|
||||
SharedModule
|
||||
],
|
||||
imports: [CoreModule, SharedModule],
|
||||
declarations: [
|
||||
ConfigurationComponent,
|
||||
ConfigurationAuthComponent,
|
||||
@ -39,6 +36,14 @@ import { GcUtility } from './gc/gc.utility';
|
||||
GcComponent
|
||||
],
|
||||
exports: [ConfigurationComponent],
|
||||
providers: [ConfigurationService, GcRepoService, GcApiRepository, GcViewModelFactory, GcUtility, ConfirmMessageHandler]
|
||||
providers: [
|
||||
ConfigurationService,
|
||||
GcRepoService,
|
||||
GcApiRepository,
|
||||
GcViewModelFactory,
|
||||
GcUtility,
|
||||
ConfirmMessageHandler,
|
||||
RobotApiRepository
|
||||
]
|
||||
})
|
||||
export class ConfigurationModule { }
|
||||
export class ConfigurationModule {}
|
||||
|
@ -44,6 +44,7 @@ import { LeavingRepositoryRouteDeactivate } from './shared/route/leaving-reposit
|
||||
import { ProjectComponent } from './project/project.component';
|
||||
import { ProjectDetailComponent } from './project/project-detail/project-detail.component';
|
||||
import { MemberComponent } from './project/member/member.component';
|
||||
import { RobotAccountComponent } from './project/robot-account/robot-account.component';
|
||||
import { ProjectLabelComponent } from "./project/project-label/project-label.component";
|
||||
import { ProjectConfigComponent } from './project/project-config/project-config.component';
|
||||
import { ProjectRoutingResolver } from './project/project-routing-resolver.service';
|
||||
@ -178,6 +179,10 @@ const harborRoutes: Routes = [
|
||||
{
|
||||
path: 'configs',
|
||||
component: ProjectConfigComponent
|
||||
},
|
||||
{
|
||||
path: 'robot-account',
|
||||
component: RobotAccountComponent
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -22,6 +22,9 @@
|
||||
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
|
||||
<a class="nav-link" routerLink="logs" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSProjectAdmin || isSystemAdmin">
|
||||
<a class="nav-link" routerLink="robot-account" routerLinkActive="active">{{'PROJECT_DETAIL.ROBOT_ACCOUNTS' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSessionValid && (isSystemAdmin || isMember)">
|
||||
<a class="nav-link" routerLink="configs" routerLinkActive="active">{{'PROJECT_DETAIL.CONFIG' | translate}}</a>
|
||||
</li>
|
||||
|
@ -30,6 +30,7 @@ import { AddGroupComponent } from './member/add-group/add-group.component';
|
||||
|
||||
import { ProjectService } from './project.service';
|
||||
import { MemberService } from './member/member.service';
|
||||
import { RobotService } from './robot-account/robot-account.service';
|
||||
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||
|
||||
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
|
||||
@ -37,6 +38,8 @@ import { ProjectLabelComponent } from "../project/project-label/project-label.co
|
||||
import { ListChartsComponent } from './list-charts/list-charts.component';
|
||||
import { ListChartVersionsComponent } from './list-chart-versions/list-chart-versions.component';
|
||||
import { ChartDetailComponent } from './chart-detail/chart-detail.component';
|
||||
import { RobotAccountComponent } from './robot-account/robot-account.component';
|
||||
import { AddRobotComponent } from './robot-account/add-robot/add-robot.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -58,10 +61,12 @@ import { ChartDetailComponent } from './chart-detail/chart-detail.component';
|
||||
AddGroupComponent,
|
||||
ListChartsComponent,
|
||||
ListChartVersionsComponent,
|
||||
ChartDetailComponent
|
||||
ChartDetailComponent,
|
||||
RobotAccountComponent,
|
||||
AddRobotComponent
|
||||
],
|
||||
exports: [ProjectComponent, ListProjectComponent],
|
||||
providers: [ProjectRoutingResolver, ProjectService, MemberService]
|
||||
providers: [ProjectRoutingResolver, ProjectService, MemberService, RobotService]
|
||||
})
|
||||
export class ProjectModule {
|
||||
|
||||
|
@ -0,0 +1,102 @@
|
||||
<clr-modal [(clrModalOpen)]="addRobotOpened"
|
||||
[clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<h3 class="modal-title">{{'ROBOT_ACCOUNT.CREAT_ROBOT_ACCOUNT' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<form #robotForm="ngForm">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label class="col-md-3
|
||||
form-group-label-override required" for="robot_name">
|
||||
{{'ROBOT_ACCOUNT.NAME' | translate}}
|
||||
</label>
|
||||
<label aria-haspopup="true" role="tooltip" class="tooltip
|
||||
tooltip-validation
|
||||
tooltip-md tooltip-bottom-left" for="robot_name"
|
||||
[class.invalid]="!isRobotNameValid">
|
||||
<input type="text"
|
||||
[(ngModel)]="robot.name"
|
||||
size="30" class="input-width"
|
||||
name="robot_name"
|
||||
id="robot_name"
|
||||
#robotName="ngModel"
|
||||
required
|
||||
pattern='[^" ~#$%]+'
|
||||
maxLengthExt="255"
|
||||
autocomplete="off"
|
||||
(keyup)='handleValidation()'>
|
||||
<span class="tooltip-content">
|
||||
{{ nameTooltipText | translate }}
|
||||
</span>
|
||||
</label>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group-label-override">{{'REPLICATION.DESCRIPTION' |
|
||||
translate}}</label>
|
||||
<input type="text" size="255" class="input-width"
|
||||
[(ngModel)]="robot.description"
|
||||
name="robot_desc" id="robot_desc">
|
||||
</div>
|
||||
<div class="form-group clr-form-control rule-width">
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox [checked]="true"
|
||||
[(ngModel)]="robot.access.isPull" name="isPull"
|
||||
id="permission-pull" class="clr-checkbox">
|
||||
<label for="permission-pull" class="clr-control-label">
|
||||
{{'ROBOT_ACCOUNT.PULL_PERMISSION' | translate}}
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox [checked]="true"
|
||||
[(ngModel)]="robot.access.isPush" name="isPush"
|
||||
id="permission-push" class="clr-checkbox">
|
||||
<label for="permission-push" class="clr-control-label">
|
||||
{{'ROBOT_ACCOUNT.PUSH_PERMISSION' | translate}}
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</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>
|
||||
</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>
|
||||
</section>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,31 @@
|
||||
.rule-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-width {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddRobotComponent } from './add-robot.component';
|
||||
|
||||
describe('AddRobotComponent', () => {
|
||||
let component: AddRobotComponent;
|
||||
let fixture: ComponentFixture<AddRobotComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AddRobotComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddRobotComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,191 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Input,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectorRef
|
||||
} 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 { ErrorHandler } from "@harbor/ui";
|
||||
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
|
||||
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
|
||||
|
||||
@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;
|
||||
isSubmitOnGoing = false;
|
||||
closable: boolean = false;
|
||||
staticBackdrop: boolean = true;
|
||||
isPull: boolean;
|
||||
isPush: boolean;
|
||||
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;
|
||||
@Input() projectId: number;
|
||||
@Input() projectName: string;
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
@ViewChild("robotForm") currentForm: NgForm;
|
||||
@ViewChild("copyAlert") copyAlert: InlineAlertComponent;
|
||||
constructor(
|
||||
private robotService: RobotService,
|
||||
private translate: TranslateService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private messageHandlerService: MessageHandlerService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
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";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openAddRobotModal(): void {
|
||||
if (this.isSubmitOnGoing) {
|
||||
return;
|
||||
}
|
||||
this.robot.name = "";
|
||||
this.robot.description = "";
|
||||
this.addRobotOpened = true;
|
||||
this.isRobotNameValid = true;
|
||||
this.robot = new Robot();
|
||||
this.nameTooltipText = "ROBOT_ACCOUNT.ROBOT_NAME";
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
this.addRobotOpened = false;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.robotNameChecker.unsubscribe();
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.isSubmitOnGoing) {
|
||||
return;
|
||||
}
|
||||
this.isSubmitOnGoing = true;
|
||||
this.robotService
|
||||
.addRobotAccount(
|
||||
this.projectId,
|
||||
this.robot.name,
|
||||
this.robot.description,
|
||||
this.projectName,
|
||||
this.robot.access.isPull,
|
||||
this.robot.access.isPush
|
||||
)
|
||||
.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;
|
||||
},
|
||||
error => {
|
||||
this.isSubmitOnGoing = false;
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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.isPush && !this.robot.access.isPull)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the form validation
|
||||
handleValidation(): void {
|
||||
let cont = this.currentForm.controls["robot_name"];
|
||||
if (cont) {
|
||||
this.robotNameChecker.next(cont.value);
|
||||
}
|
||||
}
|
||||
|
||||
onCpError($event: any): void {
|
||||
if (this.copyAlert) {
|
||||
this.copyAlert.showInlineError("PUSH_IMAGE.COPY_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
onCpSuccess($event: any): void {
|
||||
this.copyToken = false;
|
||||
this.translate
|
||||
.get("ROBOT_ACCOUNT.COPY_SUCCESS", { param: this.robotAccount })
|
||||
.subscribe((res: string) => {
|
||||
this.messageHandlerService.showSuccess(res);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<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}}'
|
||||
(filterEvt)="doSearch($event)" [currentValue]="searchRobot"></hbr-filter>
|
||||
<span class="refresh-btn" (click)="retrieve()">
|
||||
<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 class="btn btn-sm btn-secondary"
|
||||
(click)="openAddRobotModal()">
|
||||
<span><clr-icon shape="plus" size="16"></clr-icon> {{'ROBOT_ACCOUNT.NEW_ROBOT_ACCOUNT'
|
||||
| translate }}</span>
|
||||
</button>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="btn btn-sm
|
||||
btn-link"
|
||||
clrDropdownTrigger>
|
||||
<span>{{'MEMBER.ACTION' | translate}}<clr-icon shape="caret
|
||||
down"></clr-icon></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button clrDropdownItem [disabled]="!(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>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button clrDropdownItem
|
||||
(click)="openDeleteRobotsDialog(selectedRow)"
|
||||
[disabled]="!selectedRow.length">{{'ROBOT_ACCOUNT.DELETE'
|
||||
| translate}}</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</clr-dg-action-bar>
|
||||
<clr-datagrid [(clrDgSelected)]="selectedRow" [clrDgLoading]="loading">
|
||||
<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-row *clrDgItems="let r of robots" [clrDgItem]="r">
|
||||
<clr-dg-cell>{{r.name}}</clr-dg-cell>
|
||||
<clr-dg-cell [ngSwitch]="r.disabled">
|
||||
<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-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}}
|
||||
-
|
||||
{{pagination.lastItem +1 }} {{'ROBOT_ACCOUNT.OF' |
|
||||
translate}} </span>
|
||||
{{pagination.totalItems }} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<add-robot [projectId]="projectId" [projectName]="projectName"
|
||||
(create)="createAccount($event)"></add-robot>
|
||||
</div>
|
@ -0,0 +1,28 @@
|
||||
@import "../../../../lib/src/mixin";
|
||||
|
||||
.robot-space {
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
|
||||
clr-icon.red-position {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.rightPos {
|
||||
@include grid-right-top-pos;
|
||||
|
||||
.option-left {
|
||||
padding-left: 16px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
|
||||
.refresh-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RobotAccountComponent } from './robot-account.component';
|
||||
|
||||
describe('RobotAccountComponent', () => {
|
||||
let component: RobotAccountComponent;
|
||||
let fixture: ComponentFixture<RobotAccountComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ RobotAccountComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RobotAccountComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,201 @@
|
||||
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 } 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 {
|
||||
ConfirmationTargets,
|
||||
ConfirmationState,
|
||||
ConfirmationButtons
|
||||
} from "../../shared/shared.const";
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import {
|
||||
operateChanges,
|
||||
OperateInfo,
|
||||
OperationService,
|
||||
OperationState
|
||||
} from "@harbor/ui";
|
||||
|
||||
@Component({
|
||||
selector: "app-robot-account",
|
||||
templateUrl: "./robot-account.component.html",
|
||||
styleUrls: ["./robot-account.component.scss"]
|
||||
})
|
||||
export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||
@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;
|
||||
subscription: Subscription;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private robotService: RobotService,
|
||||
private OperateDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService,
|
||||
private translate: TranslateService,
|
||||
private ref: ChangeDetectorRef,
|
||||
private messageHandlerService: MessageHandlerService
|
||||
) {
|
||||
this.subscription = OperateDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (
|
||||
message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.ROBOT_ACCOUNT
|
||||
) {
|
||||
this.delRobots(message.data);
|
||||
}
|
||||
}
|
||||
);
|
||||
this.forceRefreshView(2000);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
if (this.timerHandler) {
|
||||
clearInterval(this.timerHandler);
|
||||
this.timerHandler = 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;
|
||||
}
|
||||
let robotsDelete$ = robots.map(robot => this.delOperate(robot));
|
||||
forkJoin(robotsDelete$)
|
||||
.pipe(
|
||||
catchError(err => throwError(err)),
|
||||
finalize(() => {
|
||||
this.retrieve();
|
||||
this.selectedRow = [];
|
||||
})
|
||||
)
|
||||
.subscribe(() => {});
|
||||
}
|
||||
|
||||
delOperate(robot: Robot) {
|
||||
// init operation info
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = "OPERATION.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),
|
||||
err => operateChanges(operMessage, OperationState.failure, err)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
createAccount(created: boolean): void {
|
||||
if (created) {
|
||||
this.retrieve();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
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 { Http } from "@angular/http";
|
||||
import { RobotApiRepository } from "./robot.api.repository";
|
||||
@Injectable()
|
||||
export class RobotService {
|
||||
constructor(
|
||||
private http: Http,
|
||||
private robotApiRepository: RobotApiRepository
|
||||
) {}
|
||||
public addRobotAccount(projecId, name, description, projectName, isPull, isPush): Observable<any> {
|
||||
let access = [];
|
||||
if ( isPull ) {
|
||||
access.push({"resource": "/project/" + projecId + "/repository", "action": "pull"});
|
||||
access.push({"resource": "/project/" + projectName + "/repository", "action": "pull"});
|
||||
}
|
||||
if ( isPush ) {
|
||||
access.push({"resource": "/project/" + projecId + "/repository", "action": "push"});
|
||||
access.push({"resource": "/project/" + projectName + "/repository", "action": "push"});
|
||||
}
|
||||
|
||||
let param = {
|
||||
name: name,
|
||||
description: description,
|
||||
access: access
|
||||
};
|
||||
|
||||
return this.robotApiRepository.postRobot(projecId, param);
|
||||
}
|
||||
|
||||
public deleteRobotAccount(projecId, id): Observable<any> {
|
||||
return this.robotApiRepository.deleteRobot(projecId, id);
|
||||
}
|
||||
|
||||
public listRobotAccount(projecId): Observable<any> {
|
||||
return this.robotApiRepository.listRobot(projecId);
|
||||
}
|
||||
|
||||
public getRobotAccount(projecId, id): Observable<any> {
|
||||
return this.robotApiRepository.getRobot(projecId, id);
|
||||
}
|
||||
|
||||
public toggleDisabledAccount(projecId, id, isDisabled): Observable<any> {
|
||||
let data = {
|
||||
Disabled: isDisabled
|
||||
};
|
||||
return this.robotApiRepository.toggleDisabledAccount(projecId, id, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Http } from "@angular/http";
|
||||
import { throwError as observableThrowError, Observable, pipe } from "rxjs";
|
||||
import { catchError, map } from "rxjs/operators";
|
||||
import { Robot } from './robot';
|
||||
import { HTTP_JSON_OPTIONS } from "../../shared/shared.utils";
|
||||
|
||||
@Injectable()
|
||||
export class RobotApiRepository {
|
||||
constructor(private http: Http) {}
|
||||
|
||||
public postRobot(projectId, param): Observable<any> {
|
||||
return this.http
|
||||
.post(`/api/projects/${projectId}/robots`, param)
|
||||
.pipe(map(response => response.json()))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public deleteRobot(projectId, id): Observable<any> {
|
||||
return this.http
|
||||
.delete(`/api/projects/${projectId}/robots/${id}`)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public listRobot(projectId): Observable<Robot[]> {
|
||||
return this.http
|
||||
.get(`/api/projects/${projectId}/robots`)
|
||||
.pipe(map(response => response.json() as Robot[]))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public getRobot(projectId, id): Observable<Robot[]> {
|
||||
return this.http
|
||||
.get(`/api/projects/${projectId}/robots/${id}`)
|
||||
.pipe(map(response => response.json() as Robot[]))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public toggleDisabledAccount(projectId, id, data): Observable<any> {
|
||||
return this.http
|
||||
.put(`/api/projects/${projectId}/robots/${id}`, data)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
}
|
20
src/portal/src/app/project/robot-account/robot.ts
Normal file
20
src/portal/src/app/project/robot-account/robot.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export class Robot {
|
||||
project_id: number;
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
disabled: boolean;
|
||||
access: {
|
||||
isPull: boolean;
|
||||
isPush: boolean;
|
||||
};
|
||||
|
||||
|
||||
constructor () {
|
||||
this.access = <any>{};
|
||||
// this.access[0].action = true;
|
||||
this.access.isPull = true;
|
||||
this.access.isPush = true;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
vertical-align: middle;
|
||||
width: 80%;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.batchInfoUl{
|
||||
padding: 20px; list-style-type: none;
|
||||
|
@ -35,6 +35,7 @@ export const enum ConfirmationTargets {
|
||||
EMPTY,
|
||||
PROJECT,
|
||||
PROJECT_MEMBER,
|
||||
ROBOT_ACCOUNT,
|
||||
USER,
|
||||
POLICY,
|
||||
TOGGLE_CONFIRM,
|
||||
|
@ -194,7 +194,8 @@
|
||||
"LABELS": "Labels",
|
||||
"PROJECTS": "Projects",
|
||||
"CONFIG": "Configuration",
|
||||
"HELMCHART": "Helm Charts"
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Project registry",
|
||||
@ -258,6 +259,31 @@
|
||||
"SET_ROLE": "SET ROLE",
|
||||
"REMOVE": "Remove"
|
||||
},
|
||||
"ROBOT_ACCOUNT": {
|
||||
"NAME": "Name",
|
||||
"TOKEN": "Token",
|
||||
"NEW_ROBOT_ACCOUNT": "NEW ROBOT ACCOUNT",
|
||||
"ENABLED_STATE": "Enabled state",
|
||||
"DESCRIPTION": "Description",
|
||||
"ACTION": "Action",
|
||||
"EDIT": "Edit",
|
||||
"ITEMS": "items",
|
||||
"OF": "of",
|
||||
"DISABLE_ACCOUNT": "Disable Account",
|
||||
"ENABLE_ACCOUNT": "Enable Account",
|
||||
"DELETE": "Delete",
|
||||
"CREAT_ROBOT_ACCOUNT": "Creat Robot Account",
|
||||
"PULL_PERMISSION": "Permission for Pull",
|
||||
"PUSH_PERMISSION": "Permission for Push",
|
||||
"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",
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Group",
|
||||
"GROUPS": "Groups",
|
||||
@ -802,6 +828,7 @@
|
||||
"DELETE_REPO": "Delete repository",
|
||||
"DELETE_TAG": "Delete tag",
|
||||
"DELETE_USER": "Delete user",
|
||||
"DELETE_ROBOT": "Delete robot",
|
||||
"DELETE_REGISTRY": "Delete registry",
|
||||
"DELETE_REPLICATION": "Delete replication",
|
||||
"DELETE_MEMBER": "Delete user member",
|
||||
|
@ -194,7 +194,8 @@
|
||||
"LABELS": "Labels",
|
||||
"PROJECTS": "Proyectos",
|
||||
"CONFIG": "Configuración",
|
||||
"HELMCHART": "Helm Charts"
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Registro de proyectos",
|
||||
@ -258,6 +259,31 @@
|
||||
"SET_ROLE": "SET ROLE",
|
||||
"REMOVE": "Remove"
|
||||
},
|
||||
"ROBOT_ACCOUNT": {
|
||||
"NAME": "Name",
|
||||
"TOKEN": "Token",
|
||||
"NEW_ROBOT_ACCOUNT": "NEW ROBOT ACCOUNT",
|
||||
"ENABLED_STATE": "Enabled state",
|
||||
"DESCRIPTION": "Description",
|
||||
"ACTION": "Action",
|
||||
"EDIT": "Edit",
|
||||
"ITEMS": "items",
|
||||
"OF": "of",
|
||||
"DISABLE_ACCOUNT": "Disable Account",
|
||||
"ENABLE_ACCOUNT": "Enable Account",
|
||||
"DELETE": "Delete",
|
||||
"CREAT_ROBOT_ACCOUNT": "Creat Robot Account",
|
||||
"PULL_PERMISSION": "Permission for Pull",
|
||||
"PUSH_PERMISSION": "Permission for Push",
|
||||
"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",
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Group",
|
||||
"GROUPS": "Groups",
|
||||
@ -802,6 +828,7 @@
|
||||
"DELETE_REPO": "Delete repository",
|
||||
"DELETE_TAG": "Delete tag",
|
||||
"DELETE_USER": "Delete user",
|
||||
"DELETE_ROBOT": "Delete robot",
|
||||
"DELETE_REGISTRY": "Delete registry",
|
||||
"DELETE_REPLICATION": "Delete replication",
|
||||
"DELETE_MEMBER": "Delete user member",
|
||||
|
@ -180,7 +180,8 @@
|
||||
"LABELS": "Labels",
|
||||
"PROJECTS": "Projets",
|
||||
"CONFIG": "Configuration",
|
||||
"HELMCHART": "Helm Charts"
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Dépôt du Projet",
|
||||
@ -242,6 +243,31 @@
|
||||
"SET_ROLE": "SET ROLE",
|
||||
"REMOVE": "Remove"
|
||||
},
|
||||
"ROBOT_ACCOUNT": {
|
||||
"NAME": "Nom",
|
||||
"TOKEN": "gage ",
|
||||
"NEW_ROBOT_ACCOUNT": "nouveau robot compte ",
|
||||
"ENABLED_STATE": "état d 'activation",
|
||||
"DESCRIPTION": "Description",
|
||||
"ACTION": "Action",
|
||||
"EDIT": "Edit",
|
||||
"ITEMS": "items",
|
||||
"OF": "of",
|
||||
"DISABLE_ACCOUNT": "désactiver le compte ",
|
||||
"ENABLE_ACCOUNT": "permettre à compte ",
|
||||
"DELETE": "Supprimer",
|
||||
"CREAT_ROBOT_ACCOUNT": "créat robot compte ",
|
||||
"PULL_PERMISSION": "Permission for Pull",
|
||||
"PUSH_PERMISSION": "Permission for Push",
|
||||
"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",
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "confirmer l'enlèvement des comptes du robot ",
|
||||
"DELETION_SUMMARY": "Voulez-vous supprimer la règle {{param}}?"
|
||||
},
|
||||
"GROUP": {
|
||||
"Group": "Group",
|
||||
"GROUPS": "Groups",
|
||||
@ -765,6 +791,7 @@
|
||||
"DELETE_REPO": "Delete repository",
|
||||
"DELETE_TAG": "Delete tag",
|
||||
"DELETE_USER": "Delete user",
|
||||
"DELETE_ROBOT": "Delete robot",
|
||||
"DELETE_REGISTRY": "Delete registry",
|
||||
"DELETE_REPLICATION": "Delete replication",
|
||||
"DELETE_MEMBER": "Delete member",
|
||||
|
@ -192,7 +192,8 @@
|
||||
"LABELS": "Etiquetas",
|
||||
"PROJECTS": "Projetos",
|
||||
"CONFIG": "Configuração",
|
||||
"HELMCHART": "Helm Charts"
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Registro do Projeto",
|
||||
@ -256,6 +257,31 @@
|
||||
"SET_ROLE": "DEFINIR FUNÇÃO",
|
||||
"REMOVE": "Remover"
|
||||
},
|
||||
"ROBOT_ACCOUNT": {
|
||||
"NAME": "Nome",
|
||||
"TOKEN": "Token",
|
||||
"NEW_ROBOT_ACCOUNT": "Novo robô conta",
|
||||
"ENABLED_STATE": "Enabled state",
|
||||
"DESCRIPTION": "Descrição",
|
||||
"ACTION": "AÇÃO",
|
||||
"EDIT": "Editar",
|
||||
"ITEMS": "itens",
|
||||
"OF": "de",
|
||||
"DISABLE_ACCOUNT": "Desactivar a conta",
|
||||
"ENABLE_ACCOUNT": "Ativar conta",
|
||||
"DELETE": "Remover",
|
||||
"CREAT_ROBOT_ACCOUNT": "CRIA robô conta",
|
||||
"PULL_PERMISSION": "Permission for Pull",
|
||||
"PUSH_PERMISSION": "Permission for Push",
|
||||
"FILTER_PLACEHOLDER": "Filtro robot accounts",
|
||||
"ROBOT_NAME": "Não Pode conter caracteres especiais(~#$%) e comprimento máximo deveria ser 255 caracteres.",
|
||||
"ACCOUNT_EXISTING": "Robô conta já existe.",
|
||||
"ALERT_TEXT": "É só copiar o token de acesso Pessoal não VAI ter outra oportunidade.",
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirmar a remoção do robô Contas",
|
||||
"DELETION_SUMMARY": "Você quer remover a regra {{param}}?"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Grupo",
|
||||
"GROUPS": "Grupos",
|
||||
@ -792,6 +818,7 @@
|
||||
"DELETE_REPO": "Remover repositório",
|
||||
"DELETE_TAG": "Remover tag",
|
||||
"DELETE_USER": "Remover usuário",
|
||||
"DELETE_ROBOT": "Delete robot",
|
||||
"DELETE_REGISTRY": "Remover registry",
|
||||
"DELETE_REPLICATION": "Remover replicação",
|
||||
"DELETE_MEMBER": "Remover usuário membro",
|
||||
|
@ -193,7 +193,8 @@
|
||||
"LABELS": "标签",
|
||||
"PROJECTS": "项目",
|
||||
"CONFIG": "配置管理",
|
||||
"HELMCHART": "Helm Charts"
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "机器人账户"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "项目仓库",
|
||||
@ -257,6 +258,31 @@
|
||||
"SET_ROLE": "设置角色",
|
||||
"REMOVE": "移除成员"
|
||||
},
|
||||
"ROBOT_ACCOUNT": {
|
||||
"NAME": "姓名",
|
||||
"TOKEN": "令牌",
|
||||
"NEW_ROBOT_ACCOUNT": "添加机器人账户",
|
||||
"ENABLED_STATE": "启用状态",
|
||||
"DESCRIPTION": "描述",
|
||||
"ACTION": "操作",
|
||||
"EDIT": "编辑",
|
||||
"OF": "共计",
|
||||
"ITEMS": "条记录",
|
||||
"DISABLE_ACCOUNT": "禁用账户",
|
||||
"ENABLE_ACCOUNT": "启用账户",
|
||||
"DELETE": "删除",
|
||||
"CREAT_ROBOT_ACCOUNT": "创建机器人账户",
|
||||
"PULL_PERMISSION": "Pull 权限",
|
||||
"PUSH_PERMISSION": "Push 权限",
|
||||
"FILTER_PLACEHOLDER": "过滤机器人账户",
|
||||
"ROBOT_NAME": "不能包含特殊字符(~#$%)且长度不能超过255.",
|
||||
"ACCOUNT_EXISTING": "机器人账户已经存在.",
|
||||
"ALERT_TEXT": "这是唯一一次复制您的个人访问令牌的机会",
|
||||
"CREATED_SUCCESS": "创建账户 '{{param}}' 成功.",
|
||||
"COPY_SUCCESS": "成功复制 '{{param}}' 的令牌",
|
||||
"DELETION_TITLE": "删除账户确认",
|
||||
"DELETION_SUMMARY": "你确认删除机器人账户 {{param}}?"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "组",
|
||||
"GROUPS": "组",
|
||||
@ -800,6 +826,7 @@
|
||||
"DELETE_REPO": "删除仓库",
|
||||
"DELETE_TAG": "删除镜像标签",
|
||||
"DELETE_USER": "删除用户",
|
||||
"DELETE_ROBOT": "删除账户",
|
||||
"DELETE_REGISTRY": "删除Registry",
|
||||
"DELETE_REPLICATION": "删除复制",
|
||||
"DELETE_MEMBER": "删除用户成员",
|
||||
|
@ -78,4 +78,12 @@ body {
|
||||
|
||||
.datagrid-header{
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
.color-green {
|
||||
color: #1D5100;
|
||||
}
|
||||
|
||||
.color-red {
|
||||
color: red;
|
||||
}
|
Loading…
Reference in New Issue
Block a user