mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-29 21:54:13 +01:00
Add ui code about replication enhancement
This commit is contained in:
parent
34f70ff3b6
commit
831f69595a
@ -2,9 +2,9 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||
<div>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'project_name'" *ngIf="!projectScope">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'projects'" *ngIf="!projectScope">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'target_name'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'targets'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="enabledComparator">{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'REPLICATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
@ -14,17 +14,12 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||
<button class="action-item" (click)="toggleRule(p)">{{ (p.enabled === 0 ? 'REPLICATION.ENABLE' : 'REPLICATION.DISABLE') | translate}}</button>
|
||||
<button class="action-item" (click)="deleteRule(p)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>
|
||||
<ng-template [ngIf]="!projectScope">
|
||||
<a href="javascript:void(0)" (click)="redirectTo(p)">{{p.name}}</a>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="projectScope">
|
||||
{{p.name}}
|
||||
</ng-template>
|
||||
<clr-dg-cell>{{p.name}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="!projectScope">
|
||||
<a href="javascript:void(0)" (click)="redirectTo(p)">{{p.projects[0].name}}</a>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="!projectScope">{{p.project_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.description ? p.description : '-'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.targets[0].name}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<ng-template [ngIf]="p.start_time === nullTime">-</ng-template>
|
||||
<ng-template [ngIf]="p.start_time !== nullTime">{{p.start_time | date: 'short'}}</ng-template>
|
||||
|
@ -3,8 +3,7 @@ export const REPLICATION_TEMPLATE: string = `
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between" style="height:32px;">
|
||||
<div class="flex-xs-middle option-left">
|
||||
<button *ngIf="creationAvailable" class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
||||
<create-edit-rule [projectId]="projectId" (reload)="reloadRules($event)"></create-edit-rule>
|
||||
<button *ngIf="!readonly" class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
||||
</div>
|
||||
<div class="flex-xs-middle option-right">
|
||||
<div class="select" style="float: left; top: 8px;">
|
||||
|
@ -88,6 +88,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
@Input() readonly: boolean;
|
||||
|
||||
@Output() redirect = new EventEmitter<ReplicationRule>();
|
||||
@Output() openCreateRule = new EventEmitter<any>();
|
||||
@Output() openEdit = new EventEmitter<string | number>();
|
||||
|
||||
search: SearchOption = new SearchOption();
|
||||
|
||||
@ -111,8 +113,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(ListReplicationRuleComponent)
|
||||
listReplicationRule: ListReplicationRuleComponent;
|
||||
|
||||
@ViewChild(CreateEditRuleComponent)
|
||||
createEditPolicyComponent: CreateEditRuleComponent;
|
||||
/* @ViewChild(CreateEditRuleComponent)
|
||||
createEditPolicyComponent: CreateEditRuleComponent;*/
|
||||
|
||||
@ViewChild("replicationLogViewer")
|
||||
replicationLogViewer: JobLogViewerComponent;
|
||||
@ -134,9 +136,9 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
private translateService: TranslateService) {
|
||||
}
|
||||
|
||||
public get creationAvailable(): boolean {
|
||||
/*public get creationAvailable(): boolean {
|
||||
return !this.readonly && this.projectId ? true : false;
|
||||
}
|
||||
}*/
|
||||
|
||||
public get showPaginationIndex(): boolean {
|
||||
return this.totalCount > 0;
|
||||
@ -146,6 +148,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
this.currentRuleStatus = this.ruleStatus[0];
|
||||
this.currentJobStatus = this.jobStatus[0];
|
||||
this.currentJobSearchOption = 0;
|
||||
console.log('readonly', this.readonly);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -155,7 +158,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
this.createEditPolicyComponent.openCreateEditRule(true);
|
||||
this.openCreateRule.emit();
|
||||
// this.createEditPolicyComponent.openCreateEditRule(true);
|
||||
}
|
||||
|
||||
openEditRule(rule: ReplicationRule) {
|
||||
@ -164,7 +168,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
if (rule.enabled === 1) {
|
||||
editable = false;
|
||||
}
|
||||
this.createEditPolicyComponent.openCreateEditRule(editable, rule.id);
|
||||
this.openEdit.emit(rule.id);
|
||||
// this.createEditPolicyComponent.openCreateEditRule(editable, rule.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.9.8",
|
||||
"clarity-ui": "^0.9.8",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.4.91",
|
||||
"harbor-ui": "^0.5.9-test-31",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -18,6 +18,7 @@
|
||||
<ul class="nav-list">
|
||||
<li><a class="nav-link nav-link-override" routerLink="/harbor/users" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</a></li>
|
||||
<li><a class="nav-link nav-link-override" routerLink="/harbor/replications" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a></li>
|
||||
<li><a class="nav-link nav-link-override" routerLink="/harbor/registry" routerLinkActive="active">{{'APP_TITLE.REG' | translate}}</a></li>
|
||||
<li><a class="nav-link nav-link-override" routerLink="/harbor/configs" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
@ -50,6 +50,7 @@ import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deac
|
||||
import { MemberGuard } from './shared/route/member-guard-activate.service';
|
||||
|
||||
import { TagDetailPageComponent } from './repository/tag-detail/tag-detail-page.component';
|
||||
import { ReplicationRuleComponent} from "./replication/replication-rule/replication-rule.component";
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
@ -80,23 +81,23 @@ const harborRoutes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'replications',
|
||||
component: ReplicationManagementComponent,
|
||||
component: TotalReplicationPageComponent,
|
||||
canActivate: [SystemAdminGuard],
|
||||
canActivateChild: [SystemAdminGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'rules',
|
||||
component: TotalReplicationPageComponent
|
||||
},
|
||||
{
|
||||
path: 'endpoints',
|
||||
component: DestinationPageComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'endpoints'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'replications/:id/rule',
|
||||
component: ReplicationRuleComponent,
|
||||
canActivate: [SystemAdminGuard],
|
||||
canActivateChild: [SystemAdminGuard],
|
||||
|
||||
},
|
||||
{
|
||||
path: 'replications/new-rule',
|
||||
component: ReplicationRuleComponent,
|
||||
canActivate: [SystemAdminGuard],
|
||||
canActivateChild: [SystemAdminGuard],
|
||||
|
||||
},
|
||||
{
|
||||
path: 'tags/:id/:repo',
|
||||
@ -146,6 +147,12 @@ const harborRoutes: Routes = [
|
||||
component: ConfigurationComponent,
|
||||
canActivate: [SystemAdminGuard],
|
||||
canDeactivate: [LeavingConfigRouteDeactivate]
|
||||
},
|
||||
{
|
||||
path: 'registry',
|
||||
component: DestinationPageComponent,
|
||||
canActivate: [SystemAdminGuard],
|
||||
canActivateChild: [SystemAdminGuard],
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -13,7 +13,7 @@
|
||||
<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="isSessionValid && isSystemAdmin">
|
||||
<li class="nav-item" *ngIf="isSProjectAdmin || isSystemAdmin">
|
||||
<a class="nav-link" routerLink="replications" routerLinkActive="active">{{'PROJECT_DETAIL.REPLICATION' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSessionValid && isSystemAdmin">
|
||||
|
@ -50,7 +50,12 @@ export class ProjectDetailComponent {
|
||||
|
||||
public get isSystemAdmin(): boolean {
|
||||
let account = this.sessionService.getCurrentUser();
|
||||
return account != null && account.has_admin_role > 0;
|
||||
return account && account.has_admin_role > 0;
|
||||
}
|
||||
|
||||
public get isSProjectAdmin(): boolean {
|
||||
let account = this.sessionService.projectMembers;
|
||||
return account && account[0].role_name === 'projectAdmin';
|
||||
}
|
||||
|
||||
public get isSessionValid(): boolean {
|
||||
|
@ -1,3 +1,4 @@
|
||||
<h2 class="custom-h2">{{'REPLICATION.ENDPOINTS' | translate}}</h2>
|
||||
<div style="margin-top: 24px;">
|
||||
<hbr-endpoint></hbr-endpoint>
|
||||
</div>
|
@ -1,3 +1,3 @@
|
||||
<div style="margin-top: 24px;">
|
||||
<hbr-replication #replicationView [projectId]="projectIdentify" [withReplicationJob]='true'></hbr-replication>
|
||||
<hbr-replication [readonly]="true" #replicationView [projectId]="projectIdentify" [withReplicationJob]='true'></hbr-replication>
|
||||
</div>
|
@ -0,0 +1,528 @@
|
||||
import {Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, AfterViewInit} from '@angular/core';
|
||||
import {ProjectService} from '../../project/project.service';
|
||||
import {Project} from '../../project/project';
|
||||
import {compareValue, toPromise} from 'harbor-ui/src/utils';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||
import {ReplicationRuleServie} from "./replication-rule.service";
|
||||
import {MessageHandlerService} from "../../shared/message-handler/message-handler.service";
|
||||
import {Target, Filter, ReplicationRule} from "./replication-rule";
|
||||
import {ConfirmationDialogService} from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationTargets, ConfirmationState } from '../../shared/shared.const';
|
||||
import {Subscription} from "rxjs/Subscription";
|
||||
import {ConfirmationMessage} from "../../shared/confirmation-dialog/confirmation-message";
|
||||
import {Subject} from "rxjs/Subject";
|
||||
|
||||
|
||||
const ONE_HOUR_SECONDS: number = 3600;
|
||||
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
|
||||
|
||||
@Component ({
|
||||
selector: 'repliction-rule',
|
||||
templateUrl: 'replication-rule.html',
|
||||
styleUrls: ['replication-rule.css']
|
||||
|
||||
})
|
||||
export class ReplicationRuleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
timerHandler: any;
|
||||
_localTime: Date = new Date();
|
||||
policyId: number;
|
||||
projectList: Project[] = [];
|
||||
targetList: Target[] = [];
|
||||
isFilterHide: boolean = false;
|
||||
weeklySchedule: boolean;
|
||||
isScheduleOpt: boolean;
|
||||
filterCount: number = 0;
|
||||
triggerNames: string[] = ['immediate', 'schedule', 'manual'];
|
||||
scheduleNames: string[] = ['daily', 'weekly'];
|
||||
weekly: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
filterSelect: string[] = ['repository', 'tag'];
|
||||
ruleNameTooltip: string = 'TOOLTIP.EMPTY';
|
||||
|
||||
filterListData: {[key: string]: any}[] = [];
|
||||
inProgress: boolean = false;
|
||||
inNameChecking: boolean = false;
|
||||
isBackReplication: boolean = false;
|
||||
isRuleNameExist: boolean = false;
|
||||
nameChecker: Subject<string> = new Subject<string>();
|
||||
|
||||
confirmSub: Subscription;
|
||||
ruleForm: FormGroup;
|
||||
copyUpdateForm: ReplicationRule;
|
||||
|
||||
baseFilterData(name: string, option: string[], state: boolean) {
|
||||
return {
|
||||
name: name,
|
||||
options: option,
|
||||
state: state,
|
||||
isValid: true
|
||||
};
|
||||
}
|
||||
|
||||
constructor(public projectService: ProjectService,
|
||||
private router: Router,
|
||||
private fb: FormBuilder,
|
||||
private repService: ReplicationRuleServie,
|
||||
private route: ActivatedRoute,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private confirmService: ConfirmationDialogService,
|
||||
public ref: ChangeDetectorRef) {
|
||||
this.createForm();
|
||||
|
||||
Promise.all([this.repService.getEndpoints(), this.repService.listProjects()])
|
||||
.then(res => {
|
||||
if (!res[0].length || !res[1].length) {
|
||||
this.msgHandler.error('should have project and target first');
|
||||
this.router.navigate(['/harbor/replications']);
|
||||
};
|
||||
if (res[0].length && res[1].length) {
|
||||
this.projectList = res[1];
|
||||
this.setProject([this.projectList[0]]);
|
||||
this.targetList = res[0];
|
||||
this.setTarget([this.targetList[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.policyId = +this.route.snapshot.params['id'];
|
||||
if (this.policyId) {
|
||||
this.repService.getReplicationRule(this.policyId)
|
||||
.then((response) => {
|
||||
this.copyUpdateForm = Object.assign({}, response);
|
||||
this.updateForm(response);
|
||||
}).catch(error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
this.nameChecker.debounceTime(500).distinctUntilChanged().subscribe((ruleName: string) => {
|
||||
this.isRuleNameExist = false;
|
||||
this.inNameChecking = true;
|
||||
toPromise<ReplicationRule[]>(this.repService.getReplicationRules(0, ruleName))
|
||||
.then(response => {
|
||||
if (response.some(rule => rule.name === ruleName)) {
|
||||
this.ruleNameTooltip = 'TOOLTIP.RULE_USER_EXISTING';
|
||||
this.isRuleNameExist = true;
|
||||
}
|
||||
this.inNameChecking = false;
|
||||
}).catch(() => {
|
||||
this.inNameChecking = false;
|
||||
});
|
||||
});
|
||||
this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||
if (confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED) {
|
||||
if (confirmation.source === ConfirmationTargets.CONFIG) {
|
||||
if (this.policyId) {
|
||||
this.updateForm(this.copyUpdateForm);
|
||||
} else {
|
||||
this.initFom();
|
||||
}
|
||||
if (this.isBackReplication) {
|
||||
this.router.navigate(['/harbor/replications']);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get hasFormChange() {
|
||||
if (this.copyUpdateForm) {
|
||||
return !compareValue(this.copyUpdateForm, this.ruleForm.value);
|
||||
}
|
||||
return this.ruleForm.touched && this.ruleForm.dirty;
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.confirmSub) {
|
||||
this.confirmSub.unsubscribe();
|
||||
}
|
||||
if (this.nameChecker) {
|
||||
this.nameChecker.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
createForm() {
|
||||
this.ruleForm = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
description: '',
|
||||
projects: this.fb.array([]),
|
||||
targets: this.fb.array([]),
|
||||
trigger: this.fb.group({
|
||||
kind: this.triggerNames[0],
|
||||
schedule_param: this.fb.group({
|
||||
type: this.scheduleNames[0],
|
||||
weekday: 1,
|
||||
offtime: '08:00'
|
||||
}),
|
||||
}),
|
||||
filters: this.fb.array([]),
|
||||
replicate_existing_image_now: true,
|
||||
replicate_deletion: true
|
||||
});
|
||||
}
|
||||
|
||||
updateForm(rule: ReplicationRule): void {
|
||||
rule.trigger = this.updateTrigger(rule.trigger);
|
||||
this.ruleForm.reset({
|
||||
name: rule.name,
|
||||
description: rule.description,
|
||||
trigger: rule.trigger,
|
||||
replicate_existing_image_now: rule.replicate_existing_image_now,
|
||||
replicate_deletion: rule.replicate_deletion
|
||||
});
|
||||
this.setProject(rule.projects);
|
||||
this.setTarget(rule.targets);
|
||||
if (rule.filters) {
|
||||
this.setFilter(rule.filters);
|
||||
this.updateFilter(rule.filters);
|
||||
}
|
||||
}
|
||||
|
||||
initFom(): void {
|
||||
this.ruleForm.reset({
|
||||
name: '',
|
||||
description: '',
|
||||
trigger: {kind: this.triggerNames[0], schedule_param: {
|
||||
type: this.scheduleNames[0],
|
||||
weekday: 1,
|
||||
offtime: '08:00'
|
||||
}},
|
||||
replicate_existing_image_now: true,
|
||||
replicate_deletion: true
|
||||
});
|
||||
this.setProject([this.projectList[0]]);
|
||||
this.setTarget([this.targetList[0]]);
|
||||
this.setFilter([]);
|
||||
|
||||
this.isFilterHide = false;
|
||||
this.filterListData = [];
|
||||
this.isScheduleOpt = false;
|
||||
this.weeklySchedule = false;
|
||||
this.isRuleNameExist = true;
|
||||
this.ruleNameTooltip = 'TOOLTIP.EMPTY';
|
||||
}
|
||||
|
||||
|
||||
get projects(): FormArray {
|
||||
return this.ruleForm.get('projects') as FormArray;
|
||||
}
|
||||
setProject(projects: Project[]) {
|
||||
const projectFGs = projects.map(project => this.fb.group(project));
|
||||
const projectFormArray = this.fb.array(projectFGs);
|
||||
this.ruleForm.setControl('projects', projectFormArray);
|
||||
}
|
||||
|
||||
get filters(): FormArray {
|
||||
return this.ruleForm.get('filters') as FormArray;
|
||||
}
|
||||
setFilter(filters: Filter[]) {
|
||||
const filterFGs = filters.map(filter => this.fb.group(filter));
|
||||
const filterFormArray = this.fb.array(filterFGs);
|
||||
this.ruleForm.setControl('filters', filterFormArray);
|
||||
}
|
||||
|
||||
get targets(): FormArray {
|
||||
return this.ruleForm.get('targets') as FormArray;
|
||||
}
|
||||
setTarget(targets: Target[]) {
|
||||
const targetFGs = targets.map(target => this.fb.group(target));
|
||||
const targetFormArray = this.fb.array(targetFGs);
|
||||
this.ruleForm.setControl('targets', targetFormArray);
|
||||
}
|
||||
|
||||
initFilter(name: string) {
|
||||
return this.fb.group({
|
||||
kind: name,
|
||||
pattern: ['', Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
filterChange($event: any) {
|
||||
if ($event && $event.target['value']) {
|
||||
let id: number = $event.target.id;
|
||||
let name: string = $event.target.name;
|
||||
let value: string = $event.target['value'];
|
||||
|
||||
this.filterListData.forEach((data, index) => {
|
||||
if (index === +id) {
|
||||
data.name = $event.target.name = value;
|
||||
}else {
|
||||
data.options.splice(data.options.indexOf(value), 1);
|
||||
}
|
||||
if (data.options.indexOf(name) === -1) {
|
||||
data.options.push(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
projectChange($event: any) {
|
||||
if ($event && $event.target && event.target['value']) {
|
||||
let selecedProject: Project = this.projectList.find(project => project.project_id === +$event.target['value']);
|
||||
this.setProject([selecedProject]);
|
||||
}
|
||||
}
|
||||
|
||||
targetChange($event: any) {
|
||||
if ($event && $event.target && event.target['value']) {
|
||||
let selecedTarget: Target = this.targetList.find(target => target.id === +$event.target['value']);
|
||||
this.setTarget([selecedTarget]);
|
||||
}
|
||||
}
|
||||
|
||||
addNewFilter(): void {
|
||||
if (this.filterCount === 0) {
|
||||
this.filterListData.push(this.baseFilterData(this.filterSelect[0], this.filterSelect.slice(), true));
|
||||
this.filters.push(this.initFilter(this.filterSelect[0]));
|
||||
|
||||
}else {
|
||||
let nameArr: string[] = this.filterSelect.slice();
|
||||
this.filterListData.forEach(data => {
|
||||
nameArr.splice(nameArr.indexOf(data.name), 1);
|
||||
});
|
||||
// when add a new filter,the filterListData should change the options
|
||||
this.filterListData.filter((data) => {
|
||||
data.options.splice(data.options.indexOf(nameArr[0]), 1);
|
||||
});
|
||||
this.filterListData.push(this.baseFilterData(nameArr[0], nameArr, true));
|
||||
this.filters.push(this.initFilter(nameArr[0]));
|
||||
}
|
||||
this.filterCount += 1;
|
||||
if (this.filterCount >= this.filterSelect.length) {
|
||||
this.isFilterHide = true;
|
||||
}
|
||||
}
|
||||
|
||||
// delete a filter
|
||||
deleteFilter(i: number): void {
|
||||
if (i || i === 0) {
|
||||
let delfilter = this.filterListData.splice(i, 1)[0];
|
||||
if (this.filterCount === this.filterSelect.length) {
|
||||
this.isFilterHide = false;
|
||||
}
|
||||
this.filterCount -= 1;
|
||||
if (this.filterListData.length) {
|
||||
let optionVal = delfilter.name;
|
||||
this.filterListData.filter(data => {
|
||||
if (data.options.indexOf(optionVal) === -1) {
|
||||
data.options.push(optionVal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const control = <FormArray>this.ruleForm.controls['filters'];
|
||||
control.removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
selectTrigger($event: any): void {
|
||||
if ($event && $event.target && $event.target['value']) {
|
||||
if ($event.target['value'] === this.triggerNames[1]) {
|
||||
this.isScheduleOpt = true;
|
||||
} else {
|
||||
this.isScheduleOpt = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replication Schedule select exchange
|
||||
selectSchedule($event: any): void {
|
||||
if ($event && $event.target && $event.target['value']) {
|
||||
switch ($event.target['value']) {
|
||||
case this.scheduleNames[1]:
|
||||
this.weeklySchedule = true;
|
||||
break;
|
||||
case this.scheduleNames[0]:
|
||||
/* this.dailySchedule = true;*/
|
||||
this.weeklySchedule = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkRuleName(): void {
|
||||
let ruleName: string = this.ruleForm.controls['name'].value;
|
||||
if (ruleName) {
|
||||
this.nameChecker.next(ruleName);
|
||||
} else {
|
||||
this.ruleNameTooltip = 'TOOLTIP.EMPTY';
|
||||
}
|
||||
}
|
||||
|
||||
updateFilter(filters: any) {
|
||||
let opt: string[] = this.filterSelect.slice();
|
||||
filters.forEach((filter: any) => {
|
||||
opt.splice(opt.indexOf(filter.kind), 1);
|
||||
})
|
||||
filters.forEach((filter: any) => {
|
||||
let option: string [] = opt.slice();
|
||||
option.unshift(filter.kind);
|
||||
this.filterListData.push(this.baseFilterData(filter.kind, option, true));
|
||||
});
|
||||
this.filterCount = filters.length;
|
||||
if (filters.length === this.filterSelect.length) {
|
||||
this.isFilterHide = true;
|
||||
}
|
||||
}
|
||||
|
||||
updateTrigger(trigger: any) {
|
||||
if (trigger['schedule_param']) {
|
||||
this.isScheduleOpt = true;
|
||||
trigger['schedule_param']['offtime'] = this.getOfftime(trigger['schedule_param']['offtime']);
|
||||
if (trigger['schedule_param']['weekday']) {
|
||||
this.weeklySchedule = true;
|
||||
}else {
|
||||
// set default
|
||||
trigger['schedule_param']['weekday'] = 1;
|
||||
}
|
||||
}else {
|
||||
trigger['schedule_param'] = { type: this.scheduleNames[0],
|
||||
weekday: this.weekly[0],
|
||||
offtime: '08:00'};
|
||||
}
|
||||
return trigger;
|
||||
}
|
||||
|
||||
setTriggerVaule(trigger: any) {
|
||||
if (!this.isScheduleOpt) {
|
||||
delete trigger['schedule_param'];
|
||||
return trigger;
|
||||
}else {
|
||||
if (!this.weeklySchedule) {
|
||||
delete trigger['schedule_param']['weekday'];
|
||||
}else {
|
||||
trigger['schedule_param']['weekday'] = +trigger['schedule_param']['weekday'];
|
||||
}
|
||||
trigger['schedule_param']['offtime'] = this.setOfftime(trigger['schedule_param']['offtime']);
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onSubmit() {
|
||||
// add new Replication rule
|
||||
let copyRuleForm: ReplicationRule = this.ruleForm.value;
|
||||
copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger);
|
||||
if (!this.policyId) {
|
||||
this.repService.createReplicationRule(copyRuleForm)
|
||||
.then(() => {
|
||||
this.msgHandler.showSuccess('REPLICATION.CREATED_SUCCESS');
|
||||
this.inProgress = false;
|
||||
setTimeout(() => {
|
||||
this.router.navigate(['/harbor/replications']);
|
||||
}, 2000);
|
||||
|
||||
}).catch((error: any) => {
|
||||
this.inProgress = false;
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
} else {
|
||||
this.repService.updateReplicationRule(this.policyId, this.ruleForm.value)
|
||||
.then(() => {
|
||||
this.msgHandler.showSuccess('REPLICATION.CREATED_SUCCESS');
|
||||
this.inProgress = false;
|
||||
setTimeout(() => {
|
||||
this.router.navigate(['/harbor/replications']);
|
||||
}, 2000);
|
||||
|
||||
}).catch((error: any) => {
|
||||
this.inProgress = false;
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
this.inProgress = true;
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
|
||||
console.log(this.ruleForm.valid, this.isRuleNameExist , !this.hasFormChange)
|
||||
if (this.ruleForm.dirty) {
|
||||
let msg = new ConfirmationMessage(
|
||||
'CONFIG.CONFIRM_TITLE',
|
||||
'CONFIG.CONFIRM_SUMMARY',
|
||||
'',
|
||||
null,
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
}
|
||||
// UTC time
|
||||
public getOfftime(daily_time: any): string {
|
||||
|
||||
let timeOffset: number = 0; // seconds
|
||||
if (daily_time && typeof daily_time === 'number') {
|
||||
timeOffset = +daily_time;
|
||||
}
|
||||
|
||||
// Convert to current time
|
||||
let timezoneOffset: number = this._localTime.getTimezoneOffset();
|
||||
// Local time
|
||||
timeOffset = timeOffset - timezoneOffset * 60;
|
||||
if (timeOffset < 0) {
|
||||
timeOffset = timeOffset + ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
if (timeOffset >= ONE_DAY_SECONDS) {
|
||||
timeOffset -= ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
// To time string
|
||||
let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS);
|
||||
let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60);
|
||||
|
||||
let timeStr: string = '' + hours;
|
||||
if (hours < 10) {
|
||||
timeStr = '0' + timeStr;
|
||||
}
|
||||
if (minutes < 10) {
|
||||
timeStr += ':0';
|
||||
} else {
|
||||
timeStr += ':';
|
||||
}
|
||||
timeStr += minutes;
|
||||
|
||||
return timeStr;
|
||||
}
|
||||
public setOfftime(v: string) {
|
||||
if (!v || v === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
let values: string[] = v.split(':');
|
||||
if (!values || values.length !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hours: number = +values[0];
|
||||
let minutes: number = +values[1];
|
||||
// Convert to UTC time
|
||||
let timezoneOffset: number = this._localTime.getTimezoneOffset();
|
||||
let utcTimes: number = hours * ONE_HOUR_SECONDS + minutes * 60;
|
||||
utcTimes += timezoneOffset * 60;
|
||||
if (utcTimes < 0) {
|
||||
utcTimes += ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
if (utcTimes >= ONE_DAY_SECONDS) {
|
||||
utcTimes -= ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
return utcTimes;
|
||||
}
|
||||
|
||||
backReplication(): void {
|
||||
this.isBackReplication = true;
|
||||
if (this.ruleForm.dirty) {
|
||||
this.onCancel();
|
||||
} else {
|
||||
this.router.navigate(['/harbor/replications']);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Created by pengf on 9/28/2017.
|
||||
*/
|
||||
|
||||
.select{
|
||||
width: 186px;
|
||||
}
|
||||
.select .optionMore{
|
||||
background-color: #bfbaba;
|
||||
height: 1.6em;
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
.hideFilter{ display: none;}
|
||||
h4{
|
||||
color: #666;
|
||||
}
|
||||
label:first-child {
|
||||
font-size: 15px;
|
||||
left: -10px !important;
|
||||
}
|
||||
.endpointSelect{ width: 290px;}
|
||||
.filterSelect{width: 320px;}
|
||||
.filterSelect label{width: 160px;}
|
||||
.filterSelect label input{width: 100%;}
|
||||
.cursor{cursor: pointer;}
|
||||
.padLeft0{padding-left: 0;}
|
||||
.floatSet {display: inline-block; float: left; width: 120px;margin-right: 10px;}
|
||||
.form-group{ min-height: 36px;}
|
@ -0,0 +1,122 @@
|
||||
<div>
|
||||
<a class="cursor" (click)="backReplication()">< {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a>
|
||||
<h1 class="sub-header-title">New Replication Rule</h1>
|
||||
<form [formGroup]="ruleForm" (ngSubmit)="onSubmit()" novalidate>
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">{{'REPLICATION.NAME' | translate}}<span style="color: red">*</span></label>
|
||||
<label class="col-md-8" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
|
||||
[class.invalid]='(ruleForm.controls.name.touched && ruleForm.controls.name.invalid) || isRuleNameExist'>
|
||||
<input type="text" id="ruleName" required formControlName="name" #ruleName (keyup)='checkRuleName()' autocomplete="off">
|
||||
<span class="tooltip-content">{{ruleNameTooltip | translate}}</span>
|
||||
</label><span class="spinner spinner-inline spinner-pos" [hidden]="!inNameChecking"></span>
|
||||
</div>
|
||||
<!--Description-->
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">{{'REPLICATION.DESCRIPTION' | translate}}</label>
|
||||
<textarea type="text" id="ruleDescription" style=" width: 355px;" row= 3; formControlName="description"></textarea>
|
||||
</div>
|
||||
<!--Projects-->
|
||||
<h4>Source</h4>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">{{'PROJECT.PROJECTS' | translate}}<span style="color: red">*</span></label>
|
||||
<div formArrayName="projects">
|
||||
<div class="select" *ngFor="let project of projects.controls; let i= index" [formGroupName]="i">
|
||||
<select id="ruleProject" (change)="projectChange($event)" formControlName="project_id">
|
||||
<option *ngFor="let pro of projectList" value="{{pro.project_id}}">{{pro.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--images/Filter-->
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">Filter</label>
|
||||
<div formArrayName="filters">
|
||||
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index" [formGroupName]="i">
|
||||
<div>
|
||||
<div class="select floatSet">
|
||||
<select formControlName="kind" (change)="filterChange($event)" id="{{i}}" name="{{filterListData[i].name}}">
|
||||
<option *ngFor="let filter of filterListData[i].options;" value="{{filter}}">{{filter}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
|
||||
[class.invalid]='ruleForm.controls.filters.controls[i].controls.pattern.touched && ruleForm.controls.filters.controls[i].controls.pattern.invalid'>
|
||||
<input type="text" #filterValue required size="14" formControlName="pattern">
|
||||
<span class="tooltip-content">{{'TOOLTIP.EMPTY' | translate}}</span>
|
||||
</label>
|
||||
<clr-icon shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<clr-icon shape="plus-circle" class="is-solid" [hidden]="isFilterHide" (click)="addNewFilter()" style="margin-top: 9px;"></clr-icon>
|
||||
</div>
|
||||
<!--Targets-->
|
||||
<h4>Targets</h4>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">Endpoint <span style="color: red">*</span></label>
|
||||
<div formArrayName="targets">
|
||||
<div class="select endpointSelect" *ngFor="let target of targets.controls; let i= index" [formGroupName]="i">
|
||||
<select id="ruleTarget" (change)="targetChange($event)" formControlName="id">
|
||||
<option *ngFor="let target of targetList" value="{{target.id}}">{{target.name}}: {{target.endpoint}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Trigger-->
|
||||
<h4>Trigger</h4>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">Replication schedule</label>
|
||||
<div formGroupName="trigger">
|
||||
<!--on trigger-->
|
||||
<div class="select floatSet">
|
||||
<select id="ruleTrigger" formControlName="kind" (change)="selectTrigger($event)">
|
||||
<option *ngFor="let triggerName of triggerNames" value="{{triggerName}}">{{triggerName}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<!--on push-->
|
||||
<div style="float: left;" formGroupName="schedule_param">
|
||||
<div class="select floatSet" [hidden]="!isScheduleOpt">
|
||||
<select name="scheduleType" formControlName="type" (change)="selectSchedule($event)">
|
||||
<option *ngFor="let scheduleName of scheduleNames" value="{{scheduleName}}">{{scheduleName}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<!--weekly-->
|
||||
<span style="float: left;" [hidden]="!weeklySchedule || !isScheduleOpt">on </span>
|
||||
<div [hidden]="!weeklySchedule || !isScheduleOpt" class="select floatSet">
|
||||
<select name="scheduleDay" formControlName="weekday">
|
||||
<option *ngFor="let filter of weekly; let i = index" [value]="i+1">{{filter}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<!--daily/time-->
|
||||
<span [hidden]="!isScheduleOpt">at </span>
|
||||
<input [hidden]="!isScheduleOpt" type="time" formControlName="offtime" required value="08:00" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!--Setting-->
|
||||
<h4>Setting</h4>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">Setting</label>
|
||||
<div class="col-lg-7 padLeft0">
|
||||
<clr-checkbox [clrChecked]="true" id="ruleDeletion" formControlName="replicate_deletion">
|
||||
Delete remote images when locally deleted
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
<div class="col-lg-7 padLeft0">
|
||||
<clr-checkbox [clrChecked]="true" id="ruleExit" formControlName="replicate_existing_image_now">
|
||||
exiting images immediately
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="offset-md-4">
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
<br>
|
||||
<button type="button" id="ruleBtnCancel" class="btn btn-outline" [disabled]="!ruleForm.dirty" (click)="onCancel()">{{ 'BUTTON.CANCEL' | translate }}</button>
|
||||
<button type="submit" id="ruleBtnOk" class="btn btn-primary" [disabled]="!ruleForm.valid || isRuleNameExist || !hasFormChange">{{ 'BUTTON.OK' | translate }}</button>
|
||||
</div><!-- [disabled]="!ruleForm.valid"-->
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Created by pengf on 12/5/2017.
|
||||
*/
|
||||
|
||||
import {Injectable} from "@angular/core";
|
||||
import {Http, RequestOptions, Headers, URLSearchParams} from "@angular/http";
|
||||
import {Observable} from "rxjs/Observable";
|
||||
import {ReplicationRule, Target} from "./replication-rule";
|
||||
import {HTTP_GET_OPTIONS, HTTP_JSON_OPTIONS} from "../../shared/shared.utils";
|
||||
import {Project} from "../../project/project";
|
||||
|
||||
@Injectable()
|
||||
export class ReplicationRuleServie {
|
||||
headers = new Headers({'Content-type': 'application/json'});
|
||||
options = new RequestOptions({'headers': this.headers});
|
||||
baseurl = '/api/policies/replication';
|
||||
targetUrl= '/api/targets';
|
||||
|
||||
constructor(private http: Http) {}
|
||||
|
||||
public createReplicationRule(replicationRule: ReplicationRule): Observable<any> | Promise<any> | any {
|
||||
/*if (!this._isValidRule(replicationRule)) {
|
||||
return Promise.reject('Bad argument');
|
||||
}*/
|
||||
|
||||
return this.http.post(this.baseurl, JSON.stringify(replicationRule), this.options).toPromise()
|
||||
.then(response => response)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
public getReplicationRules(projectId?: number | string, ruleName?: string): Promise<ReplicationRule[]> | ReplicationRule[] {
|
||||
let queryParams = new URLSearchParams();
|
||||
if (projectId) {
|
||||
queryParams.set('project_id', '' + projectId);
|
||||
}
|
||||
|
||||
if (ruleName) {
|
||||
queryParams.set('name', ruleName);
|
||||
}
|
||||
|
||||
return this.http.get(this.baseurl, {search: queryParams}).toPromise()
|
||||
.then(response => response.json() as ReplicationRule[])
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
public getReplicationRule(policyId: number): Promise<ReplicationRule> {
|
||||
let url: string = `${this.baseurl}/${policyId}`;
|
||||
return this.http.get(url, HTTP_GET_OPTIONS).toPromise()
|
||||
.then(response => response.json() as ReplicationRule)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
|
||||
public getEndpoints(): Promise<Target[]> | Target[] {
|
||||
return this.http
|
||||
.get(this.targetUrl)
|
||||
.toPromise()
|
||||
.then(response => response.json())
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
public listProjects(): Promise<Project[]> | Project[] {
|
||||
return this.http.get(`/api/projects`, HTTP_GET_OPTIONS).toPromise()
|
||||
.then(response => response.json())
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
public updateReplicationRule(id: number, rep: {[key: string]: any | any[] }): Observable<any> | Promise<any> | any {
|
||||
let url: string = `${this.baseurl}/${id}`;
|
||||
return this.http.put(url, JSON.stringify(rep), HTTP_JSON_OPTIONS).toPromise()
|
||||
.then(response => response)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import {Project} from "../../project/project";
|
||||
/**
|
||||
* Created by pengf on 12/7/2017.
|
||||
*/
|
||||
|
||||
export class Target {
|
||||
id: 0;
|
||||
endpoint: 'string';
|
||||
name: 'string';
|
||||
username: 'string';
|
||||
password: 'string';
|
||||
type: 0;
|
||||
insecure: true;
|
||||
creation_time: 'string';
|
||||
update_time: 'string';
|
||||
}
|
||||
|
||||
export class Filter {
|
||||
kind: string;
|
||||
pattern: string;
|
||||
constructor(kind: string, pattern: string) {
|
||||
this.kind = kind;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
export class Trigger {
|
||||
kind: string;
|
||||
schedule_param: any | {
|
||||
[key: string]: any | any[];
|
||||
};
|
||||
constructor(kind: string, param: any | { [key: string]: any | any[]; }) {
|
||||
this.kind = kind;
|
||||
this.schedule_param = param;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReplicationRule {
|
||||
name: string;
|
||||
description: string;
|
||||
projects: Project[];
|
||||
targets: Target[] ;
|
||||
trigger: Trigger ;
|
||||
filters: Filter[] ;
|
||||
replicate_existing_image_now?: boolean;
|
||||
replicate_deletion?: boolean;
|
||||
}
|
||||
|
@ -20,22 +20,29 @@ import { TotalReplicationPageComponent } from './total-replication/total-replica
|
||||
import { DestinationPageComponent } from './destination/destination-page.component';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import {ReplicationRuleComponent} from "./replication-rule/replication-rule.component";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
import {ReplicationRuleServie} from "./replication-rule/replication-rule.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule
|
||||
RouterModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
declarations: [
|
||||
ReplicationPageComponent,
|
||||
ReplicationManagementComponent,
|
||||
TotalReplicationPageComponent,
|
||||
DestinationPageComponent
|
||||
DestinationPageComponent,
|
||||
ReplicationRuleComponent,
|
||||
],
|
||||
exports: [
|
||||
ReplicationPageComponent,
|
||||
DestinationPageComponent,
|
||||
TotalReplicationPageComponent
|
||||
]
|
||||
TotalReplicationPageComponent,
|
||||
ReplicationRuleComponent,
|
||||
],
|
||||
providers: [ReplicationRuleServie]
|
||||
})
|
||||
export class ReplicationModule { }
|
@ -1,3 +1,4 @@
|
||||
<h2 class="custom-h2">{{'REPLICATION.REPLICATION_RULE' | translate}}</h2>
|
||||
<div style="margin-top: 24px;">
|
||||
<hbr-replication [withReplicationJob]='false' (redirect)="customRedirect($event)"></hbr-replication>
|
||||
<hbr-replication [readonly]="false" [withReplicationJob]='true' (openCreateRule)="openCreatePage()" (openEdit)="openEditPage($event)" (redirect)="customRedirect($event)"></hbr-replication>
|
||||
</div>
|
@ -14,7 +14,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import {Router,ActivatedRoute} from "@angular/router";
|
||||
import {ReplicationRule} from "harbor-ui";
|
||||
import {ReplicationRule} from "../replication-rule/replication-rule";
|
||||
|
||||
@Component({
|
||||
selector: 'total-replication',
|
||||
@ -26,7 +26,15 @@ export class TotalReplicationPageComponent {
|
||||
private activeRoute: ActivatedRoute){}
|
||||
customRedirect(rule: ReplicationRule): void {
|
||||
if (rule) {
|
||||
this.router.navigate(['../../projects', rule.project_id, "replications"], { relativeTo: this.activeRoute });
|
||||
this.router.navigate(['../projects', rule.projects[0].project_id, "replications"], { relativeTo: this.activeRoute });
|
||||
}
|
||||
}
|
||||
|
||||
openEditPage(id: number): void {
|
||||
this.router.navigate([id, 'rule'], { relativeTo: this.activeRoute });
|
||||
}
|
||||
|
||||
openCreatePage(): void {
|
||||
this.router.navigate(['new-rule'], { relativeTo: this.activeRoute });
|
||||
}
|
||||
}
|
@ -3,3 +3,6 @@
|
||||
margin-top: 36px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
.custom-h2 {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
@ -33,12 +33,13 @@ export class SystemAdminGuard implements CanActivate, CanActivateChild {
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
return new Promise((resolve, reject) => {
|
||||
let user = this.authService.getCurrentUser();
|
||||
let projectMem = this.authService.projectMembers;
|
||||
if (!user) {
|
||||
this.authService.retrieveUser()
|
||||
.then(() => {
|
||||
//updated user
|
||||
user = this.authService.getCurrentUser();
|
||||
if (user.has_admin_role > 0) {
|
||||
if (user.has_admin_role > 0 || projectMem[0].role_name === 'projectAdmin') {
|
||||
return resolve(true);
|
||||
} else {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
@ -60,7 +61,7 @@ export class SystemAdminGuard implements CanActivate, CanActivateChild {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (user.has_admin_role > 0) {
|
||||
if (user.has_admin_role > 0 || projectMem[0].role_name === 'projectAdmin') {
|
||||
return resolve(true);
|
||||
} else {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
|
@ -15,6 +15,7 @@ import { NgForm } from '@angular/forms';
|
||||
import { httpStatusCode, AlertType } from './shared.const';
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { Comparator, State } from 'clarity-angular';
|
||||
import {RequestOptions, Headers} from "@angular/http";
|
||||
|
||||
/**
|
||||
* To handle the error message body
|
||||
@ -155,6 +156,27 @@ export class CustomComparator<T> implements Comparator<T> {
|
||||
}
|
||||
}
|
||||
|
||||
export const HTTP_JSON_OPTIONS: RequestOptions = new RequestOptions({
|
||||
headers: new Headers({
|
||||
"Content-Type": 'application/json',
|
||||
"Accept": 'application/json',
|
||||
})
|
||||
});
|
||||
export const HTTP_GET_OPTIONS: RequestOptions = new RequestOptions({
|
||||
headers: new Headers({
|
||||
"Content-Type": 'application/json',
|
||||
"Accept": 'application/json',
|
||||
"Cache-Control": 'no-cache',
|
||||
"Pragma": 'no-cache'
|
||||
})
|
||||
});
|
||||
|
||||
export const HTTP_FORM_OPTIONS: RequestOptions = new RequestOptions({
|
||||
headers: new Headers({
|
||||
"Content-Type": 'application/x-www-form-urlencoded'
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* Filter columns via RegExp
|
||||
*
|
||||
|
@ -50,7 +50,9 @@
|
||||
"NUMBER_REQUIRED": "Field is required and should be numbers.",
|
||||
"PORT_REQUIRED": "Field is required and should be valid port number.",
|
||||
"EMAIL_EXISTING": "Email address already exists.",
|
||||
"USER_EXISTING": "Username is already in use."
|
||||
"USER_EXISTING": "Username is already in use.",
|
||||
"RULE_USER_EXISTING": "Name is already in use.",
|
||||
"EMPTY": "Name is required"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Enter current password",
|
||||
|
@ -50,7 +50,9 @@
|
||||
"NUMBER_REQUIRED": "El campo es obligatorio y debería ser un número.",
|
||||
"PORT_REQUIRED": "El campo es obligatorio y debería ser un número de puerto válido.",
|
||||
"EMAIL_EXISTING": "Esa dirección de email ya existe.",
|
||||
"USER_EXISTING": "Ese nombre de usuario ya existe."
|
||||
"USER_EXISTING": "Ese nombre de usuario ya existe.",
|
||||
"RULE_USER_EXISTING": "Name is already in use.",
|
||||
"EMPTY": "Name is required"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Introduzca la contraseña actual",
|
||||
|
@ -50,7 +50,9 @@
|
||||
"NUMBER_REQUIRED": "此项为必填项且为数字。",
|
||||
"PORT_REQUIRED": "此项为必填项且为合法端口号。",
|
||||
"EMAIL_EXISTING": "邮件地址已经存在。",
|
||||
"USER_EXISTING": "用户名已经存在。"
|
||||
"USER_EXISTING": "用户名已经存在。",
|
||||
"RULE_USER_EXISTING": "名称已经存在。",
|
||||
"EMPTY": "名称为必填项"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "输入当前密码",
|
||||
|
@ -22,22 +22,32 @@ ${HARBOR_VERSION} v1.1.1
|
||||
*** Keywords ***
|
||||
Create An New Rule With New Endpoint
|
||||
[Arguments] ${policy_name} ${policy_description} ${destination_name} ${destination_url} ${destination_username} ${destination_password}
|
||||
Click element xpath=${new_name_xpath}
|
||||
Click element xpath=${new_rule_xpath}
|
||||
Sleep 2
|
||||
|
||||
Input Text xpath=${policy_name_xpath} ${policy_name}
|
||||
Input Text xpath=${policy_description_xpath} ${policy_description}
|
||||
|
||||
Click Element xpath=//select[@id="ruleProject"]
|
||||
Click Element xpath=//select[@id="ruleProject"]//option[1]
|
||||
|
||||
Click Element xpath=//select[@id="ruleTarget"]
|
||||
Click Element xpath=//select[@id="ruleTarget"]//option[1]
|
||||
|
||||
Click Element xpath=//select[@id="ruleTrigger"]
|
||||
Click Element xpath=//select[@id="ruleTrigger"]//option[@value='immediate']
|
||||
|
||||
Mouse down xpath=//*[@id="clr-checkbox-ruleDeletion"]
|
||||
Mouse up xpath=//*[@id="clr-checkbox-ruleDeletion"]
|
||||
|
||||
Mouse down xpath=//*[@id="clr-checkbox-ruleExit"]
|
||||
Mouse up xpath=//*[@id="clr-checkbox-ruleExit"]
|
||||
|
||||
Click element xpath=${policy_enable_checkbox}
|
||||
Click element xpath=${policy_endpoint_checkbox}
|
||||
|
||||
Input text xpath=${destination_name_xpath} ${destination_name}
|
||||
Input text xpath=${destination_url_xpath} ${destination_url}
|
||||
Input text xpath=${destination_username_xpath} ${destination_username}
|
||||
Input text xpath=${destination_password_xpath} ${destination_password}
|
||||
Click element xpath=${replicaton_save_xpath}
|
||||
Click element xpath=//*[@id="ruleBtnOk"]
|
||||
Sleep 5
|
||||
Capture Page Screenshot rule_${policy_name}.png
|
||||
Wait Until Page Contains ${policy_name}
|
||||
Wait Until Page Contains ${policy_description}
|
||||
Wait Until Page Contains ${destination_name}
|
@ -16,11 +16,9 @@
|
||||
Documentation This resource provides any keywords related to the Harbor private registry appliance
|
||||
|
||||
*** Variables ***
|
||||
${new_name_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/replicaton/div/hbr-replication/div/div[1]/div/div[1]/button/clr-icon
|
||||
${policy_name_xpath} //*[@id="policy_name"]
|
||||
${policy_description_xpath} //*[@id="policy_description"]
|
||||
${policy_enable_checkbox} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/replicaton/div/hbr-replication/div/div[1]/div/div[1]/create-edit-rule/clr-modal/div/div[1]/div/div[1]/div/div[2]/form/section/div[3]/div/label
|
||||
${policy_endpoint_checkbox} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/replicaton/div/hbr-replication/div/div[1]/div/div[1]/create-edit-rule/clr-modal/div/div[1]/div/div[1]/div/div[2]/form/section/div[4]/div[2]/label
|
||||
${new_rule_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/total-replication/div/hbr-replication/div/div[1]/div/div[1]/button
|
||||
${policy_name_xpath} //*[@id="ruleName"]
|
||||
${policy_description_xpath} //*[@id="ruleDescription"]
|
||||
${destination_name_xpath} //*[@id='destination_name']
|
||||
${destination_url_xpath} //*[@id='destination_url']
|
||||
${destination_username_xpath} //*[@id='destination_username']
|
||||
|
Loading…
Reference in New Issue
Block a user