@iobroker/json-config 6.17.13 → 6.17.14

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 (61) hide show
  1. package/package.json +27 -27
  2. package/src/JsonConfig.tsx +0 -710
  3. package/src/JsonConfigComponent/ChipInput.tsx +0 -752
  4. package/src/JsonConfigComponent/ConfigAccordion.tsx +0 -278
  5. package/src/JsonConfigComponent/ConfigAlive.tsx +0 -74
  6. package/src/JsonConfigComponent/ConfigAutocomplete.tsx +0 -108
  7. package/src/JsonConfigComponent/ConfigAutocompleteSendTo.tsx +0 -183
  8. package/src/JsonConfigComponent/ConfigCRON.jsx +0 -101
  9. package/src/JsonConfigComponent/ConfigCertCollection.tsx +0 -102
  10. package/src/JsonConfigComponent/ConfigCertificateSelect.tsx +0 -92
  11. package/src/JsonConfigComponent/ConfigCertificates.tsx +0 -202
  12. package/src/JsonConfigComponent/ConfigCheckLicense.jsx +0 -662
  13. package/src/JsonConfigComponent/ConfigCheckbox.tsx +0 -67
  14. package/src/JsonConfigComponent/ConfigChip.jsx +0 -81
  15. package/src/JsonConfigComponent/ConfigColor.tsx +0 -86
  16. package/src/JsonConfigComponent/ConfigCoordinates.tsx +0 -234
  17. package/src/JsonConfigComponent/ConfigCustom.tsx +0 -246
  18. package/src/JsonConfigComponent/ConfigDatePicker.tsx +0 -48
  19. package/src/JsonConfigComponent/ConfigDeviceManager.tsx +0 -33
  20. package/src/JsonConfigComponent/ConfigFile.jsx +0 -181
  21. package/src/JsonConfigComponent/ConfigFileSelector.jsx +0 -520
  22. package/src/JsonConfigComponent/ConfigFunc.jsx +0 -90
  23. package/src/JsonConfigComponent/ConfigGeneric.tsx +0 -1027
  24. package/src/JsonConfigComponent/ConfigIP.jsx +0 -96
  25. package/src/JsonConfigComponent/ConfigImageSendTo.jsx +0 -79
  26. package/src/JsonConfigComponent/ConfigImageUpload.jsx +0 -114
  27. package/src/JsonConfigComponent/ConfigInstanceSelect.jsx +0 -172
  28. package/src/JsonConfigComponent/ConfigInterface.jsx +0 -112
  29. package/src/JsonConfigComponent/ConfigJsonEditor.jsx +0 -103
  30. package/src/JsonConfigComponent/ConfigLanguage.tsx +0 -153
  31. package/src/JsonConfigComponent/ConfigLicense.jsx +0 -148
  32. package/src/JsonConfigComponent/ConfigNumber.tsx +0 -207
  33. package/src/JsonConfigComponent/ConfigObjectId.jsx +0 -113
  34. package/src/JsonConfigComponent/ConfigPanel.tsx +0 -360
  35. package/src/JsonConfigComponent/ConfigPassword.jsx +0 -160
  36. package/src/JsonConfigComponent/ConfigPattern.jsx +0 -50
  37. package/src/JsonConfigComponent/ConfigPort.tsx +0 -232
  38. package/src/JsonConfigComponent/ConfigRoom.jsx +0 -90
  39. package/src/JsonConfigComponent/ConfigSelect.jsx +0 -124
  40. package/src/JsonConfigComponent/ConfigSelectSendTo.tsx +0 -251
  41. package/src/JsonConfigComponent/ConfigSendto.tsx +0 -340
  42. package/src/JsonConfigComponent/ConfigSetState.jsx +0 -116
  43. package/src/JsonConfigComponent/ConfigSlider.jsx +0 -97
  44. package/src/JsonConfigComponent/ConfigStaticDivider.jsx +0 -51
  45. package/src/JsonConfigComponent/ConfigStaticHeader.jsx +0 -63
  46. package/src/JsonConfigComponent/ConfigStaticImage.jsx +0 -48
  47. package/src/JsonConfigComponent/ConfigStaticText.jsx +0 -72
  48. package/src/JsonConfigComponent/ConfigTable.tsx +0 -1040
  49. package/src/JsonConfigComponent/ConfigTabs.tsx +0 -150
  50. package/src/JsonConfigComponent/ConfigText.tsx +0 -188
  51. package/src/JsonConfigComponent/ConfigTextSendTo.tsx +0 -102
  52. package/src/JsonConfigComponent/ConfigTimePicker.tsx +0 -63
  53. package/src/JsonConfigComponent/ConfigTopic.jsx +0 -78
  54. package/src/JsonConfigComponent/ConfigUUID.tsx +0 -55
  55. package/src/JsonConfigComponent/ConfigUser.jsx +0 -104
  56. package/src/JsonConfigComponent/index.tsx +0 -435
  57. package/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx +0 -145
  58. package/src/JsonConfigComponent/wrapper/Components/Editor.jsx +0 -65
  59. package/src/Utils.jsx +0 -1683
  60. package/src/index.tsx +0 -14
  61. 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);