@iobroker/json-config 6.17.13 → 6.17.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/build/JsonConfig.d.ts +3 -4
  2. package/build/JsonConfig.js +2 -2
  3. package/build/JsonConfig.js.map +1 -1
  4. package/build/JsonConfigComponent/ChipInput.js +1 -0
  5. package/build/JsonConfigComponent/ChipInput.js.map +1 -1
  6. package/build/JsonConfigComponent/ConfigAccordion.d.ts +1 -1
  7. package/build/JsonConfigComponent/ConfigAccordion.js.map +1 -1
  8. package/build/JsonConfigComponent/ConfigCertificateSelect.js +1 -0
  9. package/build/JsonConfigComponent/ConfigCertificateSelect.js.map +1 -1
  10. package/build/JsonConfigComponent/ConfigCertificates.js +1 -0
  11. package/build/JsonConfigComponent/ConfigCertificates.js.map +1 -1
  12. package/build/JsonConfigComponent/ConfigCheckLicense.d.ts +12 -1
  13. package/build/JsonConfigComponent/ConfigCheckLicense.js +13 -50
  14. package/build/JsonConfigComponent/ConfigCheckLicense.js.map +1 -1
  15. package/build/JsonConfigComponent/ConfigDeviceManager.js +11 -2
  16. package/build/JsonConfigComponent/ConfigDeviceManager.js.map +1 -1
  17. package/build/JsonConfigComponent/ConfigPanel.js.map +1 -1
  18. package/build/JsonConfigComponent/ConfigTable.js.map +1 -1
  19. package/build/JsonConfigComponent/wrapper/Components/CustomModal.d.ts +1 -1
  20. package/build/JsonConfigComponent/wrapper/Components/CustomModal.js +5 -1
  21. package/build/JsonConfigComponent/wrapper/Components/CustomModal.js.map +1 -1
  22. package/build/JsonConfigComponent/wrapper/Components/Editor.d.ts +7 -13
  23. package/build/JsonConfigComponent/wrapper/Components/Editor.js +9 -23
  24. package/build/JsonConfigComponent/wrapper/Components/Editor.js.map +1 -1
  25. package/build/index.d.ts +1 -0
  26. package/build/index.js.map +1 -1
  27. package/package.json +2 -4
  28. package/src/JsonConfig.tsx +0 -710
  29. package/src/JsonConfigComponent/ChipInput.tsx +0 -752
  30. package/src/JsonConfigComponent/ConfigAccordion.tsx +0 -278
  31. package/src/JsonConfigComponent/ConfigAlive.tsx +0 -74
  32. package/src/JsonConfigComponent/ConfigAutocomplete.tsx +0 -108
  33. package/src/JsonConfigComponent/ConfigAutocompleteSendTo.tsx +0 -183
  34. package/src/JsonConfigComponent/ConfigCRON.jsx +0 -101
  35. package/src/JsonConfigComponent/ConfigCertCollection.tsx +0 -102
  36. package/src/JsonConfigComponent/ConfigCertificateSelect.tsx +0 -92
  37. package/src/JsonConfigComponent/ConfigCertificates.tsx +0 -202
  38. package/src/JsonConfigComponent/ConfigCheckLicense.jsx +0 -662
  39. package/src/JsonConfigComponent/ConfigCheckbox.tsx +0 -67
  40. package/src/JsonConfigComponent/ConfigChip.jsx +0 -81
  41. package/src/JsonConfigComponent/ConfigColor.tsx +0 -86
  42. package/src/JsonConfigComponent/ConfigCoordinates.tsx +0 -234
  43. package/src/JsonConfigComponent/ConfigCustom.tsx +0 -246
  44. package/src/JsonConfigComponent/ConfigDatePicker.tsx +0 -48
  45. package/src/JsonConfigComponent/ConfigDeviceManager.tsx +0 -33
  46. package/src/JsonConfigComponent/ConfigFile.jsx +0 -181
  47. package/src/JsonConfigComponent/ConfigFileSelector.jsx +0 -520
  48. package/src/JsonConfigComponent/ConfigFunc.jsx +0 -90
  49. package/src/JsonConfigComponent/ConfigGeneric.tsx +0 -1027
  50. package/src/JsonConfigComponent/ConfigIP.jsx +0 -96
  51. package/src/JsonConfigComponent/ConfigImageSendTo.jsx +0 -79
  52. package/src/JsonConfigComponent/ConfigImageUpload.jsx +0 -114
  53. package/src/JsonConfigComponent/ConfigInstanceSelect.jsx +0 -172
  54. package/src/JsonConfigComponent/ConfigInterface.jsx +0 -112
  55. package/src/JsonConfigComponent/ConfigJsonEditor.jsx +0 -103
  56. package/src/JsonConfigComponent/ConfigLanguage.tsx +0 -153
  57. package/src/JsonConfigComponent/ConfigLicense.jsx +0 -148
  58. package/src/JsonConfigComponent/ConfigNumber.tsx +0 -207
  59. package/src/JsonConfigComponent/ConfigObjectId.jsx +0 -113
  60. package/src/JsonConfigComponent/ConfigPanel.tsx +0 -360
  61. package/src/JsonConfigComponent/ConfigPassword.jsx +0 -160
  62. package/src/JsonConfigComponent/ConfigPattern.jsx +0 -50
  63. package/src/JsonConfigComponent/ConfigPort.tsx +0 -232
  64. package/src/JsonConfigComponent/ConfigRoom.jsx +0 -90
  65. package/src/JsonConfigComponent/ConfigSelect.jsx +0 -124
  66. package/src/JsonConfigComponent/ConfigSelectSendTo.tsx +0 -251
  67. package/src/JsonConfigComponent/ConfigSendto.tsx +0 -340
  68. package/src/JsonConfigComponent/ConfigSetState.jsx +0 -116
  69. package/src/JsonConfigComponent/ConfigSlider.jsx +0 -97
  70. package/src/JsonConfigComponent/ConfigStaticDivider.jsx +0 -51
  71. package/src/JsonConfigComponent/ConfigStaticHeader.jsx +0 -63
  72. package/src/JsonConfigComponent/ConfigStaticImage.jsx +0 -48
  73. package/src/JsonConfigComponent/ConfigStaticText.jsx +0 -72
  74. package/src/JsonConfigComponent/ConfigTable.tsx +0 -1040
  75. package/src/JsonConfigComponent/ConfigTabs.tsx +0 -150
  76. package/src/JsonConfigComponent/ConfigText.tsx +0 -188
  77. package/src/JsonConfigComponent/ConfigTextSendTo.tsx +0 -102
  78. package/src/JsonConfigComponent/ConfigTimePicker.tsx +0 -63
  79. package/src/JsonConfigComponent/ConfigTopic.jsx +0 -78
  80. package/src/JsonConfigComponent/ConfigUUID.tsx +0 -55
  81. package/src/JsonConfigComponent/ConfigUser.jsx +0 -104
  82. package/src/JsonConfigComponent/index.tsx +0 -435
  83. package/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx +0 -145
  84. package/src/JsonConfigComponent/wrapper/Components/Editor.jsx +0 -65
  85. package/src/Utils.jsx +0 -1683
  86. package/src/index.tsx +0 -14
  87. package/src/types.d.ts +0 -372
@@ -1,710 +0,0 @@
1
- import React from 'react';
2
- import { type Styles, withStyles } from '@mui/styles';
3
- import JSON5 from 'json5';
4
- import MD5 from 'crypto-js/md5';
5
-
6
- import {
7
- Fab,
8
- Tooltip,
9
- LinearProgress,
10
- } from '@mui/material';
11
- import { Publish as PublishIcon } from '@mui/icons-material';
12
-
13
- import {
14
- I18n,
15
- Router,
16
- SaveCloseButtons,
17
- Theme as theme,
18
- Confirm as ConfirmDialog,
19
- AdminConnection,
20
- } from '@iobroker/adapter-react-v5';
21
-
22
- import type { Theme, ThemeName, ThemeType } from '@iobroker/adapter-react-v5/types';
23
- import type { ConfigItemAny, ConfigItemPanel, ConfigItemTabs } from '#JC/types';
24
- import Utils from '#JC/Utils';
25
- import ConfigGeneric from './JsonConfigComponent/ConfigGeneric';
26
- import JsonConfigComponent from './JsonConfigComponent';
27
-
28
- const styles = {
29
- root: {
30
- width: '100%',
31
- height: '100%',
32
- overflow: 'hidden',
33
- position: 'relative',
34
- },
35
- scroll: {
36
- height: 'calc(100% - 48px)',
37
- overflowY: 'auto',
38
- },
39
- exportImportButtons: {
40
- position: 'absolute',
41
- top: 5,
42
- right: 0,
43
- zIndex: 3,
44
- },
45
- button: {
46
- marginRight: 5,
47
- },
48
- } satisfies Styles<any, any>;
49
-
50
- /**
51
- * Decrypt the password/value with given key
52
- * @param key - Secret key
53
- * @param value - value to decrypt
54
- */
55
- function decryptLegacy(key: string, value: string): string {
56
- let result = '';
57
- for (let i = 0; i < value.length; i++) {
58
- // eslint-disable-next-line no-bitwise
59
- result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
60
- }
61
- return result;
62
- }
63
-
64
- /**
65
- * Encrypt the password/value with given key
66
- * @param key - Secret key
67
- * @param value - value to encrypt
68
- */
69
- function encryptLegacy(key: string, value: string): string {
70
- let result = '';
71
- for (let i = 0; i < value.length; i++) {
72
- // eslint-disable-next-line no-bitwise
73
- result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
74
- }
75
- return result;
76
- }
77
-
78
- /**
79
- * Decrypt the password/value with given key
80
- * Usage:
81
- * ```js
82
- * function load(settings, onChange) {
83
- * if (settings.password) {
84
- * settings.password = decrypt(systemSecret, settings.password);
85
- * // same as
86
- * settings.password = decrypt(settings.password);
87
- * }
88
- * // ...
89
- * }
90
- * ```
91
- * @param key - Secret key
92
- * @param value - value to decrypt
93
- */
94
- function decrypt(key: string, value: string): string {
95
- if (typeof value !== 'string') {
96
- return value;
97
- }
98
-
99
- // if not encrypted as aes-192 or key not a valid 48-digit hex -> fallback
100
- if (!value.startsWith('$/aes-192-cbc:') || !/^[0-9a-f]{48}$/.test(key)) {
101
- return decryptLegacy(key, value);
102
- }
103
-
104
- // algorithm:iv:encryptedValue
105
- const textParts = value.split(':', 3);
106
-
107
- const _key = window.CryptoJS.enc.Hex.parse(key);
108
- const iv = window.CryptoJS.enc.Hex.parse(textParts[1]);
109
-
110
- const cipherParams = window.CryptoJS.lib.CipherParams.create({ ciphertext: window.CryptoJS.enc.Hex.parse(textParts[2]) });
111
-
112
- const decryptedBinary = window.CryptoJS.AES.decrypt(cipherParams, _key, { iv });
113
-
114
- return window.CryptoJS.enc.Utf8.stringify(decryptedBinary);
115
- }
116
-
117
- /**
118
- * Encrypt the password/value with given key
119
- * Usage:
120
- * ```
121
- * function save(callback) {
122
- * ...
123
- * if (obj.password) {
124
- * obj.password = encrypt(systemSecret, obj.password);
125
- * // same as
126
- * obj.password = decrypt(obj.password);
127
- * }
128
- * ...
129
- * }
130
- * ```
131
- * @param key - Secret key
132
- * @param value - value to encrypt
133
- * @param _iv - optional initial vector for tests
134
- */
135
- function encrypt(key: string, value: string, _iv?: string): string {
136
- if (typeof value !== 'string') {
137
- return value;
138
- }
139
-
140
- if (!/^[0-9a-f]{48}$/.test(key)) {
141
- // key length is not matching for AES-192-CBC or key is no valid hex - fallback to old encryption
142
- return encryptLegacy(key, value);
143
- }
144
-
145
- let iv;
146
- if (_iv) {
147
- iv = window.CryptoJS.enc.Hex.parse(_iv);
148
- } else {
149
- iv = window.CryptoJS.lib.WordArray.random(128 / 8);
150
- }
151
-
152
- const _key = window.CryptoJS.enc.Hex.parse(key);
153
- const encrypted = window.CryptoJS.AES.encrypt(value, _key, { iv }).ciphertext;
154
-
155
- return `$/aes-192-cbc:${window.CryptoJS.enc.Hex.stringify(iv)}:${encrypted}`;
156
- }
157
-
158
- function loadScript(src: string, id: string) {
159
- if (!id || !document.getElementById(id)) {
160
- return new Promise(resolve => {
161
- const script = document.createElement('script');
162
- script.setAttribute('id', id);
163
- script.onload = resolve;
164
- script.src = src;
165
- document.getElementsByTagName('head')[0].appendChild(script);
166
- });
167
- }
168
- return document.getElementById(id)?.onload;
169
- }
170
-
171
- interface BufferObject {
172
- type: 'Buffer';
173
- data: Buffer;
174
- }
175
-
176
- interface JsonConfigProps {
177
- menuPadding: number;
178
- adapterName: string;
179
- instance: number;
180
- isFloatComma: boolean;
181
- dateFormat: string;
182
- secret: string;
183
- socket: AdminConnection;
184
- theme: Record<string, any>;
185
- themeName: ThemeName;
186
- themeType: ThemeType;
187
- /** CSS classes */
188
- classes: Record<string, any>;
189
- /** Translate method */
190
- t: typeof I18n.t;
191
- configStored: (notChanged: boolean) => void;
192
- width: 'xs' | 'sm' | 'md';
193
- }
194
-
195
- interface JsonConfigState {
196
- schema?: ConfigItemPanel | ConfigItemTabs;
197
- data?: Record<string, unknown>;
198
- originalData?: Record<string, unknown>;
199
- updateData: number;
200
- common?: ioBroker.InstanceCommon;
201
- changed: boolean;
202
- confirmDialog: boolean;
203
- theme: Theme;
204
- saveConfigDialog: boolean;
205
- hash: string;
206
- error?: boolean;
207
- }
208
-
209
- class JsonConfig extends Router<JsonConfigProps, JsonConfigState> {
210
- private fileSubscribed: string[] = [];
211
-
212
- private fileLangSubscribed = '';
213
-
214
- private secret = '';
215
-
216
- constructor(props: JsonConfigProps) {
217
- super(props);
218
-
219
- this.state = {
220
- updateData: 0,
221
- changed: false,
222
- confirmDialog: false,
223
- theme: theme(props.themeName), // buttons require special theme
224
- saveConfigDialog: false,
225
- hash: '_',
226
- };
227
-
228
- this.getInstanceObject()
229
- .then(obj => this.getConfigFile()
230
- .then(schema =>
231
- // load language
232
- // @ts-expect-error it has the static method
233
- JsonConfigComponent.loadI18n(this.props.socket, schema?.i18n, this.props.adapterName)
234
- .then((langFileName: string) => {
235
- if (langFileName) {
236
- // subscribe on changes
237
- if (!this.fileLangSubscribed) {
238
- this.fileLangSubscribed = langFileName;
239
- this.props.socket.subscribeFiles(`${this.props.adapterName}.admin`, this.fileLangSubscribed, this.onFileChange);
240
- }
241
- }
242
-
243
- if (obj) {
244
- this.setState({
245
- schema,
246
- data: obj.native,
247
- common: obj.common,
248
- // @ts-expect-error really no string?
249
- hash: MD5(JSON.stringify(schema)),
250
- });
251
- } else {
252
- window.alert(`Instance system.adapter.${this.props.adapterName}.${this.props.instance} not found!`);
253
- }
254
- })));
255
- }
256
-
257
- async componentWillUnmount(): Promise<void> {
258
- super.componentWillUnmount();
259
- if (this.fileSubscribed.length) {
260
- this.props.socket.unsubscribeFiles(`${this.props.adapterName}.admin`, this.fileSubscribed, this.onFileChange);
261
- this.fileSubscribed = [];
262
- }
263
- if (this.fileLangSubscribed) {
264
- this.props.socket.unsubscribeFiles(`${this.props.adapterName}.admin`, this.fileLangSubscribed, this.onFileChange);
265
- this.fileLangSubscribed = '';
266
- }
267
- }
268
-
269
- /**
270
- * @private
271
- * @param evt
272
- */
273
- handleFileSelect = (evt: Record<string, any>): void => {
274
- const f = evt.target.files[0];
275
- if (f) {
276
- const r = new FileReader();
277
- r.onload = async e => {
278
- if (!e.target) {
279
- return;
280
- }
281
-
282
- const contents = e.target.result as string;
283
- try {
284
- const data = JSON.parse(contents);
285
- this.setState({ data, changed: JSON.stringify(data) !== JSON.stringify(this.state.originalData) });
286
- } catch (err) {
287
- window.alert(I18n.t('[JsonConfig] Failed to parse JSON file'));
288
- }
289
- };
290
- r.readAsText(f);
291
- } else {
292
- window.alert(I18n.t('[JsonConfig] Failed to open JSON File'));
293
- }
294
- };
295
-
296
- getExportImportButtons(): React.JSX.Element {
297
- return <div className={this.props.classes.exportImportButtons}>
298
- <Tooltip title={this.props.t('Import settings from JSON file')}>
299
- <Fab
300
- size="small"
301
- classes={{ root: this.props.classes.button }}
302
- onClick={() => {
303
- const input = document.createElement('input');
304
- input.setAttribute('type', 'file');
305
- input.setAttribute('id', 'files');
306
- // @ts-expect-error check
307
- input.setAttribute('opacity', 0);
308
- input.addEventListener('change', e => this.handleFileSelect(e), false);
309
- input.click();
310
- }}
311
- >
312
- <PublishIcon />
313
- </Fab>
314
- </Tooltip>
315
- <Tooltip title={this.props.t('Export setting to JSON file')}>
316
- <Fab
317
- size="small"
318
- classes={{ root: this.props.classes.button }}
319
- onClick={() => {
320
- if (!this.state.data) {
321
- return;
322
- }
323
-
324
- Utils.generateFile(`${this.props.adapterName}.${this.props.instance}.json`, this.state.data);
325
- }}
326
- >
327
- <PublishIcon style={{ transform: 'rotate(180deg)' }} />
328
- </Fab>
329
- </Tooltip>
330
- </div>;
331
- }
332
-
333
- onFileChange = async (id: string, fileName: string, size: number): Promise<void> => {
334
- if (id === `${this.props.adapterName}.admin` && size) {
335
- if (fileName === this.fileLangSubscribed) {
336
- try {
337
- // @ts-expect-error needs types
338
- await JsonConfigComponent.loadI18n(this.props.socket, this.state.schema?.i18n, this.props.adapterName);
339
- this.setState({ hash: `${this.state.hash}1` });
340
- } catch {
341
- // ignore errors
342
- }
343
- } else if (this.fileSubscribed.includes(fileName)) {
344
- try {
345
- const schema = await this.getConfigFile(this.fileSubscribed[0]);
346
- // @ts-expect-error really no string?
347
- this.setState({ schema, hash: MD5(JSON.stringify(schema)) });
348
- } catch {
349
- // ignore errors
350
- }
351
- }
352
- }
353
- };
354
-
355
- async getInstanceObject(): Promise<ioBroker.InstanceObject | null> {
356
- try {
357
- const obj = await this.props.socket.getObject(`system.adapter.${this.props.adapterName}.${this.props.instance}`);
358
- // decode all native attributes listed in obj.encryptedNative
359
- if (Array.isArray(obj.encryptedNative)) {
360
- const systemConfig = await this.props.socket.getSystemConfig();
361
- await loadScript('../../lib/js/crypto-js/crypto-js.js', 'crypto-js');
362
- this.secret = systemConfig.native.secret;
363
- obj.encryptedNative?.forEach(attr => {
364
- if (obj.native[attr]) {
365
- obj.native[attr] = decrypt(this.secret, obj.native[attr]);
366
- }
367
- });
368
- return obj;
369
- }
370
- return obj;
371
- } catch (e) {
372
- window.alert(`[JsonConfig] Cannot read instance object: ${e}`);
373
- }
374
- return null;
375
- }
376
-
377
- renderConfirmDialog(): React.JSX.Element | null {
378
- if (!this.state.confirmDialog) {
379
- return null;
380
- }
381
- return <ConfirmDialog
382
- title={I18n.t('ra_Please confirm')}
383
- text={I18n.t('ra_Some data are not stored. Discard?')}
384
- ok={I18n.t('ra_Discard')}
385
- cancel={I18n.t('ra_Cancel')}
386
- onClose={isYes =>
387
- this.setState({ confirmDialog: false }, () => isYes && Router.doNavigate(null))}
388
- />;
389
- }
390
-
391
- async scanForInclude(json: Record<string, any>, filePaths: string[]): Promise<Record<string, any>> {
392
- if (typeof json['#include'] === 'string') {
393
- // load file
394
- const data = await this._getConfigFile(json['#include'], [...filePaths]);
395
- delete json['#include'];
396
- if (data) {
397
- // merge data
398
- json = { ...json, ...data };
399
- }
400
- return json;
401
- }
402
- const keys = Object.keys(json);
403
- for (let k = 0; k < keys.length; k++) {
404
- if (typeof json[keys[k]] === 'object') {
405
- json[keys[k]] = await this.scanForInclude(json[keys[k]], filePaths);
406
- }
407
- }
408
- return json;
409
- }
410
-
411
- async getConfigFile(fileName?: string): Promise<ConfigItemPanel | ConfigItemTabs> {
412
- return this._getConfigFile(fileName);
413
- }
414
-
415
- async _getConfigFile(fileName?: string, _filePaths?: string[]): Promise<ConfigItemPanel | ConfigItemTabs> {
416
- fileName = fileName || 'jsonConfig.json5';
417
- _filePaths = _filePaths || [];
418
-
419
- if (_filePaths.includes(fileName)) {
420
- window.alert(`[JsonConfig] Circular reference in file: ${fileName} => ${_filePaths.join(' => ')}`);
421
- return null;
422
- }
423
- _filePaths.push(fileName);
424
-
425
- try {
426
- const exist = await this.props.socket.fileExists(`${this.props.adapterName}.admin`, fileName);
427
- if (!exist) {
428
- fileName = 'jsonConfig.json';
429
- }
430
- const data: {
431
- file: string;
432
- mimeType: string;
433
- } = await this.props.socket.readFile(`${this.props.adapterName}.admin`, fileName);
434
- let content = '';
435
- let file: string | BufferObject = '';
436
-
437
- if (data.file !== undefined) {
438
- file = data.file;
439
- }
440
-
441
- if (typeof file === 'string') {
442
- content = file;
443
- // @ts-expect-error revisit
444
- } else if (file.type === 'Buffer') {
445
- let binary = '';
446
- // @ts-expect-error revisit
447
- const bytes = new Uint8Array(file.data);
448
- const len = bytes.byteLength;
449
- for (let i = 0; i < len; i++) {
450
- binary += String.fromCharCode(bytes[i]);
451
- }
452
- content = binary;
453
- }
454
-
455
- // subscribe on changes
456
- if (!this.fileSubscribed.includes(fileName)) {
457
- this.fileSubscribed.push(fileName);
458
- await this.props.socket.subscribeFiles(`${this.props.adapterName}.admin`, fileName, this.onFileChange);
459
- }
460
-
461
- try {
462
- // detect #include attr
463
- return (await this.scanForInclude(JSON5.parse(content), _filePaths)) as (ConfigItemPanel | ConfigItemTabs);
464
- } catch (e) {
465
- window.alert('[JsonConfig] Cannot parse json5 config!');
466
- console.log(e);
467
- }
468
- } catch (e1) {
469
- !this.state.schema && window.alert(`[JsonConfig] Cannot read file "${fileName}: ${e1}`);
470
- }
471
- return null;
472
- }
473
-
474
- renderSaveConfigDialog(): React.JSX.Element | null {
475
- if (!this.state.saveConfigDialog) {
476
- return null;
477
- }
478
- return <ConfirmDialog
479
- title={I18n.t('ra_Please confirm')}
480
- text={I18n.t('Save configuration?')}
481
- ok={I18n.t('ra_Save')}
482
- cancel={I18n.t('ra_Cancel')}
483
- onClose={isYes =>
484
- this.setState({ saveConfigDialog: false }, () => isYes && this.onSave(true))}
485
- />;
486
- }
487
-
488
- findAttr(attr: string, schema?: ConfigItemPanel | ConfigItemTabs): ConfigItemAny | null {
489
- schema = schema || this.state.schema;
490
- if (schema?.items) {
491
- if (attr in schema.items) {
492
- return schema.items[attr] as ConfigItemAny;
493
- }
494
- for (const _item of Object.values(schema.items)) {
495
- const item = this.findAttr(attr, _item as ConfigItemPanel | ConfigItemTabs);
496
- if (item) {
497
- return item;
498
- }
499
- }
500
- }
501
-
502
- return null;
503
- }
504
-
505
- // this function is called recursively and trims all text fields, that must be trimmed
506
- postProcessing(data: Record<string, unknown>, attr: string, schema: ConfigItemAny): void {
507
- schema = schema || this.state.schema;
508
- if (!data) {
509
- // should not happen
510
- console.error(`Data is empty in postProcessing: ${attr}, ${JSON.stringify(schema)}`);
511
- return;
512
- }
513
-
514
- const dataAttr = data[attr];
515
-
516
- if ((schema as ConfigItemTabs).items) {
517
- if (schema.type === 'table') {
518
- const table = dataAttr;
519
-
520
- if (!Array.isArray(table)) {
521
- return;
522
- }
523
-
524
- for (const entry of table) {
525
- for (const tItem of schema.items) {
526
- this.postProcessing(entry, tItem.attr as string, tItem as ConfigItemAny);
527
- }
528
- }
529
- } else {
530
- for (const [_attr, item] of Object.entries((schema as ConfigItemTabs).items)) {
531
- if ((item as any).type === 'panel' || (item as any).type === 'tabs' || (item as any).type === 'accordion') {
532
- return;
533
- }
534
- this.postProcessing(data, _attr, item);
535
- }
536
- }
537
- } else if (attr && typeof dataAttr === 'string') {
538
- // postprocessing
539
- if (schema.type === 'text') {
540
- if (schema.trim !== false) {
541
- data[attr] = dataAttr.trim();
542
- }
543
- } else if (schema.type === 'ip') {
544
- // should not happen
545
- data[attr] = dataAttr.trim();
546
- } else if (schema.type === 'number') {
547
- const dataVal = parseFloat(dataAttr.toString().replace(',', '.'));
548
-
549
- if (schema.min !== undefined && dataVal < schema.min) {
550
- data[attr] = schema.min;
551
- } else if (schema.max !== undefined && dataVal > schema.max) {
552
- data[attr] = schema.max;
553
- } else {
554
- data[attr] = dataVal;
555
- }
556
- } else if (schema.type === 'port') {
557
- const dataVal = parseInt(dataAttr.toString(), 10);
558
- if (schema.min !== undefined && dataVal < schema.min) {
559
- data[attr] = schema.min;
560
- } else if (schema.max !== undefined && dataVal > schema.max) {
561
- data[attr] = schema.max;
562
- }
563
- if (data[attr] !== 0 && dataVal < 20) {
564
- data[attr] = 20;
565
- } else if (dataVal > 0xFFFF) {
566
- data[attr] = 0xFFFF;
567
- } else {
568
- data[attr] = dataVal;
569
- }
570
- } else if (schema.type === 'checkbox') {
571
- // should not happen
572
- data[attr] = data[attr] === true || data[attr] === 'true' || data[attr] === 'on' || data[attr] === 1 || data[attr] === '1';
573
- }
574
- }
575
- }
576
-
577
- async onSave(doSave: boolean, close?: boolean): Promise<void> {
578
- if (doSave) {
579
- const obj = await this.getInstanceObject();
580
-
581
- if (!obj) {
582
- console.error('Something went wrong: may be no connection?');
583
- window.alert('Something went wrong: may be no connection?');
584
- return;
585
- }
586
-
587
- if (!this.state.data || !this.state.schema) {
588
- return;
589
- }
590
-
591
- const doNotSaveAttributes: Record<string, any> = {};
592
-
593
- for (const attr of Object.keys(this.state.data)) {
594
- const item = this.findAttr(attr);
595
- if ((!item || !item.doNotSave) && !attr.startsWith('_')) {
596
- ConfigGeneric.setValue(obj.native, attr, this.state.data[attr]);
597
- } else {
598
- ConfigGeneric.setValue(obj.native, attr, null);
599
- doNotSaveAttributes[attr] = this.state.data[attr];
600
- }
601
- }
602
-
603
- try {
604
- const encryptedObj = JSON.parse(JSON.stringify(obj));
605
- // encode all native attributes listed in obj.encryptedNative
606
- if (Array.isArray(encryptedObj.encryptedNative)) {
607
- await loadScript('../../lib/js/crypto-js/crypto-js.js', 'crypto-js');
608
-
609
- for (const attr of encryptedObj.encryptedNative) {
610
- if (encryptedObj.native[attr]) {
611
- encryptedObj.native[attr] = encrypt(this.secret, encryptedObj.native[attr]);
612
- }
613
- }
614
- }
615
-
616
- await this.props.socket.setObject(encryptedObj._id, encryptedObj);
617
- } catch (e) {
618
- window.alert(`[JsonConfig] Cannot set object: ${e}`);
619
- }
620
-
621
- /** We want to preserve the doNotSaveAttributes too, just not save it */
622
- const nativeWithNonSaved = { ...obj.native, ...doNotSaveAttributes };
623
- console.log(nativeWithNonSaved);
624
-
625
- this.setState({
626
- changed: false,
627
- data: nativeWithNonSaved,
628
- updateData: this.state.updateData + 1,
629
- originalData: nativeWithNonSaved,
630
- }, () =>
631
- close && Router.doNavigate(null));
632
- } else if (this.state.changed) {
633
- this.setState({ confirmDialog: true });
634
- } else {
635
- Router.doNavigate(null);
636
- }
637
- }
638
-
639
- componentDidUpdate(_prevProps: JsonConfigProps, prevState: JsonConfigState): void {
640
- if (prevState.changed !== this.state.changed) {
641
- this.props.configStored(!this.state.changed);
642
- }
643
- }
644
-
645
- /**
646
- * Validate the JSON config once on mount
647
- */
648
- async componentDidMount() {
649
- const link = `${window.location.protocol}//${window.location.host}${window.location.pathname}validate_config/${this.props.adapterName}`;
650
- console.log(`fetch ${link}`);
651
- await fetch(link);
652
- }
653
-
654
- render(): React.JSX.Element {
655
- const { classes } = this.props;
656
- if (!this.state.data || !this.state.schema) {
657
- return <LinearProgress />;
658
- }
659
-
660
- return <div className={this.props.classes.root}>
661
- {this.renderConfirmDialog()}
662
- {this.getExportImportButtons()}
663
- {this.renderSaveConfigDialog()}
664
- <JsonConfigComponent
665
- key={this.state.hash as string}
666
- className={classes.scroll}
667
- socket={this.props.socket}
668
- themeName={this.props.themeName}
669
- themeType={this.props.themeType}
670
- adapterName={this.props.adapterName}
671
- instance={this.props.instance}
672
- isFloatComma={this.props.isFloatComma}
673
- dateFormat={this.props.dateFormat}
674
- schema={this.state.schema}
675
- common={this.state.common}
676
- data={this.state.data}
677
- updateData={this.state.updateData}
678
- onError={error => this.setState({ error })}
679
- onChange={(data, changed, saveConfigDialog) => {
680
- if (saveConfigDialog && this.state.error) {
681
- window.alert(I18n.t('Cannot save configuration because of error in configuration'));
682
- saveConfigDialog = false;
683
- }
684
- if (saveConfigDialog && !this.state.changed && !changed) {
685
- saveConfigDialog = false;
686
- }
687
- if (data) {
688
- this.setState({ data, changed, saveConfigDialog });
689
- } else if (saveConfigDialog !== undefined) {
690
- this.setState({ saveConfigDialog });
691
- }
692
- }}
693
- />
694
- <SaveCloseButtons
695
- isIFrame={false}
696
- dense
697
- paddingLeft={0}
698
- newReact
699
- theme={this.state.theme}
700
- noTextOnButtons={this.props.width === 'xs' || this.props.width === 'sm' || this.props.width === 'md'}
701
- changed={!!(this.state.error || this.state.changed)}
702
- error={!!this.state.error}
703
- onSave={(close: any) => this.onSave(true, close)}
704
- onClose={() => this.onSave(false)}
705
- />
706
- </div>;
707
- }
708
- }
709
-
710
- export default withStyles(styles)(JsonConfig);