iobroker.eos-admin 7.9.93 → 7.9.96
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/INSTALL_TEST_V7.9.96.md +11 -0
- package/MERGE_UPDATE_README_V7.9.96.md +5 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +19 -19
- package/NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md +44 -0
- package/PUBLISH_STABLE_V7.9.96.md +10 -0
- package/RC73_EOS_ADMIN_MERGE_VALIDATION.md +78 -0
- package/README_EMS_OVERVIEW_MERGE.md +45 -0
- package/README_STABILITY_V7.9.96.md +11 -0
- package/RELEASE_ACCEPTANCE_V7.9.96.md +10 -0
- package/RELEASE_NOTES_V7.9.96.md +12 -0
- package/VALIDATION_EMS_OVERVIEW_MERGE.md +35 -0
- package/adminWww/assets/Adapters-B5_jQ7DE-v84.js +1 -1
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
- package/adminWww/assets/index-D2ymscJA-v84.js +1 -1
- package/adminWww/css/eos-ems-overview.css +62 -0
- package/adminWww/css/nexowatt-native-shell.css +104 -158
- package/adminWww/index.html +15 -11
- package/adminWww/js/eos-account-management.js +1 -1
- package/adminWww/js/eos-assistant.js +5 -556
- package/adminWww/js/eos-basic-settings.js +1 -1
- package/adminWww/js/eos-branding-sanitizer.js +1 -3
- package/adminWww/js/eos-ems-overview.js +140 -0
- package/adminWww/js/eos-native-security.js +1 -1
- package/adminWww/js/eos-policy-client.js +1 -1
- package/adminWww/js/eos-role-bootstrap.js +4 -4
- package/adminWww/js/eos-role-ui.js +34 -38
- package/adminWww/js/nexowatt-native-shell.js +11 -6
- package/adminWww/static/js/nexowatt-stable-login.js +79 -0
- package/adminWww/static/js/nexowatt-stable-v96.js +82 -0
- package/build/lib/chat/assistantKnowledge.js +6 -21
- package/build/lib/chat/chatOrchestrator.js +38 -33
- package/build/lib/chat/mcpClientManager.js +1 -1
- package/build/lib/nexowattStableAuth.js +74 -0
- package/build/lib/web.js +24 -7
- package/build/main.js +123 -308
- package/io-package.json +25 -16
- package/package.json +32 -15
- package/tools/nexowatt-account-management-selftest.cjs +2 -3
- package/tools/nexowatt-apply-ems-overview.cjs +115 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +4 -0
- package/tools/nexowatt-ems-overview-selftest.cjs +35 -0
- package/tools/nexowatt-internal-reserve-selftest.cjs +1 -3
- package/tools/nexowatt-login-layout-selftest.cjs +43 -22
- package/tools/nexowatt-merge-update-selftest.cjs +69 -0
- package/tools/nexowatt-modern-ui-selftest.cjs +4 -5
- package/tools/nexowatt-stable-v96-selftest.cjs +13 -0
- package/tools/nexowatt-sync-release-version.cjs +181 -0
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +16 -0
- package/tools/nexowatt-validate-package.cjs +41 -10
- package/tools/nexowatt-version-sync-selftest.cjs +86 -0
- package/INSTALL_TEST_V7.9.93.md +0 -12
- package/PUBLISH_STABLE_V7.9.93.md +0 -19
- package/README_STABILITY_V7.9.93.md +0 -15
- package/RELEASE_ACCEPTANCE_V7.9.93.md +0 -15
- package/RELEASE_NOTES_V7.9.93.md +0 -10
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.eos-admin",
|
|
3
|
-
"description": "NexoWatt EOS Admin
|
|
4
|
-
"version": "7.9.
|
|
3
|
+
"description": "NexoWatt EOS Admin stable product release with compact login, role-safe services and self-healing sales defaults",
|
|
4
|
+
"version": "7.9.96",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"bluefox <dogafox@gmail.com>",
|
|
7
7
|
"apollon77",
|
|
@@ -114,11 +114,23 @@
|
|
|
114
114
|
"tools/nexowatt-internal-reserve-selftest.cjs",
|
|
115
115
|
"tools/nexowatt-ensure-release-defaults.cjs",
|
|
116
116
|
"tools/nexowatt-release-defaults-selftest.cjs",
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
117
|
+
"tools/nexowatt-sync-release-version.cjs",
|
|
118
|
+
"tools/nexowatt-version-sync-selftest.cjs",
|
|
119
|
+
"tools/nexowatt-merge-update-selftest.cjs",
|
|
120
|
+
"tools/nexowatt-ems-overview-selftest.cjs",
|
|
121
|
+
"README_STABILITY_V7.9.96.md",
|
|
122
|
+
"RELEASE_NOTES_V7.9.96.md",
|
|
123
|
+
"PUBLISH_STABLE_V7.9.96.md",
|
|
124
|
+
"INSTALL_TEST_V7.9.96.md",
|
|
125
|
+
"RELEASE_ACCEPTANCE_V7.9.96.md",
|
|
126
|
+
"MERGE_UPDATE_README_V7.9.96.md",
|
|
127
|
+
"README_EMS_OVERVIEW_MERGE.md",
|
|
128
|
+
"RC73_EOS_ADMIN_MERGE_VALIDATION.md",
|
|
129
|
+
"VALIDATION_EMS_OVERVIEW_MERGE.md",
|
|
130
|
+
"NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md",
|
|
131
|
+
"tools/nexowatt-apply-ems-overview.cjs",
|
|
132
|
+
"tools/nexowatt-ui-overview-runtime-selftest.cjs",
|
|
133
|
+
"tools/nexowatt-stable-v96-selftest.cjs"
|
|
122
134
|
],
|
|
123
135
|
"scripts": {
|
|
124
136
|
"npm": "npm i -f && cd src-admin && npm i -f",
|
|
@@ -129,7 +141,7 @@
|
|
|
129
141
|
"test:integration": "mocha test/integration --exit && npm run test:gui && npm run test:rule && npm run test:unit",
|
|
130
142
|
"test:rule": "mocha test/rule --exit",
|
|
131
143
|
"test:gui": "mocha test/*.gui.js --exit",
|
|
132
|
-
"prepublishOnly": "npm run prepare:eos-release-defaults && npm run check:eos-publish-channel && npm run check:eos-package && npm run check:eos-stability",
|
|
144
|
+
"prepublishOnly": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run check:eos-publish-channel && npm run check:eos-package && npm run check:eos-stability",
|
|
133
145
|
"build": "npm run build:frontend && npm run build:backend && npm run clean:eos-runtime",
|
|
134
146
|
"build:backend": "tsc -p tsconfig.build.json && tsx tasks.mts --backend-i18n",
|
|
135
147
|
"build:frontend": "tsx tasks.mts",
|
|
@@ -153,12 +165,12 @@
|
|
|
153
165
|
"nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
|
|
154
166
|
"nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
|
|
155
167
|
"nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
|
|
156
|
-
"check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-first-login-selftest.cjs && node tools/nexowatt-account-management-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs",
|
|
168
|
+
"check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-first-login-selftest.cjs && node tools/nexowatt-account-management-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && npm run check:eos-stable-v96 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs",
|
|
157
169
|
"nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
|
|
158
170
|
"clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
|
|
159
|
-
"precheck:eos-package": "npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
|
|
160
|
-
"precheck:eos-stability": "npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
|
|
161
|
-
"prepack": "node tools/nexowatt-clean-legacy-runtime.cjs --quiet",
|
|
171
|
+
"precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
|
|
172
|
+
"precheck:eos-stability": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
|
|
173
|
+
"prepack": "node tools/nexowatt-sync-release-version.cjs --quiet && node tools/nexowatt-clean-legacy-runtime.cjs --quiet",
|
|
162
174
|
"check:eos-esm": "node tools/nexowatt-esm-syntax-selftest.cjs",
|
|
163
175
|
"check:eos-imports": "node tools/nexowatt-import-integrity-selftest.cjs",
|
|
164
176
|
"check:eos-release": "npm run check:eos-package && npm run check:eos-stability && npm pack --dry-run",
|
|
@@ -173,7 +185,11 @@
|
|
|
173
185
|
"test:eos-login-layout": "node tools/nexowatt-login-layout-selftest.cjs",
|
|
174
186
|
"test:eos-release-defaults": "node tools/nexowatt-release-defaults-selftest.cjs",
|
|
175
187
|
"prepare:eos-release-defaults": "node tools/nexowatt-ensure-release-defaults.cjs",
|
|
176
|
-
"normalize:eos-release": "npm run prepare:eos-release-defaults"
|
|
188
|
+
"normalize:eos-release": "npm run prepare:eos-release-defaults",
|
|
189
|
+
"sync:eos-version": "node tools/nexowatt-sync-release-version.cjs",
|
|
190
|
+
"test:eos-version-sync": "node tools/nexowatt-version-sync-selftest.cjs",
|
|
191
|
+
"verify:eos-merge": "node tools/nexowatt-merge-update-selftest.cjs",
|
|
192
|
+
"check:eos-stable-v96": "node tools/nexowatt-stable-v96-selftest.cjs"
|
|
177
193
|
},
|
|
178
194
|
"author": "NexoWatt",
|
|
179
195
|
"private": false,
|
|
@@ -184,6 +200,7 @@
|
|
|
184
200
|
},
|
|
185
201
|
"nexowattReleasePolicy": {
|
|
186
202
|
"distTag": "latest",
|
|
187
|
-
"purpose": "
|
|
188
|
-
}
|
|
203
|
+
"purpose": "accepted stable NexoWatt EOS sales-product release 7.9.96"
|
|
204
|
+
},
|
|
205
|
+
"publisher": "NexoWatt"
|
|
189
206
|
}
|
|
@@ -33,15 +33,14 @@ for (const code of [webSource, webBuilt]) {
|
|
|
33
33
|
'canResetInstaller', 'canResetEndUser', "targetUserId === 'system.user.admin'", 'targetUserId === access.userId',
|
|
34
34
|
"access.role === 'installer' && explicitlyManagedRole !== 'enduser'",
|
|
35
35
|
'explicit member of a managed EOS installer/end-user group', 'passwordResetAt', 'passwordResetBy',
|
|
36
|
-
"passwordResetMode = '
|
|
36
|
+
"passwordResetMode = 'initial-password'", "setEosUserPassword(targetUserId, 'nexowatt')", 'passwordlessFirstLoginAllowed = false', 'initialPasswordRequired: true', 'accountDisabled',
|
|
37
37
|
]) if (!code.includes(marker)) fail(`web account marker missing: ${marker}`);
|
|
38
|
-
if (!/randomBytes[^\n]{0,40}48/.test(code)) fail('reset does not generate an unknown random bootstrap secret');
|
|
39
38
|
if (!code.includes('const explicitInstaller = targetAccess.groups.some')) fail('reset target is not restricted to explicit installer membership');
|
|
40
39
|
if (!code.includes('const explicitEndUser = targetAccess.groups.some')) fail('reset target is not restricted to explicit end-user membership');
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
for (const marker of [
|
|
44
|
-
'
|
|
43
|
+
'v91-account-management-under-access-rights', "['admin', 'installer']", 'nexowatt/account/manage',
|
|
45
44
|
'nexowatt/account/reset', 'X-NexoWatt-EOS-Account-Reset', 'data.canResetInstaller === true',
|
|
46
45
|
"account.role === 'enduser'", 'Admin/Service', 'Installateure', 'NEXOWATT_EOS_ACCOUNT_MANAGEMENT',
|
|
47
46
|
'ensureEntrySurface', "currentRoute() !== 'tab-users'", 'eos-account-management-entry',
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(__dirname, '..');
|
|
7
|
+
const introPath = path.join(root, 'src-admin', 'src', 'tabs', 'Intro.tsx');
|
|
8
|
+
const componentPath = path.join(root, 'src-admin', 'src', 'components', 'Intro', 'NexoWattEmsOverview.tsx');
|
|
9
|
+
const cssPath = path.join(root, 'src-admin', 'src', 'index.css');
|
|
10
|
+
const packagePath = path.join(root, 'package.json');
|
|
11
|
+
|
|
12
|
+
function fail(message) {
|
|
13
|
+
console.error(`[NexoWatt EMS Overview Merge] ERROR: ${message}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
function read(file) {
|
|
17
|
+
try { return fs.readFileSync(file, 'utf8'); } catch (error) { fail(`${file} nicht lesbar: ${error.message}`); }
|
|
18
|
+
}
|
|
19
|
+
function write(file, content) { fs.writeFileSync(file, content, 'utf8'); }
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(componentPath)) fail('NexoWattEmsOverview.tsx fehlt im Merge-Paket.');
|
|
22
|
+
let intro = read(introPath);
|
|
23
|
+
if (!intro.includes("import NexoWattEmsOverview from '@/components/Intro/NexoWattEmsOverview';")) {
|
|
24
|
+
const importAnchor = "import IntroCardCamera from '@/components/Intro/IntroCardCamera';\n";
|
|
25
|
+
if (!intro.includes(importAnchor)) fail('Intro.tsx Importanker nicht gefunden. Der Admin-Quellstand ist nicht kompatibel.');
|
|
26
|
+
intro = intro.replace(importAnchor, `${importAnchor}import NexoWattEmsOverview from '@/components/Intro/NexoWattEmsOverview';\n`);
|
|
27
|
+
}
|
|
28
|
+
if (!intro.includes('<NexoWattEmsOverview')) {
|
|
29
|
+
const renderAnchor = ` {this.getInstancesCards()}\n {this.getLinkCards()}\n`;
|
|
30
|
+
if (!intro.includes(renderAnchor)) fail('Intro.tsx Kartenanker nicht gefunden. Der Admin-Quellstand ist nicht kompatibel.');
|
|
31
|
+
intro = intro.replace(renderAnchor, `${renderAnchor} <NexoWattEmsOverview\n socket={this.props.socket}\n t={this.props.t}\n lang={this.props.lang}\n theme={this.props.theme}\n />\n`);
|
|
32
|
+
}
|
|
33
|
+
write(introPath, intro);
|
|
34
|
+
|
|
35
|
+
const cssMarker = '/* nexowatt-ems-overview-v1 */';
|
|
36
|
+
let css = read(cssPath);
|
|
37
|
+
if (!css.includes(cssMarker)) {
|
|
38
|
+
css += `\n\n${cssMarker}\n` + String.raw`
|
|
39
|
+
.eos-ems-overview-card {
|
|
40
|
+
--eos-ems-status-color: #48b9ff;
|
|
41
|
+
flex: 1 1 720px;
|
|
42
|
+
min-width: min(100%, 420px);
|
|
43
|
+
max-width: 1180px;
|
|
44
|
+
min-height: 390px;
|
|
45
|
+
margin: 10px;
|
|
46
|
+
padding: 22px;
|
|
47
|
+
border: 1px solid color-mix(in srgb, var(--eos-ems-status-color) 38%, rgba(255,255,255,.08));
|
|
48
|
+
border-radius: 20px;
|
|
49
|
+
background: linear-gradient(145deg, rgba(4,24,35,.97), rgba(3,16,29,.98));
|
|
50
|
+
color: #ecf8ff;
|
|
51
|
+
box-shadow: 0 18px 55px rgba(0,0,0,.26), inset 0 0 0 1px rgba(255,255,255,.025);
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
}
|
|
54
|
+
.eos-ems-overview-card * { box-sizing: border-box; }
|
|
55
|
+
.eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:space-between; gap:20px; }
|
|
56
|
+
.eos-ems-overview-header h2 { margin:5px 0 6px; font-size:clamp(1.2rem,2vw,1.65rem); color:#f5fbff; }
|
|
57
|
+
.eos-ems-overview-header p { margin:0; max-width:760px; color:#9eb2c2; line-height:1.45; }
|
|
58
|
+
.eos-ems-overview-eyebrow { color:#49e39e; font-size:.72rem; font-weight:900; letter-spacing:.13em; text-transform:uppercase; }
|
|
59
|
+
.eos-ems-overview-state { min-width:126px; display:grid; grid-template-columns:auto 1fr; align-items:center; gap:3px 8px; padding:9px 12px; border:1px solid rgba(255,255,255,.09); border-radius:999px; background:rgba(255,255,255,.035); }
|
|
60
|
+
.eos-ems-overview-state-dot { width:9px; height:9px; border-radius:50%; background:var(--eos-ems-status-color); box-shadow:0 0 15px var(--eos-ems-status-color); }
|
|
61
|
+
.eos-ems-overview-state strong { color:var(--eos-ems-status-color); }
|
|
62
|
+
.eos-ems-overview-state small { grid-column:2; color:#8095a6; }
|
|
63
|
+
.eos-ems-overview-metrics { display:grid; grid-template-columns:repeat(4,minmax(130px,1fr)); gap:10px; margin-top:19px; }
|
|
64
|
+
.eos-ems-overview-metric { min-width:0; padding:12px; border:1px solid rgba(255,255,255,.075); border-radius:14px; background:rgba(255,255,255,.025); }
|
|
65
|
+
.eos-ems-overview-metric span,.eos-ems-overview-metric small { display:block; color:#879cad; font-size:.72rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
66
|
+
.eos-ems-overview-metric strong { display:block; margin:4px 0 2px; color:#f4fbff; font-size:1rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
67
|
+
.eos-ems-overview-budget { height:7px; margin:12px 0 10px; border-radius:999px; background:rgba(255,255,255,.06); overflow:hidden; }
|
|
68
|
+
.eos-ems-overview-budget span { display:block; height:100%; border-radius:inherit; background:linear-gradient(90deg,#01bc69,var(--eos-ems-status-color)); box-shadow:0 0 15px color-mix(in srgb,var(--eos-ems-status-color) 50%,transparent); transition:width .25s ease; }
|
|
69
|
+
.eos-ems-overview-tags { display:flex; flex-wrap:wrap; gap:7px; margin-bottom:14px; }
|
|
70
|
+
.eos-ems-overview-tags span { padding:5px 9px; border:1px solid rgba(72,185,255,.18); border-radius:999px; background:rgba(72,185,255,.07); color:#b9deef; font-size:.72rem; font-weight:750; }
|
|
71
|
+
.eos-ems-overview-tags span.is-warning { border-color:rgba(255,189,89,.3); background:rgba(255,189,89,.09); color:#ffd484; }
|
|
72
|
+
.eos-ems-overview-columns { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
73
|
+
.eos-ems-overview-panel { min-width:0; padding:14px; border:1px solid rgba(255,255,255,.065); border-radius:16px; background:rgba(0,0,0,.12); }
|
|
74
|
+
.eos-ems-overview-panel h3 { margin:0 0 10px; color:#dfeef6; font-size:.9rem; }
|
|
75
|
+
.eos-ems-overview-decision,.eos-ems-overview-event { position:relative; display:grid; gap:2px; margin-top:7px; padding:8px 9px 8px 12px; border-radius:10px; background:rgba(255,255,255,.025); overflow:hidden; }
|
|
76
|
+
.eos-ems-overview-decision::before,.eos-ems-overview-event::before { content:''; position:absolute; inset:0 auto 0 0; width:3px; background:#48b9ff; }
|
|
77
|
+
.eos-ems-overview-decision--warning::before,.eos-ems-overview-event--warning::before,.eos-ems-overview-event--warn::before { background:#ffbd59; }
|
|
78
|
+
.eos-ems-overview-decision--error::before,.eos-ems-overview-event--error::before { background:#ff5f72; }
|
|
79
|
+
.eos-ems-overview-decision strong,.eos-ems-overview-event strong { color:#e7f4fa; font-size:.78rem; }
|
|
80
|
+
.eos-ems-overview-decision span,.eos-ems-overview-event span { color:#91a5b5; font-size:.72rem; line-height:1.35; }
|
|
81
|
+
.eos-ems-overview-decision small { color:#6fcba1; font-size:.68rem; }
|
|
82
|
+
.eos-ems-overview-event { grid-template-columns:64px 1fr; align-items:start; }
|
|
83
|
+
.eos-ems-overview-event time { color:#6f8799; font-size:.68rem; font-variant-numeric:tabular-nums; }
|
|
84
|
+
.eos-ems-overview-event div { display:grid; gap:2px; min-width:0; }
|
|
85
|
+
.eos-ems-overview-empty { color:#718797; font-size:.76rem; }
|
|
86
|
+
.eos-ems-overview-footer { display:flex; justify-content:space-between; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
|
|
87
|
+
.eos-ems-overview-footer a { color:#5ee0c2; font-weight:800; text-decoration:none; }
|
|
88
|
+
.eos-ems-overview-footer a:hover,.eos-ems-overview-footer a:focus-visible { color:#9af2dd; text-decoration:underline; }
|
|
89
|
+
@media (max-width: 1100px) {
|
|
90
|
+
.eos-ems-overview-card { flex-basis:100%; max-width:none; }
|
|
91
|
+
}
|
|
92
|
+
@media (max-width: 760px) {
|
|
93
|
+
.eos-ems-overview-card { min-width:100%; margin:8px 0; padding:16px; }
|
|
94
|
+
.eos-ems-overview-header { display:grid; }
|
|
95
|
+
.eos-ems-overview-state { width:max-content; }
|
|
96
|
+
.eos-ems-overview-metrics { grid-template-columns:1fr 1fr; }
|
|
97
|
+
.eos-ems-overview-columns { grid-template-columns:1fr; }
|
|
98
|
+
.eos-ems-overview-footer { display:grid; }
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
write(cssPath, css);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const packageJson = JSON.parse(read(packagePath));
|
|
105
|
+
packageJson.files = Array.isArray(packageJson.files) ? packageJson.files : [];
|
|
106
|
+
const toolFile = 'tools/nexowatt-ems-overview-selftest.cjs';
|
|
107
|
+
if (!packageJson.files.includes(toolFile)) packageJson.files.push(toolFile);
|
|
108
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
109
|
+
const stability = String(packageJson.scripts['check:eos-stability'] || '');
|
|
110
|
+
if (stability && !stability.includes('nexowatt-ems-overview-selftest.cjs')) {
|
|
111
|
+
packageJson.scripts['check:eos-stability'] = `${stability} && node tools/nexowatt-ems-overview-selftest.cjs`;
|
|
112
|
+
}
|
|
113
|
+
write(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
114
|
+
|
|
115
|
+
console.log('[NexoWatt EMS Overview Merge] OK: Intro-Komponente, responsive Live-Diagnose und Selftest eingebunden.');
|
|
@@ -53,6 +53,10 @@ function walkFiles(dir) {
|
|
|
53
53
|
|
|
54
54
|
function isLegacyVersionedRuntime(file, activeRuntime) {
|
|
55
55
|
const name = path.basename(file);
|
|
56
|
+
// Stable product overlays use the release number in their filename, not the
|
|
57
|
+
// compiled React runtime number. They are release assets and must not be
|
|
58
|
+
// deleted merely because runtimeEntry remains on the validated v84 bundle.
|
|
59
|
+
if (/^nexowatt-stable-v\d+\.js$/i.test(name)) return false;
|
|
56
60
|
const versionMatch = name.match(/-v(\d+)\.(?:js|js\.map|css|css\.map)$/i);
|
|
57
61
|
if (versionMatch) return Number(versionMatch[1]) !== activeRuntime;
|
|
58
62
|
const remoteMatch = name.match(/^remoteEntry-v(\d+)\.(?:js|js\.map)$/i);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const root = path.resolve(__dirname, '..');
|
|
6
|
+
let failed = false;
|
|
7
|
+
const fail = message => { console.error(`[NexoWatt EMS Overview Selftest] ERROR: ${message}`); failed = true; };
|
|
8
|
+
const read = rel => { try { return fs.readFileSync(path.join(root, rel), 'utf8'); } catch (error) { fail(`${rel} nicht lesbar: ${error.message}`); return ''; } };
|
|
9
|
+
const intro = read('src-admin/src/tabs/Intro.tsx');
|
|
10
|
+
const component = read('src-admin/src/components/Intro/NexoWattEmsOverview.tsx');
|
|
11
|
+
const css = read('src-admin/src/index.css');
|
|
12
|
+
for (const marker of [
|
|
13
|
+
"import NexoWattEmsOverview from '@/components/Intro/NexoWattEmsOverview';",
|
|
14
|
+
'<NexoWattEmsOverview',
|
|
15
|
+
]) if (!intro.includes(marker)) fail(`Intro-Vertrag fehlt: ${marker}`);
|
|
16
|
+
for (const marker of [
|
|
17
|
+
'nexowatt-ems-overview-v1',
|
|
18
|
+
"getForeignStates('nexowatt-ui.*.info.adminOverview.*')",
|
|
19
|
+
'document.visibilityState',
|
|
20
|
+
'window.setInterval(() => void load(), 5_000)',
|
|
21
|
+
'Diagnose ist rein lesend',
|
|
22
|
+
'Vollständige EMS-Diagnose öffnen',
|
|
23
|
+
'system.adapter.nexowatt-ui.*.alive',
|
|
24
|
+
]) if (!component.includes(marker)) fail(`Komponenten-Vertrag fehlt: ${marker}`);
|
|
25
|
+
if ((component.match(/NexoWatt EMS · Live-Diagnose/g) || []).length !== 1) fail('EMS-Überschrift ist doppelt oder fehlt.');
|
|
26
|
+
if (!css.includes('/* nexowatt-ems-overview-v1 */')) fail('Responsive EMS-CSS fehlt.');
|
|
27
|
+
if (/setState\(|setForeignState\(|sendToHost\(/.test(component)) fail('EMS-Overview darf keine ioBroker-Schreiboperation enthalten.');
|
|
28
|
+
const assetDir = path.join(root, 'admin', 'assets');
|
|
29
|
+
if (fs.existsSync(assetDir)) {
|
|
30
|
+
const scripts = fs.readdirSync(assetDir).filter(name => name.endsWith('.js'));
|
|
31
|
+
const built = scripts.some(name => fs.readFileSync(path.join(assetDir, name), 'utf8').includes('nexowatt-ems-overview-v1'));
|
|
32
|
+
if (!built) fail('Gebautes Admin-Frontend enthält die EMS-Overview-Komponente nicht. Bitte npm run build ausführen.');
|
|
33
|
+
}
|
|
34
|
+
if (failed) process.exit(1);
|
|
35
|
+
console.log('[NexoWatt EMS Overview Selftest] OK: read-only Diagnosekarte, Intro-Einbindung und responsive Darstellung geprüft.');
|
|
@@ -16,7 +16,6 @@ const role = read('adminWww/js/eos-role-ui.js');
|
|
|
16
16
|
const boot = read('adminWww/js/eos-role-bootstrap.js');
|
|
17
17
|
const drawer = read('src-admin/src/components/Drawer.tsx');
|
|
18
18
|
const assist = read('adminWww/js/eos-assistant.js');
|
|
19
|
-
const knowledge = read('src/lib/chat/assistantKnowledge.ts');
|
|
20
19
|
const io = json('io-package.json');
|
|
21
20
|
|
|
22
21
|
for (const code of [mainSource, mainBuilt]) {
|
|
@@ -45,8 +44,7 @@ for (const marker of ['isOfficialReserveTab', 'isCustomerBackupTab', 'backitup',
|
|
|
45
44
|
for (const marker of ['System-Notfallsicherung', 'NexoWatt Sicherung', '^tab-backitup', 'nexowatt-backup|eos-backup']) {
|
|
46
45
|
if (!drawer.includes(marker)) fail(`Drawer reserve marker missing: ${marker}`);
|
|
47
46
|
}
|
|
48
|
-
if (!assist.includes('
|
|
49
|
-
if (!knowledge.includes('NexoWatt Backup') || !knowledge.includes('Admin/Service')) fail('EOS Assist knowledge lacks role-aware backup context');
|
|
47
|
+
if (!assist.includes('NEXOWATT_EOS_ASSIST_DISABLED')) fail('EOS Assist must remain disabled in the stable reserve test');
|
|
50
48
|
if (io.native?.eosHideLegacyBackupFromNonAdmins !== true) fail('eosHideLegacyBackupFromNonAdmins must default to true');
|
|
51
49
|
if (io.native?.nexowattHideLegacyBackupFromNonAdmins !== true) fail('nexowattHideLegacyBackupFromNonAdmins must default to true');
|
|
52
50
|
const reserve = (io.native?.eosProtectedAdapters || []).find(entry => entry.adapter === 'backitup');
|
|
@@ -22,41 +22,62 @@ const shellTag = String(buildInfo.shellCacheTag || buildInfo.shellCacheVersion |
|
|
|
22
22
|
|
|
23
23
|
if (css !== sourceCss) fail('login CSS source/build drift');
|
|
24
24
|
if (!css.includes('outer frame is decorative only')) fail('decorative compact-frame contract is missing');
|
|
25
|
-
if (css.includes('html.eos-login .MuiPaper-root:has(#username)'))
|
|
25
|
+
if (css.includes('html.eos-login .MuiPaper-root:has(#username)')) {
|
|
26
|
+
fail('broad :has selector still styles the outer login Paper');
|
|
27
|
+
}
|
|
26
28
|
|
|
27
29
|
const outer = css.match(/html\.eos-login main\.MuiPaper-root\s*\{([\s\S]*?)\n\}/)?.[1] || '';
|
|
28
|
-
for (const marker of [
|
|
30
|
+
for (const marker of [
|
|
31
|
+
'position: relative',
|
|
32
|
+
'width: 100%',
|
|
33
|
+
'min-height: 100vh',
|
|
34
|
+
'place-items: center',
|
|
35
|
+
'background: transparent !important',
|
|
36
|
+
'box-shadow: none !important',
|
|
37
|
+
]) {
|
|
29
38
|
if (!outer.includes(marker)) fail(`transparent viewport host rule missing: ${marker}`);
|
|
30
39
|
}
|
|
40
|
+
|
|
31
41
|
const decorative = css.match(/html\.eos-login main\.MuiPaper-root::before\s*\{([\s\S]*?)\n\}/)?.[1] || '';
|
|
32
|
-
for (const marker of [
|
|
42
|
+
for (const marker of [
|
|
43
|
+
"content: ''",
|
|
44
|
+
'width: min(520px, calc(100vw - 24px)) !important',
|
|
45
|
+
'height: min(640px, calc(100dvh - 24px)) !important',
|
|
46
|
+
'transform: translate(-50%, -50%)',
|
|
47
|
+
'pointer-events: none',
|
|
48
|
+
]) {
|
|
33
49
|
if (!decorative.includes(marker)) fail(`decorative rear frame rule missing: ${marker}`);
|
|
34
50
|
}
|
|
35
|
-
|
|
36
|
-
if (!compactBlocks.length) fail('compact login card CSS block missing');
|
|
37
|
-
for (const block of compactBlocks) {
|
|
38
|
-
if (/overflow\s*:\s*auto\s*!important/.test(block) || /overflow-y\s*:\s*(?:auto|scroll)/.test(block)) {
|
|
39
|
-
fail('compact login card still creates an internal scrollbar');
|
|
40
|
-
}
|
|
41
|
-
}
|
|
51
|
+
|
|
42
52
|
for (const marker of [
|
|
43
53
|
'html.eos-login main.MuiPaper-root > .eos-login-card-modern',
|
|
44
54
|
'html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username)',
|
|
45
55
|
'html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password)',
|
|
46
|
-
'width: min(
|
|
47
|
-
'height:
|
|
56
|
+
'width: min(460px, calc(100vw - 56px)) !important',
|
|
57
|
+
'min-height: 540px !important',
|
|
58
|
+
'height: auto !important',
|
|
59
|
+
'overflow: visible !important',
|
|
48
60
|
'max-height: none !important',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (login.includes('
|
|
61
|
+
]) {
|
|
62
|
+
if (!css.includes(marker)) fail(`inner compact card rule missing: ${marker}`);
|
|
63
|
+
}
|
|
64
|
+
if (!css.includes('.eos-login-first-status:empty { display: none; min-height: 0; margin: 0; }')) {
|
|
65
|
+
fail('empty first-login status still reserves vertical space');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!login.includes('Keep the familiar compact login page')) fail('native Login source lacks spacious-page contract');
|
|
69
|
+
if (login.includes('eos-login-role-selector-native') || login.includes('data-eos-account=')) {
|
|
70
|
+
fail('native Login source still renders role-selector buttons');
|
|
71
|
+
}
|
|
57
72
|
if (!bootstrap.includes('compact normal-login first activation')) fail('runtime compact first-login contract missing');
|
|
58
|
-
if (!bootstrap.includes("card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove())"))
|
|
73
|
+
if (!bootstrap.includes("card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove())")) {
|
|
74
|
+
fail('runtime does not remove stale role-selector surfaces');
|
|
75
|
+
}
|
|
59
76
|
if (/data-eos-account=|selector\.innerHTML/.test(bootstrap)) fail('runtime still injects a second role-selector panel');
|
|
60
|
-
|
|
77
|
+
|
|
78
|
+
if (!shellTag || !index.includes(`nexowatt-native-shell.css?v=${shellTag}`)) {
|
|
79
|
+
fail('login CSS cache key is not aligned with build information');
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
if (bad) process.exit(1);
|
|
62
83
|
console.log('[NexoWatt EOS login layout] OK');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(__dirname, '..');
|
|
8
|
+
const fail = message => {
|
|
9
|
+
console.error(`[NexoWatt EOS merge update selftest] ${message}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
};
|
|
12
|
+
const read = file => fs.readFileSync(path.join(root, file), 'utf8');
|
|
13
|
+
const exists = file => fs.existsSync(path.join(root, file));
|
|
14
|
+
|
|
15
|
+
for (const file of [
|
|
16
|
+
'MERGE_UPDATE.ps1',
|
|
17
|
+
'MERGE_UPDATE.cmd',
|
|
18
|
+
'MERGE_UPDATE.sh',
|
|
19
|
+
'MERGE_UPDATE_README_V7.9.96.md',
|
|
20
|
+
'tools/nexowatt-apply-ems-overview.cjs',
|
|
21
|
+
'tools/nexowatt-sync-release-version.cjs',
|
|
22
|
+
'tools/nexowatt-version-sync-selftest.cjs',
|
|
23
|
+
]) {
|
|
24
|
+
if (!exists(file)) fail(`${file} is missing`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ps = read('MERGE_UPDATE.ps1');
|
|
28
|
+
for (const marker of [
|
|
29
|
+
'$PSScriptRoot',
|
|
30
|
+
'nexowatt-apply-ems-overview.cjs',
|
|
31
|
+
'nexowatt-sync-release-version.cjs',
|
|
32
|
+
'npm run check:eos-package',
|
|
33
|
+
'npm run check:eos-stability',
|
|
34
|
+
'npm pack --dry-run',
|
|
35
|
+
]) {
|
|
36
|
+
if (!ps.includes(marker)) fail(`MERGE_UPDATE.ps1 is missing ${marker}`);
|
|
37
|
+
}
|
|
38
|
+
if (/npm\s+publish/i.test(ps)) fail('MERGE_UPDATE.ps1 must never publish automatically');
|
|
39
|
+
|
|
40
|
+
const cmd = read('MERGE_UPDATE.cmd');
|
|
41
|
+
if (!cmd.includes('MERGE_UPDATE.ps1') || !/ExecutionPolicy\s+Bypass/i.test(cmd)) {
|
|
42
|
+
fail('MERGE_UPDATE.cmd does not start the guarded PowerShell verifier');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sh = read('MERGE_UPDATE.sh');
|
|
46
|
+
for (const marker of [
|
|
47
|
+
'set -euo pipefail',
|
|
48
|
+
'nexowatt-apply-ems-overview.cjs',
|
|
49
|
+
'npm run sync:eos-version',
|
|
50
|
+
'npm run check:eos-package',
|
|
51
|
+
'npm run check:eos-stability',
|
|
52
|
+
'npm pack --dry-run',
|
|
53
|
+
]) {
|
|
54
|
+
if (!sh.includes(marker)) fail(`MERGE_UPDATE.sh is missing ${marker}`);
|
|
55
|
+
}
|
|
56
|
+
if (/npm\s+publish/i.test(sh)) fail('MERGE_UPDATE.sh must never publish automatically');
|
|
57
|
+
|
|
58
|
+
const pkg = JSON.parse(read('package.json'));
|
|
59
|
+
if (pkg.scripts['sync:eos-version'] !== 'node tools/nexowatt-sync-release-version.cjs') {
|
|
60
|
+
fail('sync:eos-version script is not canonical');
|
|
61
|
+
}
|
|
62
|
+
if (!pkg.scripts['precheck:eos-package']?.startsWith('npm run sync:eos-version')) {
|
|
63
|
+
fail('package validation does not self-heal stale merged version files');
|
|
64
|
+
}
|
|
65
|
+
if (!pkg.scripts.prepublishOnly?.startsWith('npm run sync:eos-version')) {
|
|
66
|
+
fail('npm publishing does not self-heal stale merged version files');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log('[NexoWatt EOS merge update selftest] OK (flat overwrite + EMS merge + version repair + guarded verification)');
|
|
@@ -19,7 +19,7 @@ for (const marker of [
|
|
|
19
19
|
'--nx-shadow-soft', '--nx-shadow-float', 'backdrop-filter: blur', '#app-paper', '.MuiDialog-paper',
|
|
20
20
|
'.MuiButton-root', '.MuiTableRow-root', ':focus-visible', 'border-radius: 24px', 'linear-gradient',
|
|
21
21
|
'@media (max-width: 720px)', '.eos-activation-steps', '.eos-account-row:hover',
|
|
22
|
-
'main.MuiPaper-root::before', 'width: min(
|
|
22
|
+
'main.MuiPaper-root::before', 'width: min(460px, calc(100vw - 56px)) !important', '.eos-assist-header-root', '.eos-overview-hero', '.eos-overview-grid',
|
|
23
23
|
'.eos-account-management-entry', 'pointer-events: auto !important', 'touch-action: manipulation', 'overflow: visible',
|
|
24
24
|
]) if (!css.includes(marker)) fail(`modern styling marker missing: ${marker}`);
|
|
25
25
|
if (sourceCss !== css) fail('modern CSS source/build drift');
|
|
@@ -29,16 +29,15 @@ for (const marker of ['eos-account-management.js','nexowatt-native-shell.css','e
|
|
|
29
29
|
for (const marker of ['NexoWatt EOS', 'Zugänge & Rollen', 'Endkunden-Zugänge', 'ensureEntrySurface']) {
|
|
30
30
|
if (!accountUi.includes(marker)) fail(`modern account surface missing label: ${marker}`);
|
|
31
31
|
}
|
|
32
|
-
for (const marker of ['installIntegratedFirstLogin','
|
|
32
|
+
for (const marker of ['installIntegratedFirstLogin','v96 compact normal-login first activation','eos-passwordless-card','Idempotent writes are essential here']) {
|
|
33
33
|
if (!bootstrap.includes(marker)) fail(`integrated first-activation surface missing: ${marker}`);
|
|
34
34
|
}
|
|
35
35
|
if (/data-eos-account=|selector\.innerHTML/.test(bootstrap)) fail('compact login still injects role buttons or a second panel');
|
|
36
36
|
if (bootstrap.includes("launcher.className = 'eos-passwordless-launcher'")) fail('old bottom first-login launcher is still rendered');
|
|
37
37
|
if (!shell.includes('ensureModernOverview') || !shell.includes("currentTab() !== 'tab-intro'")) fail('current overview is not modernized');
|
|
38
|
-
if (!role.includes('
|
|
38
|
+
if (!role.includes('never cover the real Admin Intro') || role.includes('overview.innerHTML = `') || !role.includes('navigateToTab')) fail('native role overview contract is missing');
|
|
39
39
|
if (!role.includes('ensureReserveObserver') || !role.includes('state.reserveObserver.observe(paper')) fail('scoped delayed-row reserve filter is missing');
|
|
40
|
-
if (!assist.includes('
|
|
41
|
-
if (assist.includes('position:fixed;bottom') || assist.includes('bottom:')) fail('EOS Assist still uses a bottom floating position');
|
|
40
|
+
if (!assist.includes('NEXOWATT_EOS_ASSIST_DISABLED') || !assist.includes('element.remove()')) fail('EOS Assist stable-disable guard is missing');
|
|
42
41
|
|
|
43
42
|
const observerCount = [
|
|
44
43
|
'eos-policy-client.js', 'eos-branding-sanitizer.js', 'nexowatt-native-shell.js', 'eos-native-security.js',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs=require('node:fs');const path=require('node:path');const root=path.resolve(__dirname,'..');const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const must=(ok,msg)=>{if(!ok){console.error(`[NexoWatt EOS stable v96] ${msg}`);process.exit(1)}};
|
|
4
|
+
const pkg=JSON.parse(read('package.json'));const io=JSON.parse(read('io-package.json'));const info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));const css=read('adminWww/css/nexowatt-native-shell.css');const boot=read('adminWww/js/eos-role-bootstrap.js');const role=read('adminWww/js/eos-role-ui.js');const stable=read('adminWww/static/js/nexowatt-stable-v96.js');const index=read('adminWww/index.html');
|
|
5
|
+
must(pkg.version==='7.9.96'&&io.version==='7.9.96'&&io.common.version==='7.9.96'&&info.version==='7.9.96','version drift');
|
|
6
|
+
must(info.shellCacheVersion===96&&info.shellCacheTag==='96','shell cache is not v96');
|
|
7
|
+
must(css.includes('width: min(460px, calc(100vw - 56px)) !important')&&css.includes('overflow: visible !important'),'spacious login override missing');
|
|
8
|
+
must(!/showFirstLoginPassword[\s\S]{0,5000}form\.elements\.username\.value = claim\.userName/.test(boot),'undefined claim variable remains in first-password flow');
|
|
9
|
+
must(boot.includes('form.elements.username.value = userName;')&&boot.includes('controller.abort(), 8000'),'first-password repair incomplete');
|
|
10
|
+
must(role.includes('never cover the real Admin Intro')&&!role.includes('overview.innerHTML = `'),'old action-tile overview remains');
|
|
11
|
+
must(stable.includes("VERSION = 'v96-spacious-login-first-password-fix'")&&stable.includes('width:min(460px'),'stable v96 login runtime incomplete');
|
|
12
|
+
must(index.includes('nexowatt-stable-v96.js?v=96'),'stable v96 runtime not loaded');
|
|
13
|
+
console.log('[NexoWatt EOS stable v96] OK');
|