iobroker.eos-admin 7.9.42 → 7.9.47
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 +4 -0
- package/adminWww/assets/Adapters-B5_jQ7DE.js +2 -2
- package/adminWww/assets/Instances-YdaGnS5a.js +1 -1
- package/adminWww/index.html +5 -5
- package/adminWww/js/eos-branding.js +40 -33
- package/adminWww/js/eos-objects-state-tools.js +1 -1
- package/adminWww/js/eos-security-ui.js +46 -62
- package/build/lib/web.js +6 -19
- package/build/main.js +85 -7
- package/io-package.json +25 -4
- package/package.json +1 -1
package/adminWww/index.html
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
rel="stylesheet"
|
|
32
32
|
href="css/leaflet.css"
|
|
33
33
|
/>
|
|
34
|
-
<link rel="stylesheet" href="./css/eos-branding.css?v=
|
|
34
|
+
<link rel="stylesheet" href="./css/eos-branding.css?v=47" />
|
|
35
35
|
<link
|
|
36
36
|
rel="manifest"
|
|
37
37
|
href="manifest.json"
|
|
@@ -154,10 +154,10 @@
|
|
|
154
154
|
<script type="module" crossorigin src="./assets/index-CQZugZ1z.js"></script>
|
|
155
155
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
|
|
156
156
|
<link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
|
|
157
|
-
<script defer src="./js/eos-branding.js?v=
|
|
158
|
-
<script defer src="./js/eos-security-ui.js?v=
|
|
159
|
-
<script defer src="./js/eos-assistant.js?v=
|
|
160
|
-
<script defer src="./js/eos-objects-state-tools.js?v=
|
|
157
|
+
<script defer src="./js/eos-branding.js?v=47"></script>
|
|
158
|
+
<script defer src="./js/eos-security-ui.js?v=47"></script>
|
|
159
|
+
<script defer src="./js/eos-assistant.js?v=47"></script>
|
|
160
|
+
<script defer src="./js/eos-objects-state-tools.js?v=47"></script>
|
|
161
161
|
</head>
|
|
162
162
|
<body>
|
|
163
163
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
window.NEXOWATT_EOS_UI_VERSION = '
|
|
4
|
+
window.NEXOWATT_EOS_UI_VERSION = 'v47-delete-logquiet-fix';
|
|
5
5
|
|
|
6
6
|
const BRAND = 'NexoWatt EOS';
|
|
7
7
|
const EOS_MEANING = 'Energy Operation System';
|
|
@@ -308,11 +308,9 @@
|
|
|
308
308
|
];
|
|
309
309
|
|
|
310
310
|
const normalizeSecurityPolicy = policy => {
|
|
311
|
+
// v47: delete protection is fixed to EOS core adapters. Dynamic policy entries
|
|
312
|
+
// from older settings are ignored, otherwise normal Dienste can be blocked.
|
|
311
313
|
const protectedAdapters = new Set(CORE_PROTECTED_ADAPTERS);
|
|
312
|
-
(Array.isArray(policy?.protectedAdapters) ? policy.protectedAdapters : []).forEach(item => {
|
|
313
|
-
const adapter = typeof item === 'string' ? normalizeIdentifier(item) : normalizeIdentifier(item?.adapter || item?.name);
|
|
314
|
-
if (adapter) protectedAdapters.add(adapter);
|
|
315
|
-
});
|
|
316
314
|
const isAdmin = !!(policy?.isAdmin || policy?.isAdminGroup || policy?.isEosAdminGroup || policy?.isAdministrator);
|
|
317
315
|
return {
|
|
318
316
|
loaded: true,
|
|
@@ -378,6 +376,18 @@
|
|
|
378
376
|
.some(el => pattern.test(textOfElement(el)));
|
|
379
377
|
};
|
|
380
378
|
|
|
379
|
+
const SECURITY_SCOPE_MAX_TEXT = 1400;
|
|
380
|
+
const SECURITY_SCOPE_MAX_CONTROLS = 18;
|
|
381
|
+
const isSingleSecurityScope = container => {
|
|
382
|
+
if (!container || !container.querySelectorAll) return false;
|
|
383
|
+
const text = textOfElement(container);
|
|
384
|
+
if (!text || text.length > SECURITY_SCOPE_MAX_TEXT) return false;
|
|
385
|
+
const controls = container.querySelectorAll('button,[role="button"],a,.MuiIconButton-root').length;
|
|
386
|
+
if (controls > SECURITY_SCOPE_MAX_CONTROLS) return false;
|
|
387
|
+
const nested = container.querySelectorAll('[role="row"],tr,.MuiDataGrid-row,.MuiListItem-root,.MuiCard-root,.MuiAccordion-root').length;
|
|
388
|
+
return nested <= 4;
|
|
389
|
+
};
|
|
390
|
+
|
|
381
391
|
const getSecurityContainers = () => Array.from(document.querySelectorAll([
|
|
382
392
|
'#app-paper .MuiCard-root',
|
|
383
393
|
'#app-paper tr.MuiTableRow-root',
|
|
@@ -390,7 +400,7 @@
|
|
|
390
400
|
'#app-paper .MuiFormControlLabel-root',
|
|
391
401
|
'#app-paper .MuiListItem-root',
|
|
392
402
|
'#app-paper .MuiListItemButton-root',
|
|
393
|
-
].join(','))).filter(el => !el.closest('.MuiDialog-paper'));
|
|
403
|
+
].join(','))).filter(el => !el.closest('.MuiDialog-paper') && isSingleSecurityScope(el));
|
|
394
404
|
|
|
395
405
|
const isDeleteControl = el => {
|
|
396
406
|
const text = normalize(el.textContent || el.getAttribute?.('title') || el.getAttribute?.('aria-label') || '');
|
|
@@ -399,18 +409,26 @@
|
|
|
399
409
|
return !!svg || /\b(delete|remove|uninstall|del|loschen|entfernen|deinstallieren)\b/.test(`${text} ${title}`);
|
|
400
410
|
};
|
|
401
411
|
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
control.
|
|
407
|
-
control.
|
|
408
|
-
|
|
409
|
-
control.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
const unlockDeleteControls = () => safe(() => {
|
|
413
|
+
// v47: clean up classes/attributes created by older EOS delete guards. We do not
|
|
414
|
+
// install capture listeners anymore; React handlers enforce the core protection list.
|
|
415
|
+
document.querySelectorAll('#app-paper .eos-protected-delete-control, #app-paper .eos-security-hidden-delete, #app-paper [data-eos-delete-guard-bound="true"]').forEach(control => {
|
|
416
|
+
control.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete');
|
|
417
|
+
control.removeAttribute('data-eos-delete-guard-bound');
|
|
418
|
+
control.removeAttribute('aria-disabled');
|
|
419
|
+
control.style.display = '';
|
|
420
|
+
control.style.pointerEvents = '';
|
|
421
|
+
control.style.visibility = '';
|
|
422
|
+
control.style.opacity = '';
|
|
423
|
+
if (control.hasAttribute('disabled')) control.removeAttribute('disabled');
|
|
424
|
+
if ('disabled' in control) control.disabled = false;
|
|
413
425
|
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const lockDeleteControls = container => {
|
|
429
|
+
// v47: Intentionally no DOM click blocking. Stale capture listeners and disabled
|
|
430
|
+
// attributes were the reason normal services could not be deleted via trash icon.
|
|
431
|
+
unlockDeleteControls();
|
|
414
432
|
};
|
|
415
433
|
|
|
416
434
|
|
|
@@ -460,22 +478,9 @@
|
|
|
460
478
|
});
|
|
461
479
|
|
|
462
480
|
const protectDeleteDialogs = () => {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const text = textOfElement(dialog);
|
|
467
|
-
if (!/(delete|remove|loschen|entfernen|deinstallieren|del\s+)/i.test(text)) return;
|
|
468
|
-
const protectedHit = protectedAdapters.some(adapter => containerMentionsAdapter(dialog, adapter));
|
|
469
|
-
if (!protectedHit) return;
|
|
470
|
-
dialog.classList.add('eos-protected-delete-dialog');
|
|
471
|
-
Array.from(dialog.querySelectorAll('button')).forEach(button => {
|
|
472
|
-
const label = normalize(button.textContent || button.getAttribute('aria-label') || '');
|
|
473
|
-
if (/^(ok|ja|yes|delete|remove|loschen|entfernen|deinstallieren)$/.test(label)) {
|
|
474
|
-
button.disabled = true;
|
|
475
|
-
button.classList.add('eos-protected-delete-control');
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
});
|
|
481
|
+
// v47: Do not disable delete confirmation dialogs in the DOM. The delete command
|
|
482
|
+
// guard is handled in React and keeps the fixed EOS core list protected.
|
|
483
|
+
unlockDeleteControls();
|
|
479
484
|
};
|
|
480
485
|
|
|
481
486
|
const hideSecuritySettingsForNonAdmin = () => {
|
|
@@ -523,6 +528,7 @@
|
|
|
523
528
|
const policy = state.securityPolicy;
|
|
524
529
|
applySecurityClasses();
|
|
525
530
|
releaseNotificationControls();
|
|
531
|
+
unlockDeleteControls();
|
|
526
532
|
// Do not apply EOS security decoration inside native adapter configuration pages.
|
|
527
533
|
// Adapter UIs must remain 100% functional; backend/role checks still protect EOS actions.
|
|
528
534
|
if (isAdapterConfigSurface()) return;
|
|
@@ -535,6 +541,7 @@
|
|
|
535
541
|
|
|
536
542
|
const containers = getSecurityContainers();
|
|
537
543
|
containers.forEach(container => {
|
|
544
|
+
if (!isSingleSecurityScope(container)) return;
|
|
538
545
|
if (!isAdminUser() && policy.hideLegacyAdminForNonAdmins !== false && isLegacyAdminContainer(container)) {
|
|
539
546
|
container.classList.add('eos-hidden-legacy-admin');
|
|
540
547
|
container.setAttribute('aria-hidden', 'true');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
window.NEXOWATT_EOS_OBJECTS_STATE_TOOLS_VERSION = '
|
|
4
|
+
window.NEXOWATT_EOS_OBJECTS_STATE_TOOLS_VERSION = 'v45-delete-services-hardfix';
|
|
5
5
|
|
|
6
6
|
const ACTIVE_CLASS = 'eos-objects-surface';
|
|
7
7
|
const safe = fn => { try { return fn(); } catch (e) { return undefined; } };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v47-delete-logquiet-fix';
|
|
5
5
|
const LEGACY_ADMIN = 'admin';
|
|
6
6
|
const LEGACY_ADMIN_INSTANCE = 'admin.0';
|
|
7
7
|
const CORE_PROTECTED_ADAPTERS = ['admin', 'eos-admin', 'backitup', 'nexowatt-devices', 'nexowatt-device', 'nexowatt-dev', 'nexowatt-ui'];
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
|
|
66
66
|
const isAdminUser = () => !!(state.policy?.isAdmin || state.policy?.isEosAdminGroup || state.policy?.isAdministrator);
|
|
67
67
|
const protectedAdapters = () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return
|
|
68
|
+
// v47: hard delete protection only for EOS core adapters. Dynamic/stale
|
|
69
|
+
// policy entries must never block ordinary installed adapters.
|
|
70
|
+
return new Set([LEGACY_ADMIN, ...CORE_PROTECTED_ADAPTERS].map(normalizeAdapter).filter(Boolean));
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
const isProtectedAdapter = value => {
|
|
@@ -89,7 +89,26 @@
|
|
|
89
89
|
},
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
const
|
|
92
|
+
const SECURITY_SCOPE_MAX_TEXT = 1400;
|
|
93
|
+
const SECURITY_SCOPE_MAX_CONTROLS = 18;
|
|
94
|
+
const isSingleSecurityScope = el => {
|
|
95
|
+
if (!el || !el.querySelectorAll) return false;
|
|
96
|
+
const text = normalize(el.textContent || '');
|
|
97
|
+
if (!text || text.length > SECURITY_SCOPE_MAX_TEXT) return false;
|
|
98
|
+
const controls = el.querySelectorAll('button,[role="button"],a,.MuiIconButton-root').length;
|
|
99
|
+
if (controls > SECURITY_SCOPE_MAX_CONTROLS) return false;
|
|
100
|
+
const nested = el.querySelectorAll('[role="row"],tr,.MuiDataGrid-row,.MuiListItem-root,.MuiCard-root,.MuiAccordion-root').length;
|
|
101
|
+
return nested <= 4;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const closestPanel = el => {
|
|
105
|
+
let node = el?.closest?.('.MuiCard-root, .MuiPaper-root, [role="row"], tr, .MuiListItem-root, .MuiBox-root') || null;
|
|
106
|
+
while (node && node !== document.body && node !== document.documentElement) {
|
|
107
|
+
if (isSingleSecurityScope(node)) return node;
|
|
108
|
+
node = node.parentElement?.closest?.('.MuiCard-root, .MuiPaper-root, [role="row"], tr, .MuiListItem-root, .MuiBox-root') || null;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
93
112
|
const markHidden = el => {
|
|
94
113
|
if (!el || el.classList?.contains('eos-security-keep-visible')) return;
|
|
95
114
|
el.classList.add('eos-security-hidden');
|
|
@@ -128,39 +147,29 @@
|
|
|
128
147
|
});
|
|
129
148
|
};
|
|
130
149
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
for (const protectedName of protectedSet) {
|
|
144
|
-
if (new RegExp(`\\b${protectedName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\.\\d+)?\\b`, 'i').test(text)) {
|
|
145
|
-
adapter = protectedName;
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (!adapter || !protectedSet.has(adapter)) return;
|
|
151
|
-
panel.classList.add('eos-security-protected-adapter');
|
|
152
|
-
panel.querySelectorAll('button,[role="button"],a').forEach(button => {
|
|
153
|
-
const label = normalizeFlat(`${button.textContent || ''} ${button.getAttribute('title') || ''} ${button.getAttribute('aria-label') || ''}`);
|
|
154
|
-
if (/loschen|delete|remove|deinstall|uninstall/.test(label)) {
|
|
155
|
-
button.classList.add('eos-security-hidden-delete');
|
|
156
|
-
button.setAttribute('disabled', 'disabled');
|
|
157
|
-
button.setAttribute('aria-disabled', 'true');
|
|
158
|
-
button.style.display = 'none';
|
|
159
|
-
}
|
|
160
|
-
});
|
|
150
|
+
const releaseDeleteControls = () => {
|
|
151
|
+
// v47: undo markers from older EOS DOM guards. Only touch controls that were
|
|
152
|
+
// marked by EOS scripts, not native Admin disabled states.
|
|
153
|
+
document.querySelectorAll('.eos-security-hidden-delete, .eos-protected-delete-control, [data-eos-security-locked-delete="true"]').forEach(control => {
|
|
154
|
+
control.classList?.remove('eos-security-hidden-delete', 'eos-protected-delete-control');
|
|
155
|
+
control.removeAttribute?.('data-eos-security-locked-delete');
|
|
156
|
+
control.removeAttribute?.('aria-disabled');
|
|
157
|
+
control.style.display = '';
|
|
158
|
+
control.style.visibility = '';
|
|
159
|
+
control.style.pointerEvents = '';
|
|
160
|
+
if (control.hasAttribute?.('disabled')) control.removeAttribute('disabled');
|
|
161
|
+
if ('disabled' in control) control.disabled = false;
|
|
161
162
|
});
|
|
162
163
|
};
|
|
163
164
|
|
|
165
|
+
const hideProtectedDeleteControls = () => {
|
|
166
|
+
// v47: Do not hide or capture trash buttons in the DOM. The shipped React
|
|
167
|
+
// source/bundles already block protected EOS core adapters before executing
|
|
168
|
+
// the ioBroker del command. DOM blocking caused stale/virtualized rows to make
|
|
169
|
+
// normal services undeletable.
|
|
170
|
+
releaseDeleteControls();
|
|
171
|
+
};
|
|
172
|
+
|
|
164
173
|
const replaceTextNodes = () => {
|
|
165
174
|
const map = new Map([
|
|
166
175
|
['EOS security', 'EOS Sicherheit'],
|
|
@@ -293,33 +302,8 @@
|
|
|
293
302
|
state.observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
294
303
|
};
|
|
295
304
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (!target || isAdapterConfigSurface()) return;
|
|
299
|
-
// Native Admin dialogs, install/autocomplete poppers and adapter-owned menus must stay untouched.
|
|
300
|
-
if (target.closest?.('.MuiDialog-root,.MuiModal-root,.MuiPopover-root,.MuiPopper-root,.MuiMenu-root,.MuiAutocomplete-popper,[role="dialog"],[role="listbox"],[role="menu"]')) return;
|
|
301
|
-
const label = normalizeFlat(`${target.textContent || ''} ${target.getAttribute?.('title') || ''} ${target.getAttribute?.('aria-label') || ''}`);
|
|
302
|
-
if (!/loschen|delete|remove|deinstall|uninstall/.test(label)) return;
|
|
303
|
-
|
|
304
|
-
const panel = closestPanel(target);
|
|
305
|
-
if (!panel) return;
|
|
306
|
-
const protectedSet = protectedAdapters();
|
|
307
|
-
const icon = panel.querySelector('img[src*="/adapter/"], img[src*="adapter/"]');
|
|
308
|
-
const src = icon ? String(icon.getAttribute('src') || '') : '';
|
|
309
|
-
const match = src.match(/adapter\/([^\/]+)\//i);
|
|
310
|
-
const adapterFromIcon = match ? normalizeAdapter(match[1]) : '';
|
|
311
|
-
const text = normalizeFlat(panel.textContent || '');
|
|
312
|
-
const protectedHit = adapterFromIcon
|
|
313
|
-
? isProtectedAdapter(adapterFromIcon)
|
|
314
|
-
: [...protectedSet].some(adapter => new RegExp(`\\b${adapter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\.\\d+)?\\b`, 'i').test(text));
|
|
315
|
-
if (!protectedHit) return;
|
|
316
|
-
|
|
317
|
-
target.classList.add('eos-security-hidden-delete');
|
|
318
|
-
target.setAttribute?.('aria-disabled', 'true');
|
|
319
|
-
target.style.display = 'none';
|
|
320
|
-
event.preventDefault();
|
|
321
|
-
event.stopImmediatePropagation();
|
|
322
|
-
}, true);
|
|
305
|
+
// v47: No global capture listener for delete buttons. Protected adapter deletion is
|
|
306
|
+
// handled in the React delete handlers; global DOM interception broke normal Dienste.
|
|
323
307
|
|
|
324
308
|
const start = () => {
|
|
325
309
|
loadPolicy();
|
package/build/lib/web.js
CHANGED
|
@@ -34,6 +34,7 @@ let uuid;
|
|
|
34
34
|
const page404 = (0, node_fs_1.readFileSync)(`${__dirname}/../../public/404.html`).toString('utf8');
|
|
35
35
|
const logTemplate = (0, node_fs_1.readFileSync)(`${__dirname}/../../public/logTemplate.html`).toString('utf8');
|
|
36
36
|
const CORE_PROTECTED_ADAPTER_NAMES = [
|
|
37
|
+
'admin',
|
|
37
38
|
'eos-admin',
|
|
38
39
|
'backitup',
|
|
39
40
|
'nexowatt-devices',
|
|
@@ -411,23 +412,9 @@ class Web {
|
|
|
411
412
|
return [...groups].sort();
|
|
412
413
|
}
|
|
413
414
|
getEosProtectedAdapterNames() {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (Array.isArray(value)) {
|
|
418
|
-
value.forEach(add);
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
if (typeof value === 'object') {
|
|
422
|
-
if (value.enabled === false) return;
|
|
423
|
-
add(value.adapter || value.name);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const adapter = this.normalizeEosAdapterName(String(value));
|
|
427
|
-
if (adapter) names.add(adapter);
|
|
428
|
-
};
|
|
429
|
-
if (this.settings.eosProtectAdapterDeletion !== false) add(this.settings.eosProtectedAdapters);
|
|
430
|
-
return [...names].sort();
|
|
415
|
+
// v47: frontend delete protection is core-only. Ignore old eosProtectedAdapters
|
|
416
|
+
// settings so normal installed adapters/instances remain deletable.
|
|
417
|
+
return [...new Set(CORE_PROTECTED_ADAPTER_NAMES)].sort();
|
|
431
418
|
}
|
|
432
419
|
async getEosGroupsForUser(userId) {
|
|
433
420
|
const groups = new Set();
|
|
@@ -720,7 +707,7 @@ class Web {
|
|
|
720
707
|
hideLegacyAdminForNonAdmins: true,
|
|
721
708
|
hideLegacyAdminFromNonAdmins: true,
|
|
722
709
|
restrictProtectedAdapterControls: true,
|
|
723
|
-
protectedAdapters:
|
|
710
|
+
protectedAdapters: this.getEosProtectedAdapterNames(),
|
|
724
711
|
});
|
|
725
712
|
});
|
|
726
713
|
});
|
|
@@ -809,7 +796,7 @@ class Web {
|
|
|
809
796
|
hideLegacyAdminForNonAdmins: true,
|
|
810
797
|
hideLegacyAdminFromNonAdmins: true,
|
|
811
798
|
restrictProtectedAdapterControls: true,
|
|
812
|
-
protectedAdapters:
|
|
799
|
+
protectedAdapters: this.getEosProtectedAdapterNames(),
|
|
813
800
|
});
|
|
814
801
|
});
|
|
815
802
|
};
|
package/build/main.js
CHANGED
|
@@ -164,6 +164,8 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
164
164
|
eosSecurityDebounce = null;
|
|
165
165
|
/** Avoid concurrent policy writes while the guard corrects objects. */
|
|
166
166
|
eosSecurityRunning = false;
|
|
167
|
+
/** v47: stale delete-lock cleanup is a one-shot migration, not a repeating guard. */
|
|
168
|
+
eosDeleteLockRepairFinished = false;
|
|
167
169
|
constructor(options = {}) {
|
|
168
170
|
options = {
|
|
169
171
|
...options,
|
|
@@ -816,12 +818,10 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
816
818
|
return configured || DEFAULT_LEGACY_ADMIN_LOCK_BIND;
|
|
817
819
|
}
|
|
818
820
|
getProtectedAdapterNames() {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
// but never via common.dontDelete/common.nondeletable. Those flags can interfere with ioBroker updates
|
|
824
|
-
// because updates may replace adapter objects.
|
|
821
|
+
// v47: delete protection is intentionally limited to the fixed EOS core list.
|
|
822
|
+
// Older installations may have stored additional entries in eosProtectedAdapters;
|
|
823
|
+
// those must not make ordinary runtime adapters undeletable in Dienste/Module.
|
|
824
|
+
const result = new Set([LEGACY_ADMIN_ADAPTER_NAME, ...CORE_PROTECTED_ADAPTER_NAMES]);
|
|
825
825
|
return [...result].sort();
|
|
826
826
|
}
|
|
827
827
|
scheduleEosSecurityGuard(reason) {
|
|
@@ -874,6 +874,80 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
874
874
|
}
|
|
875
875
|
return changed;
|
|
876
876
|
}
|
|
877
|
+
async clearStaleAdapterDeleteLocks(id, protectedAdapters) {
|
|
878
|
+
const obj = await this.getForeignObjectAsync(id);
|
|
879
|
+
if (!obj?.common) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
const adapter = String(id)
|
|
883
|
+
.replace(/^system\.adapter\./, '')
|
|
884
|
+
.replace(/\.\d+$/, '');
|
|
885
|
+
const isProtected = protectedAdapters.has(adapter);
|
|
886
|
+
// v47: never "repair" the fixed protected core adapters here. On some systems
|
|
887
|
+
// ioBroker or another guard restores their delete flags immediately, which created
|
|
888
|
+
// a repair/reapply loop and filled the log every debounce cycle.
|
|
889
|
+
if (isProtected) {
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
let changed = false;
|
|
893
|
+
obj.common = obj.common || {};
|
|
894
|
+
// v45/v47: old builds could leave common.dontDelete/common.nondeletable on normal
|
|
895
|
+
// adapter or instance objects. Those flags block ioBroker del even if the UI allows it.
|
|
896
|
+
if (obj.common.dontDelete === true) {
|
|
897
|
+
delete obj.common.dontDelete;
|
|
898
|
+
changed = true;
|
|
899
|
+
}
|
|
900
|
+
if (obj.common.nondeletable === true) {
|
|
901
|
+
obj.common.nondeletable = false;
|
|
902
|
+
changed = true;
|
|
903
|
+
}
|
|
904
|
+
// Also undo stale admin-only ACLs for adapters that are no longer protected by the
|
|
905
|
+
// hard core list. UI protection remains for real core modules.
|
|
906
|
+
if (!isProtected && obj.acl) {
|
|
907
|
+
const targetAcl = this.getAdminOnlyAcl();
|
|
908
|
+
const acl = obj.acl;
|
|
909
|
+
if (acl.owner === targetAcl.owner && acl.ownerGroup === targetAcl.ownerGroup && acl.object === targetAcl.object) {
|
|
910
|
+
delete obj.acl;
|
|
911
|
+
changed = true;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (changed) {
|
|
915
|
+
await this.setForeignObjectAsync(id, obj);
|
|
916
|
+
}
|
|
917
|
+
return changed;
|
|
918
|
+
}
|
|
919
|
+
async repairStaleAdapterDeleteLocks() {
|
|
920
|
+
const protectedAdapters = new Set(this.getProtectedAdapterNames());
|
|
921
|
+
const ids = new Set();
|
|
922
|
+
const collect = async (type) => {
|
|
923
|
+
try {
|
|
924
|
+
const view = await this.getObjectViewAsync('system', type, {
|
|
925
|
+
startkey: 'system.adapter.',
|
|
926
|
+
endkey: 'system.adapter.香',
|
|
927
|
+
});
|
|
928
|
+
for (const row of view.rows || []) {
|
|
929
|
+
if (row.id) {
|
|
930
|
+
ids.add(row.id);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
catch (e) {
|
|
935
|
+
this.log.warn(`Cannot scan ${type} objects for stale delete locks: ${e instanceof Error ? e.message : e}`);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
await collect('adapter');
|
|
939
|
+
await collect('instance');
|
|
940
|
+
let changed = 0;
|
|
941
|
+
for (const id of ids) {
|
|
942
|
+
if (await this.clearStaleAdapterDeleteLocks(id, protectedAdapters)) {
|
|
943
|
+
changed++;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (changed) {
|
|
947
|
+
this.log.debug(`EOS delete guard repaired stale delete locks on ${changed} adapter/instance object(s)`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
877
951
|
async ensureProtectedAdapter(adapter) {
|
|
878
952
|
if (!adapter || adapter === LEGACY_ADMIN_ADAPTER_NAME) {
|
|
879
953
|
return;
|
|
@@ -905,7 +979,7 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
905
979
|
this.log.warn(`Cannot protect instances of adapter "${adapter}": ${e instanceof Error ? e.message : e}`);
|
|
906
980
|
}
|
|
907
981
|
if (changed) {
|
|
908
|
-
this.log.
|
|
982
|
+
this.log.debug(`EOS ACL/UI delete guard applied to adapter "${adapter}"`);
|
|
909
983
|
}
|
|
910
984
|
}
|
|
911
985
|
async ensureLegacyAdminLocked() {
|
|
@@ -949,6 +1023,10 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
949
1023
|
try {
|
|
950
1024
|
await this.ensureLegacyAdminLocked();
|
|
951
1025
|
await this.ensureLegacyAdminVisibleOnlyToAdmins();
|
|
1026
|
+
if (!this.eosDeleteLockRepairFinished) {
|
|
1027
|
+
this.eosDeleteLockRepairFinished = true;
|
|
1028
|
+
await this.repairStaleAdapterDeleteLocks();
|
|
1029
|
+
}
|
|
952
1030
|
if (this.config.eosProtectAdapterDeletion !== false) {
|
|
953
1031
|
for (const adapter of this.getProtectedAdapterNames()) {
|
|
954
1032
|
await this.ensureProtectedAdapter(adapter);
|
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "eos-admin",
|
|
4
|
-
"version": "7.9.
|
|
4
|
+
"version": "7.9.47",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "NexoWatt EOS Admin",
|
|
7
7
|
"de": "NexoWatt EOS Admin",
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"connectionType": "local",
|
|
19
19
|
"dataSource": "push",
|
|
20
20
|
"news": {
|
|
21
|
+
"7.9.47": {
|
|
22
|
+
"en": "Quiet delete guard fix: stale delete-lock repair runs once and skips protected core adapters, preventing repeated log entries while service deletion remains functional.",
|
|
23
|
+
"de": "Leiser Delete-Guard-Fix: Reparatur alter Löschsperren läuft nur einmal und überspringt geschützte Core-Adapter, wodurch wiederholte Logeinträge verhindert werden; Dienste löschen bleibt funktionsfähig."
|
|
24
|
+
},
|
|
25
|
+
"7.9.44": {
|
|
26
|
+
"en": "Fix service/instance delete button: core-only protection, stale lock repair, guarded DOM scope.",
|
|
27
|
+
"de": "Fix für Dienste/Instanzen löschen: Schutz nur für EOS-Core, Reparatur alter Locks, enger DOM-Schutz."
|
|
28
|
+
},
|
|
21
29
|
"7.9.42": {
|
|
22
30
|
"en": "Forced visible update package with cache-busting version markers; adapter/instance deletion is blocked only for Admin/EOS Admin, BackItUp/Backup, NexoWatt Devices and NexoWatt UI; writable datapoints keep reliable command writes.",
|
|
23
31
|
"de": "Sichtbares Force-Update mit Cache-Busting-Versionen; Adapter-/Instanz-Löschung bleibt nur für Admin/EOS Admin, BackItUp/Backup, NexoWatt Devices und NexoWatt UI gesperrt; beschreibbare Datenpunkte schreiben Befehlswerte weiterhin zuverlässig."
|
|
@@ -189,6 +197,19 @@
|
|
|
189
197
|
"7.9.40": {
|
|
190
198
|
"en": "Fixed repository update package mapping by adding packetName and preserved technical npm/ioBroker package output in command dialogs.",
|
|
191
199
|
"de": "Repository-Update-Mapping korrigiert: packetName ergänzt und technische npm/ioBroker-Paketausgaben in Befehlsdialogen unverändert gelassen."
|
|
200
|
+
},
|
|
201
|
+
"7.9.45": {
|
|
202
|
+
"en": "Hard fix for service/module deletion: DOM security scripts no longer capture trash clicks, stale delete locks are repaired, and only EOS core adapters remain protected.",
|
|
203
|
+
"de": "Hardfix für Dienste-/Module-Löschung: DOM-Security-Skripte fangen Mülleimer-Klicks nicht mehr ab, alte Löschsperren werden repariert und nur EOS-Core-Adapter bleiben geschützt.",
|
|
204
|
+
"ru": "Исправление удаления служб/модулей: DOM security больше не перехватывает клики по корзине, устаревшие блокировки удаления исправляются, защищены только основные адаптеры EOS.",
|
|
205
|
+
"pt": "Correção de exclusão de serviços/módulos: scripts DOM security não capturam mais cliques na lixeira, bloqueios antigos são reparados e apenas adaptadores EOS core ficam protegidos.",
|
|
206
|
+
"nl": "Hardfix voor verwijderen van diensten/modules: DOM-securityscripts vangen prullenbakklikken niet meer af, oude verwijderblokkades worden hersteld en alleen EOS-coreadapters blijven beschermd.",
|
|
207
|
+
"fr": "Correctif suppression services/modules : les scripts DOM security ne capturent plus les clics corbeille, les anciens verrous sont réparés et seuls les adaptateurs cœur EOS restent protégés.",
|
|
208
|
+
"it": "Fix eliminazione servizi/moduli: gli script DOM security non intercettano più i clic sul cestino, i vecchi blocchi vengono riparati e solo gli adattatori core EOS restano protetti.",
|
|
209
|
+
"es": "Corrección de eliminación de servicios/módulos: los scripts DOM security ya no capturan clics de papelera, se reparan bloqueos antiguos y solo adaptadores core EOS quedan protegidos.",
|
|
210
|
+
"pl": "Poprawka usuwania usług/modułów: skrypty DOM security nie przechwytują kliknięć kosza, stare blokady są naprawiane i chronione pozostają tylko adaptery rdzeniowe EOS.",
|
|
211
|
+
"uk": "Виправлення видалення служб/модулів: DOM security більше не перехоплює кліки кошика, старі блокування виправляються, захищені лише основні адаптери EOS.",
|
|
212
|
+
"zh-cn": "服务/模块删除硬修复:DOM security 脚本不再拦截垃圾桶点击,会修复旧删除锁,仅 EOS 核心适配器受保护。"
|
|
192
213
|
}
|
|
193
214
|
},
|
|
194
215
|
"desc": {
|
|
@@ -226,7 +247,7 @@
|
|
|
226
247
|
"icon": "admin.svg",
|
|
227
248
|
"messagebox": true,
|
|
228
249
|
"enabled": true,
|
|
229
|
-
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
250
|
+
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.47/admin/admin.svg",
|
|
230
251
|
"keywords": [
|
|
231
252
|
"NexoWatt",
|
|
232
253
|
"EOS",
|
|
@@ -237,7 +258,7 @@
|
|
|
237
258
|
"licensed"
|
|
238
259
|
],
|
|
239
260
|
"compact": true,
|
|
240
|
-
"readme": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
261
|
+
"readme": "https://unpkg.com/iobroker.eos-admin@7.9.47/README.md",
|
|
241
262
|
"authors": [
|
|
242
263
|
"bluefox <bluefox@ccu.io>",
|
|
243
264
|
"hobbyquaker <hq@ccu.io>"
|
|
@@ -306,7 +327,7 @@
|
|
|
306
327
|
"nondeletable": false,
|
|
307
328
|
"allowAdapterUpdate": true,
|
|
308
329
|
"allowAdapterDelete": false,
|
|
309
|
-
"meta": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
330
|
+
"meta": "https://unpkg.com/iobroker.eos-admin@7.9.47/io-package.json",
|
|
310
331
|
"npmPackage": "iobroker.eos-admin",
|
|
311
332
|
"packetName": "iobroker.eos-admin"
|
|
312
333
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.eos-admin",
|
|
3
3
|
"description": "NexoWatt EOS Admin interface with reliable repository update mapping and technical log preservation.",
|
|
4
|
-
"version": "7.9.
|
|
4
|
+
"version": "7.9.47",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"bluefox <dogafox@gmail.com>",
|
|
7
7
|
"apollon77",
|