GUI2-Cluster View
Change-Id: I812439fae68d18756c707c1021ce6e070ae6afc3
diff --git a/web/gui2/src/main/webapp/app/view/cluster/cluster-details.directive.ts b/web/gui2/src/main/webapp/app/view/cluster/cluster-details.directive.ts
new file mode 100644
index 0000000..9aa2eb7
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/cluster/cluster-details.directive.ts
@@ -0,0 +1,314 @@
+/*
+ * 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 {Directive, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Output} from '@angular/core';
+import {FnService} from '../../fw/util/fn.service';
+import {LogService} from '../../log.service';
+import {MastService} from '../../fw/mast/mast.service';
+import {DetailsPanelBaseImpl} from '../../fw/widget/detailspanel.base';
+import {LoadingService} from '../../fw/layer/loading.service';
+import {IconService} from '../../fw/svg/icon.service';
+import {LionService} from '../../fw/util/lion.service';
+import {WebSocketService} from '../../fw/remote/websocket.service';
+import * as d3 from 'd3';
+import {PanelService} from '../../fw/layer/panel.service';
+import {HostListener} from '@angular/core';
+
+// internal state
+let detailsPanel,
+ pStartY,
+ pHeight,
+ top,
+ topTable,
+ bottom,
+ iconDiv,
+ wSize;
+
+
+// constants
+const topPdg = 28,
+ ctnrPdg = 24,
+ scrollSize = 17,
+ portsTblPdg = 100,
+ pName = 'details-panel',
+ propOrder = [
+ 'id', 'ip',
+ ],
+ deviceCols = [
+ 'id', 'type', 'chassisid', 'mfr',
+ 'hw', 'sw', 'protocol', 'serial',
+ ];
+
+function addProp(tbody, label, value) {
+ const tr = tbody.append('tr');
+
+ function addCell(cls, txt) {
+ tr.append('td').attr('class', cls).text(txt);
+ }
+
+ addCell('label', label + ' :');
+ addCell('value', value);
+}
+
+function addDeviceRow(tbody, device) {
+ const tr = tbody.append('tr');
+
+ deviceCols.forEach(function (col) {
+ tr.append('td').text(device[col]);
+ });
+}
+
+@Directive({
+ selector: '[onosClusterDetails]',
+})
+
+export class ClusterDetailsDirective extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges {
+ @Input() id: string;
+ @Output() closeEvent = new EventEmitter<string>();
+
+ lionFn; // Function
+
+ constructor(protected fs: FnService,
+ protected ls: LoadingService,
+ protected is: IconService,
+ protected lion: LionService,
+ protected wss: WebSocketService,
+ protected log: LogService,
+ protected mast: MastService,
+ protected ps: PanelService,
+ protected el: ElementRef,
+ @Inject('Window') private w: Window) {
+ super(fs, ls, log, wss, 'cluster');
+
+ if (this.lion.ubercache.length === 0) {
+ this.lionFn = this.dummyLion;
+ this.lion.loadCbs.set('clusterdetails', () => this.doLion());
+ } else {
+ this.doLion();
+ }
+ this.log.debug('ClusterDetailsDirective constructed');
+ }
+
+ ngOnInit() {
+ this.init();
+ this.initPanel();
+ this.log.debug('Cluster Details Component initialized');
+ }
+
+ /**
+ * Stop listening to clusterDetailsResponse on WebSocket
+ */
+ ngOnDestroy() {
+ this.lion.loadCbs.delete('clusterdetails');
+ this.destroy();
+ this.ps.destroyPanel(pName);
+ this.log.debug('Cluster Details Component destroyed');
+ }
+
+ @HostListener('window:resize', ['event'])
+ onResize(event: any) {
+ this.heightCalc();
+ this.populateDetails(this.detailsData);
+ return {
+ h: this.w.innerHeight,
+ w: this.w.innerWidth
+ };
+ }
+
+ @HostListener('document:click', ['$event'])
+ onClick(event) {
+ if (event.path !== undefined) {
+ for (let i = 0; i < event.path.length; i++) {
+ if (event.path[i].className === 'close-btn') {
+ this.close();
+ break;
+ }
+ }
+ } else if (event.target.href === undefined) {
+ if (event.target.parentNode.className === 'close-btn') {
+ this.close();
+ }
+ } else if (event.target.href.baseVal === '#xClose') {
+ this.close();
+ }
+ }
+
+ /**
+ * Details Panel Data Request on row selection changes
+ * Should be called whenever id changes
+ * If id is empty, no request is made
+ */
+ ngOnChanges() {
+ if (this.id === '') {
+ if (detailsPanel) {
+ detailsPanel.hide();
+ }
+ return '';
+ } else {
+ const query = {
+ 'id': this.id
+ };
+ this.requestDetailsPanelData(query);
+ this.heightCalc();
+
+ /*
+ * Details data takes around 2ms to come up on web-socket
+ * putting a timeout interval of 5ms
+ */
+ setTimeout(() => {
+ this.populateDetails(this.detailsData);
+ detailsPanel.show();
+ }, 500);
+
+
+ }
+ }
+
+ doLion() {
+ this.lionFn = this.lion.bundle('core.view.Cluster');
+ }
+
+ heightCalc() {
+ pStartY = this.fs.noPxStyle(d3.select('.tabular-header'), 'height')
+ + this.mast.mastHeight + topPdg;
+ wSize = this.fs.windowSize(this.fs.noPxStyle(d3.select('.tabular-header'), 'height'));
+ pHeight = wSize.height;
+ }
+
+ createDetailsPane() {
+ detailsPanel = this.ps.createPanel(pName, {
+ width: wSize.width,
+ margin: 0,
+ hideMargin: 0,
+ });
+ detailsPanel.el().style('top', pStartY + 'px');
+ detailsPanel.el().style('position', 'absolute');
+ this.hidePanel = function () {
+ detailsPanel.hide();
+ };
+ detailsPanel.hide();
+ }
+
+ initPanel() {
+ this.heightCalc();
+ this.createDetailsPane();
+ }
+
+ populateDetails(details) {
+ this.setUpPanel();
+ this.populateTop(details);
+ this.populateBottom(details.devices);
+ detailsPanel.height(pHeight);
+ }
+
+ setUpPanel() {
+ let container, closeBtn;
+ detailsPanel.empty();
+
+ container = detailsPanel.append('div').classed('container', true);
+
+ top = container.append('div').classed('top', true);
+ closeBtn = top.append('div').classed('close-btn', true);
+ this.addCloseBtn(closeBtn);
+ iconDiv = top.append('div').classed('dev-icon', true);
+ top.append('h2');
+ topTable = top.append('div').classed('top-content', true)
+ .append('table');
+ top.append('hr');
+
+ bottom = container.append('div').classed('bottom', true);
+ bottom.append('h2').classed('devices-title', true).text('Devices');
+ bottom.append('table');
+ }
+
+ addCloseBtn(div) {
+ this.is.loadEmbeddedIcon(div, 'close', 20);
+ div.on('click', this.closePanel);
+ }
+
+ closePanel(): boolean {
+ if (detailsPanel.isVisible()) {
+ detailsPanel.hide();
+ return true;
+ }
+ return false;
+ }
+
+ populateTop(details) {
+ const propLabels = this.getLionProps();
+
+ this.is.loadEmbeddedIcon(iconDiv, 'node', 40);
+ top.select('h2').text(details.id);
+
+ const tbody = topTable.append('tbody');
+
+ propOrder.forEach(function (prop, i) {
+ addProp(tbody, propLabels[i], details[prop]);
+ });
+ }
+
+ getLionDeviceCols() {
+ return [
+ this.lionFn('uri'),
+ this.lionFn('type'),
+ this.lionFn('chassis_id'),
+ this.lionFn('vendor'),
+ this.lionFn('hw_version'),
+ this.lionFn('sw_version'),
+ this.lionFn('protocol'),
+ this.lionFn('serial_number'),
+ ];
+ }
+
+ populateBottom(devices) {
+ const table = bottom.select('table'),
+ theader = table.append('thead').append('tr'),
+ tbody = table.append('tbody');
+
+ let tbWidth, tbHeight;
+
+ this.getLionDeviceCols().forEach(function (col) {
+ theader.append('th').text(col);
+ });
+ if (devices !== undefined) {
+ devices.forEach(function (device) {
+ addDeviceRow(tbody, device);
+ });
+ }
+
+ tbWidth = this.fs.noPxStyle(tbody, 'width') + scrollSize;
+ tbHeight = pHeight
+ - (this.fs.noPxStyle(detailsPanel.el()
+ .select('.top'), 'height')
+ + this.fs.noPxStyle(detailsPanel.el()
+ .select('.devices-title'), 'height')
+ + portsTblPdg);
+
+ table.style('zIndex', '0');
+ table.style('height', tbHeight + 'px');
+ table.style('width', tbWidth + 'px');
+ table.style('overflow', 'auto');
+ table.style('display', 'block');
+
+ detailsPanel.width(tbWidth + ctnrPdg);
+ }
+
+ getLionProps() {
+ return [
+ this.lionFn('node_id'),
+ this.lionFn('ip_address'),
+ ];
+ }
+}