blob: 41ce2391e2e159a214e4e2904792db399fb8467c [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';
Sean Condon5ca00262018-09-06 17:55:25 +010018import { LogService } from '../log.service';
Sean Condon2bd11b72018-06-15 08:00:48 +010019import { 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 Condon5ca00262018-09-06 17:55:25 +010028export interface TableAnnots {
Sean Condon2bd11b72018-06-15 08:00:48 +010029 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
Bhavesh72ead492018-07-19 16:29:18 +053070export interface PayloadParams {
71 devId: string;
72}
73
74
Sean Condon28ecc5f2018-06-25 12:50:16 +010075/**
Sean Condon2bd11b72018-06-15 08:00:48 +010076 * ONOS GUI -- Widget -- Table Base class
77 */
Sean Condon28ecc5f2018-06-25 12:50:16 +010078export abstract class TableBaseImpl {
Sean Condon2bd11b72018-06-15 08:00:48 +010079 // attributes from the interface
Sean Condon87b78502018-09-17 20:53:24 +010080 public annots: TableAnnots;
Sean Condon28ecc5f2018-06-25 12:50:16 +010081 protected changedData: string[] = [];
Bhavesh72ead492018-07-19 16:29:18 +053082 protected payloadParams: PayloadParams;
Sean Condon28ecc5f2018-06-25 12:50:16 +010083 protected sortParams: SortParams;
Sean Condon87b78502018-09-17 20:53:24 +010084 public selectCallback; // Function
Sean Condon28ecc5f2018-06-25 12:50:16 +010085 protected parentSelCb = null;
86 protected responseCallback; // Function
87 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
94 private root: string;
95 private req: string;
96 private resp: string;
97 private refreshPromise: any = null;
98 private handlers: string[] = [];
99
100 constructor(
101 protected fs: FnService,
102 protected ls: LoadingService,
103 protected log: LogService,
104 protected wss: WebSocketService,
105 protected tag: string,
106 protected idKey: string = 'id',
Sean Condon2bd11b72018-06-15 08:00:48 +0100107 protected selCb = () => ({}) // Function
108 ) {
109 this.root = tag + 's';
110 this.req = tag + 'DataRequest';
111 this.resp = tag + 'DataResponse';
112
Sean Condon2bd11b72018-06-15 08:00:48 +0100113 this.selectCallback = this.rowSelectionCb;
114 this.toggleRefresh = () => {
115 this.autoRefresh = !this.autoRefresh;
116 this.autoRefresh ? this.startRefresh() : this.stopRefresh();
117 };
Sean Condon28ecc5f2018-06-25 12:50:16 +0100118
119 // Mapped to the search and searchBy inputs in template
120 // Changes are handled through TableFilterPipe
121 this.tableDataFilter = <TableFilter>{
122 queryStr: '',
123 queryBy: '$',
124 };
Sean Condon2bd11b72018-06-15 08:00:48 +0100125 }
126
127 init() {
128 this.wss.bindHandlers(new Map<string, (data) => void>([
129 [this.resp, (data) => this.tableDataResponseCb(data)]
130 ]));
131 this.handlers.push(this.resp);
132
133 this.annots = <TableAnnots>{
134 noRowsMsg: ''
135 };
136
137 // Now send the WebSocket request and make it repeat every 2 seconds
138 this.requestTableData();
139 this.startRefresh();
Sean Condon28ecc5f2018-06-25 12:50:16 +0100140 this.log.debug('TableBase initialized. Calling ', this.req,
141 'every', REFRESH_INTERVAL, 'ms');
Sean Condon2bd11b72018-06-15 08:00:48 +0100142 }
143
144 destroy() {
145 this.wss.unbindHandlers(this.handlers);
146 this.stopRefresh();
147 this.ls.stop();
148 }
149
150 /**
151 * A callback that executes when the table data that was requested
152 * on WebSocketService arrives.
153 *
154 * Happens every 2 seconds
155 */
156 tableDataResponseCb(data: TableResponse) {
157 this.ls.stop();
158
159 const newTableData: any[] = Array.from(data[this.root]);
160 this.annots.noRowsMsg = data.annots.no_rows_msg;
161
Sean Condon28ecc5f2018-06-25 12:50:16 +0100162 // If the parents onResp() function is set then call it
Sean Condon2bd11b72018-06-15 08:00:48 +0100163 if (this.responseCallback) {
164 this.responseCallback(data);
165 }
166 this.changedData = [];
167
168 // checks if data changed for row flashing
169 if (JSON.stringify(newTableData) !== JSON.stringify(this.tableData)) {
170 this.log.debug('table data has changed');
171 const oldTableData: any[] = this.tableData;
Bhavesh72ead492018-07-19 16:29:18 +0530172 this.tableData = [...newTableData]; // ES6 spread syntax
Sean Condon2bd11b72018-06-15 08:00:48 +0100173 // only flash the row if the data already exists
174 if (oldTableData.length > 0) {
175 for (const idx in newTableData) {
176 if (!this.fs.containsObj(oldTableData, newTableData[idx])) {
177 this.changedData.push(newTableData[idx][this.idKey]);
178 }
179 }
180 }
181 }
182 }
183
184 /**
185 * Table Data Request
Sean Condon28ecc5f2018-06-25 12:50:16 +0100186 * Pass in sort parameters and the set will be returned sorted
187 * In the old GUI there was also a query parameter, but this was not
188 * implemented on the server end
Sean Condon2bd11b72018-06-15 08:00:48 +0100189 */
190 requestTableData() {
Sean Condon28ecc5f2018-06-25 12:50:16 +0100191 const p = Object.assign({}, this.sortParams, this.payloadParams);
Sean Condon2bd11b72018-06-15 08:00:48 +0100192
193 // Allow it to sit in pending events
194 if (this.wss.isConnected()) {
195 if (this.fs.debugOn('table')) {
196 this.log.debug('Table data REQUEST:', this.req, p);
197 }
198 this.wss.sendEvent(this.req, p);
199 this.ls.start();
200 }
201 }
202
203 /**
204 * Row Selected
205 */
Sean Condon28ecc5f2018-06-25 12:50:16 +0100206 rowSelectionCb(event: any, selRow: any): void {
Sean Condon2bd11b72018-06-15 08:00:48 +0100207 const selId: string = selRow[this.idKey];
208 this.selId = (this.selId === selId) ? undefined : selId;
Sean Condon28ecc5f2018-06-25 12:50:16 +0100209 this.log.debug('Row', selId, 'selected');
Sean Condon2bd11b72018-06-15 08:00:48 +0100210 if (this.parentSelCb) {
Sean Condon2bd11b72018-06-15 08:00:48 +0100211 this.parentSelCb(event, selRow);
212 }
213 }
214
215 /**
216 * autoRefresh functions
217 */
218 startRefresh() {
219 this.refreshPromise =
220 setInterval(() => {
221 if (!this.ls.waiting()) {
222 if (this.fs.debugOn('table')) {
223 this.log.debug('Refreshing ' + this.root + ' page');
224 }
225 this.requestTableData();
226 }
227 }, REFRESH_INTERVAL);
228 }
229
230 stopRefresh() {
231 if (this.refreshPromise) {
232 clearInterval(this.refreshPromise);
233 this.refreshPromise = null;
234 }
235 }
236
237 isChanged(id: string): boolean {
238 return (this.fs.inArray(id, this.changedData) === -1) ? false : true;
239 }
Sean Condon28ecc5f2018-06-25 12:50:16 +0100240
241 /**
242 * A dummy implementation of the lionFn until the response is received and the LION
243 * bundle is received from the WebSocket
244 */
245 dummyLion(key: string): string {
246 return '%' + key + '%';
247 }
248
249 /**
250 * Change the sort order of the data returned
251 *
252 * sortParams are passed to the server by WebSocket and the data is
253 * returned sorted
254 *
255 * This is usually assigned to the (click) event on a column, and the column
256 * name passed in e.g. (click)="onSort('origin')
257 * If the column that is passed in is already the firstCol, then reverse its direction
258 * If a new column is passed in, then make the existing col the 2nd sort order
259 */
260 onSort(colName: string) {
261 if (this.sortParams.firstCol === colName) {
262 if (this.sortParams.firstDir === SortDir.desc) {
263 this.sortParams.firstDir = SortDir.asc;
264 return;
265 } else {
266 this.sortParams.firstDir = SortDir.desc;
267 return;
268 }
269 } else {
270 this.sortParams.secondCol = this.sortParams.firstCol;
271 this.sortParams.secondDir = this.sortParams.firstDir;
272 this.sortParams.firstCol = colName;
273 this.sortParams.firstDir = SortDir.desc;
274 }
275 this.log.debug('Sort params', this.sortParams);
276 this.requestTableData();
277 }
278
279 sortIcon(column: string): string {
280 if (this.sortParams.firstCol === column) {
281 if (this.sortParams.firstDir === SortDir.asc) {
282 return 'upArrow';
283 } else {
284 return 'downArrow';
285 }
286 } else {
287 return '';
288 }
289 }
Bhavesh72ead492018-07-19 16:29:18 +0530290
291 /**
292 * De-selects the row
293 */
294 deselectRow(event) {
295 this.log.debug('Details panel close event');
296 this.selId = event;
297 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100298}