iobroker.mywebui 1.37.40 → 1.37.42
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.
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pre-start.mjs — clears stale alive state before adapter starts.
|
|
3
|
+
*
|
|
4
|
+
* Root cause of ADAPTER_ALREADY_RUNNING:
|
|
5
|
+
* onUnload() in main adapter does not call its callback →
|
|
6
|
+
* ioBroker kills the process via SIGKILL on shutdown →
|
|
7
|
+
* alive state is never cleared →
|
|
8
|
+
* next start sees alive=true → exit code 7 → adapter disabled.
|
|
9
|
+
*
|
|
10
|
+
* Fix: clear system.adapter.mywebui.0.alive before starting main.js.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
|
|
22
|
+
async function clearAliveState() {
|
|
23
|
+
try {
|
|
24
|
+
// Get ioBroker config file path using js-controller-common (available in parent node_modules)
|
|
25
|
+
let statesHost = '127.0.0.1';
|
|
26
|
+
let statesPort = 9020;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const { tools } = require('@iobroker/js-controller-common');
|
|
30
|
+
const configFile = tools.getConfigFileName();
|
|
31
|
+
const config = JSON.parse(readFileSync(configFile, 'utf8'));
|
|
32
|
+
statesHost = config?.states?.host || '127.0.0.1';
|
|
33
|
+
statesPort = config?.states?.port || 9020;
|
|
34
|
+
} catch {
|
|
35
|
+
// Fallback: try path relative to this file
|
|
36
|
+
try {
|
|
37
|
+
const configPath = path.resolve(__dirname, '../../../../iobroker-data/iobroker.json');
|
|
38
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
39
|
+
statesHost = config?.states?.host || '127.0.0.1';
|
|
40
|
+
statesPort = config?.states?.port || 9020;
|
|
41
|
+
} catch {
|
|
42
|
+
// Use defaults: 127.0.0.1:9020
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const IORedis = require('ioredis');
|
|
47
|
+
const client = new IORedis({
|
|
48
|
+
host: statesHost,
|
|
49
|
+
port: statesPort,
|
|
50
|
+
lazyConnect: false,
|
|
51
|
+
connectTimeout: 3000,
|
|
52
|
+
enableReadyCheck: false,
|
|
53
|
+
maxRetriesPerRequest: 1,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await new Promise((resolve) => {
|
|
57
|
+
client.once('ready', resolve);
|
|
58
|
+
client.once('error', resolve);
|
|
59
|
+
setTimeout(resolve, 3000);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const ts = Date.now();
|
|
63
|
+
const stateVal = JSON.stringify({
|
|
64
|
+
val: false,
|
|
65
|
+
ack: true,
|
|
66
|
+
ts,
|
|
67
|
+
lc: ts,
|
|
68
|
+
from: 'system.adapter.mywebui.0',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await client.set('io.system.adapter.mywebui.0.alive', stateVal).catch(() => {});
|
|
72
|
+
console.log('[mywebui pre-start] Cleared alive state on', statesHost + ':' + statesPort);
|
|
73
|
+
|
|
74
|
+
await client.quit().catch(() => {});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.warn('[mywebui pre-start] Could not clear alive state:', e.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await clearAliveState();
|
|
81
|
+
await import('./main.js');
|
package/io-package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.mywebui",
|
|
3
|
-
"version": "1.37.
|
|
3
|
+
"version": "1.37.42",
|
|
4
4
|
"description": "ioBroker mywebui - Custom edited mywebui by gokturk413",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/backend/
|
|
6
|
+
"main": "dist/backend/pre-start.mjs",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"sync": "node sync-package.js",
|
|
9
9
|
"esbuild": "node esbuild.js",
|
|
@@ -4,6 +4,7 @@ class VisibilityService {
|
|
|
4
4
|
static instance = new VisibilityService();
|
|
5
5
|
|
|
6
6
|
#originalDisplayStyles = new WeakMap();
|
|
7
|
+
#cleanupFns = new WeakMap();
|
|
7
8
|
|
|
8
9
|
#applyResult(element, result) {
|
|
9
10
|
if (!result.visible) {
|
|
@@ -14,7 +15,6 @@ class VisibilityService {
|
|
|
14
15
|
element.style.display = orig;
|
|
15
16
|
element.style.visibility = '';
|
|
16
17
|
}
|
|
17
|
-
|
|
18
18
|
if (!result.enabled) {
|
|
19
19
|
element.style.pointerEvents = 'none';
|
|
20
20
|
element.style.opacity = '0.5';
|
|
@@ -24,93 +24,118 @@ class VisibilityService {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async
|
|
28
|
-
|
|
29
|
-
const action = visibilityConfig.action || 'hide';
|
|
27
|
+
async applyVisibility(element, visibilityConfig, relativeSignalPath, root) {
|
|
28
|
+
if (!visibilityConfig || !visibilityConfig.enabled) return;
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
enabled: true,
|
|
35
|
-
groups: visibilityConfig.groups,
|
|
36
|
-
action: action
|
|
37
|
-
});
|
|
38
|
-
// If user is not in allowed groups, apply restriction and stop
|
|
39
|
-
if (!groupResult.visible || !groupResult.enabled) {
|
|
40
|
-
this.#applyResult(element, groupResult);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
30
|
+
if (!this.#originalDisplayStyles.has(element)) {
|
|
31
|
+
this.#originalDisplayStyles.set(element, element.style.display || '');
|
|
32
|
+
}
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let signalId = visibilityConfig.objectId;
|
|
49
|
-
let expression = null;
|
|
50
|
-
let converter = null;
|
|
51
|
-
if (typeof signalId === 'string' && signalId.startsWith('{')) {
|
|
52
|
-
try {
|
|
53
|
-
const parsed = JSON.parse(signalId);
|
|
54
|
-
signalId = parsed.signal || signalId;
|
|
55
|
-
expression = parsed.expression || null;
|
|
56
|
-
converter = parsed.converter || null;
|
|
57
|
-
} catch (e) { /* keep raw */ }
|
|
58
|
-
}
|
|
34
|
+
// Clean up any previous binding subscription
|
|
35
|
+
const prevCleanup = this.#cleanupFns.get(element);
|
|
36
|
+
if (prevCleanup) { prevCleanup(); this.#cleanupFns.delete(element); }
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
// Step 1: Group access check (if configured)
|
|
39
|
+
if (visibilityConfig.groups && visibilityConfig.groups.length > 0) {
|
|
40
|
+
const groupResult = await iobrokerHandler.checkVisibility({
|
|
41
|
+
enabled: true,
|
|
42
|
+
groups: visibilityConfig.groups,
|
|
43
|
+
action: visibilityConfig.action || 'hide'
|
|
44
|
+
});
|
|
45
|
+
if (!groupResult.visible || !groupResult.enabled) {
|
|
46
|
+
this.#applyResult(element, groupResult);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
65
50
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
51
|
+
// Step 2: Signal binding
|
|
52
|
+
if (visibilityConfig.objectId) {
|
|
53
|
+
const action = visibilityConfig.action || 'hide';
|
|
70
54
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
55
|
+
let signalStr, bindingConfig;
|
|
56
|
+
const raw = visibilityConfig.objectId;
|
|
57
|
+
if (typeof raw === 'string' && raw.startsWith('{')) {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
signalStr = parsed.signal;
|
|
61
|
+
bindingConfig = parsed;
|
|
62
|
+
} catch (e) {
|
|
63
|
+
signalStr = raw;
|
|
64
|
+
bindingConfig = {};
|
|
75
65
|
}
|
|
66
|
+
} else {
|
|
67
|
+
signalStr = raw;
|
|
68
|
+
bindingConfig = {};
|
|
69
|
+
}
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
const isActive =
|
|
71
|
+
const onValue = (v) => {
|
|
72
|
+
const isActive = v !== null && v !== undefined && v !== false && v !== 0 && v !== '';
|
|
79
73
|
this.#applyResult(element, isActive
|
|
80
74
|
? { visible: action !== 'hide', enabled: action !== 'disable' }
|
|
81
75
|
: { visible: true, enabled: true }
|
|
82
76
|
);
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const bindingsHelper = window.appShell?.bindingsHelper;
|
|
80
|
+
if (bindingsHelper && root) {
|
|
81
|
+
// Use BindingsHelper.applyBinding — supports ?, ??, {combined}, expression, converter, historic
|
|
82
|
+
// A Proxy intercepts the final evaluated value written to element.__vis
|
|
83
|
+
const proxy = new Proxy(element, {
|
|
84
|
+
set(target, prop, value) {
|
|
85
|
+
if (prop === '__vis') {
|
|
86
|
+
onValue(value);
|
|
87
|
+
} else {
|
|
88
|
+
target[prop] = value;
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
},
|
|
92
|
+
get(target, prop) { return target[prop]; }
|
|
93
|
+
});
|
|
85
94
|
|
|
86
|
-
|
|
95
|
+
const bindingObj = {
|
|
96
|
+
signal: signalStr,
|
|
97
|
+
target: 'property'
|
|
98
|
+
};
|
|
99
|
+
if (bindingConfig.expression) bindingObj.expression = bindingConfig.expression;
|
|
100
|
+
if (bindingConfig.converter) bindingObj.converter = bindingConfig.converter;
|
|
101
|
+
if (bindingConfig.historic) bindingObj.historic = bindingConfig.historic;
|
|
102
|
+
if (bindingConfig.invert) bindingObj.inverted = bindingConfig.invert;
|
|
103
|
+
|
|
104
|
+
// Format: [propertyName, bindingOptions]
|
|
105
|
+
const binding = ['__vis', bindingObj];
|
|
106
|
+
const cleanup = bindingsHelper.applyBinding(proxy, binding, relativeSignalPath || '', root);
|
|
107
|
+
if (cleanup) this.#cleanupFns.set(element, cleanup);
|
|
108
|
+
} else {
|
|
109
|
+
// Fallback: direct subscription (no ?, ?? or {combined} support)
|
|
110
|
+
await this.#applyDirect(signalStr, bindingConfig, onValue);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
87
113
|
this.#applyResult(element, { visible: true, enabled: true });
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error('[Visibility] Check failed:', err);
|
|
90
114
|
}
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
async
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await this.#checkAndApply(element, visibilityConfig, undefined);
|
|
101
|
-
|
|
102
|
-
if (visibilityConfig.objectId) {
|
|
103
|
-
let signalId = visibilityConfig.objectId;
|
|
104
|
-
if (typeof signalId === 'string' && signalId.startsWith('{')) {
|
|
105
|
-
try { signalId = JSON.parse(signalId).signal || signalId; } catch (e) { /* keep raw */ }
|
|
117
|
+
async #applyDirect(signalId, bindingConfig, onValue) {
|
|
118
|
+
const evaluate = (raw) => {
|
|
119
|
+
let v = raw;
|
|
120
|
+
if (bindingConfig.expression) {
|
|
121
|
+
try { v = new Function('__0', bindingConfig.expression)(v); } catch (e) { console.warn('[Visibility] Expression error:', e); }
|
|
106
122
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
if (bindingConfig.converter && typeof bindingConfig.converter === 'object') {
|
|
124
|
+
const k = String(v);
|
|
125
|
+
if (k in bindingConfig.converter) v = bindingConfig.converter[k];
|
|
126
|
+
}
|
|
127
|
+
onValue(v);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const state = await iobrokerHandler.connection.getState(signalId);
|
|
131
|
+
evaluate(state?.val);
|
|
132
|
+
await iobrokerHandler.subscribeState(signalId, (_id, state) => evaluate(state?.val));
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
removeVisibility(element) {
|
|
136
|
+
const prevCleanup = this.#cleanupFns.get(element);
|
|
137
|
+
if (prevCleanup) { prevCleanup(); this.#cleanupFns.delete(element); }
|
|
138
|
+
|
|
114
139
|
const originalDisplay = this.#originalDisplayStyles.get(element);
|
|
115
140
|
if (originalDisplay !== undefined) {
|
|
116
141
|
element.style.display = originalDisplay;
|
|
@@ -122,7 +147,11 @@ class VisibilityService {
|
|
|
122
147
|
}
|
|
123
148
|
|
|
124
149
|
async scanAndApply(container = document.body) {
|
|
125
|
-
//
|
|
150
|
+
// Get ScreenViewer context from shadow root host — needed for ?, ??, {} pattern resolution
|
|
151
|
+
const screenViewer = container.host || null;
|
|
152
|
+
const relativeSignalPath = screenViewer?.relativeSignalsPath || '';
|
|
153
|
+
const root = screenViewer || null;
|
|
154
|
+
|
|
126
155
|
const groupElements = container.querySelectorAll('[data-visibility-enabled="true"]');
|
|
127
156
|
const signalElements = container.querySelectorAll('[data-visibility-signal]');
|
|
128
157
|
const seen = new Set();
|
|
@@ -137,7 +166,7 @@ class VisibilityService {
|
|
|
137
166
|
action: element.getAttribute('data-visibility-action') || 'hide',
|
|
138
167
|
groups: element.getAttribute('data-visibility-groups')?.split(',').filter(g => g) || []
|
|
139
168
|
};
|
|
140
|
-
await this.applyVisibility(element, visibilityConfig);
|
|
169
|
+
await this.applyVisibility(element, visibilityConfig, relativeSignalPath, root);
|
|
141
170
|
} catch (err) {
|
|
142
171
|
console.error('[Visibility] Error:', err);
|
|
143
172
|
}
|