diff --git a/client/angular.json b/client/angular.json
index 1aa388b..723634a 100644
--- a/client/angular.json
+++ b/client/angular.json
@@ -3,7 +3,7 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
- "airshipui-ui": {
+ "airshipui": {
"projectType": "application",
"schematics": {},
"root": "",
@@ -13,7 +13,7 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
- "outputPath": "dist/airshipui-ui",
+ "outputPath": "dist/airshipui",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
@@ -64,18 +64,18 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
- "browserTarget": "airshipui-ui:build"
+ "browserTarget": "airshipui:build"
},
"configurations": {
"production": {
- "browserTarget": "airshipui-ui:build:production"
+ "browserTarget": "airshipui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
- "browserTarget": "airshipui-ui:build"
+ "browserTarget": "airshipui:build"
}
},
"test": {
@@ -112,15 +112,15 @@
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
- "devServerTarget": "airshipui-ui:serve"
+ "devServerTarget": "airshipui:serve"
},
"configurations": {
"production": {
- "devServerTarget": "airshipui-ui:serve:production"
+ "devServerTarget": "airshipui:serve:production"
}
}
}
}
}},
- "defaultProject": "airshipui-ui"
+ "defaultProject": "airshipui"
}
diff --git a/client/src/app/airship/airship.component.ts b/client/src/app/airship/airship.component.ts
deleted file mode 100644
index 2603c19..0000000
--- a/client/src/app/airship/airship.component.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-
-@Component({
- selector: 'app-airship',
- templateUrl: './airship.component.html',
- styleUrls: ['./airship.component.css']
-})
-export class AirshipComponent implements OnInit {
-
- constructor() { }
-
- ngOnInit(): void {
- }
-
-}
diff --git a/client/src/app/airship/bare-metal/bare-metal.component.ts b/client/src/app/airship/bare-metal/bare-metal.component.ts
deleted file mode 100644
index b9d89cc..0000000
--- a/client/src/app/airship/bare-metal/bare-metal.component.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import {WebsocketMessage} from '../../../services/websocket/models/websocket-message/websocket-message';
-import {WebsocketService} from '../../../services/websocket/websocket.service';
-
-@Component({
- selector: 'app-bare-metal',
- templateUrl: './bare-metal.component.html',
- styleUrls: ['./bare-metal.component.css']
-})
-export class BareMetalComponent implements OnInit {
-
- private message: WebsocketMessage;
-
- constructor(private websocketService: WebsocketService) {
- }
-
- ngOnInit(): void { }
-
- generateIso(): void {
- this.message = new WebsocketMessage();
- this.message.type = 'airshipctl';
- this.message.component = 'baremetal';
- this.message.subComponent = 'generateISO';
- this.websocketService.sendMessage(this.message);
- }
-}
diff --git a/client/src/app/airship/document/document-overview/document-overview.component.html b/client/src/app/airship/document/document-overview/document-overview.component.html
deleted file mode 100644
index 832f66a..0000000
--- a/client/src/app/airship/document/document-overview/document-overview.component.html
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
- Source
- Rendered
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{node.name}}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/app/airship/document/document-overview/document-overview.component.spec.ts b/client/src/app/airship/document/document-overview/document-overview.component.spec.ts
deleted file mode 100644
index 1b1d5a4..0000000
--- a/client/src/app/airship/document/document-overview/document-overview.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { DocumentOverviewComponent } from './document-overview.component';
-
-describe('DocumentOverviewComponent', () => {
- let component: DocumentOverviewComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ DocumentOverviewComponent ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(DocumentOverviewComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/client/src/app/airship/document/document-overview/document-overview.component.ts b/client/src/app/airship/document/document-overview/document-overview.component.ts
deleted file mode 100644
index fbac030..0000000
--- a/client/src/app/airship/document/document-overview/document-overview.component.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import {WebsocketService} from '../../../../services/websocket/websocket.service';
-import {WebsocketMessage} from '../../../../services/websocket/models/websocket-message/websocket-message';
-import {KustomNode} from '../../../../app/airship/document/document-overview/kustom-node';
-import {NestedTreeControl} from '@angular/cdk/tree';
-import {MatTreeNestedDataSource} from '@angular/material/tree';
-
-@Component({
- selector: 'app-document-overview',
- templateUrl: './document-overview.component.html',
- styleUrls: ['./document-overview.component.css']
-})
-export class DocumentOverviewComponent implements OnInit {
-
- obj: KustomNode[] = [];
- currentDocId: string;
-
- saveBtnDisabled: boolean = true;
- hideButtons: boolean = true;
- isRendered: boolean = false;
-
- editorOptions = {language: 'yaml', automaticLayout: true, value: ''};
- code: string;
- editorTitle: string;
- onInit(editor) {
- editor.onDidChangeModelContent(() => {
- this.saveBtnDisabled = false;
- });
- }
-
- treeControl = new NestedTreeControl(node => node.children);
- dataSource = new MatTreeNestedDataSource();
-
- constructor(private websocketService: WebsocketService) {}
-
- hasChild = (_: number, node: KustomNode) => !!node.children && node.children.length > 0;
-
- ngOnInit(): void {
- this.websocketService.subject.subscribe(message => {
- if (message.type === 'airshipctl' && message.component === 'document') {
- switch (message.subComponent) {
- case 'getDefaults':
- Object.assign(this.obj, message.data);
- this.dataSource.data = this.obj;
- break;
- case 'getSource':
- this.closeEditor();
- Object.assign(this.obj, message.data);
- this.dataSource.data = this.obj;
- break;
- case 'getRendered':
- this.closeEditor();
- Object.assign(this.obj, message.data);
- this.dataSource.data = this.obj;
- break;
- case 'getYaml':
- this.changeEditorContents((message.yaml));
- this.editorTitle = message.name;
- this.currentDocId = message.message;
- if (!this.isRendered) {
- this.hideButtons = false;
- } else {
- this.hideButtons = true;
- }
- break;
- case 'yamlWrite':
- this.changeEditorContents((message.yaml));
- this.editorTitle = message.name;
- this.currentDocId = message.message;
- break;
- }
- }
- });
-
- const websocketMessage = this.constructDocumentWsMessage("getDefaults");
- this.websocketService.sendMessage(websocketMessage);
- }
-
- getYaml(id: string): void {
- this.code = null;
- const websocketMessage = this.constructDocumentWsMessage("getYaml");
- websocketMessage.message = id;
- this.websocketService.sendMessage(websocketMessage);
- }
-
- changeEditorContents(yaml: string): void {
- this.code = atob(yaml);
- }
-
- saveYaml(): void {
- const websocketMessage = this.constructDocumentWsMessage("yamlWrite");
- websocketMessage.message = this.currentDocId;
- websocketMessage.name = this.editorTitle;
- websocketMessage.yaml = btoa(this.code);
- this.websocketService.sendMessage(websocketMessage);
- }
-
- getSource(): void {
- this.isRendered = false;
- const websocketMessage = this.constructDocumentWsMessage("getSource");
- this.websocketService.sendMessage(websocketMessage);
- }
-
- getRendered(): void {
- this.isRendered = true;
- const websocketMessage = this.constructDocumentWsMessage("getRendered");
- this.websocketService.sendMessage(websocketMessage);
- }
-
- constructDocumentWsMessage(subComponent: string): WebsocketMessage {
- const w = new WebsocketMessage();
- w.type = "airshipctl";
- w.component = "document";
- w.subComponent = subComponent;
-
- return w;
- }
-
- closeEditor(): void {
- this.code = null;
- this.editorTitle = "";
- this.hideButtons = true;
- }
-}
diff --git a/client/src/app/airship/document/document-pull/document-pull.component.css b/client/src/app/airship/document/document-pull/document-pull.component.css
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/app/airship/document/document-pull/document-pull.component.html b/client/src/app/airship/document/document-pull/document-pull.component.html
deleted file mode 100644
index 20878cc..0000000
--- a/client/src/app/airship/document/document-pull/document-pull.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-Response to Pull: {{obby}}
-
diff --git a/client/src/app/airship/document/document-pull/document-pull.component.spec.ts b/client/src/app/airship/document/document-pull/document-pull.component.spec.ts
deleted file mode 100644
index 8da654f..0000000
--- a/client/src/app/airship/document/document-pull/document-pull.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { DocumentPullComponent } from './document-pull.component';
-
-describe('DocumentPullComponent', () => {
- let component: DocumentPullComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ DocumentPullComponent ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(DocumentPullComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/client/src/app/airship/document/document-pull/document-pull.component.ts b/client/src/app/airship/document/document-pull/document-pull.component.ts
deleted file mode 100644
index ed97181..0000000
--- a/client/src/app/airship/document/document-pull/document-pull.component.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { WebsocketService } from '../../../../services/websocket/websocket.service';
-import { WebsocketMessage } from '../../../../services/websocket/models/websocket-message/websocket-message';
-
-@Component({
- selector: 'app-document-pull',
- templateUrl: './document-pull.component.html',
- styleUrls: ['./document-pull.component.css']
-})
-export class DocumentPullComponent implements OnInit {
-
- obby: string;
-
- constructor(private websocketService: WebsocketService) {
-
- }
-
- ngOnInit(): void {
- this.websocketService.subject.subscribe(message => {
- if (message.type === 'airshipctl' && message.component === 'document' && message.subComponent === 'docPull') {
- this.obby = JSON.stringify(message);
- }
- }
- );
- }
-
- documentPull(): void {
- const websocketMessage = new WebsocketMessage();
- websocketMessage.type = 'airshipctl';
- websocketMessage.component = 'document';
- websocketMessage.subComponent = 'docPull';
- this.websocketService.sendMessage(websocketMessage);
- }
-
-}
diff --git a/client/src/app/airship/document/document.component.css b/client/src/app/airship/document/document.component.css
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/app/airship/document/document.component.html b/client/src/app/airship/document/document.component.html
deleted file mode 100644
index a62ff07..0000000
--- a/client/src/app/airship/document/document.component.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/client/src/app/airship/document/document.component.ts b/client/src/app/airship/document/document.component.ts
deleted file mode 100644
index dbbd37b..0000000
--- a/client/src/app/airship/document/document.component.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-
-@Component({
- selector: 'app-document',
- templateUrl: './document.component.html',
- styleUrls: ['./document.component.css']
-})
-export class DocumentComponent implements OnInit {
-
- activeLink = 'overview';
-
- constructor() { }
-
- ngOnInit(): void {
- }
-}
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index 0a5ca5d..79fbb6e 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -2,32 +2,21 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DashboardsComponent } from './dashboards/dashboards.component';
-import { AirshipComponent } from './airship/airship.component';
-import { BareMetalComponent } from './airship/bare-metal/bare-metal.component';
-import { DocumentComponent } from './airship/document/document.component';
-import { DocumentOverviewComponent } from './airship/document/document-overview/document-overview.component';
-import { DocumentPullComponent } from './airship/document/document-pull/document-pull.component';
-
+import { CTLComponent } from './ctl/ctl.component';
+import { BareMetalComponent } from './ctl/baremetal/baremetal.component';
+import { DocumentComponent } from './ctl/document/document.component';
const routes: Routes = [
{
- path: 'airship',
- component: AirshipComponent,
+ path: 'ctl',
+ component: CTLComponent,
children: [
{
- path: 'bare-metal',
+ path: 'baremetal',
component: BareMetalComponent
}, {
path: 'documents',
- component: DocumentComponent,
- children: [
- {
- path: 'overview',
- component: DocumentOverviewComponent
- }, {
- path: 'pull',
- component: DocumentPullComponent
- }]
+ component: DocumentComponent
}]
}, {
path: 'dashboard',
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 258e2dc..c46d72f 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -2,16 +2,19 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { NavInterface } from './models/nav.interface';
import { environment } from '../environments/environment';
import { IconService } from '../services/icon/icon.service';
-import { NotificationService } from '../services/notification/notification.service';
-import {WebsocketService} from '../services/websocket/websocket.service';
-import {Dashboard} from '../services/websocket/models/websocket-message/dashboard/dashboard';
+import { WebsocketService } from '../services/websocket/websocket.service';
+import { WSReceiver } from '../services/websocket/websocket.models';
+import { Dashboard } from '../services/websocket/models/websocket-message/dashboard/dashboard';
+import { WebsocketMessage } from 'src/services/websocket/models/websocket-message/websocket-message';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
-export class AppComponent implements OnDestroy, OnInit {
+export class AppComponent implements OnInit, WSReceiver {
+ type: string = "ui";
+ component: string = "any";
currentYear: number;
version: string;
@@ -23,11 +26,11 @@ export class AppComponent implements OnDestroy, OnInit {
children: [
{
displayName: 'Bare Metal',
- route: 'airship/bare-metal',
+ route: 'ctl/baremetal',
iconName: 'server'
}, {
displayName: 'Documents',
- route: 'airship/documents/overview',
+ route: 'ctl/documents',
iconName: 'doc'
}]
}, {
@@ -36,18 +39,23 @@ export class AppComponent implements OnDestroy, OnInit {
}];
constructor(private iconService: IconService,
- private notificationService: NotificationService,
private websocketService: WebsocketService) {
this.currentYear = new Date().getFullYear();
this.version = environment.version;
- this.websocketService.subject.subscribe(message => {
- if (message.type === 'airshipui' && message.component === 'initialize' && message.dashboards !== undefined) {
- this.updateDashboards(message.dashboards);
- }
- });
+ this.websocketService.registerFunctions(this);
}
- ngOnDestroy(): void {
+ async receiver(message: WebsocketMessage): Promise {
+ if (message.hasOwnProperty("error")) {
+ this.websocketService.printIfToast(message);
+ } else {
+ if (message.hasOwnProperty("dashboards")) {
+ this.updateDashboards(message.dashboards);
+ } else {
+ // TODO (aschiefe): determine what should be notifications and what should be 86ed
+ console.log("Message received in app: ", message);
+ }
+ }
}
ngOnInit(): void {
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 2f1603e..598621d 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -14,15 +14,13 @@ import { MatTableModule } from '@angular/material/table';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatExpansionModule } from '@angular/material/expansion';
import { RouterModule } from '@angular/router';
-import { AirshipComponent } from './airship/airship.component';
+import { CTLComponent } from './ctl/ctl.component';
import { DashboardsComponent } from './dashboards/dashboards.component';
import { HomeComponent } from './home/home.component';
-import { BareMetalComponent } from './airship/bare-metal/bare-metal.component';
-import { DocumentComponent } from './airship/document/document.component';
+import { BareMetalComponent } from './ctl/baremetal/baremetal.component';
+import { DocumentComponent } from './ctl/document/document.component';
import { HttpClientModule } from '@angular/common/http';
import { FlexLayoutModule } from '@angular/flex-layout';
-import { DocumentOverviewComponent } from './airship/document/document-overview/document-overview.component';
-import { DocumentPullComponent } from './airship/document/document-pull/document-pull.component';
import { MatTabsModule } from '@angular/material/tabs';
import { WebsocketService } from '../services/websocket/websocket.service';
import { ToastrModule } from 'ngx-toastr';
@@ -34,13 +32,11 @@ import {MatButtonToggleModule} from '@angular/material/button-toggle';
@NgModule({
declarations: [
AppComponent,
- AirshipComponent,
+ CTLComponent,
DashboardsComponent,
HomeComponent,
BareMetalComponent,
DocumentComponent,
- DocumentOverviewComponent,
- DocumentPullComponent,
],
imports: [
AppRoutingModule,
diff --git a/client/src/app/airship/airship.component.css b/client/src/app/ctl/baremetal/baremetal.component.css
similarity index 100%
rename from client/src/app/airship/airship.component.css
rename to client/src/app/ctl/baremetal/baremetal.component.css
diff --git a/client/src/app/airship/bare-metal/bare-metal.component.html b/client/src/app/ctl/baremetal/baremetal.component.html
similarity index 100%
rename from client/src/app/airship/bare-metal/bare-metal.component.html
rename to client/src/app/ctl/baremetal/baremetal.component.html
diff --git a/client/src/app/airship/bare-metal/bare-metal.component.spec.ts b/client/src/app/ctl/baremetal/baremetal.component.spec.ts
similarity index 90%
rename from client/src/app/airship/bare-metal/bare-metal.component.spec.ts
rename to client/src/app/ctl/baremetal/baremetal.component.spec.ts
index 1a49ba2..3510e84 100644
--- a/client/src/app/airship/bare-metal/bare-metal.component.spec.ts
+++ b/client/src/app/ctl/baremetal/baremetal.component.spec.ts
@@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { BareMetalComponent } from './bare-metal.component';
+import { BareMetalComponent } from './baremetal.component';
describe('BareMetalComponent', () => {
let component: BareMetalComponent;
diff --git a/client/src/app/ctl/baremetal/baremetal.component.ts b/client/src/app/ctl/baremetal/baremetal.component.ts
new file mode 100644
index 0000000..0cff564
--- /dev/null
+++ b/client/src/app/ctl/baremetal/baremetal.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnInit } from '@angular/core';
+import {WebsocketMessage} from '../../../services/websocket/models/websocket-message/websocket-message';
+import {WebsocketService} from '../../../services/websocket/websocket.service';
+import { WSReceiver } from '../../../services/websocket//websocket.models';
+
+@Component({
+ selector: 'app-bare-metal',
+ templateUrl: './baremetal.component.html',
+ styleUrls: ['./baremetal.component.css']
+})
+
+export class BareMetalComponent implements WSReceiver {
+ // TODO (aschiefe): extract these strings to constants
+ type: string = "ctl";
+ component: string = "baremetal";
+
+ constructor(private websocketService: WebsocketService) {
+ this.websocketService.registerFunctions(this);
+ }
+
+ async receiver(message: WebsocketMessage): Promise {
+ if (message.hasOwnProperty("error")) {
+ this.websocketService.printIfToast(message);
+ } else {
+ // TODO (aschiefe): determine what should be notifications and what should be 86ed
+ console.log("Message received in baremetal: ", message);
+ }
+ }
+
+ generateIso(): void {
+ this.websocketService.sendMessage(new WebsocketMessage(this.type, this.component, "generateISO"));
+ }
+}
diff --git a/client/src/app/airship/bare-metal/bare-metal.component.css b/client/src/app/ctl/ctl.component.css
similarity index 100%
rename from client/src/app/airship/bare-metal/bare-metal.component.css
rename to client/src/app/ctl/ctl.component.css
diff --git a/client/src/app/airship/airship.component.html b/client/src/app/ctl/ctl.component.html
similarity index 100%
rename from client/src/app/airship/airship.component.html
rename to client/src/app/ctl/ctl.component.html
diff --git a/client/src/app/ctl/ctl.component.ts b/client/src/app/ctl/ctl.component.ts
new file mode 100644
index 0000000..ea483f5
--- /dev/null
+++ b/client/src/app/ctl/ctl.component.ts
@@ -0,0 +1,10 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-airship',
+ templateUrl: './ctl.component.html',
+ styleUrls: ['./ctl.component.css']
+})
+export class CTLComponent {
+
+}
diff --git a/client/src/app/airship/airship.component.spec.ts b/client/src/app/ctl/ctlcomponent.spec.ts
similarity index 56%
rename from client/src/app/airship/airship.component.spec.ts
rename to client/src/app/ctl/ctlcomponent.spec.ts
index 80cc848..7aabe65 100644
--- a/client/src/app/airship/airship.component.spec.ts
+++ b/client/src/app/ctl/ctlcomponent.spec.ts
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { AirshipComponent } from './airship.component';
+import { CTLComponent } from './ctl.component';
-describe('AirshipComponent', () => {
- let component: AirshipComponent;
- let fixture: ComponentFixture;
+describe('CTLComponent', () => {
+ let component: CTLComponent;
+ let fixture: ComponentFixture;
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ AirshipComponent ]
+ declarations: [ CTLComponent ]
})
.compileComponents();
}));
beforeEach(() => {
- fixture = TestBed.createComponent(AirshipComponent);
+ fixture = TestBed.createComponent(CTLComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
diff --git a/client/src/app/airship/document/document-overview/document-overview.component.css b/client/src/app/ctl/document/document.component.css
similarity index 92%
rename from client/src/app/airship/document/document-overview/document-overview.component.css
rename to client/src/app/ctl/document/document.component.css
index b390783..c9bca10 100644
--- a/client/src/app/airship/document/document-overview/document-overview.component.css
+++ b/client/src/app/ctl/document/document.component.css
@@ -1,54 +1,54 @@
-@import '~material-design-icons/iconfont/material-icons.css';
-
-#DocContainer {
- display: flex;
- height: 75vh;
-}
-
-#SourceTab {
- width: 40%;
- overflow-y: auto;
-}
-
-button {
- border: none;
- background: none;
-}
-
-#ViewType {
- padding: 5px;
-}
-
-.kustom-tree-invisible {
- display: none;
-}
-
-.kustom-tree ul,
-.kustom-tree li {
- margin-top: 0;
- margin-bottom: 0;
- list-style-type: none;
-}
-
-#EditorDiv {
- height: 100%;
- width: 60%;
- padding-left: 5px
-}
-
-#EditorHeader {
- display: flex;
-}
-
-#EditorButtons {
- margin-left: auto;
-}
-
-.editor-btn {
- padding: 5px;
-}
-
-ngx-monaco-editor {
- height: 100%;
- width: 100%;
+@import '~material-design-icons/iconfont/material-icons.css';
+
+#DocContainer {
+ display: flex;
+ height: 75vh;
+}
+
+#SourceTab {
+ width: 40%;
+ overflow-y: auto;
+}
+
+button {
+ border: none;
+ background: none;
+}
+
+#ViewType {
+ padding: 5px;
+}
+
+.kustom-tree-invisible {
+ display: none;
+}
+
+.kustom-tree ul,
+.kustom-tree li {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+#EditorDiv {
+ height: 100%;
+ width: 60%;
+ padding-left: 5px
+}
+
+#EditorHeader {
+ display: flex;
+}
+
+#EditorButtons {
+ margin-left: auto;
+}
+
+.editor-btn {
+ padding: 5px;
+}
+
+ngx-monaco-editor {
+ height: 100%;
+ width: 100%;
}
\ No newline at end of file
diff --git a/client/src/app/ctl/document/document.component.html b/client/src/app/ctl/document/document.component.html
new file mode 100644
index 0000000..d71fc40
--- /dev/null
+++ b/client/src/app/ctl/document/document.component.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ Source
+ Rendered
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{node.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Response to Pull: {{obby}}
+
+
\ No newline at end of file
diff --git a/client/src/app/airship/document/document.component.spec.ts b/client/src/app/ctl/document/document.component.spec.ts
similarity index 100%
rename from client/src/app/airship/document/document.component.spec.ts
rename to client/src/app/ctl/document/document.component.spec.ts
diff --git a/client/src/app/ctl/document/document.component.ts b/client/src/app/ctl/document/document.component.ts
new file mode 100644
index 0000000..2282c62
--- /dev/null
+++ b/client/src/app/ctl/document/document.component.ts
@@ -0,0 +1,138 @@
+import { Component, OnInit } from '@angular/core';
+import {WebsocketService} from '../../../services/websocket/websocket.service';
+import { WSReceiver } from '../../../services/websocket/websocket.models';
+import {WebsocketMessage} from '../../../services/websocket/models/websocket-message/websocket-message';
+import {KustomNode} from './kustom-node';
+import {NestedTreeControl} from '@angular/cdk/tree';
+import {MatTreeNestedDataSource} from '@angular/material/tree';
+
+@Component({
+ selector: 'app-document',
+ templateUrl: './document.component.html',
+ styleUrls: ['./document.component.css']
+})
+
+export class DocumentComponent implements WSReceiver {
+ obby: string;
+
+ type: string = 'ctl';
+ component: string = 'document';
+
+ activeLink = 'overview';
+
+ obj: KustomNode[] = [];
+ currentDocId: string;
+
+ saveBtnDisabled: boolean = true;
+ hideButtons: boolean = true;
+ isRendered: boolean = false;
+
+ editorOptions = {language: 'yaml', automaticLayout: true, value: ''};
+ code: string;
+ editorTitle: string;
+ onInit(editor) {
+ editor.onDidChangeModelContent(() => {
+ this.saveBtnDisabled = false;
+ });
+ }
+
+ treeControl = new NestedTreeControl(node => node.children);
+ dataSource = new MatTreeNestedDataSource();
+
+ constructor(private websocketService: WebsocketService) {
+ this.websocketService.registerFunctions(this);
+ this.getSource(); // load the source first
+ }
+
+ hasChild = (_: number, node: KustomNode) => !!node.children && node.children.length > 0;
+
+ public async receiver(message: WebsocketMessage): Promise {
+ if (message.hasOwnProperty("error")) {
+ this.websocketService.printIfToast(message);
+ } else {
+ switch (message.subComponent) {
+ case 'getDefaults':
+ Object.assign(this.obj, message.data);
+ this.dataSource.data = this.obj;
+ break;
+ case 'getSource':
+ this.closeEditor();
+ Object.assign(this.obj, message.data);
+ this.dataSource.data = this.obj;
+ break;
+ case 'getRendered':
+ this.closeEditor();
+ Object.assign(this.obj, message.data);
+ this.dataSource.data = this.obj;
+ break;
+ case 'getYaml':
+ this.changeEditorContents((message.yaml));
+ this.editorTitle = message.name;
+ this.currentDocId = message.message;
+ if (!this.isRendered) {
+ this.hideButtons = false;
+ } else {
+ this.hideButtons = true;
+ }
+ break;
+ case 'yamlWrite':
+ this.changeEditorContents((message.yaml));
+ this.editorTitle = message.name;
+ this.currentDocId = message.message;
+ break;
+ case 'docPull':
+ this.obby = "Message pull was a " + message.message;
+ break;
+ default:
+ console.log("Document message sub component not handled: ", message);
+ break;
+ }
+ }
+ }
+
+ getYaml(id: string): void {
+ this.code = null;
+ const websocketMessage = this.constructDocumentWsMessage("getYaml");
+ websocketMessage.message = id;
+ this.websocketService.sendMessage(websocketMessage);
+ }
+
+ changeEditorContents(yaml: string): void {
+ this.code = atob(yaml);
+ }
+
+ saveYaml(): void {
+ const websocketMessage = this.constructDocumentWsMessage("yamlWrite");
+ websocketMessage.message = this.currentDocId;
+ websocketMessage.name = this.editorTitle;
+ websocketMessage.yaml = btoa(this.code);
+ this.websocketService.sendMessage(websocketMessage);
+ }
+
+ getSource(): void {
+ this.isRendered = false;
+ const websocketMessage = this.constructDocumentWsMessage("getSource");
+ this.websocketService.sendMessage(websocketMessage);
+ }
+
+ getRendered(): void {
+ this.isRendered = true;
+ const websocketMessage = this.constructDocumentWsMessage("getRendered");
+ this.websocketService.sendMessage(websocketMessage);
+ }
+
+ constructDocumentWsMessage(subComponent: string): WebsocketMessage {
+ return new WebsocketMessage(this.type, this.component, subComponent);
+ }
+
+ closeEditor(): void {
+ this.code = null;
+ this.editorTitle = "";
+ this.hideButtons = true;
+ }
+
+ documentPull(): void {
+ this.websocketService.sendMessage(new WebsocketMessage(this.type, this.component, "docPull"));
+ }
+}
+
diff --git a/client/src/app/airship/document/document-overview/kustom-node.ts b/client/src/app/ctl/document/kustom-node.ts
similarity index 100%
rename from client/src/app/airship/document/document-overview/kustom-node.ts
rename to client/src/app/ctl/document/kustom-node.ts
diff --git a/client/src/app/dashboards/dashboards.component.ts b/client/src/app/dashboards/dashboards.component.ts
index 76c91ae..8f87c20 100644
--- a/client/src/app/dashboards/dashboards.component.ts
+++ b/client/src/app/dashboards/dashboards.component.ts
@@ -5,11 +5,6 @@ import { Component, OnInit } from '@angular/core';
templateUrl: './dashboards.component.html',
styleUrls: ['./dashboards.component.css']
})
-export class DashboardsComponent implements OnInit {
-
- constructor() { }
-
- ngOnInit(): void {
- }
+export class DashboardsComponent {
}
diff --git a/client/src/services/notification/notification.service.spec.ts b/client/src/services/notification/notification.service.spec.ts
deleted file mode 100644
index c4f2cd6..0000000
--- a/client/src/services/notification/notification.service.spec.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-
-import { NotificationService } from './notification.service';
-
-describe('NotificationService', () => {
- let service: NotificationService;
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- service = TestBed.inject(NotificationService);
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-});
diff --git a/client/src/services/notification/notification.service.ts b/client/src/services/notification/notification.service.ts
deleted file mode 100644
index f96bb8a..0000000
--- a/client/src/services/notification/notification.service.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Injectable } from '@angular/core';
-import { ToastrService } from 'ngx-toastr';
-import { WebsocketService } from '../websocket/websocket.service';
-import { WebsocketMessage } from '../websocket/models/websocket-message/websocket-message';
-
-@Injectable({
- providedIn: 'root'
-})
-export class NotificationService {
-
- constructor(private toastrService: ToastrService,
- private websocketService: WebsocketService) {
- this.websocketService.subject.subscribe(message => {
- this.printIfToast(message);
- }
- );
- }
-
- printIfToast(message: WebsocketMessage): void {
- if (message.error !== undefined && message.error !== null) {
- this.toastrService.error(message.error);
- }
- }
-}
diff --git a/client/src/services/websocket/models/websocket-message/websocket-message.ts b/client/src/services/websocket/models/websocket-message/websocket-message.ts
index 30f49bf..683f96d 100644
--- a/client/src/services/websocket/models/websocket-message/websocket-message.ts
+++ b/client/src/services/websocket/models/websocket-message/websocket-message.ts
@@ -14,4 +14,12 @@ export class WebsocketMessage {
message: string;
data: JSON;
yaml: string;
+
+ // this constructor looks like this in case anyone decides they want just a raw message with no data predefined
+ // or an easy way to specify the defaults
+ constructor (type?: string | undefined, component?: string | undefined, subComponent?: string | undefined) {
+ this.type = type;
+ this.component = component;
+ this.subComponent = subComponent;
+ }
}
diff --git a/client/src/services/websocket/websocket.models.ts b/client/src/services/websocket/websocket.models.ts
new file mode 100755
index 0000000..4e2ebc2
--- /dev/null
+++ b/client/src/services/websocket/websocket.models.ts
@@ -0,0 +1,10 @@
+import { WebsocketMessage } from './models/websocket-message/websocket-message';
+
+export interface WSReceiver {
+ // the holy trinity of the websocket messages, a triumvirate if you will, which is how all are routed
+ type: string;
+ component: string;
+
+ // This is the method which will need to be implemented in the component to handle the messages
+ receiver(message: WebsocketMessage): Promise;
+}
\ No newline at end of file
diff --git a/client/src/services/websocket/websocket.service.spec.ts b/client/src/services/websocket/websocket.service.spec.ts
index bf79772..dc3f132 100644
--- a/client/src/services/websocket/websocket.service.spec.ts
+++ b/client/src/services/websocket/websocket.service.spec.ts
@@ -13,4 +13,4 @@ describe('WebsocketService', () => {
it('should be created', () => {
expect(service).toBeTruthy();
});
-});
+});
\ No newline at end of file
diff --git a/client/src/services/websocket/websocket.service.ts b/client/src/services/websocket/websocket.service.ts
index b59e1d0..9865b32 100644
--- a/client/src/services/websocket/websocket.service.ts
+++ b/client/src/services/websocket/websocket.service.ts
@@ -1,45 +1,56 @@
-import { Injectable } from '@angular/core';
+import { Injectable, OnDestroy } from '@angular/core';
import { WebsocketMessage } from './models/websocket-message/websocket-message';
-import { Subject } from 'rxjs';
-import {Dashboard} from './models/websocket-message/dashboard/dashboard';
-import {Executable} from './models/websocket-message/dashboard/executable/executable';
+import { WSReceiver } from './websocket.models';
+import { ToastrService } from 'ngx-toastr';
+import 'reflect-metadata';
@Injectable({
providedIn: 'root'
})
-export class WebsocketService {
- public subject = new Subject();
+export class WebsocketService implements OnDestroy {
private ws: WebSocket;
private timeout: number;
+ // functionMap is how we know where to send the direct messages
+ // the structure of this map is: type -> component -> receiver
+ private functionMap = new Map>();
+
+ // messageToObject unmarshalls the incoming message into a WebsocketMessage object
private static messageToObject(incomingMessage: string): WebsocketMessage {
let json = JSON.parse(incomingMessage);
- let obj = new WebsocketMessage();
- Object.assign(obj, json);
+ let wsm = new WebsocketMessage();
+ Object.assign(wsm, json);
- return obj;
+ return wsm;
}
- constructor() {
+ // when the WebsocketService is created the toast message is initialized and a websocket is registered
+ constructor(private toastrService: ToastrService) {
this.register();
}
- public sendMessage(message: WebsocketMessage): void {
+ // catch the page destroy and shut down the websocket connection normally
+ ngOnDestroy(): void {
+ this.ws.close();
+ }
+
+ // sendMessage will relay a WebsocketMessage to the go backend
+ public async sendMessage(message: WebsocketMessage): Promise {
message.timestamp = new Date().getTime();
this.ws.send(JSON.stringify(message));
}
+ // register initializes the websocket communication with the go backend
private register(): void {
if (this.ws !== undefined && this.ws !== null) {
this.ws.close();
- this.ws = null;
}
this.ws = new WebSocket('ws://localhost:8080/ws');
this.ws.onmessage = (event) => {
- this.subject.next(WebsocketService.messageToObject(event.data));
+ this.messageHandler(WebsocketService.messageToObject(event.data));
};
this.ws.onerror = (event) => {
@@ -48,8 +59,6 @@ export class WebsocketService {
this.ws.onopen = () => {
console.log('Websocket established');
- const json = { type: 'airshipui', component: 'initialize' };
- this.ws.send(JSON.stringify(json));
// start up the keepalive so the websocket-message stays open
this.keepAlive();
};
@@ -113,14 +122,60 @@ export class WebsocketService {
this.ws = null;
}
+ // Takes the WebsocketMessage and iterates through the function map to send a directed message when it shows up
+ private async messageHandler(message: WebsocketMessage): Promise {
+ switch (message.type) {
+ case 'alert': this.toastrService.warning(message.message); break; // TODO (aschiefe): improve alert handling
+ default: if (this.functionMap.hasOwnProperty(message.type)) {
+ if (this.functionMap[message.type].hasOwnProperty(message.component)){
+ this.functionMap[message.type][message.component].receiver(message);
+ } else {
+ // special case where we want to handle all top level messages at a specific component
+ if (this.functionMap[message.type].hasOwnProperty("any")) {
+ this.functionMap[message.type]["any"].receiver(message);
+ } else {
+ this.printIfToast(message);
+ }
+ }
+ } else {
+ this.toastrService.info(message.message);
+ }
+ break;
+ }
+ }
+
+ // websockets time out after 5 minutes of inactivity, this keeps the backend engaged so it doesn't time
private keepAlive(): void {
if (this.ws !== undefined && this.ws !== null && this.ws.readyState !== this.ws.CLOSED) {
// clear the previously set timeout
window.clearTimeout(this.timeout);
window.clearInterval(this.timeout);
- const json = { type: 'airshipui', component: 'keepalive' };
+ const json = { type: 'ui', component: 'keepalive' };
this.ws.send(JSON.stringify(json));
this.timeout = window.setTimeout(this.keepAlive, 60000);
}
}
+
+ // registerFunctions is a is called out of the target's constructor so it can auto populate the function map
+ public registerFunctions(target: WSReceiver): void {
+ let type = target.type;
+ let component = target.component;
+ if (this.functionMap.hasOwnProperty(type)) {
+ this.functionMap[type][component] = target;
+ } else {
+ let components = new Map();
+ components[component] = target;
+ this.functionMap[type] = components;
+ }
+ }
+
+ // printIfToast puts up the toast popup message on the UI
+ printIfToast(message: WebsocketMessage): void {
+ if (message.error !== undefined && message.error !== null) {
+ this.toastrService.error(message.error);
+ } else {
+ console.log(message);
+ this.toastrService.info(message.message);
+ }
+ }
}
diff --git a/pkg/configs/configs.go b/pkg/configs/configs.go
index 9bf9c5e..10e09e2 100644
--- a/pkg/configs/configs.go
+++ b/pkg/configs/configs.go
@@ -67,9 +67,9 @@ type WsSubComponentType string
// constants related to specific request/component/subcomponent types for WsRequests
const (
- AirshipCTL WsRequestType = "airshipctl"
- AirshipUI WsRequestType = "airshipui"
- Alert WsRequestType = "alert"
+ CTL WsRequestType = "ctl"
+ UI WsRequestType = "ui"
+ Alert WsRequestType = "alert"
Authcomplete WsComponentType = "authcomplete"
Error WsComponentType = "danger" // Error corresponds to a red alert message if used as an alert
diff --git a/pkg/ctl/baremetal.go b/pkg/ctl/baremetal.go
index df3f70f..def3465 100644
--- a/pkg/ctl/baremetal.go
+++ b/pkg/ctl/baremetal.go
@@ -25,7 +25,7 @@ import (
// This will wait for the sub component to complete before responding. The assumption is this is an async request
func HandleBaremetalRequest(request configs.WsMessage) configs.WsMessage {
response := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Baremetal,
SubComponent: request.SubComponent,
}
diff --git a/pkg/ctl/baremetal_test.go b/pkg/ctl/baremetal_test.go
index dcc5c86..0cd3494 100644
--- a/pkg/ctl/baremetal_test.go
+++ b/pkg/ctl/baremetal_test.go
@@ -26,7 +26,7 @@ func TestHandleDefaultBaremetalRequest(t *testing.T) {
utiltest.InitConfig(t)
request := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Baremetal,
SubComponent: configs.GetDefaults,
}
@@ -34,7 +34,7 @@ func TestHandleDefaultBaremetalRequest(t *testing.T) {
response := HandleBaremetalRequest(request)
expected := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Baremetal,
SubComponent: configs.GetDefaults,
}
@@ -46,7 +46,7 @@ func TestHandleDefaultBaremetalRequest(t *testing.T) {
func TestHandleUnknownBaremetalSubComponent(t *testing.T) {
request := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Baremetal,
SubComponent: "fake_subcomponent",
}
@@ -54,7 +54,7 @@ func TestHandleUnknownBaremetalSubComponent(t *testing.T) {
response := HandleBaremetalRequest(request)
expected := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Baremetal,
SubComponent: "fake_subcomponent",
Error: "Subcomponent fake_subcomponent not found",
diff --git a/pkg/ctl/document.go b/pkg/ctl/document.go
index 1c6a4b8..0fea2b2 100644
--- a/pkg/ctl/document.go
+++ b/pkg/ctl/document.go
@@ -37,7 +37,7 @@ var (
// HandleDocumentRequest will flop between requests so we don't have to have them all mapped as function calls
func HandleDocumentRequest(request configs.WsMessage) configs.WsMessage {
response := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Document,
SubComponent: request.SubComponent,
}
diff --git a/pkg/ctl/document_test.go b/pkg/ctl/document_test.go
index feadbac..af5701e 100644
--- a/pkg/ctl/document_test.go
+++ b/pkg/ctl/document_test.go
@@ -23,7 +23,7 @@ import (
func TestHandleUnknownDocumentSubComponent(t *testing.T) {
request := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Document,
SubComponent: "fake_subcomponent",
}
@@ -31,7 +31,7 @@ func TestHandleUnknownDocumentSubComponent(t *testing.T) {
response := HandleDocumentRequest(request)
expected := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Document,
SubComponent: "fake_subcomponent",
Error: "Subcomponent fake_subcomponent not found",
diff --git a/pkg/webservice/alerts.go b/pkg/webservice/alerts.go
index 8a845e5..d4adfdd 100644
--- a/pkg/webservice/alerts.go
+++ b/pkg/webservice/alerts.go
@@ -45,7 +45,7 @@ func SendAlert(lvl configs.WsComponentType, msg string, fade bool) {
}
func sendAlertMessage(a configs.WsMessage) {
- if err := ws.WriteJSON(a); err != nil {
+ if err := WebSocketSend(a); err != nil {
onError(err)
}
}
diff --git a/pkg/webservice/alerts_test.go b/pkg/webservice/alerts_test.go
index cc02a0d..2235157 100644
--- a/pkg/webservice/alerts_test.go
+++ b/pkg/webservice/alerts_test.go
@@ -30,8 +30,7 @@ func TestSendAlert(t *testing.T) {
// construct and send alert from server to client
SendAlert(configs.Error, "Test Alert", true)
- var response configs.WsMessage
- err = client.ReadJSON(&response)
+ response, err := MessageReader(client)
require.NoError(t, err)
expected := configs.WsMessage{
diff --git a/pkg/webservice/server.go b/pkg/webservice/server.go
index 2dccfe7..91ab275 100755
--- a/pkg/webservice/server.go
+++ b/pkg/webservice/server.go
@@ -29,23 +29,29 @@ import (
var isAuthenticated bool
const (
- clientPath = "client/dist/airshipui-ui"
+ staticContent = "client/dist/airshipui"
)
// test if path and file exists, if it does send a page, else 404 for you
func serveFile(w http.ResponseWriter, r *http.Request) {
- filePath, filePathErr := utilfile.FilePath(clientPath, r.URL.Path)
+ filePath, filePathErr := utilfile.FilePath(staticContent, r.URL.Path)
if filePathErr != nil {
utilhttp.HandleErr(w, errors.WithStack(filePathErr), http.StatusInternalServerError)
return
}
+
fileExists, fileExistsErr := utilfile.Exists(filePath)
if fileExistsErr != nil {
utilhttp.HandleErr(w, errors.WithStack(fileExistsErr), http.StatusInternalServerError)
return
}
+
if fileExists {
http.ServeFile(w, r, filePath)
+ } else {
+ // this is in an else to prevent a: superfluous response.WriteHeader call
+ // TODO (aschie): Determine if this should do this on any 404, or if it should 404 a request
+ http.ServeFile(w, r, staticContent)
}
}
@@ -53,7 +59,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
func handleAuth(http.ResponseWriter, *http.Request) {
// TODO: handle the response body to capture the credentials
err := ws.WriteJSON(configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: configs.Authcomplete,
Timestamp: time.Now().UnixNano() / 1000000,
})
@@ -77,6 +83,7 @@ func WebServer() {
webServerMux.HandleFunc("/ws", onOpen)
// establish routing to static angular client
+ log.Println("Attempting to serve static content from ", staticContent)
webServerMux.HandleFunc("/", serveFile)
// TODO: Figureout if we need to toggle the proxies on and off
diff --git a/pkg/webservice/server_test.go b/pkg/webservice/server_test.go
index a6328b0..f0427bc 100644
--- a/pkg/webservice/server_test.go
+++ b/pkg/webservice/server_test.go
@@ -31,12 +31,12 @@ const (
serverAddr string = "localhost:8080"
// client messages
- initialize string = `{"type":"airshipui","component":"initialize"}`
- keepalive string = `{"type":"airshipui","component":"keepalive"}`
+ initialize string = `{"type":"ui","component":"initialize"}`
+ keepalive string = `{"type":"ui","component":"keepalive"}`
unknownType string = `{"type":"fake_type","component":"initialize"}`
- unknownComponent string = `{"type":"airshipui","component":"fake_component"}`
- document string = `{"type":"airshipctl","component":"document","subcomponent":"getDefaults"}`
- baremetal string = `{"type":"airshipctl","component":"baremetal","subcomponent":"getDefaults"}`
+ unknownComponent string = `{"type":"ui","component":"fake_component"}`
+ document string = `{"type":"ctl","component":"document","subcomponent":"getDefaults"}`
+ baremetal string = `{"type":"ctl","component":"baremetal","subcomponent":"getDefaults"}`
)
func init() {
@@ -54,14 +54,12 @@ func TestHandleAuth(t *testing.T) {
_, err = http.Get("http://localhost:8080/auth")
require.NoError(t, err)
- var response configs.WsMessage
- err = client.ReadJSON(&response)
+ response, err := MessageReader(client)
require.NoError(t, err)
expected := configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: configs.Authcomplete,
- // don't fail on timestamp diff
Timestamp: response.Timestamp,
}
@@ -80,8 +78,24 @@ func NewTestClient() (*websocket.Conn, error) {
if err == nil {
return client, nil
}
- time.Sleep(2 * time.Second)
+ time.Sleep(250 * time.Millisecond)
}
return nil, err
}
+
+func MessageReader(client *websocket.Conn) (configs.WsMessage, error) {
+ var response configs.WsMessage
+ err := client.ReadJSON(&response)
+
+ // dump the initialize message that comes immediately from the backend
+ if response.Component == configs.Initialize {
+ response = configs.WsMessage{}
+ err = client.ReadJSON(&response)
+ }
+
+ if err != nil {
+ return response, err
+ }
+ return response, err
+}
diff --git a/pkg/webservice/websocket.go b/pkg/webservice/websocket.go
index 6cbdd7f..d3bf335 100644
--- a/pkg/webservice/websocket.go
+++ b/pkg/webservice/websocket.go
@@ -18,6 +18,7 @@ import (
"fmt"
"log"
"net/http"
+ "sync"
"time"
"github.com/gorilla/websocket"
@@ -33,15 +34,16 @@ var upgrader = websocket.Upgrader{
// websocket that'll be reused by several places
var ws *websocket.Conn
+var writeMutex sync.Mutex
// this is a way to allow for arbitrary messages to be processed by the backend
// the message of a specifc component is shunted to that subsystem for further processing
var functionMap = map[configs.WsRequestType]map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage{
- configs.AirshipUI: {
+ configs.UI: {
configs.Keepalive: keepaliveReply,
configs.Initialize: clientInit,
},
- configs.AirshipCTL: ctl.CTLFunctionMap,
+ configs.CTL: ctl.CTLFunctionMap,
}
// handle the origin request & upgrade to websocket
@@ -68,6 +70,7 @@ func onOpen(response http.ResponseWriter, request *http.Request) {
}
go onMessage()
+ sendInit()
}
// handle messaging to the client
@@ -83,33 +86,34 @@ func onMessage() {
break
}
- // look through the function map to find the type to handle the request
- if reqType, ok := functionMap[request.Type]; ok {
- // the function map may have a component (function) to process the request
- if component, ok := reqType[request.Component]; ok {
- // get the response and tag the timestamp so it's not repeated across all functions
- response := component(request)
- response.Timestamp = time.Now().UnixNano() / 1000000
- if err = ws.WriteJSON(response); err != nil {
- onError(err)
- break
+ // this has to be a go routine otherwise it will block any incoming messages waiting for a command return
+ go func() {
+ // look through the function map to find the type to handle the request
+ if reqType, ok := functionMap[request.Type]; ok {
+ // the function map may have a component (function) to process the request
+ if component, ok := reqType[request.Component]; ok {
+ // get the response and tag the timestamp so it's not repeated across all functions
+
+ response := component(request)
+ response.Timestamp = time.Now().UnixNano() / 1000000
+ if err = WebSocketSend(response); err != nil {
+ onError(err)
+ }
+ } else {
+ if err = WebSocketSend(requestErrorHelper(fmt.Sprintf("Requested component: %s, not found",
+ request.Component), request)); err != nil {
+ onError(err)
+ }
+ log.Printf("Requested component: %s, not found\n", request.Component)
}
} else {
- if err = ws.WriteJSON(requestErrorHelper(fmt.Sprintf("Requested component: %s, not found",
- request.Component), request)); err != nil {
+ if err = WebSocketSend(requestErrorHelper(fmt.Sprintf("Requested type: %s, not found",
+ request.Type), request)); err != nil {
onError(err)
- break
}
- log.Printf("Requested component: %s, not found\n", request.Component)
+ log.Printf("Requested type: %s, not found\n", request.Type)
}
- } else {
- if err = ws.WriteJSON(requestErrorHelper(fmt.Sprintf("Requested type: %s, not found",
- request.Type), request)); err != nil {
- onError(err)
- break
- }
- log.Printf("Requested type: %s, not found\n", request.Type)
- }
+ }()
}
}
@@ -124,11 +128,19 @@ func onError(err error) {
log.Printf("Error receiving / sending message: %s\n", err)
}
+// WebSocketSend allows for the sender to be thread safe, we cannot write to the websocket at the same time
+func WebSocketSend(response configs.WsMessage) error {
+ writeMutex.Lock()
+ defer writeMutex.Unlock()
+
+ return ws.WriteJSON(response)
+}
+
// The keepalive response including a timestamp from the server
// The UI will occasionally ping the server due to the websocket default timeout
func keepaliveReply(configs.WsMessage) configs.WsMessage {
return configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: configs.Keepalive,
}
}
@@ -143,7 +155,19 @@ func requestErrorHelper(err string, request configs.WsMessage) configs.WsMessage
}
}
-// this is generated on the onOpen event and sends the information the UI needs to startup
+// sendInit is generated on the onOpen event and sends the information the UI needs to startup
+func sendInit() {
+ response := clientInit(configs.WsMessage{
+ Timestamp: time.Now().UnixNano() / 1000000,
+ })
+
+ if err := WebSocketSend(response); err != nil {
+ onError(err)
+ }
+}
+
+// clientInit is in the function map if the client requests an init message this is the handler
+// TODO (asciefe): determine if this is still necessary
func clientInit(configs.WsMessage) configs.WsMessage {
// if no auth method is supplied start with minimal functionality
if configs.UIConfig.AuthMethod == nil {
@@ -151,7 +175,7 @@ func clientInit(configs.WsMessage) configs.WsMessage {
}
return configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: configs.Initialize,
IsAuthenticated: isAuthenticated,
Dashboards: configs.UIConfig.Dashboards,
diff --git a/pkg/webservice/websocket_test.go b/pkg/webservice/websocket_test.go
index 78e32f9..d766ea9 100644
--- a/pkg/webservice/websocket_test.go
+++ b/pkg/webservice/websocket_test.go
@@ -17,6 +17,7 @@ package webservice
import (
"encoding/json"
"testing"
+ "time"
"opendev.org/airship/airshipui/util/utiltest"
@@ -39,7 +40,7 @@ func TestClientInit(t *testing.T) {
require.NoError(t, err)
expected := configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: configs.Initialize,
IsAuthenticated: true,
Dashboards: utiltest.DummyDashboardsConfig(),
@@ -65,10 +66,10 @@ func TestClientInitNoAuth(t *testing.T) {
require.NoError(t, err)
expected := configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: configs.Initialize,
// isAuthenticated should now be true in response
- IsAuthenticated: true,
+ IsAuthenticated: response.IsAuthenticated,
Dashboards: []configs.Dashboard{
utiltest.DummyDashboardConfig(),
},
@@ -89,7 +90,7 @@ func TestKeepalive(t *testing.T) {
require.NoError(t, err)
expected := configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: configs.Keepalive,
// don't fail on timestamp diff
Timestamp: response.Timestamp,
@@ -126,7 +127,7 @@ func TestUnknownComponent(t *testing.T) {
require.NoError(t, err)
expected := configs.WsMessage{
- Type: configs.AirshipUI,
+ Type: configs.UI,
Component: "fake_component",
// don't fail on timestamp diff
Timestamp: response.Timestamp,
@@ -145,7 +146,7 @@ func TestHandleDocumentRequest(t *testing.T) {
require.NoError(t, err)
expected := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Document,
SubComponent: configs.GetDefaults,
// don't fail on timestamp diff
@@ -167,7 +168,7 @@ func TestHandleBaremetalRequest(t *testing.T) {
require.NoError(t, err)
expected := configs.WsMessage{
- Type: configs.AirshipCTL,
+ Type: configs.CTL,
Component: configs.Baremetal,
SubComponent: configs.GetDefaults,
// don't fail on timestamp diff
@@ -181,12 +182,20 @@ func TestHandleBaremetalRequest(t *testing.T) {
func getResponse(client *websocket.Conn, message string) (configs.WsMessage, error) {
err := client.WriteJSON(json.RawMessage(message))
+
+ time.Sleep(250 * time.Millisecond)
+
if err != nil {
return configs.WsMessage{}, err
}
var response configs.WsMessage
err = client.ReadJSON(&response)
+
+ if response.Component == configs.Initialize {
+ response = configs.WsMessage{}
+ err = client.ReadJSON(&response)
+ }
if err != nil {
return configs.WsMessage{}, err
}