diff --git a/src/ui_ng/lib/README.md b/src/ui_ng/lib/README.md index 89e903d13..47cd19d79 100644 --- a/src/ui_ng/lib/README.md +++ b/src/ui_ng/lib/README.md @@ -74,13 +74,14 @@ Use **withTitle** to set whether self-contained a header with title or not. Defa Support two different display scope mode: under specific project or whole system. If **projectId** is set to the id of specified project, then only show the replication rules bound with the project. Otherwise, show all the rules of the whole system. +On specific project mode, without need projectId, but also need to provide projectName for display. **withReplicationJob** is used to determine whether or not show the replication jobs which are relevant with the selected replication rule. -**readonly** is to disable all the create/edit/delete actions. +**isSystemAdmin** is for judgment if user has administrator privilege, if true, user can do the create/edit/delete/replicate actions. ``` - + ``` * **Endpoint Management View** diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.css.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.css.ts index 83aaea83c..c0e232338 100644 --- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.css.ts +++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.css.ts @@ -1,5 +1,70 @@ export const CREATE_EDIT_RULE_STYLE: string = ` -.form-group-label-override { - font-size: 14px; - font-weight: 400; -}`; \ No newline at end of file +/** + * 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; +} +.colorRed{color: red;} +.colorRed a{text-decoration: underline;color: #007CBB;} +.alertLabel{display:block; margin-top:0; line-height:1em; font-size:12px;} + +.inputWidth{width: 270px;} +.endpointSelect{ width: 270px; margin-right: 20px;} +.filterSelect{width: 315px;} +.filterSelect clr-icon{margin-left: 15px;} +.filterSelect label{width: 136px;} +.filterSelect label input{width: 100%;} +.cursor{cursor: pointer;} +.pull-left{float: left;} +.padLeft0{padding-left: 0;} +.floatSetPar{display: inline-block; width: 120px;margin-right: 10px;} +.floatSet {display: inline-block; width: 82px;margin-right: 4px;} +.form-group{ min-height: 36px;} + +.projectInput{float: left;position: relative;} +.switchIcon{width:20px;height:20px; margin-top: 10px;margin-left: 10px; cursor: pointer;} +.addEndpoint{ margin-top: .25em !important;padding-left:2px;padding-right:2px;min-width:58px;margin-right:0} +.shadow{position: absolute;top: 8px;} +.is-solid{cursor: pointer;} +.selectBox{ + position: absolute; + width: 100%; + height: auto; + margin-top:-0.25rem; + border: 1px solid #ccc; + background-color: white; + border: 1px solid rgba(0,0,0,.15); + border-right-width: 2px; + border-bottom-width: 2px; + border-radius: 6px; + box-shadow: 0 5px 10px rgba(0,0,0,.2); + z-index: 100; +} +.selectBox ul li{ + list-style: none; + padding: 3px 20px +} +.selectBox ul li:hover{ + color: #262626; + background-image: linear-gradient(180deg,#f5f5f5 0,#e8e8e8); + background-repeat: repeat-x; +} +.form-group-override{ + padding-left: 170px !important; +} +.form-group>label:first-child{font-size:14px; width:6.5rem;} +.goLink{color:blue; border-bottom:1px solid blue; line-height:14px; cursor:pointer;} +`; \ No newline at end of file diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.html.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.html.ts index 046f9587b..8817523d7 100644 --- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.html.ts +++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.html.ts @@ -1,100 +1,134 @@ export const CREATE_EDIT_RULE_TEMPLATE: string = ` - - + + +
+ +
+
`; diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts index 5485cdd1a..06118b582 100644 --- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts +++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; @@ -25,52 +25,55 @@ import { JobLogDefaultService } from '../service/index'; import { EndpointService, EndpointDefaultService } from '../service/endpoint.service'; +import {ProjectDefaultService, ProjectService} from "../service/project.service"; import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component'; +import {Project} from "../project-policy-config/project"; describe('CreateEditRuleComponent (inline template)', ()=>{ let mockRules: ReplicationRule[] = [ { "id": 1, - "project_id": 1, - "project_name": "library", - "target_id": 1, - "target_name": "target_01", "name": "sync_01", - "enabled": 0, "description": "", - "cron_str": "", - "error_job_count": 2, - "deleted": 0 - }, - { - "id": 2, - "project_id": 1, - "project_name": "library", - "target_id": 3, - "target_name": "target_02", - "name": "sync_02", - "enabled": 1, - "description": "", - "cron_str": "", - "error_job_count": 1, - "deleted": 0 - }, - { - "id": 3, - "project_id": 1, - "project_name": "library", - "target_id": 2, - "target_name": "target_03", - "name": "sync_03", - "enabled": 0, - "description": "", - "cron_str": "", - "error_job_count": 0, - "deleted": 0 - } - ]; - + "projects": [{ "project_id": 1, + "owner_id": 0, + "name": 'project_01', + "creation_time": '', + "deleted": 0, + "owner_name": '', + "togglable": false, + "update_time": '', + "current_user_role_id": 0, + "repo_count": 0, + "has_project_admin_role": false, + "is_member": false, + "role_name": '', + "metadata": { + "public": '', + "enable_content_trust": '', + "prevent_vul": '', + "severity": '', + "auto_scan": '', + } + }], + "targets": [{ + "id": 1, + "endpoint": "https://10.117.4.151", + "name": "target_01", + "username": "admin", + "password": "", + "insecure": false, + "type": 0 + }], + "trigger": { + "kind": "Manual", + "schedule_param": null + }, + "filters": [], + "replicate_existing_image_now": false, + "replicate_deletion": false, + }] let mockJobs: ReplicationJobItem[] = [ { "id": 1, @@ -144,17 +147,68 @@ describe('CreateEditRuleComponent (inline template)', ()=>{ let mockRule: ReplicationRule = { "id": 1, - "project_id": 1, - "project_name": "library", - "target_id": 1, - "target_name": "target_01", "name": "sync_01", - "enabled": 0, "description": "", - "cron_str": "", - "error_job_count": 2, - "deleted": 0 - }; + "projects": [{ "project_id": 1, + "owner_id": 0, + "name": 'project_01', + "creation_time": '', + "deleted": 0, + "owner_name": '', + "togglable": false, + "update_time": '', + "current_user_role_id": 0, + "repo_count": 0, + "has_project_admin_role": false, + "is_member": false, + "role_name": '', + "metadata": { + "public": '', + "enable_content_trust": '', + "prevent_vul": '', + "severity": '', + "auto_scan": '', + } + }], + "targets": [{ + "id": 1, + "endpoint": "https://10.117.4.151", + "name": "target_01", + "username": "admin", + "password": "", + "insecure": false, + "type": 0 + }], + "trigger": { + "kind": "Manual", + "schedule_param": null + }, + "filters": [], + "replicate_existing_image_now": false, + "replicate_deletion": false, + } + let mockProjects: Project[] = [ + { "project_id": 1, + "owner_id": 0, + "name": 'project_01', + "creation_time": '', + "deleted": 0, + "owner_name": '', + "togglable": false, + "update_time": '', + "current_user_role_id": 0, + "repo_count": 0, + "has_project_admin_role": false, + "is_member": false, + "role_name": '', + "metadata": { + "public": '', + "enable_content_trust": '', + "prevent_vul": '', + "severity": '', + "auto_scan": '', + } + }]; let fixture: ComponentFixture; let fixtureCreate: ComponentFixture; @@ -164,7 +218,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{ let replicationService: ReplicationService; let endpointService: EndpointService; - + let spyRules: jasmine.Spy; let spyOneRule: jasmine.Spy; @@ -172,12 +226,11 @@ describe('CreateEditRuleComponent (inline template)', ()=>{ let spyEndpoint: jasmine.Spy; let config: IServiceConfig = { - replicationRuleEndpoint: '/api/policies/replication/testing', replicationJobEndpoint: '/api/jobs/replication/testing', targetBaseEndpoint: '/api/targets/testing' }; - beforeEach(async(()=>{ + beforeEach(async(() =>{ TestBed.configureTestingModule({ imports: [ SharedModule, @@ -198,6 +251,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{ { provide: SERVICE_CONFIG, useValue: config }, { provide: ReplicationService, useClass: ReplicationDefaultService }, { provide: EndpointService, useClass: EndpointDefaultService }, + { provide: ProjectService, useClass: ProjectDefaultService }, { provide: JobLogService, useClass: JobLogDefaultService } ] }); @@ -205,28 +259,27 @@ describe('CreateEditRuleComponent (inline template)', ()=>{ beforeEach(()=>{ fixture = TestBed.createComponent(ReplicationComponent); - + fixtureCreate = TestBed.createComponent(CreateEditRuleComponent); comp = fixture.componentInstance; + compCreate = fixtureCreate.componentInstance; comp.projectId = 1; comp.search.ruleId = 1; replicationService = fixture.debugElement.injector.get(ReplicationService); - + + + endpointService = fixtureCreate.debugElement.injector.get(EndpointService) ; + spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules)); spyOneRule = spyOn(replicationService, 'getReplicationRule').and.returnValue(Promise.resolve(mockRule)); spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob)); - fixture.detectChanges(); - }); - beforeEach(()=>{ - fixtureCreate = TestBed.createComponent(CreateEditRuleComponent); - - compCreate = fixtureCreate.componentInstance; - compCreate.projectId = 1; - endpointService = fixtureCreate.debugElement.injector.get(EndpointService); spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints)); + fixture.detectChanges(); + + }); it('Should open creation modal and load endpoints', async(()=>{ diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts index 64c367732..f15a8a810 100644 --- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts +++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts @@ -11,437 +11,699 @@ // 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 { Component, Input, Output, EventEmitter, ViewChild, AfterViewChecked } from '@angular/core'; - -import { NgForm } from '@angular/forms'; - -import { ReplicationService } from '../service/replication.service'; -import { EndpointService } from '../service/endpoint.service'; - -import { ErrorHandler } from '../error-handler/error-handler'; -import { ActionType } from '../shared/shared.const'; - +import {Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, Input, EventEmitter, Output} from '@angular/core'; +import {Filter, ReplicationRule, Endpoint} from "../service/interface"; +import {Subject} from "rxjs/Subject"; +import {Subscription} from "rxjs/Subscription"; +import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {CreateEditEndpointComponent} from "../create-edit-endpoint/create-edit-endpoint.component"; +import {Router, ActivatedRoute} from "@angular/router"; +import {compareValue, isEmptyObject, toPromise} from "../utils"; import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; +import {ReplicationService} from "../service/replication.service"; +import {CREATE_EDIT_RULE_TEMPLATE} from "./create-edit-rule.component.html"; +import {CREATE_EDIT_RULE_STYLE} from "./create-edit-rule.component.css"; +import {ErrorHandler} from "../error-handler/error-handler"; +import {TranslateService} from "@ngx-translate/core"; +import {EndpointService} from "../service/endpoint.service"; +import {ProjectService} from "../service/project.service"; +import {Project} from "../project-policy-config/project"; -import { ReplicationRule } from '../service/interface'; -import { Endpoint } from '../service/interface'; +const ONE_HOUR_SECONDS = 3600; +const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS; -import { TranslateService } from '@ngx-translate/core'; - -import { CREATE_EDIT_RULE_STYLE } from './create-edit-rule.component.css'; -import { CREATE_EDIT_RULE_TEMPLATE } from './create-edit-rule.component.html'; - -import { toPromise } from '../utils'; - -/** - * Rule form model. - */ -export interface CreateEditRule { - ruleId?: number | string; - name?: string; - description?: string; - enable?: boolean; - endpointId?: number | string; - endpointName?: string; - endpointUrl?: string; - username?: string; - password?: string; - insecure?: boolean; -} - -const FAKE_PASSWORD: string = 'ywJZnDTM'; - -@Component({ - selector: 'create-edit-rule', +@Component ({ + selector: 'hbr-create-edit-rule', template: CREATE_EDIT_RULE_TEMPLATE, - styles: [ CREATE_EDIT_RULE_STYLE ] + styles: [CREATE_EDIT_RULE_STYLE] + }) -export class CreateEditRuleComponent implements AfterViewChecked { - modalTitle: string; +export class CreateEditRuleComponent implements OnInit, OnDestroy { + _localTime: Date = new Date(); + targetList: Endpoint[] = []; + projectList: Project[] = []; + selectedProjectList: Project[] = []; + isFilterHide = false; + weeklySchedule: boolean; + isScheduleOpt: boolean; + isImmediate = false; + noProjectInfo = ""; + noEndpointInfo = ""; + noSelectedProject = true; + noSelectedEndpoint = true; + filterCount = 0; + triggerNames: string[] = ['Manual', 'Immediate', 'Scheduled']; + scheduleNames: string[] = ['Daily', 'Weekly']; + weekly: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; + filterSelect: string[] = ['repository', 'tag']; + ruleNameTooltip = 'TOOLTIP.EMPTY'; + headerTitle = 'REPLICATION.ADD_POLICY'; + createEditRuleOpened: boolean; - createEditRule: CreateEditRule = this.initCreateEditRule; - initVal: CreateEditRule = this.initCreateEditRule; - - actionType: ActionType; - - isCreateEndpoint: boolean; + filterListData: {[key: string]: any}[] = []; + inProgress = false; + inNameChecking = false; + isRuleNameExist = false; + nameChecker: Subject = new Subject(); + proNameChecker: Subject = new Subject(); + firstClick = 0; + policyId: number; + + confirmSub: Subscription; + ruleForm: FormGroup; + copyUpdateForm: ReplicationRule; + @Input() projectId: number; + @Input() projectName: string; - @Output() reload = new EventEmitter(); - - endpoints: Endpoint[]; - - pingTestMessage: string; - testOngoing: boolean; - pingStatus: boolean; - - btnAbled:boolean; - - - ruleForm: NgForm; - - staticBackdrop: boolean = true; - closable: boolean = false; - - @ViewChild('ruleForm') - currentForm: NgForm; - - hasChanged: boolean; - - editable: boolean; - - get initCreateEditRule(): CreateEditRule { - return { - endpointId: '', - name: '', - enable: false, - description: '', - endpointName: '', - endpointUrl: '', - username: '', - password: '', - insecure: false - }; - } - - get initReplicationRule(): ReplicationRule { - return { - project_id: '', - project_name: '', - target_id: '', - target_name: '', - enabled: 0, - description: '', - cron_str: '', - error_job_count: 0, - deleted: 0 - }; - } - - get initEndpoint(): Endpoint { - return { - endpoint: '', - name: '', - username: '', - password: '', - insecure: false, - type: 0 - }; - } + @Output() goToRegistry = new EventEmitter(); + @Output() reload = new EventEmitter(); @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent; - get readonly(): boolean { - return this.actionType === ActionType.EDIT && (this.createEditRule.enable || false); + emptyProject = { + project_id: -1, + name: '', } - - get untoggleable(): boolean { - return this.actionType === ActionType.EDIT && (this.initVal.enable || false); + emptyEndpoint = { + id: -1, + endpoint: '', + name: '', + username: '', + password: '', + insecure: true, + type: 0, } - - - get showNewDestination(): boolean { - return this.actionType === ActionType.ADD_NEW || (!this.createEditRule.enable || false); - } - get connectAbled():boolean{ - return !this.createEditRule.endpointId && !this.isCreateEndpoint; - - } - constructor( - private replicationService: ReplicationService, - private endpointService: EndpointService, - private errorHandler: ErrorHandler, - private translateService: TranslateService) {} - - prepareTargets(endpointId?: number | string) { + private fb: FormBuilder, + private repService: ReplicationService, + private endpointService: EndpointService, + private errorHandler: ErrorHandler, + private proService: ProjectService, + private translateService: TranslateService, + public ref: ChangeDetectorRef) { + this.createForm(); + } + + baseFilterData(name: string, option: string[], state: boolean) { + return { + name: name, + options: option, + state: state, + isValid: true + }; + } + + ngOnInit(): void { toPromise(this.endpointService .getEndpoints()) - .then(endpoints=>{ - this.endpoints = endpoints; - if(this.endpoints && this.endpoints.length > 0) { - let initialEndpoint: Endpoint | undefined; - (endpointId) ? initialEndpoint = this.endpoints.find(t=>t.id===endpointId) : initialEndpoint = this.endpoints[0]; - if(!initialEndpoint) { - return; - } - this.createEditRule.endpointId = initialEndpoint.id; - this.createEditRule.endpointName = initialEndpoint.name; - this.createEditRule.endpointUrl = initialEndpoint.endpoint; - this.createEditRule.username = initialEndpoint.username; - this.createEditRule.insecure = initialEndpoint.insecure; - this.createEditRule.password = FAKE_PASSWORD; + .then(targets => { + this.targetList = targets || []; + }).catch((error: any) => this.errorHandler.error(error)); - this.initVal.endpointId = this.createEditRule.endpointId; - this.initVal.endpointUrl = this.createEditRule.endpointUrl; - this.initVal.username = this.createEditRule.username; - this.initVal.password = this.createEditRule.password; - this.initVal.insecure = this.createEditRule.insecure; + if (!this.projectId) { + toPromise(this.proService.listProjects("", undefined)) + .then(targets => { + this.projectList = targets || []; + }).catch(error => this.errorHandler.error(error)); + } + + this.nameChecker.debounceTime(500).distinctUntilChanged().subscribe((ruleName: string) => { + this.isRuleNameExist = false; + this.inNameChecking = true; + toPromise(this.repService.getReplicationRules(0, ruleName)) + .then(response => { + if (response.some(rule => rule.name === ruleName)) { + this.ruleNameTooltip = 'TOOLTIP.RULE_USER_EXISTING'; + this.isRuleNameExist = true; } - }) - .catch(error=>{ - this.errorHandler.error(error); - this.createEditRuleOpened = false; - }); + this.inNameChecking = false; + }).catch(() => { + this.inNameChecking = false; + }); + }); + + this.proNameChecker + .debounceTime(500) + .distinctUntilChanged() + .subscribe((name: string) => { + this.noProjectInfo = ''; + this.selectedProjectList = []; + toPromise(this.proService.listProjects(name, undefined)).then((res: any) => { + if (res) { + this.selectedProjectList = res.slice(0, 10); + // if input value exit in project list + let pro = res.find((data: any) => data.name === name); + if (!pro) { + this.noProjectInfo = 'REPLICATION.PROJECT_NOT_EXIST_INFO'; + this.noSelectedProject = true; + } else { + this.noProjectInfo = ''; + this.noSelectedProject = false; + this.setProject([pro]) + } + } else { + this.noProjectInfo = 'REPLICATION.PROJECT_NOT_EXIST_INFO'; + this.noSelectedProject = true; + } + }).catch((error: any) => { + this.errorHandler.error(error); + this.noProjectInfo = 'REPLICATION.PROJECT_NOT_EXIST_INFO'; + this.noSelectedProject = true; + }); + }); } - openCreateEditRule(editable: boolean, ruleId?: number | string): void { + ngOnDestroy(): void { + if (this.confirmSub) { + this.confirmSub.unsubscribe(); + } + if (this.nameChecker) { + this.nameChecker.unsubscribe(); + } + if (this.proNameChecker) { + this.proNameChecker.unsubscribe(); + } + } - this.createEditRule = this.initCreateEditRule; - this.editable = editable; + get isValid() { + return !(this.isRuleNameExist || this.noSelectedProject || this.noSelectedEndpoint || this.inProgress ); + } - this.isCreateEndpoint = false; - this.hasChanged = false; + 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: false + }); + } - this.pingTestMessage = ''; - this.pingStatus = true; - this.testOngoing = false; + initForm(): 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: false + }); + this.setProject([this.emptyProject]); + this.setTarget([this.emptyEndpoint]); + this.setFilter([]); - if(ruleId) { - this.actionType = ActionType.EDIT; - this.translateService.get('REPLICATION.EDIT_POLICY_TITLE').subscribe(res=>this.modalTitle=res); - toPromise(this.replicationService - .getReplicationRule(ruleId)) - .then(rule=>{ - if(rule) { - this.createEditRule.ruleId = ruleId; - this.createEditRule.name = rule.name; - this.createEditRule.description = rule.description; - this.createEditRule.enable = rule.enabled === 1? true : false; - this.prepareTargets(rule.target_id); + this.copyUpdateForm = Object.assign({}, this.ruleForm.value); + } - this.initVal.name = this.createEditRule.name; - this.initVal.description = this.createEditRule.description; - this.initVal.enable = this.createEditRule.enable; + 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.noSelectedProject = false; + this.setTarget(rule.targets); + this.noSelectedEndpoint = false; - this.createEditRuleOpened = true; - } - }).catch(err=>this.errorHandler.error(err)); - } else { - if(!this.projectId) { - this.errorHandler.error('Project ID cannot be unset'); + if (rule.filters) { + this.setFilter(rule.filters); + this.updateFilter(rule.filters); + } + + // Force refresh view + let hnd = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => clearInterval(hnd), 2000); + } + + 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: Endpoint[]) { + 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); + } + }); + } + } + + targetChange($event: any) { + if ($event && $event.target) { + if ($event.target['value'] === '-1') { + this.noSelectedEndpoint = true; return; } - this.actionType = ActionType.ADD_NEW; - this.translateService.get('REPLICATION.ADD_POLICY').subscribe(res=>this.modalTitle=res); - this.prepareTargets(); - this.createEditRuleOpened = true; - } - } - - newEndpoint(checkedAddNew: boolean): void { - this.isCreateEndpoint = checkedAddNew; - if(this.isCreateEndpoint) { - this.createEditRule.endpointName = ''; - this.createEditRule.endpointUrl = ''; - this.createEditRule.username = ''; - this.createEditRule.password = ''; - this.createEditRule.insecure = false; - } else { - this.prepareTargets(); + let selecedTarget: Endpoint = this.targetList.find(target => target.id === +$event.target['value']); + this.setTarget([selecedTarget]); + this.noSelectedEndpoint = false; } } - selectEndpoint(): void { - let result: Endpoint | undefined = this.endpoints.find(target=>target.id == this.createEditRule.endpointId); - if(result) { - this.createEditRule.endpointId = result.id; - this.createEditRule.endpointUrl = result.endpoint; - this.createEditRule.username = result.username; - this.createEditRule.insecure = result.insecure; - this.createEditRule.password = FAKE_PASSWORD; + // Handle the form validation + handleValidation(): void { + let cont = this.ruleForm.controls["projects"]; + if (cont && cont.valid) { + this.proNameChecker.next(cont.value[0].name); + } } - } - - getRuleByForm(): ReplicationRule { - let rule: ReplicationRule = this.initReplicationRule; - rule.project_id = this.projectId; - rule.id = this.createEditRule.ruleId; - rule.name = this.createEditRule.name; - rule.description = this.createEditRule.description; - rule.enabled = this.createEditRule.enable ? 1 : 0; - rule.target_id = this.createEditRule.endpointId || ''; - return rule; - } - getEndpointByForm(): Endpoint { - let endpoint: Endpoint = this.initEndpoint; - endpoint.id = this.createEditRule.ruleId; - endpoint.name = this.createEditRule.endpointName || ''; - endpoint.endpoint = this.createEditRule.endpointUrl || ''; - endpoint.username = this.createEditRule.username; - endpoint.password = this.createEditRule.password; - endpoint.insecure = this.createEditRule.insecure; - return endpoint; - } - - createReplicationRule(): void { - toPromise(this.replicationService - .createReplicationRule(this.getRuleByForm())) - .then(response=>{ - this.translateService.get('REPLICATION.CREATED_SUCCESS') - .subscribe(res=>this.errorHandler.info(res)); - this.createEditRuleOpened = false; - this.reload.emit(true); - }) - .catch(error=>{ - if (error.status === 409) { - this.inlineAlert.showInlineError('REPLICATION.POLICY_ALREADY_EXISTS'); - } else { - this.inlineAlert.showInlineError(error); - } - }); - } - - updateReplicationRule(): void { - toPromise(this.replicationService - .updateReplicationRule(this.getRuleByForm())) - .then(()=>{ - this.translateService.get('REPLICATION.UPDATED_SUCCESS') - .subscribe(res=>this.errorHandler.info(res)); - this.createEditRuleOpened = false; - this.reload.emit(true); - }) - .catch(error=>{ - if (error.status === 409) { - this.inlineAlert.showInlineError('REPLICATION.POLICY_ALREADY_EXISTS'); - } else { - this.inlineAlert.showInlineError(error); - } - } - ); - } - - createWithEndpoint(actionType: ActionType): void { - toPromise(this.endpointService - .createEndpoint(this.getEndpointByForm())) - .then(()=>{ - toPromise(this.endpointService - .getEndpoints(this.createEditRule.endpointName)) - .then(endpoints=>{ - if(endpoints && endpoints.length > 0) { - let addedEndpoint: Endpoint = endpoints[0]; - this.createEditRule.endpointId = addedEndpoint.id; - switch(actionType) { - case ActionType.ADD_NEW: - this.createReplicationRule(); - break; - case ActionType.EDIT: - this.updateReplicationRule(); - break; - } - } - }) - .catch(error=>{ - this.inlineAlert.showInlineError(error); - this.errorHandler.error(error); - }); - }) - .catch(error=>{ - this.inlineAlert.showInlineError(error); - this.errorHandler.error(error); - }); - } - - onSubmit() { - if(this.isCreateEndpoint) { - this.createWithEndpoint(this.actionType); - } else { - switch(this.actionType) { - case ActionType.ADD_NEW: - this.createReplicationRule(); - break; - case ActionType.EDIT: - this.updateReplicationRule(); - break; + focusClear($event: any): void { + if (this.policyId < 0 && this.firstClick === 0) { + if ($event && $event.target && $event.target['value']) { + $event.target['value'] = ''; } + this.firstClick ++; } } - onCancel() { - if(this.hasChanged) { - this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'}); - } else { - this.createEditRuleOpened = false; - this.ruleForm.reset(); + leaveInput() { + this.selectedProjectList = []; + } + + selectedProjectName(projectName: string) { + this.noSelectedProject = false; + let pro: Project = this.selectedProjectList.find(data => data.name === projectName); + this.setProject([pro]); + this.selectedProjectList = []; + this.noProjectInfo = ""; + } + + selectedProject(project: Project): void { + if (!project) { + this.noSelectedProject = true; + }else { + this.noSelectedProject = false; + this.setProject([project]); } } - setInsecureValue($event: any) { - this.createEditRule.insecure = !$event; + 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; + } } - confirmCancel(confirmed: boolean) { - this.createEditRuleOpened = false; - this.inlineAlert.close(); - this.ruleForm.reset(); - } - - ngAfterViewChecked(): void { - this.ruleForm = this.currentForm; - if(this.ruleForm) { - let comparison: {[key: string]: any} = { - name: this.initVal.name, - description: this.initVal.description, - enable: this.initVal.enable, - endpointId: this.initVal.endpointId, - targetName: this.initVal.name, - endpointUrl: this.initVal.endpointUrl, - username: this.initVal.username, - password: this.initVal.password, - insecure: this.initVal.insecure - }; - let self: CreateEditRuleComponent | any = this; - if(self) { - self.ruleForm.valueChanges.subscribe((data: any)=>{ - for(let key in data) { - let current = data[key]; - let origin: string = comparison[key]; - if(((self.actionType === ActionType.EDIT && !self.readonly && !current ) || current) && current !== origin) { - self.hasChanged = true; - break; - } else { - self.hasChanged = false; - self.inlineAlert.close(); - } + // 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 = this.ruleForm.controls['filters']; + control.removeAt(i); } } - testConnection() { - this.pingStatus = true; - this.btnAbled=true; - this.translateService.get('REPLICATION.TESTING_CONNECTION').subscribe(res=>this.pingTestMessage=res); - this.testOngoing = !this.testOngoing; - let pingTarget: Endpoint = this.initEndpoint; - if(this.isCreateEndpoint) { - pingTarget.endpoint = this.createEditRule.endpointUrl || ''; - pingTarget.username = this.createEditRule.username; - pingTarget.password = this.createEditRule.password; - pingTarget.insecure = this.createEditRule.insecure; - } else { - for (let prop in pingTarget) { - delete pingTarget[prop]; + selectTrigger($event: any): void { + if ($event && $event.target && $event.target['value']) { + let val: string = $event.target['value']; + if (val === this.triggerNames[2]) { + this.isScheduleOpt = true; + this.isImmediate = false; + } + if (val === this.triggerNames[1]) { + this.isScheduleOpt = false; + this.isImmediate = true; + } + if (val === this.triggerNames[0]) { + this.isScheduleOpt = false; + this.isImmediate = false; } - pingTarget.id = this.createEditRule.endpointId; } - toPromise(this.endpointService - .pingEndpoint(pingTarget)) - .then(()=>{ - this.testOngoing = !this.testOngoing; - this.translateService.get('REPLICATION.TEST_CONNECTION_SUCCESS').subscribe(res=>this.pingTestMessage=res); - this.pingStatus = true; - this.btnAbled=false; - }) - .catch(error=>{ - this.testOngoing = !this.testOngoing; - this.translateService.get('REPLICATION.TEST_CONNECTION_FAILURE').subscribe(res=>this.pingTestMessage=res); - this.pingStatus = false; - this.btnAbled=false; - }); } + + // Replication Schedule select value exchange + selectSchedule($event: any): void { + if ($event && $event.target && $event.target['value']) { + switch ($event.target['value']) { + case this.scheduleNames[1]: + this.weeklySchedule = true; + this.ruleForm.patchValue({ + trigger: { + schedule_param: { + weekday: 1, + } + } + }); + break; + case this.scheduleNames[0]: + 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; + this.isImmediate = false; + 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 { + if (trigger['kind'] === this.triggerNames[0]) { + this.isImmediate = false; + } + if (trigger['kind'] === this.triggerNames[1]) { + this.isImmediate = true; + } + 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; + } + } + + public hasFormChange(): boolean { + return !isEmptyObject(this.getChanges()); + } + + onSubmit() { + // add new Replication rule + this.inProgress = true; + let copyRuleForm: ReplicationRule = this.ruleForm.value; + copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger); + if (this.policyId < 0) { + this.repService.createReplicationRule(copyRuleForm) + .then(() => { + this.translateService.get('REPLICATION.CREATED_SUCCESS') + .subscribe(res => this.errorHandler.info(res)); + this.inProgress = false; + this.reload.emit(true); + this.close(); + }).catch((error: any) => { + this.inProgress = false; + this.inlineAlert.showInlineError(error); + }); + } else { + this.repService.updateReplicationRule(this.policyId, this.ruleForm.value) + .then(() => { + this.translateService.get('REPLICATION.UPDATED_SUCCESS') + .subscribe(res => this.errorHandler.info(res)); + this.inProgress = false; + this.reload.emit(true); + this.close(); + }).catch((error: any) => { + this.inProgress = false; + this.inlineAlert.showInlineError(error); + }); + } + } + + openCreateEditRule(ruleId?: number | string): void { + this.initForm(); + this.selectedProjectList = []; + this.filterCount = 0; + this.filterListData = []; + this.firstClick = 0; + this.noSelectedProject = true; + this.noSelectedEndpoint = true; + this.isRuleNameExist = false; + + this.weeklySchedule = false; + this.isScheduleOpt = false; + this.isImmediate = false; + this.policyId = -1; + this.createEditRuleOpened = true; + + this.noProjectInfo = ""; + this.noEndpointInfo = ""; + if (this.targetList.length === 0) { + this.noEndpointInfo = 'REPLICATION.NO_ENDPOINT_INFO'; + } + if (this.projectList.length === 0 && !this.projectName) { + this.noProjectInfo = 'REPLICATION.NO_PROJECT_INFO'; + } + + if (ruleId) { + this.policyId = +ruleId; + this.headerTitle = 'REPLICATION.EDIT_POLICY_TITLE'; + toPromise(this.repService.getReplicationRule(ruleId)) + .then((response) => { + this.copyUpdateForm = Object.assign({}, response); + // set filter value is [] if callback fiter value is null. + this.copyUpdateForm.filters = response.filters ? response.filters : []; + this.updateForm(response); + }).catch((error: any) => { + this.inlineAlert.showInlineError(error); + }); + }else { + this.headerTitle = 'REPLICATION.ADD_POLICY'; + if (this.projectId) { + this.setProject([{project_id: this.projectId, name: this.projectName}]); + this.noSelectedProject = false; + } + + this.copyUpdateForm = Object.assign({}, this.ruleForm.value); + } + } + + close(): void { + this.createEditRuleOpened = false; + } + + confirmCancel(confirmed: boolean) { + this.inlineAlert.close(); + this.close(); + } + + onCancel(): void { + if (this.hasFormChange()) { + this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' }); + }else { + this.close(); + } + } + + goRegistry(): void { + this.goToRegistry.emit(); + } + + // UTC time + public getOfftime(daily_time: any): string { + + let timeOffset = 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; + } + + getChanges(): { [key: string]: any | any[] } { + let changes: { [key: string]: any | any[] } = {}; + let ruleValue: { [key: string]: any | any[] } = this.ruleForm.value; + if (!ruleValue || !this.copyUpdateForm) { + return changes; + } + for (let prop in ruleValue) { + let field: any = this.copyUpdateForm[prop]; + if (!compareValue(field, ruleValue[prop])) { + if (ruleValue[prop][0] && ruleValue[prop][0].project_id && (ruleValue[prop][0].project_id === field[0].project_id)) { + break; + } + if (ruleValue[prop][0] && ruleValue[prop][0].id && (ruleValue[prop][0].id === field[0].id)) { + break; + } + changes[prop] = ruleValue[prop]; + // Number + if (typeof field === "number") { + changes[prop] = +changes[prop]; + } + + // Trim string value + if (typeof field === "string") { + changes[prop] = ('' + changes[prop]).trim(); + } + } + } + + return changes; + } + } \ No newline at end of file diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.ts b/src/ui_ng/lib/src/endpoint/endpoint.component.ts index f500ffd7f..53dd65343 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.ts +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.ts @@ -137,23 +137,13 @@ export class EndpointComponent implements OnInit, OnDestroy { editTargets(targets: Endpoint[]) { if (targets && targets.length === 1) { - let target= targets[0]; + let target = targets[0]; let editable = true; if (!target.id) { return; } let id: number | string = target.id; - toPromise(this.endpointService - .getEndpointWithReplicationRules(id)) - .then( - rules => { - if (rules && rules.length > 0) { - rules.forEach((rule) => editable = (rule && rule.enabled !== 1)); - } - this.createEditEndpointComponent.openCreateEditTarget(editable, id); - this.forceRefreshView(1000); - }) - .catch(error => this.errorHandler.error(error)); + this.createEditEndpointComponent.openCreateEditTarget(editable, id); } } diff --git a/src/ui_ng/lib/src/index.ts b/src/ui_ng/lib/src/index.ts index 79858a122..878e2d428 100644 --- a/src/ui_ng/lib/src/index.ts +++ b/src/ui_ng/lib/src/index.ts @@ -8,6 +8,7 @@ export * from './filter/index'; export * from './endpoint/index'; export * from './repository/index'; export * from './create-edit-endpoint/index'; +export * from './create-edit-rule/index'; export * from './repository-stackview/index'; export * from './tag/index'; export * from './list-replication-rule/index'; diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts index 9faec0912..47c006bad 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts @@ -1,7 +1,7 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
- + @@ -35,7 +35,6 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = ` -
`; \ No newline at end of file diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts index 512391a18..39b2689e6 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts @@ -14,66 +14,91 @@ import { ErrorHandler } from '../error-handler/error-handler'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { ReplicationService, ReplicationDefaultService } from '../service/replication.service'; - describe('ListReplicationRuleComponent (inline template)', ()=>{ let mockRules: ReplicationRule[] = [ { "id": 1, - "project_id": 1, - "project_name": "library", - "target_id": 1, - "target_name": "target_01", + "projects": [{ + "project_id": 33, + "owner_id": 1, + "name": "aeas", + "deleted": 0, + "togglable": false, + "current_user_role_id": 0, + "repo_count": 0, + "metadata": { + "public": false, + "enable_content_trust": "", + "prevent_vul": "", + "severity": "", + "auto_scan": ""}, + "owner_name": "", + "creation_time": null, + "update_time": null, + "has_project_admin_role": true, + "is_member": true, + "role_name": "" + }], + "targets": [{ + "endpoint": "", + "id": 0, + "insecure": false, + "name": "khans3", + "username": "", + "password": "", + "type": 0, + }], "name": "sync_01", - "enabled": 0, "description": "", - "cron_str": "", + "filters": null, + "trigger": {"kind": "Manual", "schedule_param": null}, "error_job_count": 2, - "deleted": 0 + "replicate_deletion": false, + "replicate_existing_image_now": false, }, { - "id": 2, - "project_id": 1, - "project_name": "library", - "target_id": 3, - "target_name": "target_02", - "name": "sync_02", - "enabled": 1, - "description": "", - "cron_str": "", - "error_job_count": 1, - "deleted": 0 - }, - { - "id": 3, - "project_id": 1, - "project_name": "library", - "target_id": 2, - "target_name": "target_03", - "name": "sync_03", - "enabled": 0, - "description": "", - "cron_str": "", - "error_job_count": 0, - "deleted": 0 - } + "id": 2, + "projects": [{ + "project_id": 33, + "owner_id": 1, + "name": "aeas", + "deleted": 0, + "togglable": false, + "current_user_role_id": 0, + "repo_count": 0, + "metadata": { + "public": false, + "enable_content_trust": "", + "prevent_vul": "", + "severity": "", + "auto_scan": ""}, + "owner_name": "", + "creation_time": null, + "update_time": null, + "has_project_admin_role": true, + "is_member": true, + "role_name": "" + }], + "targets": [{ + "endpoint": "", + "id": 0, + "insecure": false, + "name": "khans3", + "username": "", + "password": "", + "type": 0, + }], + "name": "sync_02", + "description": "", + "filters": null, + "trigger": {"kind": "Manual", "schedule_param": null}, + "error_job_count": 2, + "replicate_deletion": false, + "replicate_existing_image_now": false, + }, ]; - - let mockRule: ReplicationRule = { - "id": 1, - "project_id": 1, - "project_name": "library", - "target_id": 1, - "target_name": "target_01", - "name": "sync_01", - "enabled": 0, - "description": "", - "cron_str": "", - "error_job_count": 2, - "deleted": 0 - }; - let fixture: ComponentFixture; let comp: ListReplicationRuleComponent; diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts index eb3d99334..ad8f33677 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts @@ -60,9 +60,8 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { @Input() isSystemAdmin: boolean; @Input() selectedId: number | string; @Input() withReplicationJob: boolean; - @Input() readonly: boolean; - @Input() loading: boolean = false; + @Input() loading = false; @Output() reload = new EventEmitter(); @Output() selectOne = new EventEmitter(); @@ -100,10 +99,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { setInterval(() => ref.markForCheck(), 500); } - public get opereateAvailable(): boolean { - return !this.readonly && !this.projectId ? true : false; - } - trancatedDescription(desc: string): string { if (desc.length > 35 ) { return desc.substr(0, 35); @@ -135,7 +130,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { retrieveRules(ruleName: string = ''): void { this.loading = true; - this.selectedRow = null; + /*this.selectedRow = null;*/ toPromise(this.replicationService .getReplicationRules(this.projectId, ruleName)) .then(rules => { @@ -156,36 +151,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { }); } - filterRuleStatus(status: string) { - if (status === 'all') { - this.changedRules = this.rules; - } else { - this.changedRules = this.rules.filter(policy => policy.enabled === +status); - } - } - - toggleConfirm(message: ConfirmationAcknowledgement) { - if (message && - message.source === ConfirmationTargets.TOGGLE_CONFIRM && - message.state === ConfirmationState.CONFIRMED) { - this.batchDelectionInfos = []; - let rule: ReplicationRule = message.data; - let initBatchMessage = new BatchInfo (); - initBatchMessage.name = rule.name; - this.batchDelectionInfos.push(initBatchMessage); - - if (rule) { - rule.enabled = rule.enabled === 0 ? 1 : 0; - toPromise(this.replicationService - .enableReplicationRule(rule.id || '', rule.enabled)) - .then(() => - this.translateService.get('REPLICATION.TOGGLED_SUCCESS') - .subscribe(res => this.batchDelectionInfos[0].status = res)) - .catch(error => this.batchDelectionInfos[0].status = error); - } - } - } - replicateRule(rules: ReplicationRule[]): void { this.replicateManual.emit(rules); } @@ -216,17 +181,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { this.editOne.emit(rule); } - toggleRule(rule: ReplicationRule) { - let toggleConfirmMessage: ConfirmationMessage = new ConfirmationMessage( - rule.enabled === 1 ? 'REPLICATION.TOGGLE_DISABLE_TITLE' : 'REPLICATION.TOGGLE_ENABLE_TITLE', - rule.enabled === 1 ? 'REPLICATION.CONFIRM_TOGGLE_DISABLE_POLICY' : 'REPLICATION.CONFIRM_TOGGLE_ENABLE_POLICY', - rule.name || '', - rule, - ConfirmationTargets.TOGGLE_CONFIRM - ); - this.toggleConfirmDialog.open(toggleConfirmMessage); - } - jobList(id: string | number): Promise { let ruleData: ReplicationJobItem[]; this.canDeleteRule = true; diff --git a/src/ui_ng/lib/src/project-policy-config/project.ts b/src/ui_ng/lib/src/project-policy-config/project.ts index 1f4a4fa6d..ed1d26e01 100644 --- a/src/ui_ng/lib/src/project-policy-config/project.ts +++ b/src/ui_ng/lib/src/project-policy-config/project.ts @@ -1,18 +1,18 @@ export class Project { project_id: number; - owner_id: number; + owner_id?: number; name: string; - creation_time: Date | string; - deleted: number; - owner_name: string; - togglable: boolean; - update_time: Date | string; - current_user_role_id: number; - repo_count: number; - has_project_admin_role: boolean; - is_member: boolean; - role_name: string; - metadata: { + creation_time?: Date | string; + deleted?: number; + owner_name?: string; + togglable?: boolean; + update_time?: Date | string; + current_user_role_id?: number; + repo_count?: number; + has_project_admin_role?: boolean; + is_member?: boolean; + role_name?: string; + metadata?: { public: string | boolean; enable_content_trust: string | boolean; prevent_vul: string | boolean; diff --git a/src/ui_ng/lib/src/replication/replication.component.html.ts b/src/ui_ng/lib/src/replication/replication.component.html.ts index d396fffb4..91af4e7aa 100644 --- a/src/ui_ng/lib/src/replication/replication.component.html.ts +++ b/src/ui_ng/lib/src/replication/replication.component.html.ts @@ -11,7 +11,7 @@ export const REPLICATION_TEMPLATE: string = `
- +
@@ -73,5 +73,6 @@ export const REPLICATION_TEMPLATE: string = `
+ `; \ No newline at end of file diff --git a/src/ui_ng/lib/src/replication/replication.component.spec.ts b/src/ui_ng/lib/src/replication/replication.component.spec.ts index 7b720ec15..d349a809b 100644 --- a/src/ui_ng/lib/src/replication/replication.component.spec.ts +++ b/src/ui_ng/lib/src/replication/replication.component.spec.ts @@ -12,7 +12,7 @@ import { DatePickerComponent } from '../datetime-picker/datetime-picker.componen import { DateValidatorDirective } from '../datetime-picker/date-validator.directive'; import { FilterComponent } from '../filter/filter.component'; import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; -import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface'; +import {ReplicationRule, ReplicationJob, Endpoint} from '../service/interface'; import { ErrorHandler } from '../error-handler/error-handler'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; @@ -20,49 +20,92 @@ import { ReplicationService, ReplicationDefaultService } from '../service/replic import { EndpointService, EndpointDefaultService } from '../service/endpoint.service'; import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component'; import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index'; +import {Project} from "../project-policy-config/project"; +import {ProjectDefaultService, ProjectService} from "service/project.service"; -describe('Replication Component (inline template)', ()=>{ +describe('Replication Component (inline template)', () => { let mockRules: ReplicationRule[] = [ - { - "id": 1, - "project_id": 1, - "project_name": "library", - "target_id": 1, - "target_name": "target_01", - "name": "sync_01", - "enabled": 0, - "description": "", - "cron_str": "", - "error_job_count": 2, - "deleted": 0 - }, - { - "id": 2, - "project_id": 1, - "project_name": "library", - "target_id": 3, - "target_name": "target_02", - "name": "sync_02", - "enabled": 1, - "description": "", - "cron_str": "", - "error_job_count": 1, - "deleted": 0 - }, - { - "id": 3, - "project_id": 1, - "project_name": "library", - "target_id": 2, - "target_name": "target_03", - "name": "sync_03", - "enabled": 0, - "description": "", - "cron_str": "", - "error_job_count": 0, - "deleted": 0 - } + { + "id": 1, + "projects": [{ + "project_id": 33, + "owner_id": 1, + "name": "aeas", + "deleted": 0, + "togglable": false, + "current_user_role_id": 0, + "repo_count": 0, + "metadata": { + "public": false, + "enable_content_trust": "", + "prevent_vul": "", + "severity": "", + "auto_scan": ""}, + "owner_name": "", + "creation_time": null, + "update_time": null, + "has_project_admin_role": true, + "is_member": true, + "role_name": "" + }], + "targets": [{ + "id": 1, + "endpoint": "https://10.117.4.151", + "name": "target_01", + "username": "admin", + "password": "", + "insecure": false, + "type": 0 + }], + "name": "sync_01", + "description": "", + "filters": null, + "trigger": {"kind": "Manual", "schedule_param": null}, + "error_job_count": 2, + "replicate_deletion": false, + "replicate_existing_image_now": false, + }, + { + "id": 2, + "projects": [{ + "project_id": 33, + "owner_id": 1, + "name": "aeas", + "deleted": 0, + "togglable": false, + "current_user_role_id": 0, + "repo_count": 0, + "metadata": { + "public": false, + "enable_content_trust": "", + "prevent_vul": "", + "severity": "", + "auto_scan": ""}, + "owner_name": "", + "creation_time": null, + "update_time": null, + "has_project_admin_role": true, + "is_member": true, + "role_name": "" + }], + "targets": [{ + "id": 1, + "endpoint": "https://10.117.4.151", + "name": "target_01", + "username": "admin", + "password": "", + "insecure": false, + "type": 0 + }], + "name": "sync_02", + "description": "", + "filters": null, + "trigger": {"kind": "Manual", "schedule_param": null}, + "error_job_count": 2, + "replicate_deletion": false, + "replicate_existing_image_now": false, + } ]; let mockJobs: ReplicationJobItem[] = [ @@ -81,7 +124,7 @@ describe('Replication Component (inline template)', ()=>{ "repository": "library/mysql", "policy_id": 1, "operation": "transfer", - "update_time": new Date("2017-05-27 12:20:33"), + "update_time": new Date("2017-05-27 12:20:33"), "tags": null }, { @@ -95,71 +138,66 @@ describe('Replication Component (inline template)', ()=>{ } ]; + let mockEndpoints: Endpoint[] = [ + { + "id": 1, + "endpoint": "https://10.117.4.151", + "name": "target_01", + "username": "admin", + "password": "", + "insecure": false, + "type": 0 + }, + { + "id": 2, + "endpoint": "https://10.117.5.142", + "name": "target_02", + "username": "AAA", + "password": "", + "insecure": false, + "type": 0 + }, + ]; + + let mockProjects: Project[] = [ + { "project_id": 1, + "owner_id": 0, + "name": 'project_01', + "creation_time": '', + "deleted": 0, + "owner_name": '', + "togglable": false, + "update_time": '', + "current_user_role_id": 0, + "repo_count": 0, + "has_project_admin_role": false, + "is_member": false, + "role_name": '', + "metadata": { + "public": '', + "enable_content_trust": '', + "prevent_vul": '', + "severity": '', + "auto_scan": '', + } + }]; + let mockJob: ReplicationJob = { metadata: {xTotalCount: 3}, data: mockJobs }; - let mockEndpoints: Endpoint[] = [ - { - "id": 1, - "endpoint": "https://10.117.4.151", - "name": "target_01", - "username": "admin", - "password": "", - "insecure": false, - "type": 0 - }, - { - "id": 2, - "endpoint": "https://10.117.5.142", - "name": "target_02", - "username": "AAA", - "password": "", - "insecure": false, - "type": 0 - }, - { - "id": 3, - "endpoint": "https://101.1.11.111", - "name": "target_03", - "username": "admin", - "password": "", - "insecure": false, - "type": 0 - }, - { - "id": 4, - "endpoint": "http://4.4.4.4", - "name": "target_04", - "username": "", - "password": "", - "insecure": false, - "type": 0 - } - ]; - - let mockRule: ReplicationRule = { - "id": 1, - "project_id": 1, - "project_name": "library", - "target_id": 1, - "target_name": "target_01", - "name": "sync_01", - "enabled": 0, - "description": "", - "cron_str": "", - "error_job_count": 2, - "deleted": 0 - }; - let fixture: ComponentFixture; + let fixtureCreate: ComponentFixture; let comp: ReplicationComponent; + let compCreate: CreateEditRuleComponent; let replicationService: ReplicationService; + let endpointService: EndpointService; let spyRules: jasmine.Spy; let spyJobs: jasmine.Spy; + let spyEndpoint: jasmine.Spy; let deGrids: DebugElement[]; let deRules: DebugElement; @@ -194,23 +232,29 @@ describe('Replication Component (inline template)', ()=>{ { provide: SERVICE_CONFIG, useValue: config }, { provide: ReplicationService, useClass: ReplicationDefaultService }, { provide: EndpointService, useClass: EndpointDefaultService }, + { provide: ProjectService, useClass: ProjectDefaultService }, { provide: JobLogService, useClass: JobLogDefaultService } ] }); })); - beforeEach(()=>{ + beforeEach(() => { fixture = TestBed.createComponent(ReplicationComponent); - + fixtureCreate = TestBed.createComponent(CreateEditRuleComponent); comp = fixture.componentInstance; + compCreate = fixtureCreate.componentInstance; comp.projectId = 1; comp.search.ruleId = 1; replicationService = fixture.debugElement.injector.get(ReplicationService); - + + endpointService = fixtureCreate.debugElement.injector.get(EndpointService); + spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules)); spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob)); - + + spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints)); + fixture.detectChanges(); fixture.whenStable().then(()=>{ fixture.detectChanges(); @@ -221,6 +265,7 @@ describe('Replication Component (inline template)', ()=>{ }); }); + it('Should load replication rules', async(()=>{ fixture.detectChanges(); fixture.whenStable().then(()=>{ diff --git a/src/ui_ng/lib/src/replication/replication.component.ts b/src/ui_ng/lib/src/replication/replication.component.ts index 7a8de6fba..5479f9ad5 100644 --- a/src/ui_ng/lib/src/replication/replication.component.ts +++ b/src/ui_ng/lib/src/replication/replication.component.ts @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Component, OnInit, ViewChild, Input, Output, OnDestroy, EventEmitter } from '@angular/core'; -import { ResponseOptions, RequestOptions } from '@angular/http'; -import { NgModel } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; @@ -23,7 +21,7 @@ import { ErrorHandler } from '../error-handler/error-handler'; import { ReplicationService } from '../service/replication.service'; import { RequestQueryParams } from '../service/RequestQueryParams'; -import { ReplicationRule, ReplicationJob, Endpoint, ReplicationJobItem } from '../service/interface'; +import { ReplicationRule, ReplicationJob, ReplicationJobItem } from '../service/interface'; import { toPromise, @@ -89,13 +87,14 @@ export class SearchOption { export class ReplicationComponent implements OnInit, OnDestroy { @Input() projectId: number | string; + @Input() projectName: string; @Input() isSystemAdmin: boolean; @Input() withReplicationJob: boolean; - @Input() readonly: boolean; @Output() redirect = new EventEmitter(); @Output() openCreateRule = new EventEmitter(); @Output() openEdit = new EventEmitter(); + @Output() goToRegistry = new EventEmitter(); search: SearchOption = new SearchOption(); @@ -106,7 +105,6 @@ export class ReplicationComponent implements OnInit, OnDestroy { currentJobStatus: { key: string, description: string }; changedRules: ReplicationRule[]; - initSelectedId: number | string; rules: ReplicationRule[]; loading: boolean; @@ -121,8 +119,8 @@ export class ReplicationComponent implements OnInit, OnDestroy { @ViewChild(ListReplicationRuleComponent) listReplicationRule: ListReplicationRuleComponent; -/* @ViewChild(CreateEditRuleComponent) - createEditPolicyComponent: CreateEditRuleComponent;*/ + @ViewChild(CreateEditRuleComponent) + createEditPolicyComponent: CreateEditRuleComponent; @ViewChild("replicationLogViewer") replicationLogViewer: JobLogViewerComponent; @@ -164,20 +162,22 @@ export class ReplicationComponent implements OnInit, OnDestroy { } } + // open replication rule openModal(): void { - this.openCreateRule.emit(); + this.createEditPolicyComponent.openCreateEditRule(); } + // edit replication rule openEditRule(rule: ReplicationRule) { if (rule) { - let editable = true; - if (rule.enabled === 1) { - editable = false; - } - this.openEdit.emit(rule.id); + this.createEditPolicyComponent.openCreateEditRule(rule.id); } } + goRegistry(): void { + this.goToRegistry.emit(); + } + //Server driven data loading clrLoadJobs(state: State): void { if (!state || !state.page || !this.search.ruleId) { @@ -209,6 +209,12 @@ export class ReplicationComponent implements OnInit, OnDestroy { } this.jobsLoading = true; + + //Do filtering and sorting + this.jobs = doFiltering(this.jobs, state); + this.jobs = doSorting(this.jobs, state); + + this.jobsLoading = false; toPromise(this.replicationService .getJobs(this.search.ruleId, params)) .then( diff --git a/src/ui_ng/lib/src/service/interface.ts b/src/ui_ng/lib/src/service/interface.ts index 6b876154d..5aa592b98 100644 --- a/src/ui_ng/lib/src/service/interface.ts +++ b/src/ui_ng/lib/src/service/interface.ts @@ -1,3 +1,4 @@ +import {Project} from "../project-policy-config/project"; /** * The base interface contains the general properties * @@ -83,18 +84,40 @@ export interface Endpoint extends Base { * * @export * @interface ReplicationRule + * @interface Filter + * @interface Trigger */ export interface ReplicationRule extends Base { - project_id: number | string; - project_name: string; - target_id: number | string; - target_name: string; - enabled: number; - description?: string; - cron_str?: string; - start_time?: Date; - error_job_count?: number; - deleted: number; + [key: string]: any; + id?: number; + name: string; + description: string; + projects: Project[]; + targets: Endpoint[] ; + trigger: Trigger ; + filters: Filter[] ; + replicate_existing_image_now?: boolean; + replicate_deletion?: boolean; +} + +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; + } } /** @@ -115,7 +138,7 @@ export interface ReplicationJob { * @interface ReplicationJob */ export interface ReplicationJobItem extends Base { - [key: string]: any | any[] + [key: string]: any | any[]; status: string; repository: string; policy_id: number; @@ -151,7 +174,7 @@ export interface AccessLog { * @interface AccessLogItem */ export interface AccessLogItem { - [key: string]: any | any[] + [key: string]: any | any[]; log_id: number; project_id: number; repo_name: string; diff --git a/src/ui_ng/lib/src/service/project.service.ts b/src/ui_ng/lib/src/service/project.service.ts index 341123dd7..6d90a20f4 100644 --- a/src/ui_ng/lib/src/service/project.service.ts +++ b/src/ui_ng/lib/src/service/project.service.ts @@ -6,7 +6,8 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { Project } from '../project-policy-config/project'; import { ProjectPolicy } from '../project-policy-config/project-policy-config.component'; -import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../utils"; +import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS, buildHttpRequestOptions} from "../utils"; +import {RequestQueryParams} from "./RequestQueryParams"; /** * Define the service methods to handle the Prject related things. @@ -38,6 +39,8 @@ export abstract class ProjectService { * @memberOf EndpointService */ abstract updateProjectPolicy(projectId: number | string, projectPolicy: ProjectPolicy): Observable | Promise | any; + + abstract listProjects(name: string, isPublic: number, page?: number, pageSize?: number): Observable | Promise | Project[]; } /** @@ -68,6 +71,27 @@ export class ProjectDefaultService extends ProjectService { .catch(error => Observable.throw(error)); } + listProjects(name: string, isPublic: number, page?: number, pageSize?: number): Observable | Promise | Project[] { + let params = new RequestQueryParams(); + if (page && pageSize) { + params.set('page', page + ''); + params.set('page_size', pageSize + ''); + } + if (name && name.trim() !== "") { + params.set('name', name); + + } + if (isPublic !== undefined) { + params.set('public', '' + isPublic); + } + + // let options = new RequestOptions({ headers: this.getHeaders, search: params }); + return this.http + .get(`/api/projects`, buildHttpRequestOptions(params)) + .map(response => response.json()) + .catch(error => Observable.throw(error)); + } + public updateProjectPolicy(projectId: number | string, projectPolicy: ProjectPolicy): any { return this.http .put(`/api/projects/${projectId}`, { 'metadata': { diff --git a/src/ui_ng/lib/src/service/replication.service.ts b/src/ui_ng/lib/src/service/replication.service.ts index ee111c279..ecb5b1797 100644 --- a/src/ui_ng/lib/src/service/replication.service.ts +++ b/src/ui_ng/lib/src/service/replication.service.ts @@ -1,6 +1,6 @@ import { Observable } from 'rxjs/Observable'; import { RequestQueryParams } from './RequestQueryParams'; -import { ReplicationJob, ReplicationRule, ReplicationJobItem } from './interface'; +import {ReplicationJob, ReplicationRule, ReplicationJobItem} from './interface'; import { Injectable, Inject } from "@angular/core"; import 'rxjs/add/observable/of'; import { Http, RequestOptions } from '@angular/http'; @@ -62,7 +62,7 @@ export abstract class ReplicationService { * * @memberOf ReplicationService */ - abstract updateReplicationRule(replicationRule: ReplicationRule): Observable | Promise | any; + abstract updateReplicationRule(id: number, rep: ReplicationRule): Observable | Promise | any; /** * Delete the specified replication rule. @@ -159,7 +159,7 @@ export class ReplicationDefaultService extends ReplicationService { //Private methods //Check if the rule object is valid _isValidRule(rule: ReplicationRule): boolean { - return rule !== undefined && rule != null && rule.name !== undefined && rule.name.trim() !== '' && rule.target_id !== 0; + return rule !== undefined && rule != null && rule.name !== undefined && rule.name.trim() !== '' && rule.targets.length !== 0; } public getReplicationRules(projectId?: number | string, ruleName?: string, queryParams?: RequestQueryParams): Observable | Promise | ReplicationRule[] { @@ -177,7 +177,7 @@ export class ReplicationDefaultService extends ReplicationService { return this.http.get(this._ruleBaseUrl, buildHttpRequestOptions(queryParams)).toPromise() .then(response => response.json() as ReplicationRule[]) - .catch(error => Promise.reject(error)) + .catch(error => Promise.reject(error)); } public getReplicationRule(ruleId: number | string): Observable | Promise | ReplicationRule { @@ -201,13 +201,13 @@ export class ReplicationDefaultService extends ReplicationService { .catch(error => Promise.reject(error)); } - public updateReplicationRule(replicationRule: ReplicationRule): Observable | Promise | any { - if (!this._isValidRule(replicationRule) || !replicationRule.id) { + public updateReplicationRule(id: number, rep: ReplicationRule): Observable | Promise | any { + if (!this._isValidRule(rep)) { return Promise.reject('Bad argument'); } - let url: string = `${this._ruleBaseUrl}/${replicationRule.id}`; - return this.http.put(url, JSON.stringify(replicationRule), HTTP_JSON_OPTIONS).toPromise() + let url = `${this._ruleBaseUrl}/${id}`; + return this.http.put(url, JSON.stringify(rep), HTTP_JSON_OPTIONS).toPromise() .then(response => response) .catch(error => Promise.reject(error)); } @@ -298,7 +298,7 @@ export class ReplicationDefaultService extends ReplicationService { return Promise.reject('Bad argument'); } - let logUrl: string = `${this._jobBaseUrl}/${jobId}/log`; + let logUrl = `${this._jobBaseUrl}/${jobId}/log`; return this.http.get(logUrl, HTTP_GET_OPTIONS).toPromise() .then(response => response.text()) .catch(error => Promise.reject(error)); diff --git a/src/ui_ng/lib/src/shared/shared.module.ts b/src/ui_ng/lib/src/shared/shared.module.ts index 365b14ee9..63d85fa16 100644 --- a/src/ui_ng/lib/src/shared/shared.module.ts +++ b/src/ui_ng/lib/src/shared/shared.module.ts @@ -2,8 +2,8 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HttpModule, Http } from '@angular/http'; import { ClarityModule } from 'clarity-angular'; -import { FormsModule } from '@angular/forms'; -import { TranslateModule, TranslateLoader, TranslateService, MissingTranslationHandler } from '@ngx-translate/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule, TranslateLoader, MissingTranslationHandler } from '@ngx-translate/core'; import { MyMissingTranslationHandler } from '../i18n/missing-trans.handler'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslatorJsonLoader } from '../i18n/local-json.loader'; @@ -41,6 +41,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) { CommonModule, HttpModule, FormsModule, + ReactiveFormsModule, ClipboardModule, CookieModule.forRoot(), ClarityModule.forRoot(), @@ -60,6 +61,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) { CommonModule, HttpModule, FormsModule, + ReactiveFormsModule, CookieModule, ClipboardModule, ClarityModule, diff --git a/src/ui_ng/package.json b/src/ui_ng/package.json index 2afe1600e..88b5993f2 100644 --- a/src/ui_ng/package.json +++ b/src/ui_ng/package.json @@ -31,7 +31,7 @@ "clarity-icons": "^0.10.17", "clarity-ui": "^0.10.17", "core-js": "^2.4.1", - "harbor-ui": "0.6.45", + "harbor-ui": "0.6.46", "intl": "^1.2.5", "mutationobserver-shim": "^0.3.2", "ngx-cookie": "^1.0.0", diff --git a/src/ui_ng/src/app/harbor-routing.module.ts b/src/ui_ng/src/app/harbor-routing.module.ts index 42e083df2..9b84013cd 100644 --- a/src/ui_ng/src/app/harbor-routing.module.ts +++ b/src/ui_ng/src/app/harbor-routing.module.ts @@ -50,8 +50,6 @@ 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"; -import {LeavingNewRuleRouteDeactivate} from "./shared/route/leaving-new-rule-deactivate.service"; import { LeavingRepositoryRouteDeactivate } from './shared/route/leaving-repository-deactivate.service'; const harborRoutes: Routes = [ @@ -92,20 +90,6 @@ const harborRoutes: Routes = [ canActivate: [SystemAdminGuard], canActivateChild: [SystemAdminGuard], }, - { - path: 'replications/:id/rule', - component: ReplicationRuleComponent, - canActivate: [SystemAdminGuard], - canActivateChild: [SystemAdminGuard], - canDeactivate: [LeavingNewRuleRouteDeactivate] - }, - { - path: 'replications/new-rule', - component: ReplicationRuleComponent, - canActivate: [SystemAdminGuard], - canActivateChild: [SystemAdminGuard], - canDeactivate: [LeavingNewRuleRouteDeactivate] - }, { path: 'tags/:id/:repo', component: TagRepositoryComponent, diff --git a/src/ui_ng/src/app/replication/replication-page.component.html b/src/ui_ng/src/app/replication/replication-page.component.html index 83bea8c9b..e61451484 100644 --- a/src/ui_ng/src/app/replication/replication-page.component.html +++ b/src/ui_ng/src/app/replication/replication-page.component.html @@ -1,3 +1,3 @@
- +
\ No newline at end of file diff --git a/src/ui_ng/src/app/replication/replication-page.component.ts b/src/ui_ng/src/app/replication/replication-page.component.ts index 69c25e83d..863c36e0e 100644 --- a/src/ui_ng/src/app/replication/replication-page.component.ts +++ b/src/ui_ng/src/app/replication/replication-page.component.ts @@ -15,6 +15,8 @@ import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import { ReplicationComponent } from 'harbor-ui'; import {SessionService} from "../shared/session.service"; +import {Project} from "../project/project"; +import {ProjectService} from "../project/project.service"; @Component({ selector: 'replicaton', @@ -23,13 +25,25 @@ import {SessionService} from "../shared/session.service"; export class ReplicationPageComponent implements OnInit, AfterViewInit { projectIdentify: string | number; @ViewChild("replicationView") replicationView: ReplicationComponent; + projectName: string; constructor(private route: ActivatedRoute, - private router: Router, + private proService: ProjectService, private session: SessionService) { } ngOnInit(): void { this.projectIdentify = +this.route.snapshot.parent.params['id']; + + this.proService.listProjects("", undefined).toPromise() + .then(response => { + let projects = response.json() as Project[]; + if (projects.length) { + let project = projects.find(data => data.project_id === this.projectIdentify); + if (project) { + this.projectName = project.name; + } + } + }); } public get isSystemAdmin(): boolean { @@ -37,14 +51,6 @@ export class ReplicationPageComponent implements OnInit, AfterViewInit { return account != null && account.has_admin_role > 0; } - openEditPage(id: number): void { - this.router.navigate(['harbor', 'replications', id, 'rule', { projectId: this.projectIdentify}]); - } - - openCreatePage(): void { - this.router.navigate(['harbor', 'replications', 'new-rule', { projectId: this.projectIdentify}] ); - } - ngAfterViewInit(): void { let isCreated: boolean = this.route.snapshot.queryParams['is_create']; if (isCreated) { diff --git a/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.css b/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.css deleted file mode 100644 index ab5ec9250..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.css +++ /dev/null @@ -1,5 +0,0 @@ -.datagrid .datagrid-head{border: 0;} -.option-right{ position: absolute; right: 30px; top: 55px;} -:host >>> .datagrid-head{height: 0;border-width: 0;} -:host >>> .datagrid-scroll-wrapper .datagrid{margin-top: 0;} -.modal-body{height: 30em; overflow-y: auto; margin-top: 20px;} diff --git a/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.html b/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.html deleted file mode 100644 index 0af247b58..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - diff --git a/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.ts b/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.ts deleted file mode 100644 index bf9738c99..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/list-project-model/list-project-model.component.ts +++ /dev/null @@ -1,181 +0,0 @@ -// 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 { - Component, - Output, - Input, - ChangeDetectionStrategy, - ChangeDetectorRef, - OnDestroy, EventEmitter -} from '@angular/core'; -import { Router, NavigationExtras } from '@angular/router'; - -import { SessionService } from '../../../shared/session.service'; -import { SearchTriggerService } from '../../../base/global-search/search-trigger.service'; -import { ProjectTypes, RoleInfo} from '../../../shared/shared.const'; -import { CustomComparator, doFiltering, doSorting, calculatePage } from '../../../shared/shared.utils'; - -import { Comparator, State } from 'clarity-angular'; -import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service'; -import { StatisticHandler } from '../../../shared/statictics/statistic-handler.service'; -import { Subscription } from 'rxjs/Subscription'; -import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { ConfirmationMessage } from '../../../shared/confirmation-dialog/confirmation-message'; -import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../../../shared/shared.const'; -import {ProjectService} from "../../../project/project.service"; -import {Project} from "../../../project/project"; - -@Component({ - selector: 'list-project-model', - templateUrl: 'list-project-model.component.html', - styleUrls: ['list-project-model.component.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ListProjectModelComponent { - projectTypes = ProjectTypes; - loading: boolean = true; - projects: Project[] = []; - filteredType: number = 0;//All projects - searchKeyword: string = ""; - ismodelOpen: boolean ; - currentFilteredType: number = 0;//all projects - projectName: string = ""; - selectedProject: Project; - - roleInfo = RoleInfo; - repoCountComparator: Comparator = new CustomComparator("repo_count", "number"); - timeComparator: Comparator = new CustomComparator("creation_time", "date"); - accessLevelComparator: Comparator = new CustomComparator("public", "number"); - roleComparator: Comparator = new CustomComparator("current_user_role_id", "number"); - currentPage: number = 1; - totalCount: number = 0; - pageSize: number = 10; - currentState: State; - @Output() selectedPro = new EventEmitter(); - - constructor( - private session: SessionService, - private router: Router, - private searchTrigger: SearchTriggerService, - private proService: ProjectService, - private msgHandler: MessageHandlerService, - private statisticHandler: StatisticHandler, - private deletionDialogService: ConfirmationDialogService, - private ref: ChangeDetectorRef) { - } - - get selecteType(): number { - return this.currentFilteredType; - } - set selecteType(_project: number) { - this.currentFilteredType = _project; - if (window.sessionStorage) { - window.sessionStorage['projectTypeValue'] = _project; - } - } - - - clrLoad(state: State) { - this.selectedProject = null; - //Keep state for future filtering and sorting - this.currentState = state; - - let pageNumber: number = calculatePage(state); - if (pageNumber <= 0) { pageNumber = 1; } - - this.loading = true; - - let passInFilteredType: number = undefined; - if (this.filteredType > 0) { - passInFilteredType = this.filteredType - 1; - } - this.proService.listProjects(this.searchKeyword, passInFilteredType, pageNumber, this.pageSize).toPromise() - .then(response => { - //Get total count - if (response.headers) { - let xHeader: string = response.headers.get("X-Total-Count"); - if (xHeader) { - this.totalCount = parseInt(xHeader, 0); - } - } - - this.projects = response.json() as Project[]; - //Do customising filtering and sorting - this.projects = doFiltering(this.projects, state); - this.projects = doSorting(this.projects, state); - - this.loading = false; - }) - .catch(error => { - this.loading = false; - this.msgHandler.handleError(error); - }); - - //Force refresh view - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 3000); - } - - openModel(): void { - this.selectedProject = null; - this.ismodelOpen = true; - //Force refresh view - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 2000); - } - - refresh(): void { - this.currentPage = 1; - this.filteredType = 0; - this.searchKeyword = ''; - - this.reload(); - } - - doFilterProject(): void { - this.currentPage = 1; - this.filteredType = this.selecteType; - this.reload(); - } - - doSearchProject(proName: string): void { - this.projectName = proName; - this.currentPage = 1; - this.searchKeyword = proName; - this.reload(); - } - - reload(): void { - let st: State = this.currentState; - if (!st) { - st = { - page: {} - }; - } - st.page.from = 0; - st.page.to = this.pageSize - 1; - st.page.size = this.pageSize; - - this.clrLoad(st); - } - - oKModel() { - this.ismodelOpen = false; - this.selectedPro.emit(this.selectedProject); - } - - closeModel(): void { - this.ismodelOpen = false; - } -} diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.component.ts b/src/ui_ng/src/app/replication/replication-rule/replication-rule.component.ts deleted file mode 100644 index f45431806..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.component.ts +++ /dev/null @@ -1,590 +0,0 @@ -import {Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, AfterViewInit} from '@angular/core'; -import {ProjectService} from '../../project/project.service'; -import {Project} from '../../project/project'; -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"; -import {ListProjectModelComponent} from "./list-project-model/list-project-model.component"; -import {toPromise, isEmptyObject, compareValue} from "harbor-ui/src/utils"; -import {CreateEditEndpointComponent} from "harbor-ui/src/create-edit-endpoint/create-edit-endpoint.component"; - -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, OnDestroy { - _localTime: Date = new Date(); - policyId: number; - projectId: number; - targetList: Target[] = []; - isFilterHide: boolean = false; - weeklySchedule: boolean; - isScheduleOpt: boolean; - isImmediate: boolean = false; - noProjectInfo: string = ""; - noSelectedProject: boolean = true; - noSelectedEndpoint: boolean = true; - filterCount: number = 0; - selectedprojectList: Project[] = []; - triggerNames: string[] = ['Manual', 'Immediate', 'Scheduled']; - scheduleNames: string[] = ['Daily', 'Weekly']; - weekly: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; - filterSelect: string[] = ['repository', 'tag']; - ruleNameTooltip: string = 'TOOLTIP.EMPTY'; - headerTitle: string = 'REPLICATION.ADD_POLICY'; - - filterListData: {[key: string]: any}[] = []; - inProgress: boolean = false; - inNameChecking: boolean = false; - isRuleNameExist: boolean = false; - isSubmitOver: boolean = false; - nameChecker: Subject = new Subject(); - - confirmSub: Subscription; - ruleForm: FormGroup; - copyUpdateForm: ReplicationRule; - emptyEndpoint = new Target(); - - @ViewChild(ListProjectModelComponent) - projectListModel: ListProjectModelComponent; - - @ViewChild(CreateEditEndpointComponent) - createEditEndpointComponent: CreateEditEndpointComponent; - - 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]) { - this.noSelectedEndpoint = true; - }else { - this.targetList = res[0]; - if (!this.policyId) { - res[0].unshift(this.emptyEndpoint); - this.setTarget([res[0][0]]); - } - } - if (!res[1]) { - this.noProjectInfo = 'REPLICATION.NO_PROJECT_INFO'; - }else { - if (!this.policyId && !this.projectId) { - this.setProject([res[1][0]]); - } - if (!this.policyId && this.projectId) { - this.setProject( res[1].filter(rule => rule.project_id === this.projectId)); - this.noSelectedProject = false; - } - } - if (!this.policyId) { - this.copyUpdateForm = Object.assign({}, this.ruleForm.value); - } - }); - } - - ngOnInit(): void { - this.policyId = +this.route.snapshot.params['id']; - this.projectId = +this.route.snapshot.params['projectId']; - if (this.policyId) { - this.headerTitle = 'REPLICATION.EDIT_POLICY_TITLE'; - this.repService.getReplicationRule(this.policyId) - .then((response) => { - this.copyUpdateForm = Object.assign({}, response); - // set filter value is [] if callback fiter value is null. - this.copyUpdateForm.filters = response.filters ? response.filters : []; - this.updateForm(response); - }).catch(error => { - this.msgHandler.handleError(error); - }); - } - - this.nameChecker.debounceTime(500).distinctUntilChanged().subscribe((ruleName: string) => { - this.isRuleNameExist = false; - this.inNameChecking = true; - toPromise(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; - }); - }); - } - - ngOnDestroy(): void { - if (this.confirmSub) { - this.confirmSub.unsubscribe(); - } - if (this.nameChecker) { - this.nameChecker.unsubscribe(); - } - } - - get isVaild() { - return !(this.isRuleNameExist || this.noSelectedProject || this.noSelectedEndpoint || this.inProgress || this.isSubmitOver); - } - - 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: false - }); - } - - 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.noSelectedProject = false; - this.setTarget(rule.targets); - this.noSelectedEndpoint = false; - - if (rule.filters) { - this.setFilter(rule.filters); - this.updateFilter(rule.filters); - } - - // Force refresh view - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 2000); - } - - 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); - } - }); - } - } - - targetChange($event: any) { - if ($event && $event.target && event.target['value']) { - if ($event.target['value'] === '-1') { - this.noSelectedEndpoint = true; - return; - } - let selecedTarget: Target = this.targetList.find(target => target.id === +$event.target['value']); - this.setTarget([selecedTarget]); - this.noSelectedEndpoint = false; - } - } - - openProjectModel(): void { - this.projectListModel.openModel(); - } - - selectedProject(project: Project): void { - if (!project) { - this.noSelectedProject = true; - }else { - this.noSelectedProject = false; - this.setProject([project]); - } - } - - 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 = this.ruleForm.controls['filters']; - control.removeAt(i); - } - } - - selectTrigger($event: any): void { - if ($event && $event.target && $event.target['value']) { - let val: string = $event.target['value']; - if (val === this.triggerNames[2]) { - this.isScheduleOpt = true; - this.isImmediate = false; - } - if (val === this.triggerNames[1]) { - this.isScheduleOpt = false; - this.isImmediate = true; - } - if (val === this.triggerNames[0]) { - this.isScheduleOpt = false; - this.isImmediate = false; - } - } - } - - // Replication Schedule select value exchange - selectSchedule($event: any): void { - if ($event && $event.target && $event.target['value']) { - switch ($event.target['value']) { - case this.scheduleNames[1]: - this.weeklySchedule = true; - this.ruleForm.patchValue({ - trigger: { - schedule_param: { - weekday: 1, - } - } - }) - break; - case this.scheduleNames[0]: - 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; - this.isImmediate = false; - 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 { - if (trigger['kind'] === this.triggerNames[0]) { - this.isImmediate = false; - } - 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; - } - } - - public hasFormChange(): boolean { - return !isEmptyObject(this.getChanges()); - } - - onSubmit() { - // add new Replication rule - this.inProgress = true; - 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; - this.isSubmitOver = true; - setTimeout(() => { - this.copyUpdateForm = Object.assign({}, this.ruleForm.value); - if (this.projectId) { - this.router.navigate(['harbor/projects', this.projectId, 'replications']); - }else { - 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.UPDATED_SUCCESS'); - this.inProgress = false; - this.isSubmitOver = true; - setTimeout(() => { - this.copyUpdateForm = Object.assign({}, this.ruleForm.value); - if (this.projectId) { - this.router.navigate(['harbor/projects', this.projectId, 'replications']); - }else { - this.router.navigate(['/harbor/replications']); - } - }, 2000); - - }).catch((error: any) => { - this.inProgress = false; - this.msgHandler.handleError(error); - }); - } - } - - openModal() { - this.createEditEndpointComponent.openCreateEditTarget(true); - } - - reload($event: boolean) { - if ($event) { - Promise.all([this.repService.getEndpoints()]).then(res => { - this.targetList = res[0]; - this.setTarget([this.targetList[this.targetList.length - 1]]); - this.noSelectedEndpoint = false; - }); - } - } - - onCancel(): void { - this.router.navigate(['/harbor/replications']); - } - - // 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.router.navigate(['/harbor/replications']); - } - backProjectReplication(): void { - this.router.navigate(['harbor/projects', this.projectId, 'replications']); - } - - - getChanges(): { [key: string]: any | any[] } { - let changes: { [key: string]: any | any[] } = {}; - let ruleValue: { [key: string]: any | any[] } = this.ruleForm.value; - if (!ruleValue || !this.copyUpdateForm) { - return changes; - } - for (let prop in ruleValue) { - let field = this.copyUpdateForm[prop]; - if (!compareValue(field, ruleValue[prop])) { - changes[prop] = ruleValue[prop]; - //Number - if (typeof field === "number") { - changes[prop] = +changes[prop]; - } - - //Trim string value - if (typeof field === "string") { - changes[prop] = ('' + changes[prop]).trim(); - } - } - } - - return changes; - } - -} diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.css b/src/ui_ng/src/app/replication/replication-rule/replication-rule.css deleted file mode 100644 index 4942247e5..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.css +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 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; -} -.colorRed{color: red;} -.colorRed a{text-decoration: underline;color: #007CBB;} - -label:first-child { - font-size: 15px; - left: -10px !important; -} -.inputWidth{width: 310px;} -.endpointSelect{ width: 290px; margin-right: 34px;} -.filterSelect{width: 350px;} -.filterSelect clr-icon{margin-left: 10px;} -.filterSelect label{width: 175px;} -.filterSelect label input{width: 100%;} -.cursor{cursor: pointer;} -.pull-left{float: left;} -.padLeft0{padding-left: 0;} -.floatSet {display: inline-block; width: 120px;margin-right: 10px;} -.form-group{ min-height: 36px;} - -.projectInput{float: left;} -.projectInput input{background-color: white;} -.switchIcon{width:20px;height:20px; margin-top: 10px;margin-left: 10px; cursor: pointer;} -.addEndpoint{ margin-top: .25em !important;} -.shadow{position: absolute;top: 8px;} -.is-solid{cursor: pointer;} \ No newline at end of file diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.html b/src/ui_ng/src/app/replication/replication-rule/replication-rule.html deleted file mode 100644 index b8a1b2bd6..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.html +++ /dev/null @@ -1,128 +0,0 @@ -
- < {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}} - <{{'SIDE_NAV.PROJECTS' | translate}}   {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate | lowercase}} -

{{headerTitle | translate}}

-
-
-
- - -
- -
- - -
- -
- -
-
- -
-
- - -
-
- - -
- -
-
-
-
- -
- - -
-
-
- -
- -
- -
-
- -
- -
-
- - -
- -
- -
- -
- -
-
- -
- - on    -
- -
- - at    - -
-
-
- - {{'REPLICATION.DELETE_REMOTE_IMAGES' | translate}} - -
-
- - {{'REPLICATION.REPLICATE_IMMEDIATE' | translate}} - -
-
- -
- -
- - -
-
-
- - -
\ No newline at end of file diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.service.ts b/src/ui_ng/src/app/replication/replication-rule/replication-rule.service.ts deleted file mode 100644 index fbe5a1c4c..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.service.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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 | Promise | 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[] { - 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 { - 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[] { - return this.http - .get(this.targetUrl) - .toPromise() - .then(response => response.json()) - .catch(error => Promise.reject(error)); - } - - public listProjects(): Promise | 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 | Promise | 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)); - } - -} diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.ts b/src/ui_ng/src/app/replication/replication-rule/replication-rule.ts deleted file mode 100644 index bd320c9fa..000000000 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {Project} from "../../project/project"; -/** - * Created by pengf on 12/7/2017. - */ - -export class Target { - id: number; - endpoint: string; - name: string; - username: string; - password: string; - type: number; - insecure: true; - creation_time: string; - update_time: string; - constructor() { - this.id = -1; - this.endpoint = ""; - this.name = ""; - this.username = ""; - this.password = ""; - this.type = 0; - this.insecure = true; - this.creation_time = ""; - this.update_time = ""; - } -} - -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 { - id?: number; - name: string; - description: string; - projects: Project[]; - targets: Target[] ; - trigger: Trigger ; - filters: Filter[] ; - replicate_existing_image_now?: boolean; - replicate_deletion?: boolean; -} - diff --git a/src/ui_ng/src/app/replication/replication.module.ts b/src/ui_ng/src/app/replication/replication.module.ts index 06cfca749..b8ca09742 100644 --- a/src/ui_ng/src/app/replication/replication.module.ts +++ b/src/ui_ng/src/app/replication/replication.module.ts @@ -20,10 +20,7 @@ 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"; -import {ListProjectModelComponent} from "./replication-rule/list-project-model/list-project-model.component"; @NgModule({ imports: [ @@ -36,15 +33,11 @@ import {ListProjectModelComponent} from "./replication-rule/list-project-model/l ReplicationManagementComponent, TotalReplicationPageComponent, DestinationPageComponent, - ReplicationRuleComponent, - ListProjectModelComponent, ], exports: [ ReplicationPageComponent, DestinationPageComponent, TotalReplicationPageComponent, - ReplicationRuleComponent, - ], - providers: [ReplicationRuleServie] + ] }) export class ReplicationModule { } \ No newline at end of file diff --git a/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html b/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html index c4ef6f6e5..799257e08 100644 --- a/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html +++ b/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html @@ -1,4 +1,4 @@

{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}

- +
\ No newline at end of file diff --git a/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.ts b/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.ts index c9e951046..2d38707a7 100644 --- a/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.ts +++ b/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.ts @@ -13,9 +13,10 @@ // limitations under the License. import { Component } from '@angular/core'; -import {Router,ActivatedRoute} from "@angular/router"; -import {ReplicationRule} from "../replication-rule/replication-rule"; +import {Router, ActivatedRoute} from "@angular/router"; + import {SessionService} from "../../shared/session.service"; +import {ReplicationRule} from "harbor-ui"; @Component({ selector: 'total-replication', @@ -31,17 +32,12 @@ export class TotalReplicationPageComponent { this.router.navigate(['../projects', rule.projects[0].project_id, 'replications'], { relativeTo: this.activeRoute }); } } + goRegistry(): void { + this.router.navigate(['../registries'], { relativeTo: this.activeRoute }); + } public get isSystemAdmin(): boolean { let account = this.session.getCurrentUser(); return account != null && account.has_admin_role > 0; } - - openEditPage(id: number): void { - this.router.navigate([id, 'rule'], { relativeTo: this.activeRoute }); - } - - openCreatePage(): void { - this.router.navigate(['new-rule'], { relativeTo: this.activeRoute }); - } } diff --git a/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.html b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.html index b6a469e64..03f462614 100644 --- a/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.html +++ b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.html @@ -32,7 +32,7 @@