@iobroker/json-config 9.0.20 → 9.0.22

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.
package/README.md CHANGED
@@ -459,6 +459,9 @@ Each option in `options` can have:
459
459
  | `value` | Value of the option |
460
460
  | `color` | Color of the option text |
461
461
  | `hidden` | Formula or boolean value to show or hide the option |
462
+ | `os` | Show the option only on these operating systems of the host |
463
+ | `notOs` | Do not show the option on these operating systems of the host |
464
+ | `docker` | Show the option only if the ioBroker runs (`true`) or not (`false`) in docker |
462
465
  | `description` | Description shown below the option label (can be translatable) |
463
466
  | `icon` | Icon URL or base64 string to display next to the option (from v8.3.3) |
464
467
 
@@ -1496,6 +1499,9 @@ In the Settings of the Web developer tools, you can create your own devices with
1496
1499
  | `label` | String or object like {en: 'Name', ru: 'Имя'} |
1497
1500
  | `hidden` | JS function that could use `native.attribute` for calculation |
1498
1501
  | `hideOnlyControl` | if hidden the place will be shown, but no control |
1502
+ | `os` | Show this element only on these operating systems of the host, on which the instance runs: `"win32"` or `["linux", "darwin"]` |
1503
+ | `notOs` | Do not show this element on these operating systems of the host, on which the instance runs: `"win32"` or `["linux", "darwin"]` |
1504
+ | `docker` | Show this element only if the ioBroker runs (`true`) or does not run (`false`) in a docker container |
1499
1505
  | `disabled` | JS function that could use `native.attribute` for calculation |
1500
1506
  | `help` | help text (multi-language) |
1501
1507
  | `helpLink` | href to help (could be used only together with `help`) |
@@ -1514,6 +1520,73 @@ In the Settings of the Web developer tools, you can create your own devices with
1514
1520
  | `noMultiEdit` | if this flag set to true, this field will not be shown if user selected more than one object for edit. |
1515
1521
  | `expertMode` | if this flag set to true, this field will be shown only if the expert mode is true (from Admin 7.4.3) |
1516
1522
 
1523
+ ### Show elements depending on the operating system
1524
+
1525
+ Every element (also `panel`, `tabs`, table columns and single `select` options) can be limited to the
1526
+ operating system of the ioBroker host, **on which the configured instance runs**. It is not the operating
1527
+ system of the browser.
1528
+
1529
+ ```json5
1530
+ {
1531
+ "comPort": { "type": "text", "label": "COM port", "os": "win32" },
1532
+ "ttyPort": { "type": "text", "label": "Serial device", "os": ["linux", "darwin"] },
1533
+ "sudoHint": { "type": "staticText", "text": "The service must be started with sudo", "notOs": "win32" }
1534
+ }
1535
+ ```
1536
+
1537
+ Allowed values are the values of the node.js `process.platform` (like in `common.os` of `io-package.json`):
1538
+ `aix`, `android`, `cygwin`, `darwin`, `freebsd`, `haiku`, `linux`, `netbsd`, `openbsd`, `sunos`, `win32`.
1539
+
1540
+ - If `os` is defined, the element will be shown **only** on the given operating systems.
1541
+ - If `notOs` is defined, the element will be shown on all operating systems **except** the given ones.
1542
+ - If the operating system of the host cannot be detected (e.g., the host object is not readable), the element
1543
+ will be shown. It is better to show one element too much than to hide a required one.
1544
+ - A not shown element is not deleted: the value stays unchanged in the configuration, exactly like by `hidden`.
1545
+ But the `default` value of such an element will not be written into the configuration.
1546
+
1547
+ For more complex conditions, the variables `_os`, `_arch` and `_host` can be used in every JS function
1548
+ (`hidden`, `disabled`, `validator`, `defaultFunc`, `onChange.calculateFunc`, `confirm.condition`) and in
1549
+ the text patterns of `label`, `help` and so on:
1550
+
1551
+ ```json5
1552
+ {
1553
+ "type": "text",
1554
+ "label": "Path to the executable file",
1555
+ "disabled": "_os === 'win32'",
1556
+ "defaultFunc": "_os === 'win32' ? 'C:\\\\Program Files\\\\app.exe' : '/usr/bin/app'",
1557
+ "help": "Host ${_host.id} runs ${_os} on ${_arch}"
1558
+ }
1559
+ ```
1560
+
1561
+ **Note:** old admin versions do not know `_os` and would evaluate `"hidden": "_os !== 'linux'"` to `true`
1562
+ and so hide the element everywhere. Because of that, `os`/`notOs` should be preferred, as they are simply
1563
+ ignored by old admin versions (the element will be shown). If a JS function must be used, write it
1564
+ defensively: `"hidden": "!!_os && _os !== 'linux'"`.
1565
+
1566
+ #### Docker
1567
+
1568
+ If an element depends on whether the ioBroker itself runs in a docker container, the attribute `docker`
1569
+ can be used:
1570
+
1571
+ ```json5
1572
+ {
1573
+ "service": { "type": "checkbox", "label": "Install as service", "docker": false },
1574
+ "volumeHint": { "type": "staticText", "text": "The directory must be mapped as volume", "docker": true }
1575
+ }
1576
+ ```
1577
+
1578
+ - `"docker": true` - the element will be shown only if the ioBroker runs in a docker container.
1579
+ - `"docker": false` - the element will be shown only if the ioBroker does not run in a docker container.
1580
+ - The docker state cannot be read from the objects, it must be requested from a **running** host. If the host
1581
+ does not answer, the state stays unknown and the element will be shown.
1582
+ - The request will only be sent if the configuration really uses `docker` or `_host.docker`, so all other
1583
+ configurations do not cause any additional traffic.
1584
+ - In the JS functions the state is available as `_host.docker` (`true`, `false` or `undefined` if unknown) and
1585
+ the version of the official ioBroker docker image as `_host.dockerVersion`.
1586
+
1587
+ Do not mix it up with the [`checkDocker`](#checkdocker) control: that one checks if a docker installation is
1588
+ available **on the host** to control containers, and not if the ioBroker itself runs in docker.
1589
+
1517
1590
  ### Options with detailed configuration
1518
1591
 
1519
1592
  #### `defaultSendTo`
@@ -1632,6 +1705,9 @@ const func = new Function(
1632
1705
  '_changed', // indicator if some data was changed and must be saved
1633
1706
  '_href', // Current browser href
1634
1707
  'getObject', // You can call `await getObject(data.id)`in hidden, disabled, pattern functions
1708
+ '_os', // Operating system of the host, where the instance runs: 'win32', 'linux', 'darwin', ...
1709
+ '_arch', // Architecture of the host, where the instance runs: 'x64', 'arm64', ...
1710
+ '_host', // Information about the host: {id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}
1635
1711
  myValidator.includes('return') ? myValidator : 'return ' + myValidator); // e.g. "_alive === true"
1636
1712
 
1637
1713
  const isValid = func(data, systemConfig.common, instanceAlive, adapter.common, this.props.socket);
@@ -1649,6 +1725,9 @@ The following variables are available in JS function in adapter settings:
1649
1725
  - `_instance` - instance number
1650
1726
  - `arrayIndex` - used only in table and represent current line in an array
1651
1727
  - `globalData` - used only in table for all settings and not only one table line
1728
+ - `_os` - operating system of the host, on which the instance runs (`process.platform`), e.g. `linux`, `win32`, `darwin`. Empty string if unknown
1729
+ - `_arch` - architecture of the host, on which the instance runs, e.g. `x64`, `arm64`
1730
+ - `_host` - information about the host: `{id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}`. `docker` is `undefined` if the docker state was not requested or the host did not answer
1652
1731
 
1653
1732
  ### Custom settings dialog
1654
1733
 
@@ -1666,6 +1745,9 @@ const func = new Function(
1666
1745
  "customObj",
1667
1746
  "_socket",
1668
1747
  arrayIndex,
1748
+ "_os",
1749
+ "_arch",
1750
+ "_host",
1669
1751
  myValidator.includes("return") ? myValidator : "return " + myValidator
1670
1752
  ); // e.g. "_alive === true"
1671
1753
 
@@ -1689,6 +1771,9 @@ The following variables are available in JS function in custom settings:
1689
1771
  - `_socket` - socket
1690
1772
  - `arrayIndex` - used only in table and represent current line in an array
1691
1773
  - `globalData` - used only in table for all settings and not only one table line
1774
+ - `_os` - operating system of the host, on which the instance runs (`process.platform`), e.g. `linux`, `win32`, `darwin`. Empty string if unknown
1775
+ - `_arch` - architecture of the host, on which the instance runs, e.g. `x64`, `arm64`
1776
+ - `_host` - information about the host: `{id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}`. `docker` is `undefined` if the docker state was not requested or the host did not answer
1692
1777
 
1693
1778
  ```json5
1694
1779
  {
@@ -1822,6 +1907,13 @@ The schema is used here: https://github.com/SchemaStore/schemastore/blob/6da29cd
1822
1907
  ### **WORK IN PROGRESS**
1823
1908
  -->
1824
1909
  ## Changelog
1910
+ ### 9.0.22 (2026-08-21)
1911
+ - (@GermanBluefox) Corrected layout of Config view
1912
+
1913
+ ### 9.0.21 (2026-08-19)
1914
+ - (@GermanBluefox) Added the possibility to show or hide elements depending on the operating system of the host: `os`, `notOs` and the JS variables `_os`, `_arch`, `_host`
1915
+ - (@GermanBluefox) Added the possibility to show or hide elements depending on the docker installation: `docker` and `_host.docker`
1916
+
1825
1917
  ### 9.0.20 (2026-08-13)
1826
1918
  - (@GermanBluefox) Correcting ConfigSelect component
1827
1919
 
@@ -1,6 +1,6 @@
1
1
  import React, { Component, type JSX } from 'react';
2
2
  import { type Connection, type ThemeType, type ThemeName, type IobTheme } from '@iobroker/gui-components';
3
- import type { ConfigIconType, ConfigItemAny, ConfigItemConfirmData, JsonConfigContext } from '../types';
3
+ import type { ConfigIconType, ConfigItem, ConfigItemAny, ConfigItemConfirmData, JsonConfigContext, JsonConfigHostInfo } from '../types';
4
4
  export declare function isObject(it: any): it is Record<string, any>;
5
5
  export interface DeviceManagerPropsProps {
6
6
  socket: Connection;
@@ -135,6 +135,18 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
135
135
  * @param data the data object the function was executed against
136
136
  */
137
137
  protected debugLog(funcName: string, func: string, result: unknown, data?: Record<string, any>): void;
138
+ /**
139
+ * Check if the element may be shown on the host, where the instance runs.
140
+ *
141
+ * It evaluates the `os`, `notOs` and `docker` attributes. If the operating system or the docker state
142
+ * cannot be detected, the element will be shown, because it is better to show one element too much
143
+ * than to hide a required one.
144
+ *
145
+ * @param schema config item or select option with the optional `os`, `notOs` and `docker` attributes
146
+ * @param hostInfo information about the host, where the instance runs
147
+ * @returns false if the element must not be shown on this host
148
+ */
149
+ static isHostAllowed(schema: Pick<ConfigItem, 'os' | 'notOs' | 'docker'>, hostInfo?: JsonConfigHostInfo): boolean;
138
150
  execute(func: string | boolean | Record<string, string> | undefined, defaultValue: string | number | boolean | undefined, data: Record<string, any> | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string): Promise<string | number | boolean | undefined>;
139
151
  executeCustom(func: string | boolean | Record<string, string> | undefined, data: Record<string, any>, customObj: Record<string, any> | undefined, instanceObj: ioBroker.InstanceObject | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string): Promise<string | boolean | number | null | undefined>;
140
152
  calculate(schema: Record<string, any>): Promise<{
@@ -567,6 +567,43 @@ export default class ConfigGeneric extends Component {
567
567
  console.log(label, 'background:#33691e;color:#fff;border-radius:3px;padding:1px 4px', 'color:#7cb342', payload);
568
568
  }
569
569
  }
570
+ /**
571
+ * Check if the element may be shown on the host, where the instance runs.
572
+ *
573
+ * It evaluates the `os`, `notOs` and `docker` attributes. If the operating system or the docker state
574
+ * cannot be detected, the element will be shown, because it is better to show one element too much
575
+ * than to hide a required one.
576
+ *
577
+ * @param schema config item or select option with the optional `os`, `notOs` and `docker` attributes
578
+ * @param hostInfo information about the host, where the instance runs
579
+ * @returns false if the element must not be shown on this host
580
+ */
581
+ static isHostAllowed(schema, hostInfo) {
582
+ if (!schema?.os && !schema?.notOs && schema?.docker === undefined) {
583
+ return true;
584
+ }
585
+ const os = hostInfo?.os;
586
+ // If the operating system of the host is unknown, the `os`/`notOs` attributes will be ignored
587
+ if (os) {
588
+ if (schema.os) {
589
+ const allowed = Array.isArray(schema.os) ? schema.os : [schema.os];
590
+ if (!allowed.includes(os)) {
591
+ return false;
592
+ }
593
+ }
594
+ if (schema.notOs) {
595
+ const forbidden = Array.isArray(schema.notOs) ? schema.notOs : [schema.notOs];
596
+ if (forbidden.includes(os)) {
597
+ return false;
598
+ }
599
+ }
600
+ }
601
+ // If the docker state of the host is unknown, the `docker` attribute will be ignored
602
+ if (schema.docker !== undefined && hostInfo?.docker !== undefined && schema.docker !== hostInfo.docker) {
603
+ return false;
604
+ }
605
+ return true;
606
+ }
570
607
  async execute(func, defaultValue, data, arrayIndex, globalData, funcName) {
571
608
  let fun;
572
609
  if (isObject(func)) {
@@ -586,8 +623,8 @@ export default class ConfigGeneric extends Component {
586
623
  fun = ConfigGeneric.ensureAwaitGetObject(fun);
587
624
  }
588
625
  try {
589
- const f = new this.AsyncFunction('data', 'originalData', '_system', '_alive', '_common', '_socket', '_instance', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', fun.includes('return') ? fun : `return ${fun}`);
590
- const result = await f(data || this.props.data || {}, this.props.originalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.oContext.instance, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject);
626
+ const f = new this.AsyncFunction('data', 'originalData', '_system', '_alive', '_common', '_socket', '_instance', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', fun.includes('return') ? fun : `return ${fun}`);
627
+ const result = await f(data || this.props.data || {}, this.props.originalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.oContext.instance, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
591
628
  this.debugLog(funcName || 'JS function', fun, result, data);
592
629
  return result;
593
630
  }
@@ -616,8 +653,8 @@ export default class ConfigGeneric extends Component {
616
653
  fun = ConfigGeneric.ensureAwaitGetObject(fun);
617
654
  }
618
655
  try {
619
- const f = new this.AsyncFunction('data', 'originalData', '_system', 'instanceObj', 'customObj', '_socket', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', fun.includes('return') ? fun : `return ${fun}`);
620
- const result = await f(data || this.props.data, this.props.originalData, this.props.oContext.systemConfig, instanceObj || {}, customObj || {}, this.props.oContext.socket, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject);
656
+ const f = new this.AsyncFunction('data', 'originalData', '_system', 'instanceObj', 'customObj', '_socket', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', fun.includes('return') ? fun : `return ${fun}`);
657
+ const result = await f(data || this.props.data, this.props.originalData, this.props.oContext.systemConfig, instanceObj || {}, customObj || {}, this.props.oContext.socket, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
621
658
  this.debugLog(funcName || 'JS function', fun, result, data);
622
659
  return result;
623
660
  }
@@ -788,15 +825,15 @@ export default class ConfigGeneric extends Component {
788
825
  }
789
826
  try {
790
827
  if (this.props.custom) {
791
- const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', 'getObject', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
792
- const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject);
828
+ const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
829
+ const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
793
830
  if (noTranslation) {
794
831
  return text;
795
832
  }
796
833
  return I18n.t(text);
797
834
  }
798
- const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', 'getObject', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
799
- const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject);
835
+ const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
836
+ const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
800
837
  if (noTranslation) {
801
838
  return text;
802
839
  }
@@ -831,15 +868,15 @@ export default class ConfigGeneric extends Component {
831
868
  }
832
869
  try {
833
870
  if (this.props.custom) {
834
- const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
835
- const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject);
871
+ const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
872
+ const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
836
873
  if (noTranslation) {
837
874
  return text;
838
875
  }
839
876
  return I18n.t(text);
840
877
  }
841
- const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
842
- const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href);
878
+ const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
879
+ const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
843
880
  if (noTranslation) {
844
881
  return text;
845
882
  }
@@ -881,6 +918,10 @@ export default class ConfigGeneric extends Component {
881
918
  if (this.props.expertMode === false && schema.expertMode) {
882
919
  return null;
883
920
  }
921
+ // Do not show this component if it is not intended for the host, where the instance runs
922
+ if (!ConfigGeneric.isHostAllowed(schema, this.props.oContext.hostInfo)) {
923
+ return null;
924
+ }
884
925
  if (this.props.alive && this.defaultSendToDone === false) {
885
926
  this.sendToTimeout = setTimeout(async () => {
886
927
  this.sendToTimeout = null;