]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.ts
import 15.2.0 Octopus source
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / datatable / table-key-value / table-key-value.component.ts
1 import {
2 Component,
3 EventEmitter,
4 Input,
5 OnChanges,
6 OnInit,
7 Output,
8 ViewChild
9 } from '@angular/core';
10
11 import * as _ from 'lodash';
12
13 import { CellTemplate } from '../../enum/cell-template.enum';
14 import { CdTableColumn } from '../../models/cd-table-column';
15 import { CdDatePipe } from '../../pipes/cd-date.pipe';
16 import { TableComponent } from '../table/table.component';
17
18 interface KeyValueItem {
19 key: string;
20 value: any;
21 }
22
23 /**
24 * Display the given data in a 2 column data table. The left column
25 * shows the 'key' attribute, the right column the 'value' attribute.
26 * The data table has the following characteristics:
27 * - No header and footer is displayed
28 * - The relation of the width for the columns 'key' and 'value' is 1:3
29 * - The 'key' column is displayed in bold text
30 */
31 @Component({
32 selector: 'cd-table-key-value',
33 templateUrl: './table-key-value.component.html',
34 styleUrls: ['./table-key-value.component.scss']
35 })
36 export class TableKeyValueComponent implements OnInit, OnChanges {
37 @ViewChild(TableComponent, { static: true })
38 table: TableComponent;
39
40 @Input()
41 data: any;
42 @Input()
43 autoReload: any = 5000;
44 @Input()
45 renderObjects = false;
46 // Only used if objects are rendered
47 @Input()
48 appendParentKey = true;
49 @Input()
50 hideEmpty = false;
51 @Input()
52 hideKeys: string[] = []; // Keys of pairs not to be displayed
53
54 // If set, the classAddingTpl is used to enable different css for different values
55 @Input()
56 customCss?: { [css: string]: number | string | ((any: any) => boolean) };
57
58 columns: Array<CdTableColumn> = [];
59 tableData: KeyValueItem[];
60
61 /**
62 * The function that will be called to update the input data.
63 */
64 @Output()
65 fetchData = new EventEmitter();
66
67 constructor(private datePipe: CdDatePipe) {}
68
69 ngOnInit() {
70 this.columns = [
71 {
72 prop: 'key',
73 flexGrow: 1,
74 cellTransformation: CellTemplate.bold
75 },
76 {
77 prop: 'value',
78 flexGrow: 3
79 }
80 ];
81 if (this.customCss) {
82 this.columns[1].cellTransformation = CellTemplate.classAdding;
83 }
84 // We need to subscribe the 'fetchData' event here and not in the
85 // HTML template, otherwise the data table will display the loading
86 // indicator infinitely if data is only bound via '[data]="xyz"'.
87 // See for 'loadingIndicator' in 'TableComponent::ngOnInit()'.
88 if (this.fetchData.observers.length > 0) {
89 this.table.fetchData.subscribe(() => {
90 // Forward event triggered by the 'cd-table' data table.
91 this.fetchData.emit();
92 });
93 }
94 this.useData();
95 }
96
97 ngOnChanges() {
98 this.useData();
99 }
100
101 useData() {
102 if (!this.data) {
103 return; // Wait for data
104 }
105 let pairs = this.makePairs(this.data);
106 if (this.hideKeys) {
107 pairs = pairs.filter((pair) => !this.hideKeys.includes(pair.key));
108 }
109 this.tableData = pairs;
110 }
111
112 private makePairs(data: any): KeyValueItem[] {
113 let result: KeyValueItem[] = [];
114 if (!data) {
115 return undefined; // Wait for data
116 } else if (_.isArray(data)) {
117 result = this.makePairsFromArray(data);
118 } else if (_.isObject(data)) {
119 result = this.makePairsFromObject(data);
120 } else {
121 throw new Error('Wrong data format');
122 }
123 result = result
124 .map((item) => {
125 item.value = this.convertValue(item.value);
126 return item;
127 })
128 .filter((i) => i.value !== null);
129 return _.sortBy(this.renderObjects ? this.insertFlattenObjects(result) : result, 'key');
130 }
131
132 private makePairsFromArray(data: any[]): KeyValueItem[] {
133 let temp: any[] = [];
134 const first = data[0];
135 if (_.isArray(first)) {
136 if (first.length === 2) {
137 temp = data.map((a) => ({
138 key: a[0],
139 value: a[1]
140 }));
141 } else {
142 throw new Error(
143 `Array contains too many elements (${first.length}). ` +
144 `Needs to be of type [string, any][]`
145 );
146 }
147 } else if (_.isObject(first)) {
148 if (_.has(first, 'key') && _.has(first, 'value')) {
149 temp = [...data];
150 } else {
151 temp = data.reduce(
152 (previous: any[], item) => previous.concat(this.makePairsFromObject(item)),
153 temp
154 );
155 }
156 }
157 return temp;
158 }
159
160 private makePairsFromObject(data: any): KeyValueItem[] {
161 return Object.keys(data).map((k) => ({
162 key: k,
163 value: data[k]
164 }));
165 }
166
167 private insertFlattenObjects(data: KeyValueItem[]): any[] {
168 return _.flattenDeep(
169 data.map((item) => {
170 const value = item.value;
171 const isObject = _.isObject(value);
172 if (!isObject || _.isEmpty(value)) {
173 if (isObject) {
174 item.value = '';
175 }
176 return item;
177 }
178 return this.splitItemIntoItems(item);
179 })
180 );
181 }
182
183 /**
184 * Split item into items will call _makePairs inside _makePairs (recursion), in oder to split
185 * the object item up into items as planned.
186 */
187 private splitItemIntoItems(data: { key: string; value: object }): KeyValueItem[] {
188 return this.makePairs(data.value).map((item) => {
189 if (this.appendParentKey) {
190 item.key = data.key + ' ' + item.key;
191 }
192 return item;
193 });
194 }
195
196 private convertValue(value: any): KeyValueItem {
197 if (_.isArray(value)) {
198 if (_.isEmpty(value) && this.hideEmpty) {
199 return null;
200 }
201 value = value.map((item) => (_.isObject(item) ? JSON.stringify(item) : item)).join(', ');
202 } else if (_.isObject(value)) {
203 if ((this.hideEmpty && _.isEmpty(value)) || !this.renderObjects) {
204 return null;
205 }
206 } else if (_.isString(value)) {
207 if (value === '' && this.hideEmpty) {
208 return null;
209 }
210 if (this.isDate(value)) {
211 value = this.datePipe.transform(value) || value;
212 }
213 }
214
215 return value;
216 }
217
218 private isDate(s: string) {
219 const sep = '[ -:.TZ]';
220 const n = '\\d{2}' + sep;
221 // year - m - d - h : m : s . someRest Z (if UTC)
222 return s.match(new RegExp('^\\d{4}' + sep + n + n + n + n + n + '\\d*' + 'Z?$'));
223 }
224 }