blob: 0093f72311734ee2a6a7f01edf8c9df490c0d7ed [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';
17import { LoadingService } from '../layer/loading.service';
18import { LogService } from '../../log.service';
19import { WebSocketService } from '../remote/websocket.service';
Sean Condon28ecc5f2018-06-25 12:50:16 +010020import { Observable, of } from 'rxjs';
Sean Condon2bd11b72018-06-15 08:00:48 +010021
22const REFRESH_INTERVAL = 2000;
Sean Condon28ecc5f2018-06-25 12:50:16 +010023const SEARCH_REGEX = '\\W';
Sean Condon2bd11b72018-06-15 08:00:48 +010024
25/**
Sean Condon28ecc5f2018-06-25 12:50:16 +010026 * Model of table annotations within this table base class
Sean Condon2bd11b72018-06-15 08:00:48 +010027 */
Sean Condon2bd11b72018-06-15 08:00:48 +010028interface TableAnnots {
29 noRowsMsg: string;
30}
31
32/**
Sean Condon28ecc5f2018-06-25 12:50:16 +010033 * A model of data returned from Web Socket in a TableResponse
Sean Condon2bd11b72018-06-15 08:00:48 +010034 *
35 * There is an interface extending from this one in the parent component
36 */
37export interface TableResponse {
38 annots: any;
39 // There will be other parts to the response depending on table type
40 // Expect one called tag+'s' e.g. devices or apps
41}
42
43/**
Sean Condon28ecc5f2018-06-25 12:50:16 +010044 * A criteria for filtering the tableData
45 */
46export interface TableFilter {
47 queryStr: string;
48 queryBy: string;
49 sortBy: string;
50}
51
52/**
53 * Enumerated values for the sort dir
54 */
55export enum SortDir {
56 asc = 'asc', desc = 'desc'
57}
58
59/**
60 * A structure to format sort params for table
61 * This is sent to WebSocket as part of table request
62 */
63export interface SortParams {
64 firstCol: string;
65 firstDir: SortDir;
66 secondCol: string;
67 secondDir: SortDir;
68}
69
70/**
Sean Condon2bd11b72018-06-15 08:00:48 +010071 * ONOS GUI -- Widget -- Table Base class
72 */
Sean Condon28ecc5f2018-06-25 12:50:16 +010073export abstract class TableBaseImpl {
Sean Condon2bd11b72018-06-15 08:00:48 +010074 // attributes from the interface
Sean Condon28ecc5f2018-06-25 12:50:16 +010075 protected annots: TableAnnots;
76 protected changedData: string[] = [];
77 protected payloadParams: any;
78 protected sortParams: SortParams;
79 protected selectCallback; // Function
80 protected parentSelCb = null;
81 protected responseCallback; // Function
82 selId: string = undefined;
83 tableData: any[] = [];
84 tableDataFilter: TableFilter;
85 toggleRefresh; // Function
Sean Condon2bd11b72018-06-15 08:00:48 +010086 autoRefresh: boolean = true;
87 autoRefreshTip: string = 'Toggle auto refresh'; // TODO: get LION string
Sean Condon2bd11b72018-06-15 08:00:48 +010088
89 private root: string;
90 private req: string;
91 private resp: string;
92 private refreshPromise: any = null;
93 private handlers: string[] = [];
94
95 constructor(
96 protected fs: FnService,
97 protected ls: LoadingService,
98 protected log: LogService,
99 protected wss: WebSocketService,
100 protected tag: string,
101 protected idKey: string = 'id',
Sean Condon2bd11b72018-06-15 08:00:48 +0100102 protected selCb = () => ({}) // Function
103 ) {
104 this.root = tag + 's';
105 this.req = tag + 'DataRequest';
106 this.resp = tag + 'DataResponse';
107
Sean Condon2bd11b72018-06-15 08:00:48 +0100108 this.selectCallback = this.rowSelectionCb;
109 this.toggleRefresh = () => {
110 this.autoRefresh = !this.autoRefresh;
111 this.autoRefresh ? this.startRefresh() : this.stopRefresh();
112 };
Sean Condon28ecc5f2018-06-25 12:50:16 +0100113
114 // Mapped to the search and searchBy inputs in template
115 // Changes are handled through TableFilterPipe
116 this.tableDataFilter = <TableFilter>{
117 queryStr: '',
118 queryBy: '$',
119 };
Sean Condon2bd11b72018-06-15 08:00:48 +0100120 }
121
122 init() {
123 this.wss.bindHandlers(new Map<string, (data) => void>([
124 [this.resp, (data) => this.tableDataResponseCb(data)]
125 ]));
126 this.handlers.push(this.resp);
127
128 this.annots = <TableAnnots>{
129 noRowsMsg: ''
130 };
131
132 // Now send the WebSocket request and make it repeat every 2 seconds
133 this.requestTableData();
134 this.startRefresh();
Sean Condon28ecc5f2018-06-25 12:50:16 +0100135 this.log.debug('TableBase initialized. Calling ', this.req,
136 'every', REFRESH_INTERVAL, 'ms');
Sean Condon2bd11b72018-06-15 08:00:48 +0100137 }
138
139 destroy() {
140 this.wss.unbindHandlers(this.handlers);
141 this.stopRefresh();
142 this.ls.stop();
143 }
144
145 /**
146 * A callback that executes when the table data that was requested
147 * on WebSocketService arrives.
148 *
149 * Happens every 2 seconds
150 */
151 tableDataResponseCb(data: TableResponse) {
152 this.ls.stop();
153
154 const newTableData: any[] = Array.from(data[this.root]);
155 this.annots.noRowsMsg = data.annots.no_rows_msg;
156
Sean Condon28ecc5f2018-06-25 12:50:16 +0100157 // If the parents onResp() function is set then call it
Sean Condon2bd11b72018-06-15 08:00:48 +0100158 if (this.responseCallback) {
159 this.responseCallback(data);
160 }
161 this.changedData = [];
162
163 // checks if data changed for row flashing
164 if (JSON.stringify(newTableData) !== JSON.stringify(this.tableData)) {
165 this.log.debug('table data has changed');
166 const oldTableData: any[] = this.tableData;
167 this.tableData = [ ...newTableData ]; // ES6 spread syntax
168 // only flash the row if the data already exists
169 if (oldTableData.length > 0) {
170 for (const idx in newTableData) {
171 if (!this.fs.containsObj(oldTableData, newTableData[idx])) {
172 this.changedData.push(newTableData[idx][this.idKey]);
173 }
174 }
175 }
176 }
177 }
178
179 /**
180 * Table Data Request
Sean Condon28ecc5f2018-06-25 12:50:16 +0100181 * Pass in sort parameters and the set will be returned sorted
182 * In the old GUI there was also a query parameter, but this was not
183 * implemented on the server end
Sean Condon2bd11b72018-06-15 08:00:48 +0100184 */
185 requestTableData() {
Sean Condon28ecc5f2018-06-25 12:50:16 +0100186 const p = Object.assign({}, this.sortParams, this.payloadParams);
Sean Condon2bd11b72018-06-15 08:00:48 +0100187
188 // Allow it to sit in pending events
189 if (this.wss.isConnected()) {
190 if (this.fs.debugOn('table')) {
191 this.log.debug('Table data REQUEST:', this.req, p);
192 }
193 this.wss.sendEvent(this.req, p);
194 this.ls.start();
195 }
196 }
197
198 /**
199 * Row Selected
200 */
Sean Condon28ecc5f2018-06-25 12:50:16 +0100201 rowSelectionCb(event: any, selRow: any): void {
Sean Condon2bd11b72018-06-15 08:00:48 +0100202 const selId: string = selRow[this.idKey];
203 this.selId = (this.selId === selId) ? undefined : selId;
Sean Condon28ecc5f2018-06-25 12:50:16 +0100204 this.log.debug('Row', selId, 'selected');
Sean Condon2bd11b72018-06-15 08:00:48 +0100205 if (this.parentSelCb) {
Sean Condon2bd11b72018-06-15 08:00:48 +0100206 this.parentSelCb(event, selRow);
207 }
208 }
209
210 /**
211 * autoRefresh functions
212 */
213 startRefresh() {
214 this.refreshPromise =
215 setInterval(() => {
216 if (!this.ls.waiting()) {
217 if (this.fs.debugOn('table')) {
218 this.log.debug('Refreshing ' + this.root + ' page');
219 }
220 this.requestTableData();
221 }
222 }, REFRESH_INTERVAL);
223 }
224
225 stopRefresh() {
226 if (this.refreshPromise) {
227 clearInterval(this.refreshPromise);
228 this.refreshPromise = null;
229 }
230 }
231
232 isChanged(id: string): boolean {
233 return (this.fs.inArray(id, this.changedData) === -1) ? false : true;
234 }
Sean Condon28ecc5f2018-06-25 12:50:16 +0100235
236 /**
237 * A dummy implementation of the lionFn until the response is received and the LION
238 * bundle is received from the WebSocket
239 */
240 dummyLion(key: string): string {
241 return '%' + key + '%';
242 }
243
244 /**
245 * Change the sort order of the data returned
246 *
247 * sortParams are passed to the server by WebSocket and the data is
248 * returned sorted
249 *
250 * This is usually assigned to the (click) event on a column, and the column
251 * name passed in e.g. (click)="onSort('origin')
252 * If the column that is passed in is already the firstCol, then reverse its direction
253 * If a new column is passed in, then make the existing col the 2nd sort order
254 */
255 onSort(colName: string) {
256 if (this.sortParams.firstCol === colName) {
257 if (this.sortParams.firstDir === SortDir.desc) {
258 this.sortParams.firstDir = SortDir.asc;
259 return;
260 } else {
261 this.sortParams.firstDir = SortDir.desc;
262 return;
263 }
264 } else {
265 this.sortParams.secondCol = this.sortParams.firstCol;
266 this.sortParams.secondDir = this.sortParams.firstDir;
267 this.sortParams.firstCol = colName;
268 this.sortParams.firstDir = SortDir.desc;
269 }
270 this.log.debug('Sort params', this.sortParams);
271 this.requestTableData();
272 }
273
274 sortIcon(column: string): string {
275 if (this.sortParams.firstCol === column) {
276 if (this.sortParams.firstDir === SortDir.asc) {
277 return 'upArrow';
278 } else {
279 return 'downArrow';
280 }
281 } else {
282 return '';
283 }
284 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100285}