Implemented WebSockets for GUI2
Change-Id: I4776ce392b1e8e94ebee938cf7df22791a1e0b8f
diff --git a/web/gui2/src/main/webapp/app/detectbrowser.directive.ts b/web/gui2/src/main/webapp/app/detectbrowser.directive.ts
index b69ca39..b8a3c77 100644
--- a/web/gui2/src/main/webapp/app/detectbrowser.directive.ts
+++ b/web/gui2/src/main/webapp/app/detectbrowser.directive.ts
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import { Inject } from '@angular/core';
import { Directive } from '@angular/core';
import { FnService } from './fw/util/fn.service';
import { LogService } from './log.service';
@@ -28,10 +29,9 @@
constructor(
private fs: FnService,
private log: LogService,
- private onos: OnosService
+ private onos: OnosService,
+ @Inject(Window) private w: Window
) {
- log.debug('DetectBrowserDirective constructed');
-
const body: HTMLBodyElement = document.getElementsByTagName('body')[0];
// let body = d3.select('body');
let browser = '';
@@ -44,7 +44,10 @@
} else if (fs.isFirefox()) {
browser = 'firefox';
} else {
- this.log.warn('Unknown browser:', window.navigator.vendor);
+ this.log.warn('Unknown browser. ',
+ 'Vendor:', this.w.navigator.vendor,
+ 'Agent:', this.w.navigator.userAgent);
+ return;
}
body.classList.add(browser);
// body.classed(browser, true);
diff --git a/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts b/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
index e01b004..6292a8d 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
@@ -24,21 +24,23 @@
import { LoadingService } from './loading.service';
import { PanelService } from './panel.service';
import { QuickHelpService } from './quickhelp.service';
-import { VeilService } from './veil.service';
+import { VeilComponent } from './veil/veil.component';
/**
* ONOS GUI -- Layers Module
*/
@NgModule({
exports: [
- FlashComponent
+ FlashComponent,
+ VeilComponent
],
imports: [
CommonModule,
UtilModule
],
declarations: [
- FlashComponent
+ FlashComponent,
+ VeilComponent
],
providers: [
DetailsPanelService,
@@ -46,8 +48,7 @@
EditableTextService,
LoadingService,
PanelService,
- QuickHelpService,
- VeilService
+ QuickHelpService
]
})
export class LayerModule { }
diff --git a/web/gui2/src/main/webapp/app/fw/layer/veil.service.ts b/web/gui2/src/main/webapp/app/fw/layer/veil.service.ts
deleted file mode 100644
index d755e5d..0000000
--- a/web/gui2/src/main/webapp/app/fw/layer/veil.service.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Foundation
- *
- * 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 { Injectable } from '@angular/core';
-import { FnService } from '../util/fn.service';
-import { GlyphService } from '../svg/glyph.service';
-import { KeyService } from '../util/key.service';
-import { LogService } from '../../log.service';
-import { WebSocketService } from '../remote/websocket.service';
-
-/**
- * ONOS GUI -- Layer -- Veil Service
- *
- * Provides a mechanism to display an overlaying div with information.
- * Used mainly for web socket connection interruption.
- */
-@Injectable()
-export class VeilService {
-
- constructor(
- private fs: FnService,
- private gs: GlyphService,
- private ks: KeyService,
- private log: LogService,
- private wss: WebSocketService
- ) {
- this.log.debug('VeilService constructed');
- }
-
-}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.css b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.css
new file mode 100644
index 0000000..851d6e3
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.css
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Veil Service (layout) -- CSS file
+ */
+
+#veil {
+ z-index: 5000;
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: 60px;
+}
+
+#veil p {
+ display: block;
+ text-align: left;
+ font-size: 14pt;
+ font-style: italic;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.html b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.html
new file mode 100644
index 0000000..79d202b
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.html
@@ -0,0 +1,9 @@
+<div id="veil" *ngIf="enabled">
+ <p *ngFor="let msg of messages">{{ msg }}</p>
+ <svg [attr.width]="fs.windowSize().width" [attr.height]="fs.windowSize().height">
+ <use [attr.width]="birdDim" [attr.height]="birdDim" class="glyph"
+ style="opacity: 0.2;"
+ xlink:href = "#bird" [attr.transform]="trans"/>
+
+ </svg>
+</div>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.spec.ts b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.spec.ts
new file mode 100644
index 0000000..0ed493f
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.spec.ts
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Layer -- Veil Service - Unit Tests
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VeilComponent } from './veil.component';
+import { ConsoleLoggerService } from '../../../consolelogger.service';
+import { LogService } from '../../../log.service';
+import { KeyService } from '../../util/key.service';
+import { GlyphService } from '../../svg/glyph.service';
+
+class MockKeyService {}
+
+class MockGlyphService {}
+
+describe('VeilComponent', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ declarations: [ VeilComponent ],
+ providers: [
+ { provide: LogService, useValue: log },
+ { provide: KeyService, useClass: MockKeyService },
+ { provide: GlyphService, useClass: MockGlyphService },
+ ]
+ });
+ });
+
+ it('should create', () => {
+ const fixture = TestBed.createComponent(VeilComponent);
+ const component = fixture.componentInstance;
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.theme.css b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.theme.css
new file mode 100644
index 0000000..7a3bded
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.theme.css
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Veil Service (theme) -- CSS file
+ */
+
+.light, #veil {
+ background-color: rgba(0,0,0,0.75);
+}
+.dark, #veil {
+ background-color: rgba(64,64,64,0.75);
+}
+
+#veil p {
+ color: #ddd;
+}
+
+#veil svg .glyph {
+ fill: #222;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.ts b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.ts
new file mode 100644
index 0000000..cea6bed
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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, OnInit } from '@angular/core';
+import { FnService } from '../../util/fn.service';
+import { GlyphService } from '../../svg/glyph.service';
+import { KeyService } from '../../util/key.service';
+import { LogService } from '../../../log.service';
+import { SvgUtilService } from '../../svg/svgutil.service';
+import { WebSocketService } from '../../remote/websocket.service';
+
+const BIRD = 'bird';
+
+/**
+ * ONOS GUI -- Layer -- Veil Component
+ *
+ * Provides a mechanism to display an overlaying div with information.
+ * Used mainly for web socket connection interruption.
+ *
+ * It can be added to an component's template as follows:
+ * <onos-veil #veil></onos-veil>
+ * <p (click)="veil.show(['t1','t2','t3'])">Test Veil</p>
+ */
+@Component({
+ selector: 'onos-veil',
+ templateUrl: './veil.component.html',
+ styleUrls: ['./veil.component.css', './veil.component.theme.css']
+})
+export class VeilComponent implements OnInit {
+ ww: number;
+ wh: number;
+ birdSvg: string;
+ birdDim: number;
+ enabled: boolean = false;
+ trans: string;
+ messages: string[] = [];
+ veilStyle: string;
+
+ constructor(
+ private fs: FnService,
+ private gs: GlyphService,
+ private ks: KeyService,
+ private log: LogService,
+ private sus: SvgUtilService,
+ private wss: WebSocketService
+ ) {
+ const wSize = this.fs.windowSize();
+ this.ww = wSize.width;
+ this.wh = wSize.height;
+ const shrink = this.wh * 0.3;
+ this.birdDim = this.wh - shrink;
+ const birdCenter = (this.ww - this.birdDim) / 2;
+ this.trans = this.sus.translate([birdCenter, shrink / 2]);
+
+ this.log.debug('VeilComponent with ' + BIRD + ' constructed');
+ }
+
+ ngOnInit() {
+ }
+
+ // msg should be an array of strings
+ show(msgs: string[]): void {
+ this.messages = msgs;
+ this.enabled = true;
+// this.ks.enableKeys(false);
+ }
+
+ hide(): void {
+ this.veilStyle = 'display: none';
+// this.ks.enableKeys(true);
+ }
+
+
+}
diff --git a/web/gui2/src/main/webapp/app/fw/remote/urlfn.service.ts b/web/gui2/src/main/webapp/app/fw/remote/urlfn.service.ts
index 4685a65..381a0c9 100644
--- a/web/gui2/src/main/webapp/app/fw/remote/urlfn.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/remote/urlfn.service.ts
@@ -13,27 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Injectable } from '@angular/core';
+import { Injectable, Inject } from '@angular/core';
import { LogService } from '../../log.service';
-const uiContext = '/onos/ui/';
-const rsSuffix = uiContext + 'rs/';
-const wsSuffix = uiContext + 'websock/';
+const UICONTEXT = '/onos/ui/';
+const RSSUFFIX = UICONTEXT + 'rs/';
+const WSSUFFIX = UICONTEXT + 'websock/';
/**
* ONOS GUI -- Remote -- General Purpose URL Functions
*/
-@Injectable()
+@Injectable({
+ providedIn: 'root',
+})
export class UrlFnService {
constructor(
private log: LogService,
- private window: Window
+ @Inject(Window) private w: Window
) {
this.log.debug('UrlFnService constructed');
}
matchSecure(protocol: string): string {
- const p: string = window.location.protocol;
+ const p: string = this.w.location.protocol;
const secure: boolean = (p === 'https' || p === 'wss');
return secure ? protocol + 's' : protocol;
}
@@ -42,32 +44,35 @@
* behind a proxy and has an app prefix, e.g.
* http://host:port/my/app/onos/ui...
* This bit of regex grabs everything after the host:port and
- * before the uiContext (/onos/ui/) and uses that as an app
+ * before the UICONTEXT (/onos/ui/) and uses that as an app
* prefix by inserting it back into the WS URL.
* If no prefix, then no insert.
*/
- urlBase(protocol: string, port: string, host: string): string {
- const match = window.location.href.match('.*//[^/]+/(.+)' + uiContext);
+ urlBase(protocol: string, port: string = '', host: string = ''): string {
+ const match = this.w.location.href.match('.*//[^/]+/(.+)' + UICONTEXT);
const appPrefix = match ? '/' + match[1] : '';
- return this.matchSecure(protocol) + '://' +
- (host || window.location.hostname) + ':' +
- (port || window.location.port) + appPrefix;
+ return this.matchSecure(protocol) +
+ '://' +
+ (host === '' ? this.w.location.hostname : host) +
+ ':' +
+ (port === '' ? this.w.location.port : port) +
+ appPrefix;
}
httpPrefix(suffix: string): string {
- return this.urlBase('http', '', '') + suffix;
+ return this.urlBase('http') + suffix;
}
- wsPrefix(suffix: string, wsport: any, host: string): string {
+ wsPrefix(suffix: string, wsport: string, host: string): string {
return this.urlBase('ws', wsport, host) + suffix;
}
rsUrl(path: string): string {
- return this.httpPrefix(rsSuffix) + path;
+ return this.httpPrefix(RSSUFFIX) + path;
}
- wsUrl(path: string, wsport: any, host: string): string {
- return this.wsPrefix(wsSuffix, wsport, host) + path;
+ wsUrl(path: string, wsport?: string, host?: string): string {
+ return this.wsPrefix(WSSUFFIX, wsport, host) + path;
}
}
diff --git a/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
index 0691c72..e470ec1 100644
--- a/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
@@ -18,37 +18,111 @@
import { GlyphService } from '../svg/glyph.service';
import { LogService } from '../../log.service';
import { UrlFnService } from './urlfn.service';
+import { VeilComponent } from '../layer/veil/veil.component';
import { WSock } from './wsock.service';
/**
* Event Type structure for the WebSocketService
*/
-interface EventType {
+export interface EventType {
event: string;
- payload: any;
+ payload: Object;
+}
+
+export interface Callback {
+ id: number;
+ error: string;
+ cb(host: string, url: string): void;
+}
+
+interface ClusterNode {
+ id: string;
+ ip: string;
+ m_uiAttached: boolean;
+}
+
+interface Bootstrap {
+ user: string;
+ clusterNodes: ClusterNode[];
+ glyphs: any[]; // TODO: narrow this down to a known type
+}
+
+interface ErrorData {
+ message: string;
+}
+
+export interface WsOptions {
+ wsport: number;
}
/**
* ONOS GUI -- Remote -- Web Socket Service
+ *
+ * To see debug messages add ?debug=txrx to the URL
*/
-@Injectable()
+@Injectable({
+ providedIn: 'root',
+})
export class WebSocketService {
// internal state
- private webSockOpts; // web socket options
- private ws = null; // web socket reference
- private wsUp = false; // web socket is good to go
- private handlers = {}; // event handler bindings
+ private webSockOpts: WsOptions; // web socket options
+ private ws: WebSocket = null; // web socket reference
+ private wsUp: boolean = false; // web socket is good to go
+
+ // A map of event handler bindings - names and functions (that accept data and return void)
+ private handlers = new Map<string, (data: any) => void>([]);
private pendingEvents: EventType[] = []; // events TX'd while socket not up
private host: string; // web socket host
private url; // web socket URL
- private clusterNodes = []; // ONOS instances data for failover
+ private clusterNodes: ClusterNode[] = []; // ONOS instances data for failover
private clusterIndex = -1; // the instance to which we are connected
private glyphs = [];
- private connectRetries = 0; // limit our attempts at reconnecting
- private openListeners = {}; // registered listeners for websocket open()
- private nextListenerId = 1; // internal ID for open listeners
- private loggedInUser = null; // name of logged-in user
+ private connectRetries: number = 0; // limit our attempts at reconnecting
+ // A map of registered Callbacks for websocket open()
+ private openListeners = new Map<number, Callback>([]);
+ private nextListenerId: number = 1; // internal ID for open listeners
+ private loggedInUser = null; // name of logged-in user
+ private lcd: any; // The loading component delegate
+ private vcd: any; // The veil component delegate
+
+ /**
+ * built-in handler for the 'boostrap' event
+ */
+ private bootstrap(data: Bootstrap) {
+ this.log.debug('bootstrap data', data);
+ this.loggedInUser = data.user;
+
+ this.clusterNodes = data.clusterNodes;
+ this.clusterNodes.forEach((d, i) => {
+ if (d.m_uiAttached) {
+ this.clusterIndex = i;
+ this.log.info('Connected to cluster node ' + d.ip);
+ // TODO: add connect info to masthead somewhere
+ }
+ });
+ this.glyphs = data.glyphs;
+ const glyphsMap = new Map<string, string>([]);
+ this.glyphs.forEach((d, i) => {
+ glyphsMap.set('_' + d.id, d.viewbox);
+ glyphsMap.set(d.id, d.path);
+ this.gs.registerGlyphs(glyphsMap);
+ });
+ }
+
+ private error(data: ErrorData) {
+ const m: string = data.message || 'error from server';
+ this.log.error(m, data);
+
+ // Unrecoverable error - throw up the veil...
+ if (this.vcd) {
+ this.vcd.show([
+ 'Oops!',
+ 'Server reports error...',
+ m,
+ ]);
+ }
+ }
constructor(
private fs: FnService,
@@ -59,90 +133,319 @@
private window: Window
) {
this.log.debug(window.location.hostname);
+
+ // Bind the boot strap event by default
+ this.bindHandlers(new Map<string, (data) => void>([
+ ['bootstrap', (data) => this.bootstrap(data)],
+ ['error', (data) => this.error(data)]
+ ]));
+
this.log.debug('WebSocketService constructed');
}
+
+ // ==========================
+ // === Web socket callbacks
+
+ /**
+ * Called when WebSocket has just opened
+ *
+ * Lift the Veil if it is displayed
+ * If there are any events pending, send them
+ * Mark the WSS as up and inform any listeners for this open event
+ */
+ handleOpen(): void {
+ this.log.info('Web socket open - ', this.url);
+ // Hide the veil
+ if (this.vcd) {
+ this.vcd.hide();
+ }
+
+ if (this.fs.debugOn('txrx')) {
+ this.log.debug('Sending ' + this.pendingEvents.length + ' pending event(s)...');
+ }
+ this.pendingEvents.forEach((ev) => {
+ this.send(ev);
+ });
+ this.pendingEvents = [];
+
+ this.connectRetries = 0;
+ this.wsUp = true;
+ this.informListeners(this.host, this.url);
+ }
+
+ /**
+ * Function called when WebSocket send a message
+ */
+ handleMessage(msgEvent: MessageEvent): void {
+ let ev: EventType;
+ let h;
+ try {
+ ev = JSON.parse(msgEvent.data);
+ } catch (e) {
+ this.log.error('Message.data is not valid JSON', msgEvent.data, e);
+ return null;
+ }
+ if (this.fs.debugOn('txrx')) {
+ this.log.debug(' << *Rx* ', ev.event, ev.payload);
+ }
+ h = this.handlers.get(ev.event);
+ if (h) {
+ try {
+ h(ev.payload);
+ } catch (e) {
+ this.log.error('Problem handling event:', ev, e);
+ return null;
+ }
+ } else {
+ this.log.warn('Unhandled event:', ev);
+ }
+ }
+
+ /**
+ * Called by the WebSocket if it is closed from the server end
+ *
+ * If the loading component is shown, call stop() on it
+ * Try to find another node in the cluster to connect to
+ * If this is not possible then show the Veil Component
+ */
+ handleClose(): void {
+ this.log.warn('Web socket closed');
+ if (this.lcd) {
+ this.lcd.stop();
+ }
+ this.wsUp = false;
+ let gsucc;
+
+ if (gsucc = this.findGuiSuccessor()) {
+ this.url = this.createWebSocket(this.webSockOpts, gsucc);
+ } else {
+ // If no controllers left to contact, show the Veil...
+ if (this.vcd) {
+ this.vcd.show([
+ 'Oops!', // TODO: Localize this
+ 'Web-socket connection to server closed...',
+ 'Try refreshing the page.',
+ ]);
+ }
+ }
+ }
+
+ // ==============================
+ // === Private Helper Functions
+
+ /**
+ * Find the next node in the ONOS cluster.
+ *
+ * This is used if the WebSocket connection closes because a
+ * node in the cluster ges down - fail over should be automatic
+ */
+ findGuiSuccessor(): string {
+ const ncn = this.clusterNodes.length;
+ let ip: string;
+ let node;
+
+ while (this.connectRetries < ncn && !ip) {
+ this.connectRetries++;
+ this.clusterIndex = (this.clusterIndex + 1) % ncn;
+ node = this.clusterNodes[this.clusterIndex];
+ ip = node && node.ip;
+ }
+
+ return ip;
+ }
+
+ /**
+ * When the WebSocket is opened, inform any listeners that registered
+ * for that event
+ */
+ informListeners(host: string, url: string): void {
+ for (const [key, cb] of this.openListeners.entries()) {
+ cb.cb(host, url);
+ }
+ }
+
+ send(ev: EventType): void {
+ if (this.fs.debugOn('txrx')) {
+ this.log.debug(' *Tx* >> ', ev.event, ev.payload);
+ }
+ this.ws.send(JSON.stringify(ev));
+ }
+
+ /**
+ * Check if there are no WSS event handlers left
+ */
+ noHandlersWarn(handlers: Map<string, Object>, caller: string): boolean {
+ if (!handlers || handlers.size === 0) {
+ this.log.warn('WSS.' + caller + '(): no event handlers');
+ return true;
+ }
+ return false;
+ }
+
/* ===================
* === API Functions
- *
+ */
+
+ /**
* Required for unit tests to set to known state
*/
- resetState() {
+ resetState(): void {
this.webSockOpts = undefined;
this.ws = null;
this.wsUp = false;
this.host = undefined;
this.url = undefined;
this.pendingEvents = [];
- this.handlers = {};
+ this.handlers.clear();
this.clusterNodes = [];
this.clusterIndex = -1;
this.glyphs = [];
this.connectRetries = 0;
- this.openListeners = {};
+ this.openListeners.clear();
this.nextListenerId = 1;
}
- /* Currently supported opts:
+ /*
+ * Currently supported opts:
* wsport: web socket port (other than default 8181)
* host: if defined, is the host address to use
*/
- createWebSocket(opts, _host_: string = '') {
- const wsport = (opts && opts.wsport) || null;
-
+ createWebSocket(opts?: WsOptions, host?: string) {
this.webSockOpts = opts; // preserved for future calls
+ this.host = host === undefined ? this.window.location.host : host;
+ this.url = this.ufs.wsUrl('core', opts === undefined ? '' : opts['wsport'].toString(), host);
-// this.host = _host_ || this.host();
- const url = this.ufs.wsUrl('core', wsport, _host_);
-
- this.log.debug('Attempting to open websocket to: ' + url);
- this.ws = this.wsock.newWebSocket(url);
+ this.log.debug('Attempting to open websocket to: ' + this.url);
+ this.ws = this.wsock.newWebSocket(this.url);
if (this.ws) {
- this.ws.onopen = this.handleOpen();
- this.ws.onmessage = this.handleMessage('???');
- this.ws.onclose = this.handleClose();
+ // fat arrow => syntax means that the 'this' context passed will
+ // be of WebSocketService, not the WebSocket
+ this.ws.onopen = (() => this.handleOpen());
+ this.ws.onmessage = ((msgEvent) => this.handleMessage(msgEvent));
+ this.ws.onclose = (() => this.handleClose());
-// sendEvent('authentication', { token: onosAuth });
- this.sendEvent('authentication token', '');
+ this.sendEvent('authentication token', { token: 'testAuth' });
}
// Note: Wsock logs an error if the new WebSocket call fails
- return url;
+ return this.url;
}
- handleOpen() {
- this.log.debug('WebSocketService: handleOpen() not yet implemented');
+
+ /**
+ * Binds the message handlers to their message type (event type) as
+ * specified in the given map. Note that keys are the event IDs; values
+ * are either:
+ * * the event handler function, or
+ * * an API object which has an event handler for the key
+ */
+ bindHandlers(handlerMap: Map<string, (data) => void>): void {
+ const dups: string[] = [];
+
+ if (this.noHandlersWarn(handlerMap, 'bindHandlers')) {
+ return null;
+ }
+ for (const [eventId, api] of handlerMap) {
+ this.log.debug('Adding handler for ', eventId);
+ const fn = this.fs.isF(api) || this.fs.isF(api[eventId]);
+ if (!fn) {
+ this.log.warn(eventId + ' handler not a function');
+ return;
+ }
+
+ if (this.handlers.get(eventId)) {
+ dups.push(eventId);
+ } else {
+ this.handlers.set(eventId, fn);
+ }
+ }
+ if (dups.length) {
+ this.log.warn('duplicate bindings ignored:', dups);
+ }
}
- handleMessage(msgEvent: any) {
- this.log.debug('WebSocketService: handleMessage() not yet implemented');
+ /**
+ * Unbinds the specified message handlers.
+ * Expected that the same map will be used, but we only care about keys
+ */
+ unbindHandlers(handlerMap: Map<string, (data) => void>): void {
+ if (this.noHandlersWarn(handlerMap, 'unbindHandlers')) {
+ return null;
+ }
+
+ for (const [eventId, api] of handlerMap) {
+ this.handlers.delete(eventId);
+ }
}
- handleClose() {
- this.log.debug('WebSocketService: handleClose() not yet implemented');
+ /**
+ * Add a listener function for listening for WebSocket opening.
+ * The function must give a host and url and return void
+ */
+ addOpenListener(callback: (host: string, url: string) => void ): Callback {
+ const id: number = this.nextListenerId++;
+ const cb = this.fs.isF(callback);
+ const o: Callback = <Callback>{ id: id, cb: cb };
+
+ if (cb) {
+ this.openListeners.set(id, o);
+ } else {
+ this.log.error('WSS.addOpenListener(): callback not a function');
+ o.error = 'No callback defined';
+ }
+ return o;
}
- /* Formulates an event message and sends it via the web-socket.
+ /**
+ * Remove a listener of WebSocket opening
+ */
+ removeOpenListener(lsnr: Callback): void {
+ const id = this.fs.isO(lsnr) && lsnr.id;
+ let o;
+
+ if (!id) {
+ this.log.warn('WSS.removeOpenListener(): invalid listener', lsnr);
+ return null;
+ }
+ o = this.openListeners[id];
+
+ if (o) {
+ this.openListeners.delete(id);
+ }
+ }
+
+ /**
+ * Formulates an event message and sends it via the web-socket.
* If the websocket is not up yet, we store it in a pending list.
*/
- sendEvent(evType, payload) {
+ sendEvent(evType: string, payload: Object ): void {
const ev = <EventType> {
event: evType,
payload: payload
};
if (this.wsUp) {
- this._send(ev);
+ this.send(ev);
} else {
this.pendingEvents.push(ev);
}
}
- _send(ev: EventType) {
- if (this.fs.debugOn('txrx')) {
- this.log.debug(' *Tx* >> ', ev.event, ev.payload);
- }
- this.ws.send(JSON.stringify(ev));
+ /**
+ * Binds the veil service as a delegate.
+ */
+ setVeilDelegate(vd: VeilComponent): void {
+ this.vcd = vd;
+ }
+
+ /**
+ * Binds the loading service as a delegate
+ */
+ setLoadingDelegate(ld: any): void {
+ // TODO - Investigate changing Loading Service to LoadingComponent
+ this.log.debug('Loading delegate set', ld);
+ this.lcd = ld;
}
}
diff --git a/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts b/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts
index 176b5d3..86e0625 100644
--- a/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts
@@ -21,7 +21,9 @@
*
* This service provided specifically so that it can be mocked in unit tests.
*/
-@Injectable()
+@Injectable({
+ providedIn: 'root',
+})
export class WSock {
constructor(
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts b/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts
index 674cbbf..c6a81e9 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts
@@ -222,7 +222,7 @@
'xlink:href': '#' + glyphId,
width: dim,
height: dim,
- transform: this.sus.translate(xlate, xlate),
+ transform: this.sus.translate([xlate], xlate),
});
return g;
}
diff --git a/web/gui2/src/main/webapp/app/fw/svg/svgutil.service.ts b/web/gui2/src/main/webapp/app/fw/svg/svgutil.service.ts
index 51d5d2a..9cba079 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/svgutil.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/svg/svgutil.service.ts
@@ -22,7 +22,9 @@
*
* The SVG Util Service provides a miscellany of utility functions.
*/
-@Injectable()
+@Injectable({
+ providedIn: 'root',
+})
export class SvgUtilService {
constructor(
@@ -32,7 +34,7 @@
this.log.debug('SvgUtilService constructed');
}
- translate(x, y) {
+ translate(x: number[], y?: any): string {
if (this.fs.isA(x) && x.length === 2 && !y) {
return 'translate(' + x[0] + ',' + x[1] + ')';
}
diff --git a/web/gui2/src/main/webapp/app/fw/util/fn.service.ts b/web/gui2/src/main/webapp/app/fw/util/fn.service.ts
index 0726afc..d355ce9 100644
--- a/web/gui2/src/main/webapp/app/fw/util/fn.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/util/fn.service.ts
@@ -13,13 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Injectable } from '@angular/core';
+import { Injectable, Inject } from '@angular/core';
import { ActivatedRoute, Router} from '@angular/router';
import { LogService } from '../../log.service';
// Angular>=2 workaround for missing definition
declare const InstallTrigger: any;
+const matcher = /<\/?([a-zA-Z0-9]+)*(.*?)\/?>/igm;
+const whitelist: string[] = ['b', 'i', 'p', 'em', 'strong', 'br'];
+const evillist: string[] = ['script', 'style', 'iframe'];
+
+/**
+ * Used with the Window size function;
+ **/
+export interface WindowSize {
+ width: number;
+ height: number;
+}
+
+/**
+ * For the sanitize() and analyze() functions
+ */
+export interface Match {
+ full: string;
+ name: string;
+}
// TODO Move all this trie stuff to its own class
// Angular>=2 Tightened up on types to avoid compiler errors
@@ -133,13 +152,14 @@
@Injectable()
export class FnService {
// internal state
- debugFlags = new Map<string, boolean>([
+ private debugFlags = new Map<string, boolean>([
// [ "LoadingService", true ]
]);
constructor(
private route: ActivatedRoute,
- private log: LogService
+ private log: LogService,
+ @Inject(Window) private w: Window
) {
this.route.queryParams.subscribe(params => {
const debugparam: string = params['debug'];
@@ -149,33 +169,155 @@
log.debug('FnService constructed');
}
- isF(f) {
+ /**
+ * Test if an argument is a function
+ *
+ * Note: the need for this would go away if all functions
+ * were strongly typed
+ */
+ isF(f: any): any {
return typeof f === 'function' ? f : null;
}
- isA(a) {
+ /**
+ * Test if an argument is an array
+ *
+ * Note: the need for this would go away if all arrays
+ * were strongly typed
+ */
+ isA(a: any): any {
// NOTE: Array.isArray() is part of EMCAScript 5.1
return Array.isArray(a) ? a : null;
}
- isS(s) {
+ /**
+ * Test if an argument is a string
+ *
+ * Note: the need for this would go away if all strings
+ * were strongly typed
+ */
+ isS(s: any): string {
return typeof s === 'string' ? s : null;
}
- isO(o) {
+ /**
+ * Test if an argument is an object
+ *
+ * Note: the need for this would go away if all objects
+ * were strongly typed
+ */
+ isO(o: any): Object {
return (o && typeof o === 'object' && o.constructor === Object) ? o : null;
}
-// contains: contains,
-// areFunctions: areFunctions,
-// areFunctionsNonStrict: areFunctionsNonStrict,
-// windowSize: windowSize,
+ /**
+ * Test that an array contains an object
+ */
+ contains(a: any[], x: any): boolean {
+ return this.isA(a) && a.indexOf(x) > -1;
+ }
+
+ /**
+ * Returns width and height of window inner dimensions.
+ * offH, offW : offset width/height are subtracted, if present
+ */
+ windowSize(offH: number = 0, offW: number = 0): WindowSize {
+ return {
+ height: this.w.innerHeight - offH,
+ width: this.w.innerWidth - offW
+ };
+ }
+
+ /**
+ * Returns true if all names in the array are defined as functions
+ * on the given api object; false otherwise.
+ * Also returns false if there are properties on the api that are NOT
+ * listed in the array of names.
+ *
+ * This gets extra complicated when the api Object is an
+ * Angular service - while the functions can be retrieved
+ * by an indexed get, the ownProperties does not show the
+ * functions of the class. We have to dive in to the prototypes
+ * properties to get these - and even then we have to filter
+ * out the constructor and any member variables
+ */
+ areFunctions(api: Object, fnNames: string[]): boolean {
+ const fnLookup: Map<string, boolean> = new Map();
+ let extraFound: boolean = false;
+
+ if (!this.isA(fnNames)) {
+ return false;
+ }
+
+ const n: number = fnNames.length;
+ let i: number;
+ let name: string;
+
+ for (i = 0; i < n; i++) {
+ name = fnNames[i];
+ if (!this.isF(api[name])) {
+ return false;
+ }
+ fnLookup.set(name, true);
+ }
+
+ // check for properties on the API that are not listed in the array,
+ const keys = Object.getOwnPropertyNames(api);
+ if (keys.length === 0) {
+ return true;
+ }
+ // If the api is a class it will have a name,
+ // else it will just be called 'Object'
+ const apiObjectName: string = api.constructor.name;
+ if (apiObjectName === 'Object') {
+ Object.keys(api).forEach((key) => {
+ if (!fnLookup.get(key)) {
+ extraFound = true;
+ }
+ });
+ } else { // It is a class, so its functions will be in the child (prototype)
+ const pObj: Object = Object.getPrototypeOf(api);
+ for ( const key in Object.getOwnPropertyDescriptors(pObj) ) {
+ if (key === 'constructor') { // Filter out constructor
+ continue;
+ }
+ const value = Object.getOwnPropertyDescriptor(pObj, key);
+ // Only compare functions. Look for any not given in the map
+ if (this.isF(value.value) && !fnLookup.get(key)) {
+ extraFound = true;
+ }
+ }
+ }
+ return !extraFound;
+ }
+
+ /**
+ * Returns true if all names in the array are defined as functions
+ * on the given api object; false otherwise. This is a non-strict version
+ * that does not care about other properties on the api.
+ */
+ areFunctionsNonStrict(api, fnNames): boolean {
+ if (!this.isA(fnNames)) {
+ return false;
+ }
+ const n = fnNames.length;
+ let i;
+ let name;
+
+ for (i = 0; i < n; i++) {
+ name = fnNames[i];
+ if (!this.isF(api[name])) {
+ return false;
+ }
+ }
+ return true;
+ }
/**
* Returns true if current browser determined to be a mobile device
*/
isMobile() {
- const ua = window.navigator.userAgent;
+ const ua = this.w.navigator.userAgent;
const patt = /iPhone|iPod|iPad|Silk|Android|BlackBerry|Opera Mini|IEMobile/;
return patt.test(ua);
}
@@ -184,10 +326,10 @@
* Returns true if the current browser determined to be Chrome
*/
isChrome() {
- const isChromium = (window as any).chrome;
- const vendorName = window.navigator.vendor;
+ const isChromium = (this.w as any).chrome;
+ const vendorName = this.w.navigator.vendor;
- const isOpera = window.navigator.userAgent.indexOf('OPR') > -1;
+ const isOpera = this.w.navigator.userAgent.indexOf('OPR') > -1;
return (isChromium !== null &&
isChromium !== undefined &&
vendorName === 'Google Inc.' &&
@@ -195,8 +337,8 @@
}
isChromeHeadless() {
- const vendorName = window.navigator.vendor;
- const headlessChrome = window.navigator.userAgent.indexOf('HeadlessChrome') > -1;
+ const vendorName = this.w.navigator.vendor;
+ const headlessChrome = this.w.navigator.userAgent.indexOf('HeadlessChrome') > -1;
return (vendorName === 'Google Inc.' && headlessChrome === true);
}
@@ -205,8 +347,8 @@
* Returns true if the current browser determined to be Safari
*/
isSafari() {
- return (window.navigator.userAgent.indexOf('Safari') !== -1 &&
- window.navigator.userAgent.indexOf('Chrome') === -1);
+ return (this.w.navigator.userAgent.indexOf('Safari') !== -1 &&
+ this.w.navigator.userAgent.indexOf('Chrome') === -1);
}
/**
@@ -217,13 +359,90 @@
}
/**
+ * search through an array of objects, looking for the one with the
+ * tagged property matching the given key. tag defaults to 'id'.
+ * returns the index of the matching object, or -1 for no match.
+ */
+ find(key: string, array: Object[], tag: string = 'id'): number {
+ let idx: number;
+ const n: number = array.length;
+
+ for (idx = 0 ; idx < n; idx++) {
+ const d: Object = array[idx];
+ if (d[tag] === key) {
+ return idx;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * search through array to find (the first occurrence of) item,
+ * returning its index if found; otherwise returning -1.
+ */
+ inArray(item: any, array: any[]): number {
+ if (this.isA(array)) {
+ for (let i = 0; i < array.length; i++) {
+ if (array[i] === item) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * remove (the first occurrence of) the specified item from the given
+ * array, if any. Return true if the removal was made; false otherwise.
+ */
+ removeFromArray(item: any, array: any[]): boolean {
+ const i: number = this.inArray(item, array);
+ if (i >= 0) {
+ array.splice(i, 1);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * return true if the object is empty, return false otherwise
+ */
+ isEmptyObject(obj: Object): boolean {
+ for (const key in obj) {
+ if (true) { return false; }
+ }
+ return true;
+ }
+
+ /**
* Return the given string with the first character capitalized.
*/
- cap(s) {
+ cap(s: string): string {
return s ? s[0].toUpperCase() + s.slice(1).toLowerCase() : s;
}
/**
+ * return the parameter without a px suffix
+ */
+ noPx(num: string): number {
+ return Number(num.replace(/px$/, ''));
+ }
+
+ /**
+ * return an element's given style property without px suffix
+ */
+ noPxStyle(elem: any, prop: string): number {
+ return Number(elem.style(prop).replace(/px$/, ''));
+ }
+
+ /**
+ * Return true if a str ends with suffix
+ */
+ endsWith(str: string, suffix: string) {
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ }
+
+ /**
* output debug message to console, if debug tag set...
* e.g. fs.debug('mytag', arg1, arg2, ...)
*/
@@ -233,7 +452,7 @@
}
}
- parseDebugFlags(dbgstr: string): void {
+ private parseDebugFlags(dbgstr: string): void {
const bits = dbgstr ? dbgstr.split(',') : [];
bits.forEach((key) => {
this.debugFlags.set(key, true);
@@ -248,4 +467,66 @@
return this.debugFlags.get(tag);
}
+
+
+ // -----------------------------------------------------------------
+ // The next section deals with sanitizing external strings destined
+ // to be loaded via a .html() function call.
+ //
+ // See definition of matcher, evillist and whitelist at the top of this file
+
+ /*
+ * Returns true if the tag is in the evil list, (and is not an end-tag)
+ */
+ inEvilList(tag: any): boolean {
+ return (evillist.indexOf(tag.name) !== -1 && tag.full.indexOf('/') === -1);
+ }
+
+ /*
+ * Returns an array of Matches of matcher in html
+ */
+ analyze(html: string): Match[] {
+ const matches: Match[] = [];
+ let match;
+
+ // extract all tags
+ while ((match = matcher.exec(html)) !== null) {
+ matches.push({
+ full: match[0],
+ name: match[1],
+ // NOTE: ignoring attributes {match[2].split(' ')} for now
+ });
+ }
+
+ return matches;
+ }
+
+ /*
+ * Returns a cleaned version of html
+ */
+ sanitize(html: string): string {
+ const matches: Match[] = this.analyze(html);
+
+ // completely obliterate evil tags and their contents...
+ evillist.forEach((tag) => {
+ const re = new RegExp('<' + tag + '(.*?)>(.*?[\r\n])*?(.*?)(.*?[\r\n])*?<\/' + tag + '>', 'gim');
+ html = html.replace(re, '');
+ });
+
+ // filter out all but white-listed tags and end-tags
+ matches.forEach((tag) => {
+ if (whitelist.indexOf(tag.name) === -1) {
+ html = html.replace(tag.full, '');
+ if (this.inEvilList(tag)) {
+ this.log.warn('Unsanitary HTML input -- ' +
+ tag.full + ' detected!');
+ }
+ }
+ });
+
+ // TODO: consider encoding HTML entities, e.g. '&' -> '&'
+
+ return html;
+ }
+
}
diff --git a/web/gui2/src/main/webapp/app/fw/util/lion.service.ts b/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
index a33ef6b..5ab9f65 100644
--- a/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
@@ -29,19 +29,43 @@
/**
* ONOS GUI -- Lion -- Localization Utilities
*/
-@Injectable()
+@Injectable({
+ providedIn: 'root',
+})
export class LionService {
ubercache: any[];
+ /**
+ * Handler for uberlion event from WSS
+ */
+ uberlion(data: Lion) {
+ this.ubercache = data.lion;
+
+ this.log.info('LION service: Locale... [' + data.locale + ']');
+ this.log.info('LION service: Bundles installed...');
+
+ for (const p in this.ubercache) {
+ if (this.ubercache[p]) {
+ this.log.info(' :=> ', p);
+ }
+ }
+
+ this.log.debug('LION service: uber-lion bundle received:', data);
+ }
+
constructor(
private log: LogService,
private wss: WebSocketService
) {
+ this.wss.bindHandlers(new Map<string, (data) => void>([
+ ['uberlion', (data) => this.uberlion(data) ]
+ ]));
this.log.debug('LionService constructed');
}
- /* returns a lion bundle (function) for the given bundle ID
+ /**
+ * Returns a lion bundle (function) for the given bundle ID (string)
* returns a function that takes a string and returns a string
*/
bundle(bundleId: string): (string) => string {
@@ -58,21 +82,4 @@
getKey(key: string): string {
return this.bundle[key] || '%' + key + '%';
}
-
- /* handler for uberlion event..
- */
- uberlion(data: Lion) {
- this.ubercache = data.lion;
-
- this.log.info('LION service: Locale... [' + data.locale + ']');
- this.log.info('LION service: Bundles installed...');
-
- for (const p in this.ubercache) {
- if (this.ubercache[p]) {
- this.log.info(' :=> ', p);
- }
- }
-
- this.log.debug('LION service: uber-lion bundle received:', data);
- }
}
diff --git a/web/gui2/src/main/webapp/app/onos.common.css b/web/gui2/src/main/webapp/app/onos.common.css
new file mode 100644
index 0000000..9a3f5a6
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/onos.common.css
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- common -- CSS file
+ */
+
+.clickable {
+ cursor: pointer;
+}
+
+.light .editable {
+ border-bottom: 1px dashed #ca504b;
+}
+
+.dark .editable {
+ border-bottom: 1px dashed #df4f4a;
+}
+
+.light svg.embeddedIcon .icon .glyph {
+ fill: #0071bd;
+}
+
+.dark svg.embeddedIcon .icon .glyph {
+ fill: #375b7f;
+}
diff --git a/web/gui2/src/main/webapp/app/onos.component.css b/web/gui2/src/main/webapp/app/onos.component.css
index e69de29..60933d8 100644
--- a/web/gui2/src/main/webapp/app/onos.component.css
+++ b/web/gui2/src/main/webapp/app/onos.component.css
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- core (layout) -- CSS file
+ */
+
+#view {
+ padding: 6px;
+ width: 100%;
+ height: 100%;
+}
+
+#view h2 {
+ -webkit-margin-before: 0;
+ -webkit-margin-after: 0;
+ margin: 32px 0 4px 16px;
+ padding: 0;
+ font-size: 18pt;
+ font-weight: lighter;
+}
diff --git a/web/gui2/src/main/webapp/app/onos.component.html b/web/gui2/src/main/webapp/app/onos.component.html
index 566c972..42d2bdd 100644
--- a/web/gui2/src/main/webapp/app/onos.component.html
+++ b/web/gui2/src/main/webapp/app/onos.component.html
@@ -1,6 +1,8 @@
<div style="text-align:center" onosDetectBrowser>
<onos-mast></onos-mast>
<onos-nav></onos-nav>
+ <onos-veil #veil></onos-veil>
+ <div>{{ wss.setVeilDelegate(veil) }}</div>
<router-outlet></router-outlet>
</div>
diff --git a/web/gui2/src/main/webapp/app/onos.component.ts b/web/gui2/src/main/webapp/app/onos.component.ts
index 02b3a44..bbec955 100644
--- a/web/gui2/src/main/webapp/app/onos.component.ts
+++ b/web/gui2/src/main/webapp/app/onos.component.ts
@@ -19,11 +19,10 @@
import { KeyService } from './fw/util/key.service';
import { ThemeService } from './fw/util/theme.service';
import { GlyphService } from './fw/svg/glyph.service';
-import { VeilService } from './fw/layer/veil.service';
import { PanelService } from './fw/layer/panel.service';
import { QuickHelpService } from './fw/layer/quickhelp.service';
import { EeService } from './fw/util/ee.service';
-import { WebSocketService } from './fw/remote/websocket.service';
+import { WebSocketService, WsOptions } from './fw/remote/websocket.service';
import { SpriteService } from './fw/svg/sprite.service';
import { OnosService, View } from './onos.service';
@@ -61,7 +60,7 @@
@Component({
selector: 'onos-root',
templateUrl: './onos.component.html',
- styleUrls: ['./onos.component.css']
+ styleUrls: ['./onos.component.css', './onos.common.css']
})
export class OnosComponent implements OnInit {
public title = 'onos';
@@ -81,7 +80,6 @@
private ks: KeyService,
private ts: ThemeService,
private gs: GlyphService,
- private vs: VeilService,
private ps: PanelService,
private qhs: QuickHelpService,
private ee: EeService,
@@ -101,6 +99,8 @@
log.warn('OnosComponent: testing logger.warn()');
log.error('OnosComponent: testing logger.error()');
+ this.wss.createWebSocket(<WsOptions>{ wsport: 8181});
+
log.debug('OnosComponent constructed');
}
diff --git a/web/gui2/src/main/webapp/app/onos.css b/web/gui2/src/main/webapp/app/onos.css
deleted file mode 100644
index fa485ac..0000000
--- a/web/gui2/src/main/webapp/app/onos.css
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2014-present Open Networking Foundation
- *
- * 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.
- */
-
-/*
- ONOS GUI -- core (layout) -- CSS file
- */
-
-html {
- font-family: 'Open Sans', sans-serif;
- -webkit-text-size-adjust: 100%;
- -ms-text-size-adjust: 100%;
- height: 100%;
-}
-
-/*
- overflow hidden is to ensure that the body does not expand to account
- for any flyout panes, that are positioned "off screen".
- */
-body {
- height: 100%;
- margin: 0;
- overflow: hidden;
-}
-
-#view {
- padding: 6px;
- width: 100%;
- height: 100%;
-}
-
-#view h2 {
- -webkit-margin-before: 0;
- -webkit-margin-after: 0;
- margin: 32px 0 4px 16px;
- padding: 0;
- font-size: 18pt;
- font-weight: lighter;
-}
diff --git a/web/gui2/src/main/webapp/app/view/device/device.component.ts b/web/gui2/src/main/webapp/app/view/device/device.component.ts
index db42c4d..6929f9b 100644
--- a/web/gui2/src/main/webapp/app/view/device/device.component.ts
+++ b/web/gui2/src/main/webapp/app/view/device/device.component.ts
@@ -58,7 +58,7 @@
ngOnInit() {
this.log.debug('DeviceComponent initialized');
// TODO: Remove this - it's only for demo purposes
- this.ls.startAnim();
+// this.ls.startAnim();
}
}
diff --git a/web/gui2/src/main/webapp/app/view/device/device.module.ts b/web/gui2/src/main/webapp/app/view/device/device.module.ts
index 9cd50cb..8f0f351 100644
--- a/web/gui2/src/main/webapp/app/view/device/device.module.ts
+++ b/web/gui2/src/main/webapp/app/view/device/device.module.ts
@@ -17,7 +17,7 @@
import { CommonModule } from '@angular/common';
import { DeviceComponent } from './device.component';
import { DeviceDetailsPanelDirective } from './devicedetailspanel.directive';
-
+import { RemoteModule } from '../../fw/remote/remote.module';
/**
* ONOS GUI -- Device View Module
*/
@@ -26,7 +26,8 @@
DeviceComponent
],
imports: [
- CommonModule
+ CommonModule,
+ RemoteModule
],
declarations: [
DeviceComponent,