]> 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 new upstream nautilus stable release 14.2.8
[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)
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
52 // If set, the classAddingTpl is used to enable different css for different values
53 @Input()
54 customCss?: { [css: string]: number | string | ((any) => boolean) };
55
56 columns: Array<CdTableColumn> = [];
57 tableData: KeyValueItem[];
58
59 /**
60 * The function that will be called to update the input data.
61 */
62 @Output()
63 fetchData = new EventEmitter();
64
65 constructor(private datePipe: CdDatePipe) {}
66
67 ngOnInit() {
68 this.columns = [
69 {
70 prop: 'key',
71 flexGrow: 1,
72 cellTransformation: CellTemplate.bold
73 },
74 {
75 prop: 'value',
76 flexGrow: 3
77 }
78 ];
79 if (this.customCss) {
80 this.columns[1].cellTransformation = CellTemplate.classAdding;
81 }
82 // We need to subscribe the 'fetchData' event here and not in the
83 // HTML template, otherwise the data table will display the loading
84 // indicator infinitely if data is only bound via '[data]="xyz"'.
85 // See for 'loadingIndicator' in 'TableComponent::ngOnInit()'.
86 if (this.fetchData.observers.length > 0) {
87 this.table.fetchData.subscribe(() => {
88 // Forward event triggered by the 'cd-table' data table.
89 this.fetchData.emit();
90 });
91 }
92 this.useData();
93 }
94
95 ngOnChanges() {
96 this.useData();
97 }
98
99 useData() {
100 if (!this.data) {
101 return; // Wait for data
102 }
103 this.tableData = this.makePairs(this.data);
104 }
105
106 private makePairs(data: any): KeyValueItem[] {
107 let result: KeyValueItem[] = [];
108 if (!data) {
109 return; // Wait for data
110 } else if (_.isArray(data)) {
111 result = this.makePairsFromArray(data);
112 } else if (_.isObject(data)) {
113 result = this.makePairsFromObject(data);
114 } else {
115 throw new Error('Wrong data format');
116 }
117 result = result
118 .map((item) => {
119 item.value = this.convertValue(item.value);
120 return item;
121 })
122 .filter((i) => i.value !== null);
123 return _.sortBy(this.renderObjects ? this.insertFlattenObjects(result) : result, 'key');
124 }
125
126 private makePairsFromArray(data: any[]): KeyValueItem[] {
127 let temp = [];
128 const first = data[0];
129 if (_.isArray(first)) {
130 if (first.length === 2) {
131 temp = data.map((a) => ({
132 key: a[0],
133 value: a[1]
134 }));
135 } else {
136 throw new Error(
137 `Array contains too many elements (${first.length}). ` +
138 `Needs to be of type [string, any][]`
139 );
140 }
141 } else if (_.isObject(first)) {
142 if (_.has(first, 'key') && _.has(first, 'value')) {
143 temp = [...data];
144 } else {
145 temp = data.reduce(
146 (previous: any[], item) => previous.concat(this.makePairsFromObject(item)),
147 temp
148 );
149 }
150 }
151 return temp;
152 }
153
154 private makePairsFromObject(data: object): KeyValueItem[] {
155 return Object.keys(data).map((k) => ({
156 key: k,
157 value: data[k]
158 }));
159 }
160
161 private insertFlattenObjects(data: KeyValueItem[]): any[] {
162 return _.flattenDeep(
163 data.map((item) => {
164 const value = item.value;
165 const isObject = _.isObject(value);
166 if (!isObject || _.isEmpty(value)) {
167 if (isObject) {
168 item.value = '';
169 }
170 return item;
171 }
172 return this.splitItemIntoItems(item);
173 })
174 );
175 }
176
177 /**
178 * Split item into items will call _makePairs inside _makePairs (recursion), in oder to split
179 * the object item up into items as planned.
180 */
181 private splitItemIntoItems(data: { key: string; value: object }): KeyValueItem[] {
182 return this.makePairs(data.value).map((item) => {
183 if (this.appendParentKey) {
184 item.key = data.key + ' ' + item.key;
185 }
186 return item;
187 });
188 }
189
190 private convertValue(value: any): KeyValueItem {
191 if (_.isArray(value)) {
192 if (_.isEmpty(value) && this.hideEmpty) {
193 return null;
194 }
195 value = value.map((item) => (_.isObject(item) ? JSON.stringify(item) : item)).join(', ');
196 } else if (_.isObject(value)) {
197 if ((this.hideEmpty && _.isEmpty(value)) || !this.renderObjects) {
198 return null;
199 }
200 } else if (_.isString(value)) {
201 if (value === '' && this.hideEmpty) {
202 return null;
203 }
204 if (this.isDate(value)) {
205 value = this.datePipe.transform(value) || value;
206 }
207 }
208
209 return value;
210 }
211
212 private isDate(s) {
213 const sep = '[ -:.TZ]';
214 const n = '\\d{2}' + sep;
215 // year - m - d - h : m : s . someRest Z (if UTC)
216 return s.match(new RegExp('^\\d{4}' + sep + n + n + n + n + n + '\\d*' + 'Z?$'));
217 }
218 }