GUI2 Topo Allow selection of multiple nodes at once
Change-Id: I0bb226d4697e3df49da0a049d440a70aed172263
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.ts
index 85282e1..21ad66e 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.ts
@@ -13,22 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {
- Component,
- Input,
- OnChanges,
- OnDestroy,
- OnInit,
- SimpleChanges
-} from '@angular/core';
+import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core';
import {animate, state, style, transition, trigger} from '@angular/animations';
-import {
- DetailsPanelBaseImpl,
- FnService, LionService,
- LogService,
- WebSocketService
-} from 'gui2-fw-lib';
-import {Host, Link, LinkType, UiElement} from '../../layer/forcesvg/models';
+import {DetailsPanelBaseImpl, FnService, LionService, LogService, WebSocketService} from 'gui2-fw-lib';
+import {Host, Link, LinkType, NodeType, UiElement} from '../../layer/forcesvg/models';
import {Params, Router} from '@angular/router';
@@ -68,6 +56,22 @@
tt: 'tt_ctl_show_pipeconf',
path: 'pipeconf',
};
+const RELATEDINTENTS: ButtonAttrs = {
+ gid: 'm_relatedIntents',
+ tt: 'tr_btn_show_related_traffic',
+ path: 'relatedIntents',
+};
+const CREATEHOSTTOHOSTFLOW: ButtonAttrs = {
+ gid: 'm_endstation',
+ tt: 'tr_btn_create_h2h_flow',
+ path: 'create_h2h_flow',
+};
+const CREATEMULTISOURCEFLOW: ButtonAttrs = {
+ gid: 'm_flows',
+ tt: 'tr_btn_create_msrc_flow',
+ path: 'create_msrc_flow',
+};
+
interface ShowDetails {
buttons: string[];
@@ -83,6 +87,12 @@
* ONOS GUI -- Topology Details Panel.
* Displays details of selected device. When no device is selected the panel slides
* off to the side and disappears
+ *
+ * This Panel is a child of the Topology component and it gets the 'selectedNodes'
+ * from there as an input component. See TopologyComponent.nodeSelected()
+ * The topology component gets these by listening to events from ForceSvgComponent
+ * which gets them in turn from Device, Host, SubRegion and Link components. This
+ * is so that each component respects the hierarchy
*/
@Component({
selector: 'onos-details',
@@ -108,11 +118,12 @@
]
})
export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges {
- @Input() selectedNode: UiElement = undefined; // Populated when user selects node or link
+ @Input() selectedNodes: UiElement[] = []; // Populated when user selects node or link
@Input() on: boolean = false; // Override the parent class attribute
// deferred localization strings
- lionFn; // Function
+ lionFnTopo; // Function
+ lionFnFlow; // Function for flow bundle
showDetails: ShowDetails; // Will be populated on callback. Cleared if nothing is selected
constructor(
@@ -125,8 +136,9 @@
super(fs, log, wss, 'topo');
if (this.lion.ubercache.length === 0) {
- this.lionFn = this.dummyLion;
- this.lion.loadCbs.set('flow', () => this.doLion());
+ this.lionFnTopo = this.dummyLion;
+ this.lionFnFlow = this.dummyLion;
+ this.lion.loadCbs.set('detailscore', () => this.doLion());
} else {
this.doLion();
}
@@ -160,32 +172,75 @@
/**
* If changes are detected on the Input param selectedNode, call on WSS sendEvent
+ * and expect ShowDetails to be updated from data sent back from server.
+ *
* Note the difference in call to the WSS with requestDetails between a node
* and a link - the handling is done in TopologyViewMessageHandler#RequestDetails.process()
*
+ * When multiple items are selected fabricate the ShowDetails here, and
+ * present buttons that allow custom actions
+ *
* The WSS will call back asynchronously (see fn in ngOnInit())
*
* @param changes Simple Changes set of updates
*/
ngOnChanges(changes: SimpleChanges): void {
- if (changes['selectedNode']) {
- this.selectedNode = changes['selectedNode'].currentValue;
+ if (changes['selectedNodes']) {
+ this.selectedNodes = changes['selectedNodes'].currentValue;
let type: any;
-
- if (this.selectedNode === undefined) {
+ if (this.selectedNodes.length === 0) {
// Selection has been cleared
this.showDetails = <ShowDetails>{};
return;
+ } else if (this.selectedNodes.length > 1) {
+ // Don't send message to WSS just form dialog here
+ const propOrder: string[] = [];
+ const propValues: Object = {};
+ const propLabels: Object = {};
+ let numHosts: number = 0;
+ for (let i = 0; i < this.selectedNodes.length; i++) {
+ propOrder.push(i.toString());
+ propLabels[i.toString()] = i.toString();
+ propValues[i.toString()] = this.selectedNodes[i].id;
+ if (this.selectedNodes[i].hasOwnProperty('nodeType') &&
+ (<Host>this.selectedNodes[i]).nodeType === NodeType.HOST) {
+ numHosts++;
+ } else {
+ numHosts = -128; // Negate the whole thing so other buttons will not be shown
+ }
+ }
+ const buttons: string[] = [];
+ if (numHosts === 2) {
+ buttons.push('createHostToHostFlow');
+ } else if (numHosts > 2) {
+ buttons.push('createMultiSourceFlow');
+ }
+ buttons.push('relatedIntents');
+
+ this.showDetails = <ShowDetails>{
+ buttons: buttons,
+ glyphId: undefined,
+ id: 'multiple',
+ navPath: undefined,
+ propLabels: propLabels,
+ propOrder: propOrder,
+ propValues: propValues,
+ title: this.lionFnTopo('title_selected_items')
+ };
+ this.log.debug('Details panel generated from multiple devices', this.showDetails);
+ return;
}
- if (this.selectedNode.hasOwnProperty('nodeType')) { // For Device, Host, SubRegion
- type = (<Host>this.selectedNode).nodeType;
+ // If only one thing has been selected then request details of that from the server
+ const selectedNode = this.selectedNodes[0];
+ if (selectedNode.hasOwnProperty('nodeType')) { // For Device, Host, SubRegion
+ type = (<Host>selectedNode).nodeType;
this.wss.sendEvent('requestDetails', {
- id: this.selectedNode.id,
+ id: selectedNode.id,
class: type,
});
- } else if (this.selectedNode.hasOwnProperty('type')) { // Must be link
- const link: Link = <Link>this.selectedNode;
+ } else if (selectedNode.hasOwnProperty('type')) { // Must be link
+ const link: Link = <Link>selectedNode;
if (<LinkType><unknown>LinkType[link.type] === LinkType.UiEdgeLink) { // Number based enum
this.wss.sendEvent('requestDetails', {
key: link.id,
@@ -207,7 +262,7 @@
});
}
} else {
- this.log.warn('Unexpected type for selected element', this.selectedNode);
+ this.log.warn('Unexpected type for selected element', selectedNode);
}
}
}
@@ -231,6 +286,12 @@
return SHOWMETERVIEW;
case 'showPipeConfView':
return SHOWPIPECONFVIEW;
+ case 'relatedIntents':
+ return RELATEDINTENTS;
+ case 'createHostToHostFlow':
+ return CREATEHOSTTOHOSTFLOW;
+ case 'createMultiSourceFlow':
+ return CREATEMULTISOURCEFLOW;
default:
return <ButtonAttrs>{
gid: btnName,
@@ -244,19 +305,46 @@
* e.g. if params are 'meter', 'device' and 'null:0000000000001' then the
* navigation URL will become "http://localhost:4200/#/meter?devId=null:0000000000000002"
*
+ * When multiple hosts are selected other actions have to be accommodated
+ *
* @param path The path to navigate to
* @param navPath The parameter name to use
* @param selId the parameter value to use
*/
- navto(path: string, navPath: string, selId: string): void {
- this.log.debug('navigate to', path, 'for', navPath, '=', selId);
- // Special case until it's fixed
- if (selId) {
+ navto(path: string): void {
+ this.log.debug('navigate to', path, 'for',
+ this.showDetails.navPath, '=', this.showDetails.id);
+
+ const ids: string[] = [];
+ Object.values(this.showDetails.propValues).forEach((v) => ids.push(v));
+ if (path === 'relatedIntents' && this.showDetails.id === 'multiple') {
+ this.wss.sendEvent('requestRelatedIntents', {
+ 'ids': ids,
+ 'hover': ''
+ });
+
+ } else if (path === 'create_h2h_flow' && this.showDetails.id === 'multiple') {
+ this.wss.sendEvent('addHostIntent', {
+ 'one': ids[0],
+ 'two': ids[1],
+ 'ids': ids
+ });
+
+ } else if (path === 'create_msrc_flow' && this.showDetails.id === 'multiple') {
+ // Should only happen when there are 3 or more ids
+ this.wss.sendEvent('addMultiSourceIntent', {
+ 'src': ids.slice(0, ids.length - 1),
+ 'dst': ids[ids.length - 1],
+ 'ids': ids
+ });
+
+ } else if (this.showDetails.id) {
+ let navPath = this.showDetails.navPath;
if (navPath === 'device') {
navPath = 'devId';
}
const queryPar: Params = {};
- queryPar[navPath] = selId;
+ queryPar[navPath] = this.showDetails.id;
this.router.navigate([path], { queryParams: queryPar });
}
}
@@ -265,8 +353,8 @@
* Read the LION bundle for Details panel and set up the lionFn
*/
doLion() {
- this.lionFn = this.lion.bundle('core.view.Flow');
-
+ this.lionFnTopo = this.lion.bundle('core.view.Topo');
+ this.lionFnFlow = this.lion.bundle('core.view.Flow');
}
}