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