blob: 9a99e5aade221f895bfa5cf2a71f45d4dbb6599c [file] [log] [blame]
Sean Condon2bd11b72018-06-15 08:00:48 +01001/*
Sean Condon28ecc5f2018-06-25 12:50:16 +01002 * Copyright 2018-present Open Networking Foundation
Sean Condon2bd11b72018-06-15 08:00:48 +01003 *
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 Condon2bd11b72018-06-15 08:00:48 +010016import { FnService } from '../util/fn.service';
Sean Condon5ca00262018-09-06 17:55:25 +010017import { LogService } from '../log.service';
Sean Condon2bd11b72018-06-15 08:00:48 +010018import { WebSocketService } from '../remote/websocket.service';
Sean Condon28ecc5f2018-06-25 12:50:16 +010019import { Observable, of } from 'rxjs';
Sean Condon2bd11b72018-06-15 08:00:48 +010020
21const REFRESH_INTERVAL = 2000;
Sean Condon28ecc5f2018-06-25 12:50:16 +010022const SEARCH_REGEX = '\\W';
Sean Condon2bd11b72018-06-15 08:00:48 +010023
24/**
Sean Condon28ecc5f2018-06-25 12:50:16 +010025 * Model of table annotations within this table base class
Sean Condon2bd11b72018-06-15 08:00:48 +010026 */
Sean Condon5ca00262018-09-06 17:55:25 +010027export interface TableAnnots {
Sean Condon2bd11b72018-06-15 08:00:48 +010028 noRowsMsg: string;
29}
30
31/**
Sean Condon28ecc5f2018-06-25 12:50:16 +010032 * A model of data returned from Web Socket in a TableResponse
Sean Condon2bd11b72018-06-15 08:00:48 +010033 *
34 * There is an interface extending from this one in the parent component
35 */
36export interface TableResponse {
37 annots: any;
38 // There will be other parts to the response depending on table type
39 // Expect one called tag+'s' e.g. devices or apps
40}
41
42/**
Sean Condon28ecc5f2018-06-25 12:50:16 +010043 * A criteria for filtering the tableData
44 */
45export interface TableFilter {
46 queryStr: string;
47 queryBy: string;
48 sortBy: string;
49}
50
51/**
52 * Enumerated values for the sort dir
53 */
54export enum SortDir {
55 asc = 'asc', desc = 'desc'
56}
57
58/**
59 * A structure to format sort params for table
60 * This is sent to WebSocket as part of table request
61 */
62export interface SortParams {
63 firstCol: string;
64 firstDir: SortDir;
65 secondCol: string;
66 secondDir: SortDir;
67}
68
Bhavesh72ead492018-07-19 16:29:18 +053069export interface PayloadParams {
70 devId: string;
71}
72
73
Sean Condon28ecc5f2018-06-25 12:50:16 +010074/**
Sean Condon2bd11b72018-06-15 08:00:48 +010075 * ONOS GUI -- Widget -- Table Base class
76 */
Sean Condon28ecc5f2018-06-25 12:50:16 +010077export abstract class TableBaseImpl {
Sean Condon2bd11b72018-06-15 08:00:48 +010078 // attributes from the interface
Sean Condon87b78502018-09-17 20:53:24 +010079 public annots: TableAnnots;
Sean Condon28ecc5f2018-06-25 12:50:16 +010080 protected changedData: string[] = [];
Bhavesh72ead492018-07-19 16:29:18 +053081 protected payloadParams: PayloadParams;
Sean Condon28ecc5f2018-06-25 12:50:16 +010082 protected sortParams: SortParams;
Sean Condon87b78502018-09-17 20:53:24 +010083 public selectCallback; // Function
Sean Condon28ecc5f2018-06-25 12:50:16 +010084 protected parentSelCb = null;
85 protected responseCallback; // Function
Sean Condon95fb5742019-04-02 12:16:55 +010086 public loadingIconShown: boolean = false;
Sean Condon28ecc5f2018-06-25 12:50:16 +010087 selId: string = undefined;
88 tableData: any[] = [];
89 tableDataFilter: TableFilter;
90 toggleRefresh; // Function
Sean Condon2bd11b72018-06-15 08:00:48 +010091 autoRefresh: boolean = true;
92 autoRefreshTip: string = 'Toggle auto refresh'; // TODO: get LION string
Sean Condon2bd11b72018-06-15 08:00:48 +010093
Sean Condon55c30532018-10-29 12:26:57 +000094 readonly root: string;
95 readonly req: string;
96 readonly resp: string;
Sean Condon2bd11b72018-06-15 08:00:48 +010097 private refreshPromise: any = null;
98 private handlers: string[] = [];
99
Sean Condon55c30532018-10-29 12:26:57 +0000100 protected constructor(
Sean Condon2bd11b72018-06-15 08:00:48 +0100101 protected fs: FnService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100102 protected log: LogService,
103 protected wss: WebSocketService,
104 protected tag: string,
105 protected idKey: string = 'id',
Sean Condon2bd11b72018-06-15 08:00:48 +0100106 protected selCb = () => ({}) // Function
107 ) {
108 this.root = tag + 's';
109 this.req = tag + 'DataRequest';
110 this.resp = tag + 'DataResponse';
111
Sean Condon2bd11b72018-06-15 08:00:48 +0100112 this.selectCallback = this.rowSelectionCb;
113 this.toggleRefresh = () => {
114 this.autoRefresh = !this.autoRefresh;
115 this.autoRefresh ? this.startRefresh() : this.stopRefresh();
116 };
Sean Condon28ecc5f2018-06-25 12:50:16 +0100117
118 // Mapped to the search and searchBy inputs in template
119 // Changes are handled through TableFilterPipe
120 this.tableDataFilter = <TableFilter>{
121 queryStr: '',
122 queryBy: '$',
123 };
Sean Condon2bd11b72018-06-15 08:00:48 +0100124 }
125
126 init() {
127 this.wss.bindHandlers(new Map<string, (data) => void>([
128 [this.resp, (data) => this.tableDataResponseCb(data)]
129 ]));
130 this.handlers.push(this.resp);
131
132 this.annots = <TableAnnots>{
133 noRowsMsg: ''
134 };
135
136 // Now send the WebSocket request and make it repeat every 2 seconds
137 this.requestTableData();
138 this.startRefresh();
Sean Condon28ecc5f2018-06-25 12:50:16 +0100139 this.log.debug('TableBase initialized. Calling ', this.req,
140 'every', REFRESH_INTERVAL, 'ms');
Sean Condon2bd11b72018-06-15 08:00:48 +0100141 }
142
143 destroy() {
144 this.wss.unbindHandlers(this.handlers);
145 this.stopRefresh();
Sean Condon95fb5742019-04-02 12:16:55 +0100146 this.loadingIconShown = false;
Sean Condon2bd11b72018-06-15 08:00:48 +0100147 }
148
149 /**
150 * A callback that executes when the table data that was requested
151 * on WebSocketService arrives.
152 *
153 * Happens every 2 seconds
154 */
155 tableDataResponseCb(data: TableResponse) {
Sean Condon95fb5742019-04-02 12:16:55 +0100156 this.loadingIconShown = false;
Sean Condon2bd11b72018-06-15 08:00:48 +0100157
158 const newTableData: any[] = Array.from(data[this.root]);
159 this.annots.noRowsMsg = data.annots.no_rows_msg;
160
Sean Condon28ecc5f2018-06-25 12:50:16 +0100161 // If the parents onResp() function is set then call it
Sean Condon2bd11b72018-06-15 08:00:48 +0100162 if (this.responseCallback) {
163 this.responseCallback(data);
164 }
165 this.changedData = [];
166
167 // checks if data changed for row flashing
168 if (JSON.stringify(newTableData) !== JSON.stringify(this.tableData)) {
169 this.log.debug('table data has changed');
170 const oldTableData: any[] = this.tableData;
Bhavesh72ead492018-07-19 16:29:18 +0530171 this.tableData = [...newTableData]; // ES6 spread syntax
Sean Condon2bd11b72018-06-15 08:00:48 +0100172 // only flash the row if the data already exists
173 if (oldTableData.length > 0) {
174 for (const idx in newTableData) {
175 if (!this.fs.containsObj(oldTableData, newTableData[idx])) {
176 this.changedData.push(newTableData[idx][this.idKey]);
177 }
178 }
179 }
180 }
181 }
182
183 /**
184 * Table Data Request
Sean Condon28ecc5f2018-06-25 12:50:16 +0100185 * Pass in sort parameters and the set will be returned sorted
186 * In the old GUI there was also a query parameter, but this was not
187 * implemented on the server end
Sean Condon2bd11b72018-06-15 08:00:48 +0100188 */
189 requestTableData() {
Sean Condon28ecc5f2018-06-25 12:50:16 +0100190 const p = Object.assign({}, this.sortParams, this.payloadParams);
Sean Condon2bd11b72018-06-15 08:00:48 +0100191
192 // Allow it to sit in pending events
193 if (this.wss.isConnected()) {
194 if (this.fs.debugOn('table')) {
195 this.log.debug('Table data REQUEST:', this.req, p);
196 }
197 this.wss.sendEvent(this.req, p);
Sean Condon95fb5742019-04-02 12:16:55 +0100198 this.loadingIconShown = true;
Sean Condon2bd11b72018-06-15 08:00:48 +0100199 }
200 }
201
202 /**
203 * Row Selected
204 */
Sean Condon28ecc5f2018-06-25 12:50:16 +0100205 rowSelectionCb(event: any, selRow: any): void {
Sean Condon2bd11b72018-06-15 08:00:48 +0100206 const selId: string = selRow[this.idKey];
207 this.selId = (this.selId === selId) ? undefined : selId;
Sean Condon28ecc5f2018-06-25 12:50:16 +0100208 this.log.debug('Row', selId, 'selected');
Sean Condon2bd11b72018-06-15 08:00:48 +0100209 if (this.parentSelCb) {
Sean Condon2bd11b72018-06-15 08:00:48 +0100210 this.parentSelCb(event, selRow);
211 }
212 }
213
214 /**
215 * autoRefresh functions
216 */
217 startRefresh() {
218 this.refreshPromise =
219 setInterval(() => {
Sean Condon95fb5742019-04-02 12:16:55 +0100220 if (!this.loadingIconShown) {
Sean Condon2bd11b72018-06-15 08:00:48 +0100221 if (this.fs.debugOn('table')) {
222 this.log.debug('Refreshing ' + this.root + ' page');
223 }
224 this.requestTableData();
225 }
226 }, REFRESH_INTERVAL);
227 }
228
229 stopRefresh() {
230 if (this.refreshPromise) {
231 clearInterval(this.refreshPromise);
232 this.refreshPromise = null;
233 }
234 }
235
236 isChanged(id: string): boolean {
237 return (this.fs.inArray(id, this.changedData) === -1) ? false : true;
238 }
Sean Condon28ecc5f2018-06-25 12:50:16 +0100239
240 /**
241 * A dummy implementation of the lionFn until the response is received and the LION
242 * bundle is received from the WebSocket
243 */
244 dummyLion(key: string): string {
245 return '%' + key + '%';
246 }
247
248 /**
249 * Change the sort order of the data returned
250 *
251 * sortParams are passed to the server by WebSocket and the data is
252 * returned sorted
253 *
254 * This is usually assigned to the (click) event on a column, and the column
255 * name passed in e.g. (click)="onSort('origin')
256 * If the column that is passed in is already the firstCol, then reverse its direction
257 * If a new column is passed in, then make the existing col the 2nd sort order
258 */
259 onSort(colName: string) {
260 if (this.sortParams.firstCol === colName) {
261 if (this.sortParams.firstDir === SortDir.desc) {
262 this.sortParams.firstDir = SortDir.asc;
263 return;
264 } else {
265 this.sortParams.firstDir = SortDir.desc;
266 return;
267 }
268 } else {
269 this.sortParams.secondCol = this.sortParams.firstCol;
270 this.sortParams.secondDir = this.sortParams.firstDir;
271 this.sortParams.firstCol = colName;
272 this.sortParams.firstDir = SortDir.desc;
273 }
274 this.log.debug('Sort params', this.sortParams);
275 this.requestTableData();
276 }
277
278 sortIcon(column: string): string {
279 if (this.sortParams.firstCol === column) {
280 if (this.sortParams.firstDir === SortDir.asc) {
281 return 'upArrow';
282 } else {
283 return 'downArrow';
284 }
285 } else {
286 return '';
287 }
288 }
Bhavesh72ead492018-07-19 16:29:18 +0530289
290 /**
291 * De-selects the row
292 */
293 deselectRow(event) {
294 this.log.debug('Details panel close event');
295 this.selId = event;
296 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100297}