diff --git a/app/.vs/VSWorkspaceState.json b/app/.vs/VSWorkspaceState.json index 93ffcca..079caa0 100644 --- a/app/.vs/VSWorkspaceState.json +++ b/app/.vs/VSWorkspaceState.json @@ -11,6 +11,6 @@ "\\src\\assets\\i18n", "\\src\\assets\\i18n\\networking" ], - "SelectedNode": "\\src\\app\\instances\\instances.component.html", + "SelectedNode": "\\src\\app\\instances\\machines.component.html", "PreviewInSolutionExplorer": false } \ No newline at end of file diff --git a/app/src/app/app-routing.module.ts b/app/src/app/app-routing.module.ts index c041396..79e775e 100644 --- a/app/src/app/app-routing.module.ts +++ b/app/src/app/app-routing.module.ts @@ -13,7 +13,7 @@ const appRoutes: Routes = [ }, { path: 'machines', - loadChildren: () => import('./instances/instances.module').then(x => x.InstancesModule), + loadChildren: () => import('./machines/machines.module').then(x => x.MachinesModule), canActivate: [AuthGuardService], canLoad: [AuthGuardService], }, diff --git a/app/src/app/app.module.ts b/app/src/app/app.module.ts index 54e0890..8bcd416 100644 --- a/app/src/app/app.module.ts +++ b/app/src/app/app.module.ts @@ -14,7 +14,7 @@ import { SharedModule } from './shared.module'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { DashboardComponent } from './pages/dashboard/dashboard.component'; +import { DashboardComponent } from './pages/machines/machines.component'; import { UnauthorizedComponent } from './pages/unauthorized/unauthorized.component'; import { NotFoundComponent } from './pages/not-found/not-found.component'; import { NavMenuComponent } from './components/nav-menu/nav-menu.component'; diff --git a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.html b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.html index 282bbf2..d7f30d3 100644 --- a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.html +++ b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.html @@ -7,7 +7,7 @@

Create image from machine

-

Fill in the name and version for a new image based on the "{{ instance.name }}" machine

+

Fill in the name and version for a new image based on the "{{ machine.name }}" machine

diff --git a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.ts b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.ts index edf2a95..8041db3 100644 --- a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.ts +++ b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.ts @@ -13,7 +13,7 @@ import { filter, takeUntil } from 'rxjs/operators'; export class CustomImageEditorComponent implements OnInit { @Input() - instance: any; + machine: any; save = new Subject(); editorForm: FormGroup; diff --git a/app/src/app/catalog/helpers/catalog.service.ts b/app/src/app/catalog/helpers/catalog.service.ts index 9066d0d..d3a3b22 100644 --- a/app/src/app/catalog/helpers/catalog.service.ts +++ b/app/src/app/catalog/helpers/catalog.service.ts @@ -129,12 +129,12 @@ export class CatalogService } // ---------------------------------------------------------------------------------------------------------------- - createImage(instanceId: string, name: string, version: string, + createImage(machineId: string, name: string, version: string, description?: string, homepage?: string, eula?: string, acl?: string, tags?: string): Observable { return this.httpClient.post(`/api/my/images`, { - machine: instanceId, + machine: machineId, name, version, description, diff --git a/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.html b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.html new file mode 100644 index 0000000..381178e --- /dev/null +++ b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.html @@ -0,0 +1,43 @@ + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
diff --git a/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.scss b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.scss new file mode 100644 index 0000000..bcf57b6 --- /dev/null +++ b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.scss @@ -0,0 +1,7 @@ +.form-control +{ + background: #0c1321; + border-color: #00e7ff; + border-radius: 3rem; + color: #ff9c07; +} diff --git a/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.spec.ts b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.spec.ts new file mode 100644 index 0000000..ae2206b --- /dev/null +++ b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AffinityRuleEditorComponent } from './affinity-rule-editor.component'; + +describe('AffinityRuleEditorComponent', () => { + let component: AffinityRuleEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AffinityRuleEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AffinityRuleEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.ts b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.ts new file mode 100644 index 0000000..da57a0f --- /dev/null +++ b/app/src/app/components/affinity-rule-editor/affinity-rule-editor.component.ts @@ -0,0 +1,144 @@ +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-affinity-rule-editor', + templateUrl: './affinity-rule-editor.component.html', + styleUrls: ['./affinity-rule-editor.component.scss'] +}) +export class AffinityRuleEditorComponent implements OnInit, OnDestroy +{ + @Input() + disabled: boolean; + + @Output() + saved = new EventEmitter(); + + editorVisible: boolean; + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // -------------------------------------------------------------------------------------------------- + constructor(private readonly elementRef: ElementRef, + private readonly fb: FormBuilder) { } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + strict: [null, Validators.required], + operator: [null, Validators.required], + target: [null, Validators.required], + tagName: [null], + value: [null, Validators.required] + }); + + this.editorForm.get('target').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(target => + { + if (target === 'tagName') + this.editorForm.get('tagName').setValidators(Validators.required); + else + this.editorForm.get('tagName').clearValidators(); + }); + } + + // -------------------------------------------------------------------------------------------------- + showEditor() + { + if (this.disabled) return; + + this.editorVisible = true; + + addEventListener('click', this.onDocumentClick.bind(this)); + } + + // -------------------------------------------------------------------------------------------------- + saveChanges() + { + event.preventDefault(); + event.stopPropagation(); + + this.editorVisible = false; + + this.removeEventListeners(); + + let rule: string; + + if (this.editorForm.get('target').value === 'machine') + rule = `machine${this.editorForm.get('operator').value}${this.editorForm.get('strict').value}${this.editorForm.get('value').value}`; + else + rule = `${this.editorForm.get('tagName').value}${this.editorForm.get('operator').value}${this.editorForm.get('strict').value}${this.editorForm.get('value').value}`; + + this.saved.emit({ + strict: this.editorForm.get('strict').value === '=', + closeTo: this.editorForm.get('operator').value === '=', + targetMachine: this.editorForm.get('target').value === 'machine', + tagName: this.editorForm.get('tagName').value, + value: this.editorForm.get('value').value, + rule + }); + + this.resetForm(); + } + + // -------------------------------------------------------------------------------------------------- + cancelChanges() + { + this.editorVisible = false; + + this.removeEventListeners(); + + this.resetForm(); + } + + // -------------------------------------------------------------------------------------------------- + private resetForm() + { + this.editorForm.get('strict').setValue(null); + this.editorForm.get('operator').setValue(null); + this.editorForm.get('target').setValue(null); + this.editorForm.get('tagName').setValue(null); + this.editorForm.get('value').setValue(null); + } + + // -------------------------------------------------------------------------------------------------- + @HostListener('document:keydown.escape', ['$event']) + escapePressed(event) + { + this.cancelChanges(); + } + + // -------------------------------------------------------------------------------------------------- + protected onDocumentClick(event: MouseEvent) + { + if (!this.elementRef.nativeElement.contains(event.target)) + this.cancelChanges(); + } + + // -------------------------------------------------------------------------------------------------- + private removeEventListeners() + { + removeEventListener('click', this.onDocumentClick); + removeEventListener('document:keydown.escape', this.escapePressed); + } + + // -------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } + + // -------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.removeEventListeners(); + + this.destroy$.next(); + } +} diff --git a/app/src/app/components/inline-editor/inline-editor.component.ts b/app/src/app/components/inline-editor/inline-editor.component.ts index a95bf33..27594da 100644 --- a/app/src/app/components/inline-editor/inline-editor.component.ts +++ b/app/src/app/components/inline-editor/inline-editor.component.ts @@ -102,9 +102,8 @@ export class InlineEditorComponent implements OnInit, OnDestroy else this.saved.emit(this.editorForm.get('key').value); - this.editorForm.get('key').setValue(null); - this.editorForm.get('value').setValue(null); - } + this.resetForm(); + } // -------------------------------------------------------------------------------------------------- cancelChanges() @@ -113,6 +112,12 @@ export class InlineEditorComponent implements OnInit, OnDestroy this.removeEventListeners(); + this.resetForm(); + } + + // -------------------------------------------------------------------------------------------------- + private resetForm() + { this.editorForm.get('key').setValue(null); this.editorForm.get('value').setValue(null); } diff --git a/app/src/app/components/nav-menu/nav-menu.component.html b/app/src/app/components/nav-menu/nav-menu.component.html index 302c68f..ea9bc6c 100644 --- a/app/src/app/components/nav-menu/nav-menu.component.html +++ b/app/src/app/components/nav-menu/nav-menu.component.html @@ -3,7 +3,7 @@