Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 1 | /* |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 2 | * Copyright 2019-present Open Networking Foundation |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 16 | import { |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 17 | Component, |
| 18 | Input, |
| 19 | OnChanges, |
| 20 | OnDestroy, |
| 21 | OnInit, |
| 22 | SimpleChanges |
| 23 | } from '@angular/core'; |
| 24 | import {animate, state, style, transition, trigger} from '@angular/animations'; |
| 25 | import { |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 26 | DetailsPanelBaseImpl, |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 27 | FnService, LionService, |
| 28 | LoadingService, |
| 29 | LogService, |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 30 | WebSocketService |
| 31 | } from 'gui2-fw-lib'; |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 32 | import {Host, Link, LinkType, UiElement} from '../../layer/forcesvg/models'; |
| 33 | import {Params, Router} from '@angular/router'; |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 34 | |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 35 | |
| 36 | interface ButtonAttrs { |
| 37 | gid: string; |
| 38 | tt: string; |
| 39 | path: string; |
| 40 | } |
| 41 | |
| 42 | const SHOWDEVICEVIEW: ButtonAttrs = { |
| 43 | gid: 'deviceTable', |
| 44 | tt: 'tt_ctl_show_device', |
| 45 | path: 'device', |
| 46 | }; |
| 47 | const SHOWFLOWVIEW: ButtonAttrs = { |
| 48 | gid: 'flowTable', |
| 49 | tt: 'title_flows', |
| 50 | path: 'flow', |
| 51 | }; |
| 52 | const SHOWPORTVIEW: ButtonAttrs = { |
| 53 | gid: 'portTable', |
| 54 | tt: 'tt_ctl_show_port', |
| 55 | path: 'port', |
| 56 | }; |
| 57 | const SHOWGROUPVIEW: ButtonAttrs = { |
| 58 | gid: 'groupTable', |
| 59 | tt: 'tt_ctl_show_group', |
| 60 | path: 'group', |
| 61 | }; |
| 62 | const SHOWMETERVIEW: ButtonAttrs = { |
| 63 | gid: 'meterTable', |
| 64 | tt: 'tt_ctl_show_meter', |
| 65 | path: 'meter', |
| 66 | }; |
| 67 | const SHOWPIPECONFVIEW: ButtonAttrs = { |
| 68 | gid: 'pipeconfTable', |
| 69 | tt: 'tt_ctl_show_pipeconf', |
| 70 | path: 'pipeconf', |
| 71 | }; |
| 72 | |
| 73 | interface ShowDetails { |
| 74 | buttons: string[]; |
| 75 | glyphId: string; |
| 76 | id: string; |
| 77 | navPath: string; |
| 78 | propLabels: Object; |
| 79 | propOrder: string[]; |
| 80 | propValues: Object; |
| 81 | title: string; |
| 82 | } |
| 83 | /** |
| 84 | * ONOS GUI -- Topology Details Panel. |
| 85 | * Displays details of selected device. When no device is selected the panel slides |
| 86 | * off to the side and disappears |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 87 | */ |
| 88 | @Component({ |
| 89 | selector: 'onos-details', |
| 90 | templateUrl: './details.component.html', |
| 91 | styleUrls: [ |
| 92 | './details.component.css', './details.theme.css', |
| 93 | '../../topology.common.css', |
| 94 | '../../../../fw/widget/panel.css', '../../../../fw/widget/panel-theme.css' |
| 95 | ], |
| 96 | animations: [ |
| 97 | trigger('detailsPanelState', [ |
| 98 | state('true', style({ |
| 99 | transform: 'translateX(0%)', |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 100 | opacity: '1.0' |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 101 | })), |
| 102 | state('false', style({ |
| 103 | transform: 'translateX(100%)', |
| 104 | opacity: '0' |
| 105 | })), |
| 106 | transition('0 => 1', animate('100ms ease-in')), |
| 107 | transition('1 => 0', animate('100ms ease-out')) |
| 108 | ]) |
| 109 | ] |
| 110 | }) |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 111 | export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges { |
| 112 | @Input() selectedNode: UiElement = undefined; // Populated when user selects node or link |
| 113 | |
| 114 | // deferred localization strings |
| 115 | lionFn; // Function |
| 116 | showDetails: ShowDetails; // Will be populated on callback. Cleared if nothing is selected |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 117 | |
| 118 | constructor( |
| 119 | protected fs: FnService, |
| 120 | protected log: LogService, |
| 121 | protected ls: LoadingService, |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 122 | protected router: Router, |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 123 | protected wss: WebSocketService, |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 124 | private lion: LionService |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 125 | ) { |
| 126 | super(fs, ls, log, wss, 'topo'); |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 127 | |
| 128 | if (this.lion.ubercache.length === 0) { |
| 129 | this.lionFn = this.dummyLion; |
| 130 | this.lion.loadCbs.set('flow', () => this.doLion()); |
| 131 | } else { |
| 132 | this.doLion(); |
| 133 | } |
| 134 | |
| 135 | this.log.debug('Topo DetailsComponent constructed'); |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 136 | } |
| 137 | |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 138 | /** |
| 139 | * When the component is initializing set up the handler for callbacks of |
| 140 | * ShowDetails from the WSS. Set the variable showDetails when ever a callback |
| 141 | * is made |
| 142 | */ |
| 143 | ngOnInit(): void { |
Sean Condon | 55c3053 | 2018-10-29 12:26:57 +0000 | [diff] [blame] | 144 | this.on = false; |
Sean Condon | 9148182 | 2019-01-01 13:56:14 +0000 | [diff] [blame^] | 145 | this.wss.bindHandlers(new Map<string, (data) => void>([ |
| 146 | ['showDetails', (data) => { |
| 147 | this.showDetails = data; |
| 148 | // this.log.debug('showDetails received', data); |
| 149 | } |
| 150 | ] |
| 151 | ])); |
| 152 | this.log.debug('Topo DetailsComponent initialized'); |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * When the component is being unloaded then unbind the WSS handler. |
| 157 | */ |
| 158 | ngOnDestroy(): void { |
| 159 | this.wss.unbindHandlers(['showDetails']); |
| 160 | this.log.debug('Topo DetailsComponent destroyed'); |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * If changes are detected on the Input param selectedNode, call on WSS sendEvent |
| 165 | * Note the difference in call to the WSS with requestDetails between a node |
| 166 | * and a link - the handling is done in TopologyViewMessageHandler#RequestDetails.process() |
| 167 | * |
| 168 | * The WSS will call back asynchronously (see fn in ngOnInit()) |
| 169 | * |
| 170 | * @param changes Simple Changes set of updates |
| 171 | */ |
| 172 | ngOnChanges(changes: SimpleChanges): void { |
| 173 | if (changes['selectedNode']) { |
| 174 | this.selectedNode = changes['selectedNode'].currentValue; |
| 175 | let type: any; |
| 176 | |
| 177 | if (this.selectedNode === undefined) { |
| 178 | // Selection has been cleared |
| 179 | this.showDetails = <ShowDetails>{}; |
| 180 | return; |
| 181 | } |
| 182 | |
| 183 | if (this.selectedNode.hasOwnProperty('nodeType')) { // For Device, Host, SubRegion |
| 184 | type = (<Host>this.selectedNode).nodeType; |
| 185 | this.wss.sendEvent('requestDetails', { |
| 186 | id: this.selectedNode.id, |
| 187 | class: type, |
| 188 | }); |
| 189 | } else if (this.selectedNode.hasOwnProperty('type')) { // Must be link |
| 190 | const link: Link = <Link>this.selectedNode; |
| 191 | if (<LinkType><unknown>LinkType[link.type] === LinkType.UiEdgeLink) { // Number based enum |
| 192 | this.wss.sendEvent('requestDetails', { |
| 193 | key: link.id, |
| 194 | class: 'link', |
| 195 | sourceId: link.epA, |
| 196 | targetId: Link.deviceNameFromEp(link.epB), |
| 197 | targetPort: link.portB, |
| 198 | isEdgeLink: true |
| 199 | }); |
| 200 | } else { |
| 201 | this.wss.sendEvent('requestDetails', { |
| 202 | key: link.id, |
| 203 | class: 'link', |
| 204 | sourceId: Link.deviceNameFromEp(link.epA), |
| 205 | sourcePort: link.portA, |
| 206 | targetId: Link.deviceNameFromEp(link.epB), |
| 207 | targetPort: link.portB, |
| 208 | isEdgeLink: false |
| 209 | }); |
| 210 | } |
| 211 | } else { |
| 212 | this.log.warn('Unexpected type for selected element', this.selectedNode); |
| 213 | } |
| 214 | } else { |
| 215 | this.log.warn('Unexpected change in Topo DetailsComponent'); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Table of core button attributes to return per button icon |
| 221 | * @param btnName The name of the button |
| 222 | * @returns A structure with the button attributes |
| 223 | */ |
| 224 | buttonAttribs(btnName: string): ButtonAttrs { |
| 225 | switch (btnName) { |
| 226 | case 'showDeviceView': |
| 227 | return SHOWDEVICEVIEW; |
| 228 | case 'showFlowView': |
| 229 | return SHOWFLOWVIEW; |
| 230 | case 'showPortView': |
| 231 | return SHOWPORTVIEW; |
| 232 | case 'showGroupView': |
| 233 | return SHOWGROUPVIEW; |
| 234 | case 'showMeterView': |
| 235 | return SHOWMETERVIEW; |
| 236 | case 'showPipeConfView': |
| 237 | return SHOWPIPECONFVIEW; |
| 238 | default: |
| 239 | return <ButtonAttrs>{ |
| 240 | gid: btnName, |
| 241 | path: btnName |
| 242 | }; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * Navigate using Angular Routing. Combines the parameters to generate a relative URL |
| 248 | * e.g. if params are 'meter', 'device' and 'null:0000000000001' then the |
| 249 | * navigation URL will become "http://localhost:4200/#/meter?devId=null:0000000000000002" |
| 250 | * |
| 251 | * @param path The path to navigate to |
| 252 | * @param navPath The parameter name to use |
| 253 | * @param selId the parameter value to use |
| 254 | */ |
| 255 | navto(path: string, navPath: string, selId: string): void { |
| 256 | this.log.debug('navigate to', path, 'for', navPath, '=', selId); |
| 257 | // Special case until it's fixed |
| 258 | if (selId) { |
| 259 | if (navPath === 'device') { |
| 260 | navPath = 'devId'; |
| 261 | } |
| 262 | const queryPar: Params = {}; |
| 263 | queryPar[navPath] = selId; |
| 264 | this.router.navigate([path], { queryParams: queryPar }); |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * Read the LION bundle for Details panel and set up the lionFn |
| 270 | */ |
| 271 | doLion() { |
| 272 | this.lionFn = this.lion.bundle('core.view.Flow'); |
| 273 | |
Sean Condon | f4f54a1 | 2018-10-10 23:25:46 +0100 | [diff] [blame] | 274 | } |
| 275 | |
| 276 | } |