iobroker.eos-admin 7.9.96 → 7.9.98
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.98.md +39 -0
- package/MERGE_UPDATE_README_V7.9.98.md +29 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +20 -18
- package/PUBLISH_STABLE_V7.9.98.md +33 -0
- package/README.md +16 -6
- package/README_STABILITY_V7.9.98.md +22 -0
- package/RELEASE_ACCEPTANCE_V7.9.98.md +48 -0
- package/RELEASE_NOTES_V7.9.98.md +32 -0
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +1 -1
- package/adminWww/assets/bootstrap-COulQZax-v84.js +3 -3
- package/adminWww/assets/index-D2ymscJA-v84.js +1 -1
- package/adminWww/css/eos-ems-overview.css +21 -0
- package/adminWww/css/nexowatt-native-shell.css +174 -40
- package/adminWww/index.html +15 -15
- package/adminWww/js/eos-account-management.js +26 -13
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-ems-overview.js +136 -39
- package/adminWww/js/eos-role-bootstrap.js +7 -7
- package/adminWww/js/eos-role-ui.js +2 -2
- package/adminWww/js/nexowatt-native-shell.js +9 -2
- package/adminWww/static/js/nexowatt-stable-login.js +28 -62
- package/adminWww/static/js/nexowatt-stable-v98.js +33 -0
- package/build/lib/web.js +112 -79
- package/build/main.js +1 -1
- package/io-package.json +15 -5
- package/package.json +14 -12
- package/tools/nexowatt-account-management-selftest.cjs +16 -57
- package/tools/nexowatt-account-password-flow-selftest.cjs +186 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +33 -10
- package/tools/nexowatt-login-layout-selftest.cjs +12 -81
- package/tools/nexowatt-merge-update-selftest.cjs +3 -1
- package/tools/nexowatt-modern-ui-selftest.cjs +2 -2
- package/tools/nexowatt-password-write-selftest.cjs +48 -0
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +9 -1
- package/tools/nexowatt-stable-v98-selftest.cjs +14 -0
- package/tools/nexowatt-sync-release-version.cjs +42 -1
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +11 -11
- package/tools/nexowatt-validate-package.cjs +20 -11
- package/tools/nexowatt-version-sync-selftest.cjs +18 -1
- package/INSTALL_TEST_V7.9.96.md +0 -11
- package/MERGE_UPDATE_README_V7.9.96.md +0 -5
- package/PUBLISH_STABLE_V7.9.96.md +0 -10
- package/README_STABILITY_V7.9.96.md +0 -11
- package/RELEASE_ACCEPTANCE_V7.9.96.md +0 -10
- package/RELEASE_NOTES_V7.9.96.md +0 -12
- package/adminWww/static/js/nexowatt-stable-v96.js +0 -82
- package/tools/nexowatt-stable-v96-selftest.cjs +0 -13
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const vm = require('node:vm');
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(__dirname, '..');
|
|
10
|
+
const built = fs.readFileSync(path.join(root, 'build/lib/web.js'), 'utf8');
|
|
11
|
+
const accountUi = fs.readFileSync(path.join(root, 'adminWww/js/eos-account-management.js'), 'utf8');
|
|
12
|
+
const firstLoginUi = fs.readFileSync(path.join(root, 'adminWww/js/eos-role-bootstrap.js'), 'utf8');
|
|
13
|
+
|
|
14
|
+
function methodSlice(startMarker, endMarker) {
|
|
15
|
+
const start = built.indexOf(startMarker);
|
|
16
|
+
const end = built.indexOf(endMarker, start + startMarker.length);
|
|
17
|
+
assert(start >= 0 && end > start, `method block not found: ${startMarker}`);
|
|
18
|
+
return built.slice(start, end);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const methods = [
|
|
22
|
+
methodSlice(' normalizeEosUserId(value) {', ' normalizeEosAdapterName(value) {'),
|
|
23
|
+
methodSlice(' async readEosCurrentUser(req) {', ' getEosConfiguredRoleGroups(...keys) {'),
|
|
24
|
+
methodSlice(' async getEosRequestAccess(req) {', ' getEosRoleCapabilities(role) {'),
|
|
25
|
+
methodSlice(' validateEosFirstLoginPassword(password, passwordRepeat, userId) {', ' getEosPasswordUserName(userId) {'),
|
|
26
|
+
methodSlice(' getEosPasswordUserName(userId) {', ' async destroyEosRequestSessions(req) {'),
|
|
27
|
+
methodSlice(' async saveEosFirstLoginPassword(req, res) {', ' isEosPasswordlessFirstLoginEnabled() {'),
|
|
28
|
+
methodSlice(' async resetEosAccountPassword(req, res) {', ' async getEosHistoryInstances() {'),
|
|
29
|
+
methodSlice(' isEosSameOriginWrite(req) {', ' async saveEosBasicSettings(req, res) {'),
|
|
30
|
+
].join('\n');
|
|
31
|
+
|
|
32
|
+
const sandbox = { module: { exports: null }, URL, Set, console, EOS_PASSWORD_SERVICE_USER: 'system.user.admin' };
|
|
33
|
+
vm.runInNewContext(`module.exports = class Harness {\n${methods}\n}`, sandbox);
|
|
34
|
+
const Harness = sandbox.module.exports;
|
|
35
|
+
|
|
36
|
+
function responseMock() {
|
|
37
|
+
return {
|
|
38
|
+
statusCode: 200,
|
|
39
|
+
payload: null,
|
|
40
|
+
headers: {},
|
|
41
|
+
clearedCookies: [],
|
|
42
|
+
setHeader(name, value) { this.headers[String(name).toLowerCase()] = value; },
|
|
43
|
+
status(code) { this.statusCode = code; return this; },
|
|
44
|
+
json(value) { this.payload = value; return this; },
|
|
45
|
+
clearCookie(name) { this.clearedCookies.push(name); },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function requestMock(body = {}, headers = {}) {
|
|
50
|
+
return {
|
|
51
|
+
body,
|
|
52
|
+
headers: {
|
|
53
|
+
host: '192.168.10.124:8081',
|
|
54
|
+
origin: 'http://192.168.10.124:8081',
|
|
55
|
+
'sec-fetch-site': 'same-origin',
|
|
56
|
+
...headers,
|
|
57
|
+
},
|
|
58
|
+
query: {},
|
|
59
|
+
socket: { remoteAddress: '192.168.10.50' },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function resetScenario(requesterRole, targetRole, targetGroups) {
|
|
64
|
+
const harness = new Harness();
|
|
65
|
+
const writes = [];
|
|
66
|
+
const metadata = {};
|
|
67
|
+
harness.getEosRequestAccess = async () => ({ userId: requesterRole === 'admin' ? 'system.user.admin' : 'system.user.installer', role: requesterRole });
|
|
68
|
+
harness.normalizeEosUserId = value => String(value || '').startsWith('system.user.') ? String(value) : `system.user.${value}`;
|
|
69
|
+
harness.getEosAccessForUserId = async () => ({ role: targetRole, groups: targetGroups });
|
|
70
|
+
harness.getEosInstallerGroups = () => ['system.group.installer', 'system.group.installateur'];
|
|
71
|
+
harness.getEosEndUserGroups = () => ['system.group.endkunde', 'system.group.user'];
|
|
72
|
+
harness.adapter = {
|
|
73
|
+
setPasswordAsync: async (user, password) => writes.push([`system.user.${user}`, password]),
|
|
74
|
+
checkPasswordAsync: async (user, password) => [password === 'nexowatt', `system.user.${user}`],
|
|
75
|
+
getForeignObjectAsync: async id => ({ _id: id, common: { enabled: true, password: '$hash' }, native: { keep: true, nexowattEosAccount: {} } }),
|
|
76
|
+
extendForeignObjectAsync: async (id, patch) => {
|
|
77
|
+
metadata.id = id;
|
|
78
|
+
metadata.native = patch.native;
|
|
79
|
+
},
|
|
80
|
+
log: { warn: () => undefined, info: () => undefined },
|
|
81
|
+
};
|
|
82
|
+
harness.invalidateEosPasswordClaimsForUser = () => undefined;
|
|
83
|
+
const res = responseMock();
|
|
84
|
+
await harness.resetEosAccountPassword(
|
|
85
|
+
requestMock({ user: targetRole === 'installer' ? 'system.user.installer-target' : 'system.user.user' }, { 'x-nexowatt-eos-account-reset': '1' }),
|
|
86
|
+
res,
|
|
87
|
+
);
|
|
88
|
+
return { res, writes, metadata };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
(async () => {
|
|
92
|
+
// Authenticated request identities are normalized to the real ioBroker user object id.
|
|
93
|
+
const accessHarness = new Harness();
|
|
94
|
+
accessHarness.getEosAccessForUserId = async userId => ({ userId, role: userId === 'system.user.admin' ? 'admin' : 'enduser' });
|
|
95
|
+
const directAdmin = await accessHarness.getEosRequestAccess({ user: 'admin' });
|
|
96
|
+
assert.equal(directAdmin.userId, 'system.user.admin');
|
|
97
|
+
assert.equal(directAdmin.role, 'admin');
|
|
98
|
+
const objectAdmin = await accessHarness.getEosRequestAccess({ user: { id: 'system.user.admin' } });
|
|
99
|
+
assert.equal(objectAdmin.userId, 'system.user.admin');
|
|
100
|
+
assert.equal(objectAdmin.role, 'admin');
|
|
101
|
+
|
|
102
|
+
// Admin/Service can reset an explicitly managed End User and Installer account.
|
|
103
|
+
const adminEndUser = await resetScenario('admin', 'enduser', ['system.group.endkunde']);
|
|
104
|
+
assert.equal(adminEndUser.res.statusCode, 200);
|
|
105
|
+
assert.deepEqual(adminEndUser.writes, [['system.user.user', 'nexowatt']]);
|
|
106
|
+
assert.equal(adminEndUser.metadata.native.nexowattEosAccount.forcePasswordChange, true);
|
|
107
|
+
assert.equal(adminEndUser.metadata.native.nexowattEosAccount.passwordResetMode, 'initial-password');
|
|
108
|
+
assert.equal(adminEndUser.metadata.native.nexowattPasswordChangeRequired, true);
|
|
109
|
+
|
|
110
|
+
const adminInstaller = await resetScenario('admin', 'installer', ['system.group.installer']);
|
|
111
|
+
assert.equal(adminInstaller.res.statusCode, 200);
|
|
112
|
+
assert.deepEqual(adminInstaller.writes, [['system.user.installer-target', 'nexowatt']]);
|
|
113
|
+
|
|
114
|
+
// Installer may never reset another installer account.
|
|
115
|
+
const installerDenied = await resetScenario('installer', 'installer', ['system.group.installer']);
|
|
116
|
+
assert.equal(installerDenied.res.statusCode, 403);
|
|
117
|
+
assert.equal(installerDenied.res.payload.error, 'permissionError');
|
|
118
|
+
assert.equal(installerDenied.writes.length, 0);
|
|
119
|
+
|
|
120
|
+
// First-login password save must write the real account and clear every force-change flag.
|
|
121
|
+
const harness = new Harness();
|
|
122
|
+
const passwordWrites = [];
|
|
123
|
+
let metadata;
|
|
124
|
+
let sessionsDestroyed = false;
|
|
125
|
+
harness.getEosRequestAccess = async () => ({ userId: 'system.user.user', role: 'enduser' });
|
|
126
|
+
harness.getEosFirstLoginPasswordState = async () => ({ required: true, initialized: false, minLength: 10, version: 1 });
|
|
127
|
+
harness.getEosFirstLoginPasswordMinLength = () => 10;
|
|
128
|
+
harness.destroyEosRequestSessions = async () => { sessionsDestroyed = true; };
|
|
129
|
+
harness.adapter = {
|
|
130
|
+
setPasswordAsync: async (user, password) => passwordWrites.push([`system.user.${user}`, password]),
|
|
131
|
+
checkPasswordAsync: async (user, password) => [password === 'NexoWatt2025!', `system.user.${user}`],
|
|
132
|
+
getForeignObjectAsync: async id => ({ _id: id, common: { enabled: true, password: '$hash' }, native: { nexowattEosAccount: {} } }),
|
|
133
|
+
extendForeignObjectAsync: async (id, patch) => { metadata = { id, native: patch.native }; },
|
|
134
|
+
log: { info: () => undefined },
|
|
135
|
+
};
|
|
136
|
+
const acceptedPassword = harness.validateEosFirstLoginPassword(
|
|
137
|
+
'NexoWatt2025!',
|
|
138
|
+
'NexoWatt2025!',
|
|
139
|
+
'system.user.user',
|
|
140
|
+
);
|
|
141
|
+
assert.equal(acceptedPassword.valid, true);
|
|
142
|
+
assert.equal(acceptedPassword.password, 'NexoWatt2025!');
|
|
143
|
+
const saveRes = responseMock();
|
|
144
|
+
await harness.saveEosFirstLoginPassword(
|
|
145
|
+
requestMock(
|
|
146
|
+
{ password: 'NexoWatt2025!', passwordRepeat: 'NexoWatt2025!' },
|
|
147
|
+
{ 'x-nexowatt-eos-first-login': '1' },
|
|
148
|
+
),
|
|
149
|
+
saveRes,
|
|
150
|
+
);
|
|
151
|
+
assert.equal(saveRes.statusCode, 200);
|
|
152
|
+
assert.equal(saveRes.payload.success, true);
|
|
153
|
+
assert.equal(saveRes.payload.logoutRequired, true);
|
|
154
|
+
assert.deepEqual(passwordWrites, [['system.user.user', 'NexoWatt2025!']]);
|
|
155
|
+
assert.equal(metadata.native.nexowattEosAccount.passwordInitialized, true);
|
|
156
|
+
assert.equal(metadata.native.nexowattEosAccount.forcePasswordChange, false);
|
|
157
|
+
assert.equal(metadata.native.nexowattPasswordChangeRequired, false);
|
|
158
|
+
assert.equal(metadata.native.eosFirstLoginRequired, false);
|
|
159
|
+
assert.equal(sessionsDestroyed, true);
|
|
160
|
+
assert.deepEqual(saveRes.clearedCookies.sort(), ['access_token', 'connect.sid', 'refresh_token'].sort());
|
|
161
|
+
|
|
162
|
+
// Same-origin logic must support direct access and a reverse proxy without accepting cross-site writes.
|
|
163
|
+
const originHarness = new Harness();
|
|
164
|
+
assert.equal(originHarness.isEosSameOriginWrite(requestMock()), true);
|
|
165
|
+
assert.equal(originHarness.isEosSameOriginWrite(requestMock({}, {
|
|
166
|
+
host: '127.0.0.1:8081',
|
|
167
|
+
origin: 'https://eos.example.test',
|
|
168
|
+
'x-forwarded-host': 'eos.example.test',
|
|
169
|
+
'sec-fetch-site': 'same-origin',
|
|
170
|
+
})), true);
|
|
171
|
+
assert.equal(originHarness.isEosSameOriginWrite(requestMock({}, {
|
|
172
|
+
origin: 'https://attacker.example',
|
|
173
|
+
'sec-fetch-site': 'cross-site',
|
|
174
|
+
})), false);
|
|
175
|
+
|
|
176
|
+
// Browser writes must preserve the authenticated session cookie.
|
|
177
|
+
assert.match(accountUi, /nexowatt\/account\/reset/);
|
|
178
|
+
assert.match(accountUi, /method:\s*'POST',\s*credentials:\s*'include'/);
|
|
179
|
+
assert.match(firstLoginUi, /nexowatt\/account\/first-password/);
|
|
180
|
+
assert.match(firstLoginUi, /method:\s*'POST',\s*credentials:\s*'include'/);
|
|
181
|
+
|
|
182
|
+
console.log('[NexoWatt EOS account password flow] OK: Admin/Installer permissions, initial password reset, personal password save, session and origin checks verified.');
|
|
183
|
+
})().catch(error => {
|
|
184
|
+
console.error(error && error.stack ? error.stack : error);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
});
|
|
@@ -36,6 +36,24 @@ function runtimeNumber(root) {
|
|
|
36
36
|
return value;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function shellNumber(root) {
|
|
40
|
+
const info = readJson(path.join(root, 'NEXOWATT_EOS_BUILD_INFO.json'));
|
|
41
|
+
const candidates = [info.shellCacheVersion, info.shellCacheTag, info.label];
|
|
42
|
+
for (const candidate of candidates) {
|
|
43
|
+
const value = Number(String(candidate ?? '').replace(/^v/i, ''));
|
|
44
|
+
if (Number.isFinite(value) && value > 0) return value;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const pkg = readJson(path.join(root, 'package.json'));
|
|
48
|
+
const patch = Number(String(pkg.version || '').split('.')[2]);
|
|
49
|
+
if (Number.isFinite(patch) && patch > 0) return patch;
|
|
50
|
+
} catch (_) {
|
|
51
|
+
// The build info remains the canonical source. The package fallback is
|
|
52
|
+
// only used for isolated cleanup fixtures.
|
|
53
|
+
}
|
|
54
|
+
throw new Error('Invalid shellCacheVersion in NEXOWATT_EOS_BUILD_INFO.json');
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
function walkFiles(dir) {
|
|
40
58
|
if (!fs.existsSync(dir)) return [];
|
|
41
59
|
const result = [];
|
|
@@ -51,12 +69,14 @@ function walkFiles(dir) {
|
|
|
51
69
|
return result;
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
function isLegacyVersionedRuntime(file, activeRuntime) {
|
|
72
|
+
function isLegacyVersionedRuntime(file, activeRuntime, activeShell) {
|
|
55
73
|
const name = path.basename(file);
|
|
56
|
-
// Stable product overlays use the release number in their filename,
|
|
57
|
-
// compiled React runtime
|
|
58
|
-
//
|
|
59
|
-
|
|
74
|
+
// Stable product overlays use the product release number in their filename,
|
|
75
|
+
// while the compiled React runtime can intentionally remain on v84. Keep
|
|
76
|
+
// only the active stable overlay and remove stale release assets left behind
|
|
77
|
+
// by a flat ZIP update (for example v96 when installing v97).
|
|
78
|
+
const stableMatch = name.match(/^nexowatt-stable-v(\d+)\.js$/i);
|
|
79
|
+
if (stableMatch) return Number(stableMatch[1]) !== activeShell;
|
|
60
80
|
const versionMatch = name.match(/-v(\d+)\.(?:js|js\.map|css|css\.map)$/i);
|
|
61
81
|
if (versionMatch) return Number(versionMatch[1]) !== activeRuntime;
|
|
62
82
|
const remoteMatch = name.match(/^remoteEntry-v(\d+)\.(?:js|js\.map)$/i);
|
|
@@ -69,23 +89,25 @@ function isLegacyVersionedRuntime(file, activeRuntime) {
|
|
|
69
89
|
|
|
70
90
|
function collectLegacyRuntimeFiles(root = DEFAULT_ROOT) {
|
|
71
91
|
const activeRuntime = runtimeNumber(root);
|
|
92
|
+
const activeShell = shellNumber(root);
|
|
72
93
|
const scanRoots = [
|
|
73
94
|
path.join(root, 'adminWww', 'assets'),
|
|
74
95
|
path.join(root, 'adminWww'),
|
|
75
96
|
path.join(root, 'src-admin', 'build', 'assets'),
|
|
76
97
|
path.join(root, 'src-admin', 'build'),
|
|
98
|
+
path.join(root, 'src-admin', 'public'),
|
|
77
99
|
];
|
|
78
100
|
const files = new Set();
|
|
79
101
|
for (const scanRoot of scanRoots) {
|
|
80
102
|
for (const file of walkFiles(scanRoot)) {
|
|
81
|
-
if (isLegacyVersionedRuntime(file, activeRuntime)) files.add(file);
|
|
103
|
+
if (isLegacyVersionedRuntime(file, activeRuntime, activeShell)) files.add(file);
|
|
82
104
|
}
|
|
83
105
|
}
|
|
84
106
|
for (const rel of LEGACY_OVERLAYS) {
|
|
85
107
|
const file = path.join(root, rel);
|
|
86
108
|
if (fs.existsSync(file)) files.add(file);
|
|
87
109
|
}
|
|
88
|
-
return { activeRuntime, files: [...files].sort() };
|
|
110
|
+
return { activeRuntime, activeShell, files: [...files].sort() };
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
function cleanLegacyRuntime(options = {}) {
|
|
@@ -93,7 +115,7 @@ function cleanLegacyRuntime(options = {}) {
|
|
|
93
115
|
const dryRun = options.dryRun === true;
|
|
94
116
|
const quiet = options.quiet === true;
|
|
95
117
|
const silent = options.silent === true;
|
|
96
|
-
const { activeRuntime, files } = collectLegacyRuntimeFiles(root);
|
|
118
|
+
const { activeRuntime, activeShell, files } = collectLegacyRuntimeFiles(root);
|
|
97
119
|
const removed = [];
|
|
98
120
|
const failed = [];
|
|
99
121
|
|
|
@@ -125,13 +147,13 @@ function cleanLegacyRuntime(options = {}) {
|
|
|
125
147
|
|
|
126
148
|
if (!silent) {
|
|
127
149
|
if (!quiet) {
|
|
128
|
-
console.log(`[NexoWatt EOS runtime cleanup] OK (runtime v${activeRuntime}, removed ${removed.length} legacy file${removed.length === 1 ? '' : 's'})`);
|
|
150
|
+
console.log(`[NexoWatt EOS runtime cleanup] OK (runtime v${activeRuntime}, shell v${activeShell}, removed ${removed.length} legacy file${removed.length === 1 ? '' : 's'})`);
|
|
129
151
|
} else if (removed.length) {
|
|
130
152
|
console.log(`[NexoWatt EOS runtime cleanup] removed ${removed.length} legacy runtime files before validation`);
|
|
131
153
|
}
|
|
132
154
|
}
|
|
133
155
|
|
|
134
|
-
return { activeRuntime, removed };
|
|
156
|
+
return { activeRuntime, activeShell, removed };
|
|
135
157
|
}
|
|
136
158
|
|
|
137
159
|
if (require.main === module) {
|
|
@@ -148,4 +170,5 @@ module.exports = {
|
|
|
148
170
|
collectLegacyRuntimeFiles,
|
|
149
171
|
cleanLegacyRuntime,
|
|
150
172
|
isLegacyVersionedRuntime,
|
|
173
|
+
shellNumber,
|
|
151
174
|
};
|
|
@@ -1,83 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs =
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const css = read('adminWww/css/nexowatt-native-shell.css');
|
|
16
|
-
const sourceCss = read('src-admin/public/css/nexowatt-native-shell.css');
|
|
17
|
-
const login = read('src-admin/src/login/Login.tsx');
|
|
18
|
-
const bootstrap = read('adminWww/js/eos-role-bootstrap.js');
|
|
19
|
-
const index = read('adminWww/index.html');
|
|
20
|
-
const buildInfo = JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));
|
|
21
|
-
const shellTag = String(buildInfo.shellCacheTag || buildInfo.shellCacheVersion || '');
|
|
22
|
-
|
|
23
|
-
if (css !== sourceCss) fail('login CSS source/build drift');
|
|
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)')) {
|
|
26
|
-
fail('broad :has selector still styles the outer login Paper');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const outer = css.match(/html\.eos-login main\.MuiPaper-root\s*\{([\s\S]*?)\n\}/)?.[1] || '';
|
|
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
|
-
]) {
|
|
38
|
-
if (!outer.includes(marker)) fail(`transparent viewport host rule missing: ${marker}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const decorative = css.match(/html\.eos-login main\.MuiPaper-root::before\s*\{([\s\S]*?)\n\}/)?.[1] || '';
|
|
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
|
-
]) {
|
|
49
|
-
if (!decorative.includes(marker)) fail(`decorative rear frame rule missing: ${marker}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for (const marker of [
|
|
53
|
-
'html.eos-login main.MuiPaper-root > .eos-login-card-modern',
|
|
54
|
-
'html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username)',
|
|
55
|
-
'html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password)',
|
|
56
|
-
'width: min(460px, calc(100vw - 56px)) !important',
|
|
57
|
-
'min-height: 540px !important',
|
|
58
|
-
'height: auto !important',
|
|
59
|
-
'overflow: visible !important',
|
|
60
|
-
'max-height: none !important',
|
|
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
|
-
}
|
|
72
|
-
if (!bootstrap.includes('compact normal-login first activation')) fail('runtime compact first-login contract missing');
|
|
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
|
-
}
|
|
76
|
-
if (/data-eos-account=|selector\.innerHTML/.test(bootstrap)) fail('runtime still injects a second role-selector panel');
|
|
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
|
-
|
|
82
|
-
if (bad) process.exit(1);
|
|
83
|
-
console.log('[NexoWatt EOS login layout] OK');
|
|
3
|
+
const fs=require('node:fs');const path=require('node:path');const root=path.resolve(__dirname,'..');
|
|
4
|
+
const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');let bad=false;const fail=msg=>{console.error(`[NexoWatt EOS login layout] ${msg}`);bad=true;};
|
|
5
|
+
const css=read('adminWww/css/nexowatt-native-shell.css');const sourceCss=read('src-admin/public/css/nexowatt-native-shell.css');const login=read('src-admin/src/login/Login.tsx');const stableLogin=read('adminWww/static/js/nexowatt-stable-login.js');const stable=read('adminWww/static/js/nexowatt-stable-v98.js');const index=read('adminWww/index.html');const info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));const bootstrap=read('adminWww/assets/bootstrap-COulQZax-v84.js');
|
|
6
|
+
if(css!==sourceCss)fail('login CSS source/build drift');
|
|
7
|
+
for(const marker of ['NexoWatt EOS 7.9.98 – stable scrolling','html.eos-app body','overflow: hidden !important','overflow-y: auto !important','width: min(456px','width: min(400px','min-height: 0 !important','overflow: visible !important','.MuiInputLabel-root { display: none !important; }','.MuiOutlinedInput-input::placeholder','margin-top: 28px !important'])if(!css.includes(marker))fail(`login marker missing: ${marker}`);
|
|
8
|
+
if(!login.includes("minHeight: 0")||!login.includes("maxWidth: 400")||!login.includes('placeholder={`${I18n.t(\'enterLogin\')} *`}')||!login.includes('placeholder={`${I18n.t(\'enterPassword\')} *`}'))fail('native Login source does not use clean v98 placeholders');
|
|
9
|
+
if(!bootstrap.includes('placeholder:')||!bootstrap.includes('I18n.t("enterLogin")+" *"')||!bootstrap.includes('I18n.t("enterPassword")+" *"'))fail('productive login bundle does not use placeholders');
|
|
10
|
+
if(/label:.*I18n\.t\("enterLogin"\)|label:.*I18n\.t\("enterPassword"\)/.test(bootstrap.slice(1335000,1340500)))fail('productive login bundle still uses floating labels');
|
|
11
|
+
if(/data-nw-login-card|data-nw-login-shell|min-height:590px|width:min\(460px|createElement\(['"]style/.test(stableLogin))fail('stable-login runtime mutates geometry');
|
|
12
|
+
if(/width:min\(460px|min-height:540px|createElement\(['"]style/.test(stable))fail('stable v98 runtime injects geometry');
|
|
13
|
+
if(!index.includes(`nexowatt-native-shell.css?v=${info.shellCacheTag}`)||!index.includes('nexowatt-stable-v98.js?v=98'))fail('v98 login assets not active');
|
|
14
|
+
if(bad)process.exit(1);console.log('[NexoWatt EOS login layout] OK');
|
|
@@ -16,7 +16,7 @@ for (const file of [
|
|
|
16
16
|
'MERGE_UPDATE.ps1',
|
|
17
17
|
'MERGE_UPDATE.cmd',
|
|
18
18
|
'MERGE_UPDATE.sh',
|
|
19
|
-
'MERGE_UPDATE_README_V7.9.
|
|
19
|
+
'MERGE_UPDATE_README_V7.9.98.md',
|
|
20
20
|
'tools/nexowatt-apply-ems-overview.cjs',
|
|
21
21
|
'tools/nexowatt-sync-release-version.cjs',
|
|
22
22
|
'tools/nexowatt-version-sync-selftest.cjs',
|
|
@@ -29,6 +29,7 @@ for (const marker of [
|
|
|
29
29
|
'$PSScriptRoot',
|
|
30
30
|
'nexowatt-apply-ems-overview.cjs',
|
|
31
31
|
'nexowatt-sync-release-version.cjs',
|
|
32
|
+
'npm run clean:eos-runtime',
|
|
32
33
|
'npm run check:eos-package',
|
|
33
34
|
'npm run check:eos-stability',
|
|
34
35
|
'npm pack --dry-run',
|
|
@@ -47,6 +48,7 @@ for (const marker of [
|
|
|
47
48
|
'set -euo pipefail',
|
|
48
49
|
'nexowatt-apply-ems-overview.cjs',
|
|
49
50
|
'npm run sync:eos-version',
|
|
51
|
+
'npm run clean:eos-runtime',
|
|
50
52
|
'npm run check:eos-package',
|
|
51
53
|
'npm run check:eos-stability',
|
|
52
54
|
'npm pack --dry-run',
|
|
@@ -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(400px, calc(100vw - 48px)) !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,7 +29,7 @@ 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','v98 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');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const vm = require('node:vm');
|
|
7
|
+
const root = path.resolve(__dirname, '..');
|
|
8
|
+
const built = fs.readFileSync(path.join(root, 'build/lib/web.js'), 'utf8');
|
|
9
|
+
const source = fs.readFileSync(path.join(root, 'src/lib/web.ts'), 'utf8');
|
|
10
|
+
const begin = built.indexOf(' getEosPasswordUserName(userId) {');
|
|
11
|
+
const end = built.indexOf(' async destroyEosRequestSessions', begin);
|
|
12
|
+
assert(begin >= 0 && end > begin, 'password methods not found in built backend');
|
|
13
|
+
const methods = built.slice(begin, end);
|
|
14
|
+
const sandbox = { module: { exports: null }, EOS_PASSWORD_SERVICE_USER: 'system.user.admin' };
|
|
15
|
+
vm.runInNewContext(`module.exports = class Harness {\nconstructor(adapter){this.adapter=adapter;}\n${methods}\n}`, sandbox);
|
|
16
|
+
const Harness = sandbox.module.exports;
|
|
17
|
+
(async () => {
|
|
18
|
+
const calls = [];
|
|
19
|
+
const userObject = { _id: 'system.user.user', common: { password: '$2b$hash' }, native: { keep: true } };
|
|
20
|
+
const adapter = {
|
|
21
|
+
setPasswordAsync: async (user, password, options) => calls.push(['set', user, password, options]),
|
|
22
|
+
checkPasswordAsync: async (user, password, options) => { calls.push(['check', user, password, options]); return [true, `system.user.${user}`]; },
|
|
23
|
+
getForeignObjectAsync: async id => { calls.push(['get', id]); return JSON.parse(JSON.stringify(userObject)); },
|
|
24
|
+
extendForeignObjectAsync: async (id, patch, options) => calls.push(['extend', id, patch, options]),
|
|
25
|
+
setForeignObjectAsync: async () => { throw new Error('full user object replacement must not be used'); },
|
|
26
|
+
};
|
|
27
|
+
const harness = new Harness(adapter);
|
|
28
|
+
await harness.setEosUserPassword('system.user.user', 'NexoWatt2025!');
|
|
29
|
+
assert.deepEqual(calls[0].slice(0, 3), ['set', 'user', 'NexoWatt2025!']);
|
|
30
|
+
assert.deepEqual(calls[1].slice(0, 3), ['check', 'user', 'NexoWatt2025!']);
|
|
31
|
+
await harness.updateEosAccountMetadata('system.user.user', (native, account) => {
|
|
32
|
+
account.passwordInitialized = true;
|
|
33
|
+
native.nexowattPasswordChangeRequired = false;
|
|
34
|
+
});
|
|
35
|
+
const extend = calls.find(row => row[0] === 'extend');
|
|
36
|
+
assert(extend, 'native metadata was not extended');
|
|
37
|
+
assert.equal(extend[1], 'system.user.user');
|
|
38
|
+
assert.equal(extend[2].native.keep, true);
|
|
39
|
+
assert.equal(extend[2].native.nexowattEosAccount.passwordInitialized, true);
|
|
40
|
+
assert(!Object.prototype.hasOwnProperty.call(extend[2], 'common'), 'password/common must not be overwritten by metadata update');
|
|
41
|
+
for (const code of [source, built]) {
|
|
42
|
+
assert.match(code, /setPasswordAsync\(userName, password/);
|
|
43
|
+
assert.match(code, /checkPasswordAsync\(userName, password/);
|
|
44
|
+
assert.match(code, /extendForeignObjectAsync/);
|
|
45
|
+
assert.match(code, /passwordVerificationFailed/);
|
|
46
|
+
}
|
|
47
|
+
console.log('[NexoWatt EOS password write] OK: real account name, verified password API and metadata-only update');
|
|
48
|
+
})().catch(error => { console.error(error.stack || error); process.exit(1); });
|
|
@@ -13,13 +13,18 @@ try {
|
|
|
13
13
|
fs.mkdirSync(path.join(temp, 'adminWww', 'js'), { recursive: true });
|
|
14
14
|
fs.mkdirSync(path.join(temp, 'adminWww', 'css'), { recursive: true });
|
|
15
15
|
fs.mkdirSync(path.join(temp, 'src-admin', 'public', 'js'), { recursive: true });
|
|
16
|
-
fs.
|
|
16
|
+
fs.mkdirSync(path.join(temp, 'src-admin', 'public', 'static', 'js'), { recursive: true });
|
|
17
|
+
fs.mkdirSync(path.join(temp, 'adminWww', 'static', 'js'), { recursive: true });
|
|
18
|
+
fs.writeFileSync(path.join(temp, 'NEXOWATT_EOS_BUILD_INFO.json'), JSON.stringify({ runtimeEntry: 'v82', shellCacheVersion: 98 }));
|
|
17
19
|
|
|
18
20
|
const keep = [
|
|
19
21
|
'adminWww/assets/bootstrap-current-v82.js',
|
|
20
22
|
'adminWww/assets/Objects-current-v82.js',
|
|
21
23
|
'adminWww/remoteEntry-v82.js',
|
|
22
24
|
'adminWww/remoteEntry.js',
|
|
25
|
+
'adminWww/static/js/nexowatt-stable-v98.js',
|
|
26
|
+
'src-admin/public/static/js/nexowatt-stable-v98.js',
|
|
27
|
+
'adminWww/static/js/nexowatt-stable-login.js',
|
|
23
28
|
];
|
|
24
29
|
const remove = [
|
|
25
30
|
'adminWww/assets/bootstrap-old-v54.js',
|
|
@@ -29,6 +34,8 @@ try {
|
|
|
29
34
|
'adminWww/js/eos-branding.js',
|
|
30
35
|
'adminWww/css/eos-branding.css',
|
|
31
36
|
'src-admin/public/js/eos-security-ui.js',
|
|
37
|
+
'adminWww/static/js/nexowatt-stable-v97.js',
|
|
38
|
+
'src-admin/public/static/js/nexowatt-stable-v95.js',
|
|
32
39
|
];
|
|
33
40
|
for (const rel of [...keep, ...remove]) {
|
|
34
41
|
const file = path.join(temp, rel);
|
|
@@ -38,6 +45,7 @@ try {
|
|
|
38
45
|
|
|
39
46
|
const result = cleanLegacyRuntime({ root: temp, quiet: true, silent: true });
|
|
40
47
|
assert.strictEqual(result.activeRuntime, 82);
|
|
48
|
+
assert.strictEqual(result.activeShell, 98);
|
|
41
49
|
assert.strictEqual(result.removed.length, remove.length);
|
|
42
50
|
for (const rel of keep) assert.ok(fs.existsSync(path.join(temp, rel)), `current runtime removed: ${rel}`);
|
|
43
51
|
for (const rel of remove) assert.ok(!fs.existsSync(path.join(temp, rel)), `legacy file remained: ${rel}`);
|
|
@@ -0,0 +1,14 @@
|
|
|
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 v98] ${msg}`);process.exit(1)}};
|
|
4
|
+
const pkg=JSON.parse(read('package.json')),io=JSON.parse(read('io-package.json')),info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json')),css=read('adminWww/css/nexowatt-native-shell.css'),stable=read('adminWww/static/js/nexowatt-stable-v98.js'),login=read('adminWww/static/js/nexowatt-stable-login.js'),index=read('adminWww/index.html'),web=read('src/lib/web.ts'),built=read('build/lib/web.js'),account=read('adminWww/js/eos-account-management.js'),bootstrap=read('adminWww/assets/bootstrap-COulQZax-v84.js'),ems=read('adminWww/js/eos-ems-overview.js');
|
|
5
|
+
must(pkg.version==='7.9.98'&&io.version==='7.9.98'&&io.common.version==='7.9.98'&&info.version==='7.9.98','version drift');
|
|
6
|
+
must(info.shellCacheVersion===98&&info.shellCacheTag==='98','shell cache not v98');
|
|
7
|
+
must(css.includes('overflow-y: auto !important')&&css.includes('scrollbar-color: rgba(0, 229, 138'),'cockpit vertical scrollbar missing');
|
|
8
|
+
must(css.includes('.MuiInputLabel-root { display: none !important; }')&&bootstrap.includes('I18n.t("enterLogin")+" *"'),'clean login field contract missing');
|
|
9
|
+
must(stable.includes("VERSION = 'v98-scroll-login-password-write'")&&!login.includes('data-nw-login-card'),'stable runtime incorrect');
|
|
10
|
+
must(index.includes('nexowatt-stable-v98.js?v=98')&&index.includes('nexowatt-native-shell.css?v=98'),'v98 assets not loaded');
|
|
11
|
+
for(const code of [web,built])for(const marker of ['getEosPasswordUserName','setPasswordAsync(userName, password','checkPasswordAsync(userName, password','passwordVerificationFailed','updateEosAccountMetadata','extendForeignObjectAsync'])must(code.includes(marker),`password marker missing: ${marker}`);
|
|
12
|
+
must(account.includes('Startpasswort „nexowatt“')&&account.includes('credentials: \'include\''),'reset UI does not describe/use stable initial password flow');
|
|
13
|
+
must(ems.includes("REQUIRED_UI_VERSION = '0.8.198'")&&ems.includes('readUiAdapterInfo'),'working EMS diagnosis was lost');
|
|
14
|
+
console.log('[NexoWatt EOS stable v98] OK');
|
|
@@ -19,6 +19,14 @@ function setValue(changes, label, object, key, value) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function setJsonValue(changes, label, object, key, value) {
|
|
23
|
+
const previous = object[key];
|
|
24
|
+
if (JSON.stringify(previous) !== JSON.stringify(value)) {
|
|
25
|
+
changes.push({ label, previous, value });
|
|
26
|
+
object[key] = value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
function syncReleaseVersion(rootDir) {
|
|
23
31
|
const root = path.resolve(rootDir);
|
|
24
32
|
const packageFile = path.join(root, 'package.json');
|
|
@@ -62,7 +70,40 @@ function syncReleaseVersion(rootDir) {
|
|
|
62
70
|
update(json) {
|
|
63
71
|
setValue(changes, 'package-lock.json version', json, 'version', version);
|
|
64
72
|
if (json.packages?.['']) {
|
|
65
|
-
|
|
73
|
+
const rootPackage = json.packages[''];
|
|
74
|
+
setValue(changes, 'package-lock.json packages[""] version', rootPackage, 'version', version);
|
|
75
|
+
// A merge archive can update package.json while leaving stale release scripts,
|
|
76
|
+
// files and policy metadata in package-lock.json. npm reads the lock root during
|
|
77
|
+
// packaging, so keep every release-critical root field canonical as well.
|
|
78
|
+
for (const key of [
|
|
79
|
+
'name',
|
|
80
|
+
'description',
|
|
81
|
+
'license',
|
|
82
|
+
'author',
|
|
83
|
+
'private',
|
|
84
|
+
'engines',
|
|
85
|
+
'main',
|
|
86
|
+
'files',
|
|
87
|
+
'scripts',
|
|
88
|
+
'dependencies',
|
|
89
|
+
'devDependencies',
|
|
90
|
+
'overrides',
|
|
91
|
+
'keywords',
|
|
92
|
+
'publishConfig',
|
|
93
|
+
'nexowattReleasePolicy',
|
|
94
|
+
'publisher',
|
|
95
|
+
'copyright',
|
|
96
|
+
]) {
|
|
97
|
+
if (Object.prototype.hasOwnProperty.call(pkg, key)) {
|
|
98
|
+
setJsonValue(
|
|
99
|
+
changes,
|
|
100
|
+
`package-lock.json packages[""] ${key}`,
|
|
101
|
+
rootPackage,
|
|
102
|
+
key,
|
|
103
|
+
pkg[key],
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
66
107
|
}
|
|
67
108
|
},
|
|
68
109
|
},
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const fs=require('node:fs');const path=require('node:path');const root=path.resolve(__dirname,'..');
|
|
4
|
-
const read=
|
|
5
|
-
const runtime=read('adminWww/js/eos-ems-overview.js');const source=read('src-admin/public/js/eos-ems-overview.js');const css=read('adminWww/css/eos-ems-overview.css');const index=read('adminWww/index.html');const role=read('adminWww/js/eos-role-ui.js');const shell=read('adminWww/js/nexowatt-native-shell.js');const intro=read('src-admin/src/tabs/Intro.tsx');const app=read('src-admin/src/App.tsx');const bundle=read('adminWww/assets/index-D2ymscJA-v84.js');
|
|
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 fail=msg=>{console.error(`[NexoWatt EOS UI overview runtime] ${msg}`);process.exit(1)};
|
|
4
|
+
const runtime=read('adminWww/js/eos-ems-overview.js');const source=read('src-admin/public/js/eos-ems-overview.js');const css=read('adminWww/css/eos-ems-overview.css');const index=read('adminWww/index.html');const role=read('adminWww/js/eos-role-ui.js');const shell=read('adminWww/js/nexowatt-native-shell.js');const intro=read('src-admin/src/tabs/Intro.tsx');const introBundle=read('adminWww/assets/Intro-DkwRiz1n-v84.js');const bootstrap=read('adminWww/assets/bootstrap-COulQZax-v84.js');const generic=read('adminWww/assets/index-D2ymscJA-v84.js');
|
|
6
5
|
if(runtime!==source)fail('runtime source/build drift');
|
|
7
|
-
for(const marker of ['nexowatt-ems-overview-v1','nexowatt-ui.*.info.adminOverview.*','system.adapter.nexowatt-ui.*.alive','POLL_MS = 5000','STALE_MS = 20000','getForeignStates','Keine Kachel besitzt Schreibhoheit','currentDecisions','§14a-Fallback'])if(!runtime.includes(marker))fail(`runtime marker missing: ${marker}`);
|
|
6
|
+
for(const marker of ['nexowatt-ems-overview-v1','nexowatt-ui.*.info.adminOverview.*','system.adapter.nexowatt-ui.*.alive','POLL_MS = 5000','STALE_MS = 20000',"REQUIRED_UI_VERSION = '0.8.198'",'getForeignStates','Keine Kachel besitzt Schreibhoheit','currentDecisions','§14a-Fallback','nexowatt-eos-admin-socket-ready','readUiAdapterInfo','socketFromReact'])if(!runtime.includes(marker))fail(`runtime marker missing: ${marker}`);
|
|
8
7
|
for(const forbidden of ['setForeignState','setState(','sendToHost','writeFile','extendObject'])if(runtime.includes(forbidden))fail(`read-only runtime contains ${forbidden}`);
|
|
9
|
-
for(const marker of ['eos-ems-overview.css?v=
|
|
10
|
-
if(!css.includes('#eos-ems-overview-runtime')||!css.includes('.eos-ems-overview-metrics'))fail('EMS card CSS incomplete');
|
|
11
|
-
if(role.includes(
|
|
12
|
-
if(!shell.includes("label: 'Installateur'")||!shell.includes("label: 'Endkunde'"))fail('native
|
|
13
|
-
if(!intro.includes('
|
|
14
|
-
if(!
|
|
15
|
-
if(!
|
|
8
|
+
for(const marker of ['eos-ems-overview.css?v=98','eos-ems-overview.js?v=98','nexowatt-stable-v98.js?v=98'])if(!index.includes(marker))fail(`index marker missing: ${marker}`);
|
|
9
|
+
if(!css.includes('#eos-ems-overview-runtime')||!css.includes('.eos-ems-overview-metrics')||!css.includes('startup and compatibility messages stay compact'))fail('EMS card CSS incomplete');
|
|
10
|
+
if(role.includes('overview.innerHTML = `')||!role.includes('never cover the real Admin Intro'))fail('obsolete role overview still active');
|
|
11
|
+
if(!shell.includes("label: 'Installateur'")||!shell.includes("label: 'Endkunde'")||!shell.includes('eosNativeReactOverview'))fail('native overview role bridge incomplete');
|
|
12
|
+
if(!intro.includes('NEXOWATT_EOS_ADMIN_SOCKET')||!intro.includes('nexowatt-eos-admin-socket-ready'))fail('source Intro socket bridge missing');
|
|
13
|
+
if(!introBundle.includes('NEXOWATT_EOS_ADMIN_SOCKET')||!introBundle.includes('nexowatt-eos-admin-socket-ready'))fail('productive Intro socket bridge missing');
|
|
14
|
+
if(!bootstrap.includes('window.NEXOWATT_EOS_ADMIN_SOCKET=window.NEXOWATT_EOS_SOCKET=new AdminConnection'))fail('productive main AdminConnection exposure missing');
|
|
15
|
+
if(generic.includes('this.socket=window.NEXOWATT_EOS_ADMIN_SOCKET=new M({'))fail('generic adapter helper still overwrites main AdminConnection');
|
|
16
16
|
console.log('[NexoWatt EOS UI overview runtime] OK');
|