diff --git a/client/src/app/ctl/config/config-manifest/config-manifest.component.html b/client/src/app/ctl/config/config-manifest/config-manifest.component.html index 0310a13..ad81d10 100644 --- a/client/src/app/ctl/config/config-manifest/config-manifest.component.html +++ b/client/src/app/ctl/config/config-manifest/config-manifest.component.html @@ -4,48 +4,43 @@

{{manifest.name}}

- RepoName: - - - - URL: - - - - Branch: - - - - CommitHash: - - - - Tag: - - - - RemoteRef: - - - -

- Force: -

-

- IsPhase: -

- SubPath: - - - - TargetPath: - - - - MetadataPath: - - - +
+ + Target Path + + + + Metadata Path + + +
Repositories
+ + + {{repoName}} + + + +
+ + URL + + + + {{repo.controls.checkoutLabel.value}} + + +

+ Force +

+

+ Is Phase +

+
+
+ +
- +

diff --git a/client/src/app/ctl/config/config-manifest/config-manifest.component.spec.ts b/client/src/app/ctl/config/config-manifest/config-manifest.component.spec.ts index a3360b1..53fd6a0 100644 --- a/client/src/app/ctl/config/config-manifest/config-manifest.component.spec.ts +++ b/client/src/app/ctl/config/config-manifest/config-manifest.component.spec.ts @@ -16,9 +16,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ToastrModule } from 'ngx-toastr'; import { CtlManifest, Manifest, RepoCheckout, Repository } from '../config.models'; @@ -41,7 +43,13 @@ describe('ConfigManifestComponent', () => { MatButtonModule, ReactiveFormsModule, ToastrModule.forRoot(), - MatExpansionModule + MatExpansionModule, + MatSelectModule, + MatDialogModule + ], + providers: [ + {provide: MAT_DIALOG_DATA, useValue: {name: 'default'}}, + {provide: MatDialogRef, useValue: {}} ] }) .compileComponents(); diff --git a/client/src/app/ctl/config/config-manifest/config-manifest.component.ts b/client/src/app/ctl/config/config-manifest/config-manifest.component.ts index 24a5927..6ea505b 100644 --- a/client/src/app/ctl/config/config-manifest/config-manifest.component.ts +++ b/client/src/app/ctl/config/config-manifest/config-manifest.component.ts @@ -14,9 +14,11 @@ import { Component, Input, OnInit } from '@angular/core'; import { Manifest, ManifestOptions, Repository } from '../config.models'; -import { FormControl } from '@angular/forms'; +import { FormControl, FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms'; import { WsService } from 'src/services/ws/ws.service'; import { WsMessage, WsConstants } from 'src/services/ws/ws.models'; +import { RepositoryComponent } from './repository/repository.component'; +import { MatDialog } from '@angular/material/dialog'; @Component({ @@ -31,97 +33,118 @@ export class ConfigManifestComponent implements OnInit { component = WsConstants.CONFIG; locked = true; - Name = new FormControl({value: '', disabled: true}); - RepoName = new FormControl({value: '', disabled: true}); - URL = new FormControl({value: '', disabled: true}); - Branch = new FormControl({value: '', disabled: true}); - CommitHash = new FormControl({value: '', disabled: true}); - Tag = new FormControl({value: '', disabled: true}); - RemoteRef = new FormControl({value: '', disabled: true}); - Force = new FormControl({value: false, disabled: true}); - IsPhase = new FormControl({value: false, disabled: true}); - SubPath = new FormControl({value: '', disabled: true}); - TargetPath = new FormControl({value: '', disabled: true}); - MetadataPath = new FormControl({value: '', disabled: true}); + group: FormGroup; + repoArray = new FormArray([]); + selectArray: string[] = []; + selectedIndex = 0; + checkoutTypes = ['Branch', 'CommitHash', 'Tag']; - controlsArray = [ - this.Name, - this.RepoName, - this.URL, - this.Branch, - this.CommitHash, - this.Tag, - this.RemoteRef, - this.Force, - this.IsPhase, - this.SubPath, - this.TargetPath, - this.MetadataPath - ]; - - constructor(private websocketService: WsService) { } - - ngOnInit(): void { - this.Name.setValue(this.manifest.name); - - // TODO(mfuller): not sure yet how to handle multiple repositories, - // so for now, I'm just showing the phase repository (primary) - const repoName = this.manifest.manifest.phaseRepositoryName; - this.RepoName.setValue(repoName); - const primaryRepo: Repository = this.manifest.manifest.repositories[repoName]; - this.URL.setValue(primaryRepo.url); - this.Branch.setValue(primaryRepo.checkout.branch); - this.CommitHash.setValue(primaryRepo.checkout.commitHash); - this.Tag.setValue(primaryRepo.checkout.tag); - this.RemoteRef.setValue(primaryRepo.checkout.remoteRef); - this.Force.setValue(primaryRepo.checkout.force); - // TODO(mfuller): this value doesn't come from the config file, but if set to true, - // it appears to set the phaseRepositoryName key, and since that's - // the only repo I'm showing, set to true for now - this.IsPhase.setValue(true); - this.SubPath.setValue(this.manifest.manifest.subPath); - this.TargetPath.setValue(this.manifest.manifest.targetPath); - this.MetadataPath.setValue(this.manifest.manifest.metadataPath); + constructor(private websocketService: WsService, + private fb: FormBuilder, + public dialog: MatDialog) { + this.group = this.fb.group({ + name: new FormControl({value: '', disabled: true}), + repositories: this.repoArray, + targetPath: new FormControl({value: '', disabled: true}, Validators.required), + metadataPath: new FormControl({value: '', disabled: true}, Validators.required) + }); } - toggleLock(): void { - for (const control of this.controlsArray) { - if (this.locked) { - control.enable(); - } else { - control.disable(); + addRepo(name: string, repo: Repository): void { + const repoArray = this.group.controls.repositories as FormArray; + const repoGroup = new FormGroup({ + repoName: new FormControl({value: name, disabled: true}), + url: new FormControl({value: repo.url, disabled: true}, Validators.required), + checkoutLabel: new FormControl(''), + checkoutReference: new FormControl({value: '', disabled: true}, Validators.required), + force: new FormControl({value: repo.checkout.force, disabled: true}), + isPhase: new FormControl({value: this.manifest.manifest.phaseRepositoryName === name, disabled: true}), + }); + + const checkout = this.getCheckoutRef(repo); + repoGroup.controls.checkoutLabel.setValue(checkout[0]); + repoGroup.controls.checkoutReference.setValue(checkout[1]); + repoArray.push(repoGroup); + this.selectArray.push(name); + } + + getCheckoutRef(repo: Repository): string[] { + for (const t of this.checkoutTypes) { + const key = t[0].toLowerCase() + t.substring(1); + if (repo.checkout[key] !== null && repo.checkout[key] !== '') { + return [t, repo.checkout[key]]; } } + return null; + } + newRepoDialog(): void { + const dialogRef = this.dialog.open(RepositoryComponent, { + width: '400px', + height: '520px', + data: { + name: this.manifest.name, + } + }); + } + + ngOnInit(): void { + this.group.controls.name.setValue(this.manifest.name); + this.group.controls.targetPath.setValue(this.manifest.manifest.targetPath); + this.group.controls.metadataPath.setValue(this.manifest.manifest.metadataPath); + for (const [name, repo] of Object.entries(this.manifest.manifest.repositories)) { + this.addRepo(name, repo); + } + } + + // once set to true, 'isPhase' and 'force' cannot be set to false using airshipctl's + // setters, so those controls won't be enabled if true. 'isPhase' can only be + // set to false by setting it to true for another repo + toggleLock(): void { + this.toggleControl(this.group.controls.targetPath as FormControl); + this.toggleControl(this.group.controls.metadataPath as FormControl); + for (const grp of this.repoArray.controls as FormGroup[]) { + Object.keys(grp.controls).forEach(key => { + this.toggleControl(grp.controls[key] as FormControl); + }); + } this.locked = !this.locked; } - setManifest(): void { - const msg = new WsMessage(this.type, this.component, WsConstants.SET_MANIFEST); - msg.name = this.manifest.name; + toggleControl(ctrl: FormControl): void { + if (ctrl.disabled && ctrl.value !== true) { + ctrl.enable(); + } else { + ctrl.disable(); + } + } - // TODO(mfuller): since "Force" and "IsPhase" can only be set by passing in - // CLI flags rather than passing in values, there doesn't appear to be a way - // to unset them once they're true without manually editing the config file. - // Open a bug for this? Or is this intentional? I may have to write a custom - // setter to set the value directly in the Config struct - const opts: ManifestOptions = { - Name: this.Name.value, - RepoName: this.RepoName.value, - URL: this.URL.value, - Branch: this.Branch.value, - CommitHash: this.CommitHash.value, - Tag: this.Tag.value, - RemoteRef: this.RemoteRef.value, - Force: this.Force.value, - IsPhase: this.IsPhase.value, - SubPath: this.SubPath.value, - TargetPath: this.TargetPath.value, - MetadataPath: this.MetadataPath.value - }; + setManifest(index: number): void { + const m = this.repoArray.at(index) as FormGroup; + const controls = m.controls; + if (controls !== undefined) { + const msg = new WsMessage(this.type, this.component, WsConstants.SET_MANIFEST); + msg.name = this.manifest.name; - msg.data = JSON.parse(JSON.stringify(opts)); - this.websocketService.sendMessage(msg); - this.toggleLock(); + const opts: ManifestOptions = { + Name: this.manifest.name, + RepoName: controls.repoName.value, + URL: controls.url.value, + Branch: null, + CommitHash: null, + Tag: null, + RemoteRef: null, + Force: controls.force.value, + IsPhase: controls.isPhase.value, + TargetPath: this.group.controls.targetPath.value, + MetadataPath: this.group.controls.metadataPath.value + }; + + opts[controls.checkoutLabel.value] = controls.checkoutReference.value; + + msg.data = JSON.parse(JSON.stringify(opts)); + this.websocketService.sendMessage(msg); + this.toggleLock(); + } } } diff --git a/client/src/app/ctl/config/config-manifest/config-manifest.module.ts b/client/src/app/ctl/config/config-manifest/config-manifest.module.ts index fcf3a69..ec80b71 100644 --- a/client/src/app/ctl/config/config-manifest/config-manifest.module.ts +++ b/client/src/app/ctl/config/config-manifest/config-manifest.module.ts @@ -18,7 +18,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatInputModule } from '@angular/material/input'; - +import { MatSelectModule } from '@angular/material/select'; +import { RepositoryModule } from './repository/repository.module'; @NgModule({ imports: [ @@ -27,10 +28,11 @@ import { MatInputModule } from '@angular/material/input'; MatCardModule, MatButtonModule, ReactiveFormsModule, - MatCheckboxModule - ], - declarations: [ + MatCheckboxModule, + MatSelectModule, + RepositoryModule ], + declarations: [], providers: [] }) export class ConfigManifestModule { } diff --git a/client/src/app/ctl/config/config-manifest/repository/repository.component.css b/client/src/app/ctl/config/config-manifest/repository/repository.component.css new file mode 100644 index 0000000..7c23c12 --- /dev/null +++ b/client/src/app/ctl/config/config-manifest/repository/repository.component.css @@ -0,0 +1,21 @@ +/* +# 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. +*/ + +mat-form-field { + width: 100%; +} + +.set-btn { + margin-left: 5px; +} \ No newline at end of file diff --git a/client/src/app/ctl/config/config-manifest/repository/repository.component.html b/client/src/app/ctl/config/config-manifest/repository/repository.component.html new file mode 100644 index 0000000..5f1d185 --- /dev/null +++ b/client/src/app/ctl/config/config-manifest/repository/repository.component.html @@ -0,0 +1,29 @@ +

New Repository

+
+ + Repository Name + + + + URL + + + + + {{type}} + + + + + +

+ Force +

+

+ Is Phase +

+
+
+ + +
diff --git a/client/src/app/ctl/config/config-manifest/repository/repository.component.spec.ts b/client/src/app/ctl/config/config-manifest/repository/repository.component.spec.ts new file mode 100644 index 0000000..083cb76 --- /dev/null +++ b/client/src/app/ctl/config/config-manifest/repository/repository.component.spec.ts @@ -0,0 +1,62 @@ +/* +# 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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ToastrModule } from 'ngx-toastr'; + +import { RepositoryComponent } from './repository.component'; + +describe('RepositoryComponent', () => { + let component: RepositoryComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RepositoryComponent ], + imports: [ + BrowserAnimationsModule, + FormsModule, + MatButtonModule, + MatCheckboxModule, + MatInputModule, + MatSelectModule, + ReactiveFormsModule, + MatDialogModule, + ToastrModule.forRoot() + ], + providers: [ + {provide: MAT_DIALOG_DATA, useValue: {name: 'default'}}, + {provide: MatDialogRef, useValue: {}} + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RepositoryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/ctl/config/config-manifest/repository/repository.component.ts b/client/src/app/ctl/config/config-manifest/repository/repository.component.ts new file mode 100644 index 0000000..c4cb219 --- /dev/null +++ b/client/src/app/ctl/config/config-manifest/repository/repository.component.ts @@ -0,0 +1,79 @@ +/* +# 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, Inject, OnInit } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; +import { MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; +import { WsConstants, WsMessage } from 'src/services/ws/ws.models'; +import { WsService } from 'src/services/ws/ws.service'; +import { ManifestOptions } from '../../config.models'; + +@Component({ + selector: 'app-repository', + templateUrl: './repository.component.html', + styleUrls: ['./repository.component.css'] +}) +export class RepositoryComponent implements OnInit{ + + group: FormGroup; + checkoutTypes = ['Branch', 'CommitHash', 'Tag']; + checkoutType = 'Branch'; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { + name: string, + }, + private ws: WsService) {} + + ngOnInit(): void { + this.group = new FormGroup({ + repoName: new FormControl('', Validators.required), + url: new FormControl('', Validators.required), + checkoutReference: new FormControl('', Validators.required), + force: new FormControl(false), + isPhase: new FormControl(false) + }); + } + + cancel(): void { + this.dialogRef.close(); + } + + setRepo(): void { + const msg = new WsMessage(WsConstants.CTL, WsConstants.CONFIG, WsConstants.SET_MANIFEST); + msg.name = this.data.name; + const opts: ManifestOptions = { + Name: this.data.name, + RepoName: this.group.controls.repoName.value, + URL: this.group.controls.url.value, + Branch: null, + CommitHash: null, + Tag: null, + RemoteRef: null, + Force: this.group.controls.force.value, + IsPhase: this.group.controls.isPhase.value, + TargetPath: null, + MetadataPath: null + }; + + opts[this.checkoutType] = this.group.controls.checkoutReference.value; + + msg.data = JSON.parse(JSON.stringify(opts)); + this.ws.sendMessage(msg); + + this.dialogRef.close(); + } + +} diff --git a/client/src/app/ctl/config/config-manifest/repository/repository.module.ts b/client/src/app/ctl/config/config-manifest/repository/repository.module.ts new file mode 100644 index 0000000..137859e --- /dev/null +++ b/client/src/app/ctl/config/config-manifest/repository/repository.module.ts @@ -0,0 +1,36 @@ +/* +# 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 { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatInputModule } from '@angular/material/input'; +import { RepositoryComponent } from './repository.component'; + +@NgModule({ + imports: [ + FormsModule, + MatInputModule, + MatCheckboxModule, + ReactiveFormsModule, + MatButtonModule, + MatDialogModule + ], + declarations: [], + providers: [] +}) + +export class RepositoryModule { } diff --git a/client/src/app/ctl/config/config.models.ts b/client/src/app/ctl/config/config.models.ts index 000c7fd..ae7ec1a 100644 --- a/client/src/app/ctl/config/config.models.ts +++ b/client/src/app/ctl/config/config.models.ts @@ -12,6 +12,8 @@ # limitations under the License. */ +import { FormControl } from '@angular/forms'; + export class EncryptionConfig { name: string; encryptionKeyPath: string; @@ -101,7 +103,6 @@ export class ManifestOptions { RemoteRef = ''; Force = false; IsPhase = false; - SubPath = ''; TargetPath = ''; MetadataPath = ''; } diff --git a/client/src/app/ctl/config/config.module.ts b/client/src/app/ctl/config/config.module.ts index 133fc5d..31c5d23 100755 --- a/client/src/app/ctl/config/config.module.ts +++ b/client/src/app/ctl/config/config.module.ts @@ -30,6 +30,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatExpansionModule } from '@angular/material/expansion'; import { ConfigNewComponent } from './config-new/config-new.component'; import { MatSelectModule } from '@angular/material/select'; +import { RepositoryComponent } from './config-manifest/repository/repository.component'; @NgModule({ imports: [ @@ -52,7 +53,8 @@ import { MatSelectModule } from '@angular/material/select'; ConfigManagementComponent, ConfigManifestComponent, ConfigInitComponent, - ConfigNewComponent + ConfigNewComponent, + RepositoryComponent ], providers: [], schemas: [CUSTOM_ELEMENTS_SCHEMA]