iobroker.eos-admin 7.10.0 → 7.10.2
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.10.2.md +22 -0
- package/MERGE_UPDATE_README_V7.10.2.md +11 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +19 -21
- package/NEXOWATT_EOS_PREBUILT_MANIFEST.json +1410 -0
- package/PUBLISH_STABLE_V7.10.2.md +21 -0
- package/README.md +16 -6
- package/README_STABILITY_V7.10.2.md +20 -0
- package/RELEASE_ACCEPTANCE_V7.10.2.md +13 -0
- package/RELEASE_NOTES_V7.10.2.md +11 -0
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +1 -1
- package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
- package/adminWww/css/eos-ems-overview.css +5 -3
- package/adminWww/css/nexowatt-native-shell.css +85 -413
- package/adminWww/index.html +20 -21
- package/adminWww/js/eos-account-management.js +28 -19
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-ems-overview.js +1 -1
- package/adminWww/js/eos-role-bootstrap.js +17 -80
- package/adminWww/js/eos-role-ui.js +28 -4
- package/adminWww/js/nexowatt-native-shell.js +1 -1
- package/adminWww/static/js/nexowatt-stable-login.js +43 -70
- package/adminWww/static/js/nexowatt-stable-v7101.js +27 -0
- package/build/lib/eosAutoUpdate.js +418 -20
- package/build/lib/eosAutoUpdate.js.map +1 -0
- package/build/lib/eosRequestSecurity.js +4 -1
- package/build/lib/eosRequestSecurity.js.map +1 -0
- package/build/lib/web.js +161 -254
- package/build/lib/web.js.map +1 -1
- package/build/main.js +39 -67
- package/io-package.json +72 -7
- package/package.json +24 -25
- package/tools/nexowatt-account-management-selftest.cjs +15 -19
- package/tools/nexowatt-account-password-flow-selftest.cjs +15 -0
- package/tools/nexowatt-apply-ems-overview.cjs +4 -2
- package/tools/nexowatt-auto-update-selftest.cjs +144 -32
- package/tools/nexowatt-backend-runtime-selftest.cjs +147 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +1 -1
- package/tools/nexowatt-ems-overview-selftest.cjs +1 -1
- package/tools/nexowatt-ensure-release-defaults.cjs +1 -1
- package/tools/nexowatt-login-layout-selftest.cjs +9 -13
- package/tools/nexowatt-merge-update-selftest.cjs +7 -1
- package/tools/nexowatt-modern-ui-selftest.cjs +10 -46
- package/tools/nexowatt-password-write-selftest.cjs +46 -16
- package/tools/nexowatt-prebuilt-release-selftest.cjs +292 -0
- package/tools/nexowatt-release-defaults-selftest.cjs +1 -1
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -8
- package/tools/nexowatt-stable-v7102-selftest.cjs +51 -0
- package/tools/nexowatt-sync-release-version.cjs +43 -5
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +2 -3
- package/tools/nexowatt-validate-package.cjs +54 -29
- package/tools/nexowatt-version-sync-selftest.cjs +18 -1
- package/INSTALL_TEST_V7.10.0.md +0 -11
- package/MERGE_UPDATE_README_V7.10.0.md +0 -5
- package/PUBLISH_STABLE_V7.10.0.md +0 -15
- package/README_STABILITY_V7.10.0.md +0 -3
- package/RELEASE_ACCEPTANCE_V7.10.0.md +0 -11
- package/RELEASE_NOTES_V7.10.0.md +0 -17
- package/adminWww/js/eos-release-watch.js +0 -77
- package/adminWww/static/js/nexowatt-stable-v7100.js +0 -111
- package/build/lib/eosPassword.js +0 -72
- package/build/lib/nexowattStableAuth.js +0 -74
- package/src/lib/eosAutoUpdate.ts +0 -320
- package/tools/nexowatt-cache-update-selftest.cjs +0 -12
- package/tools/nexowatt-request-security-selftest.cjs +0 -11
- package/tools/nexowatt-stable-v7100-selftest.cjs +0 -17
|
@@ -1,34 +1,146 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
objects
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
})
|
|
3
|
+
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const { NexoWattStableUpdateManager, isNexoWattRepositoryEntry } = require('../build/lib/eosAutoUpdate.js');
|
|
6
|
+
|
|
7
|
+
const copy = value => JSON.parse(JSON.stringify(value));
|
|
8
|
+
|
|
9
|
+
function createFixture() {
|
|
10
|
+
const objects = new Map();
|
|
11
|
+
const states = new Map();
|
|
12
|
+
const objectWrites = [];
|
|
13
|
+
const stateWrites = [];
|
|
14
|
+
const refresh = [];
|
|
15
|
+
|
|
16
|
+
objects.set('system.adapter.eos-admin.0', {
|
|
17
|
+
_id: 'system.adapter.eos-admin.0',
|
|
18
|
+
common: { name: 'eos-admin', version: '7.10.2' },
|
|
19
|
+
native: {
|
|
20
|
+
eosNexoWattAutoUpdate: true,
|
|
21
|
+
eosNexoWattAutoUpdateState: {
|
|
22
|
+
previousPolicies: {},
|
|
23
|
+
lastSync: 1,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
objects.set('system.config', {
|
|
28
|
+
_id: 'system.config',
|
|
29
|
+
common: {
|
|
30
|
+
activeRepo: ['stable'],
|
|
31
|
+
adapterAutoUpgrade: { repositories: { stable: false }, defaultPolicy: 'none' },
|
|
32
|
+
},
|
|
33
|
+
native: {},
|
|
34
|
+
});
|
|
35
|
+
objects.set('system.repositories', {
|
|
36
|
+
_id: 'system.repositories',
|
|
37
|
+
native: {
|
|
38
|
+
repositories: {
|
|
39
|
+
stable: {
|
|
40
|
+
json: {
|
|
41
|
+
'eos-admin': { name: 'eos-admin', version: '7.10.2', meta: 'https://github.com/NexoWatt/ioBroker.eos-admin' },
|
|
42
|
+
'nexowatt-ui': { name: 'nexowatt-ui', version: '0.8.202', publisher: 'NexoWatt' },
|
|
43
|
+
'nexowatt-beta': { name: 'nexowatt-beta', version: '1.0.0-beta.1', publisher: 'NexoWatt' },
|
|
44
|
+
admin: { name: 'admin', version: '7.7.0', publisher: 'ioBroker' },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
objects.set('system.adapter.eos-admin', {
|
|
51
|
+
_id: 'system.adapter.eos-admin',
|
|
52
|
+
common: { name: 'eos-admin', version: '7.10.2', automaticUpgrade: 'patch' },
|
|
53
|
+
native: {},
|
|
54
|
+
});
|
|
55
|
+
objects.set('system.adapter.nexowatt-ui', {
|
|
56
|
+
_id: 'system.adapter.nexowatt-ui',
|
|
57
|
+
common: { name: 'nexowatt-ui', version: '0.8.202' },
|
|
58
|
+
native: {},
|
|
59
|
+
});
|
|
60
|
+
objects.set('system.adapter.admin', {
|
|
61
|
+
_id: 'system.adapter.admin',
|
|
62
|
+
common: { name: 'admin', version: '7.7.0', automaticUpgrade: 'minor' },
|
|
63
|
+
native: {},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const adapter = {
|
|
67
|
+
namespace: 'eos-admin.0',
|
|
68
|
+
host: 'eos-host',
|
|
69
|
+
log: { debug() {}, info() {}, warn() {}, error() {} },
|
|
70
|
+
getForeignObjectAsync: async id => copy(objects.get(id) || null),
|
|
71
|
+
setForeignObjectAsync: async (id, object) => {
|
|
72
|
+
objectWrites.push(id);
|
|
73
|
+
objects.set(id, copy(object));
|
|
74
|
+
},
|
|
75
|
+
getObjectViewAsync: async () => ({
|
|
76
|
+
rows: [...objects.entries()]
|
|
77
|
+
.filter(([id]) => /^system\.adapter\.[^.]+$/.test(id))
|
|
78
|
+
.map(([id, value]) => ({ id, value: copy(value) })),
|
|
79
|
+
}),
|
|
80
|
+
getStateAsync: async id => copy(states.get(id) || null),
|
|
81
|
+
setStateAsync: async (id, value, ack) => {
|
|
82
|
+
const state = value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'val')
|
|
83
|
+
? { ...value }
|
|
84
|
+
: { val: value, ack: Boolean(ack) };
|
|
85
|
+
stateWrites.push(id);
|
|
86
|
+
states.set(id, copy(state));
|
|
87
|
+
},
|
|
88
|
+
sendToHostAsync: async (...args) => { refresh.push(args); },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return { adapter, objects, states, objectWrites, stateWrites, refresh };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
(async () => {
|
|
95
|
+
assert.equal(isNexoWattRepositoryEntry('nexowatt-ui', { publisher: 'NexoWatt' }), true);
|
|
96
|
+
assert.equal(isNexoWattRepositoryEntry('admin', { publisher: 'ioBroker' }), false);
|
|
97
|
+
|
|
98
|
+
const fixture = createFixture();
|
|
99
|
+
const manager = new NexoWattStableUpdateManager(fixture.adapter);
|
|
100
|
+
|
|
101
|
+
const enabled = await manager.reconcile('test');
|
|
102
|
+
assert.equal(enabled.enabled, true);
|
|
103
|
+
assert.equal(enabled.repository, 'stable');
|
|
104
|
+
assert.deepEqual(enabled.managedAdapters, ['eos-admin', 'nexowatt-ui']);
|
|
105
|
+
assert.equal(fixture.objects.get('system.adapter.eos-admin').common.automaticUpgrade, 'major');
|
|
106
|
+
assert.equal(fixture.objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade, 'major');
|
|
107
|
+
assert.equal(fixture.objects.get('system.adapter.admin').common.automaticUpgrade, 'minor');
|
|
108
|
+
assert.equal(fixture.objects.get('system.config').common.adapterAutoUpgrade.repositories.stable, true);
|
|
109
|
+
assert.equal(fixture.states.get('info.nexowattStableUpdatesEnabled').val, true);
|
|
110
|
+
assert.ok(JSON.parse(fixture.states.get('info.nexowattStableUpdatesState').val).lastSync > 1);
|
|
111
|
+
|
|
112
|
+
// Regression guard for 7.10.1: runtime status must never be persisted into
|
|
113
|
+
// system.adapter.eos-admin.0.native, because that restarts the running adapter.
|
|
114
|
+
assert.equal(fixture.objectWrites.includes('system.adapter.eos-admin.0'), false);
|
|
115
|
+
|
|
116
|
+
// A second reconciliation may refresh state timestamps, but must not rewrite
|
|
117
|
+
// unchanged adapter policies or system.config.
|
|
118
|
+
const objectWriteCount = fixture.objectWrites.length;
|
|
119
|
+
await manager.reconcile('repeat');
|
|
120
|
+
assert.equal(fixture.objectWrites.length, objectWriteCount);
|
|
121
|
+
|
|
122
|
+
// A manual user change while EOS management is active must not be overwritten on disable.
|
|
123
|
+
fixture.objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade = 'minor';
|
|
124
|
+
const disabled = await manager.setEnabled(false);
|
|
125
|
+
assert.equal(disabled.enabled, false);
|
|
126
|
+
assert.equal(fixture.objects.get('system.adapter.eos-admin').common.automaticUpgrade, 'patch');
|
|
127
|
+
assert.equal(fixture.objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade, 'minor');
|
|
128
|
+
assert.equal(fixture.objects.get('system.adapter.admin').common.automaticUpgrade, 'minor');
|
|
129
|
+
assert.equal(fixture.objects.get('system.config').common.adapterAutoUpgrade.repositories.stable, false);
|
|
130
|
+
assert.equal(fixture.states.get('info.nexowattStableUpdatesEnabled').val, false);
|
|
131
|
+
assert.equal(fixture.objectWrites.includes('system.adapter.eos-admin.0'), false);
|
|
132
|
+
manager.stop();
|
|
133
|
+
|
|
134
|
+
// Startup must only schedule reconciliation. It must not modify repository or
|
|
135
|
+
// adapter objects while the HTTP server is serving the first browser assets.
|
|
136
|
+
const startupFixture = createFixture();
|
|
137
|
+
const startupManager = new NexoWattStableUpdateManager(startupFixture.adapter);
|
|
138
|
+
await startupManager.start();
|
|
139
|
+
assert.deepEqual(startupFixture.objectWrites, []);
|
|
140
|
+
startupManager.stop();
|
|
141
|
+
|
|
142
|
+
console.log('[NexoWatt EOS auto update] OK (restart-safe state persistence, delayed startup, stable-only scope, third-party isolation and restore)');
|
|
143
|
+
})().catch(error => {
|
|
144
|
+
console.error(error.stack || error);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const root = path.resolve(__dirname, '..');
|
|
9
|
+
const entry = path.join(root, 'build', 'main.js');
|
|
10
|
+
const packageFile = path.join(root, 'package.json');
|
|
11
|
+
const failures = [];
|
|
12
|
+
const visited = new Set();
|
|
13
|
+
const runtimeFiles = new Set();
|
|
14
|
+
|
|
15
|
+
function fail(message) {
|
|
16
|
+
failures.push(message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalize(relative) {
|
|
20
|
+
return String(relative).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveLocal(fromFile, specifier) {
|
|
24
|
+
const base = path.resolve(path.dirname(fromFile), specifier);
|
|
25
|
+
const candidates = [base, `${base}.js`, `${base}.json`, path.join(base, 'index.js')];
|
|
26
|
+
if (fs.existsSync(base) && fs.statSync(base).isDirectory()) {
|
|
27
|
+
const packageJsonFile = path.join(base, 'package.json');
|
|
28
|
+
if (fs.existsSync(packageJsonFile)) {
|
|
29
|
+
try {
|
|
30
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf8'));
|
|
31
|
+
if (packageJson.main) candidates.unshift(path.resolve(base, packageJson.main));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
fail(`${normalize(path.relative(root, packageJsonFile))} is invalid JSON: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return candidates.find(candidate => fs.existsSync(candidate) && fs.statSync(candidate).isFile()) || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function inspect(file) {
|
|
41
|
+
const absolute = path.resolve(file);
|
|
42
|
+
if (visited.has(absolute)) return;
|
|
43
|
+
visited.add(absolute);
|
|
44
|
+
runtimeFiles.add(absolute);
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(absolute)) {
|
|
47
|
+
fail(`missing runtime file ${normalize(path.relative(root, absolute))}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!absolute.endsWith('.js')) return;
|
|
51
|
+
|
|
52
|
+
const text = fs.readFileSync(absolute, 'utf8');
|
|
53
|
+
const patterns = [
|
|
54
|
+
/\brequire\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
55
|
+
/\brequire\.resolve\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
56
|
+
/\bimport\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
57
|
+
];
|
|
58
|
+
for (const pattern of patterns) {
|
|
59
|
+
pattern.lastIndex = 0;
|
|
60
|
+
let match;
|
|
61
|
+
while ((match = pattern.exec(text))) {
|
|
62
|
+
const specifier = match[1];
|
|
63
|
+
if (!specifier.startsWith('.')) continue;
|
|
64
|
+
const target = resolveLocal(absolute, specifier);
|
|
65
|
+
if (!target) {
|
|
66
|
+
fail(`${normalize(path.relative(root, absolute))} -> ${specifier} cannot be resolved`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
inspect(target);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function packageRules() {
|
|
75
|
+
if (!fs.existsSync(packageFile)) {
|
|
76
|
+
fail('package.json is missing');
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
81
|
+
if (!Array.isArray(pkg.files) || pkg.files.length === 0) {
|
|
82
|
+
fail('package.json files whitelist is missing');
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
return pkg.files
|
|
86
|
+
.map(rule => normalize(rule).replace(/\/+$/, ''))
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
fail(`package.json is invalid JSON: ${error.message}`);
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isDeclaredForPackage(relative, rules) {
|
|
95
|
+
const value = normalize(relative);
|
|
96
|
+
return rules.some(rule => value === rule || value.startsWith(`${rule}/`));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!fs.existsSync(entry)) fail('build/main.js is missing');
|
|
100
|
+
else inspect(entry);
|
|
101
|
+
|
|
102
|
+
for (const file of runtimeFiles) {
|
|
103
|
+
if (!file.endsWith('.js')) continue;
|
|
104
|
+
const result = spawnSync(process.execPath, ['--check', file], {
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
windowsHide: true,
|
|
107
|
+
});
|
|
108
|
+
if (result.error) {
|
|
109
|
+
fail(`${normalize(path.relative(root, file))} syntax check could not start: ${result.error.message}`);
|
|
110
|
+
} else if (result.status !== 0) {
|
|
111
|
+
fail(`${normalize(path.relative(root, file))} does not parse: ${(result.stderr || result.stdout || '').trim()}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Do not start a nested `npm pack` process here. This self-test runs from
|
|
116
|
+
// prepublishOnly, and spawning npm.cmd from a Node child process is unreliable
|
|
117
|
+
// on Windows and can also re-enter npm lifecycle hooks. The final npm artifact
|
|
118
|
+
// is still verified externally by `npm publish --dry-run`; this check proves
|
|
119
|
+
// that every reachable local backend dependency is covered by package.json's
|
|
120
|
+
// explicit files whitelist without requiring npm, tsc, tsx or node_modules.
|
|
121
|
+
const rules = packageRules();
|
|
122
|
+
for (const file of runtimeFiles) {
|
|
123
|
+
const relative = normalize(path.relative(root, file));
|
|
124
|
+
if (!isDeclaredForPackage(relative, rules)) {
|
|
125
|
+
fail(`runtime dependency is not declared for the npm package: ${relative}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const required of [
|
|
130
|
+
'build/main.js',
|
|
131
|
+
'build/lib/web.js',
|
|
132
|
+
'build/lib/eosAutoUpdate.js',
|
|
133
|
+
'build/lib/eosRequestSecurity.js',
|
|
134
|
+
]) {
|
|
135
|
+
if (!runtimeFiles.has(path.join(root, required))) {
|
|
136
|
+
fail(`entrypoint closure does not include required module ${required}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (failures.length) {
|
|
141
|
+
console.error(`[NexoWatt EOS backend runtime] ${failures.length} failure(s):\n${failures.join('\n')}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(
|
|
146
|
+
`[NexoWatt EOS backend runtime] OK (${runtimeFiles.size} local runtime files resolve, parse and are declared by the npm files whitelist; dependency-free on Windows)`,
|
|
147
|
+
);
|
|
@@ -74,7 +74,7 @@ function isLegacyVersionedRuntime(file, activeRuntime, activeShell) {
|
|
|
74
74
|
// Stable product overlays use the product release number in their filename,
|
|
75
75
|
// while the compiled React runtime can intentionally remain on v84. Keep
|
|
76
76
|
// only the active stable overlay and remove stale release assets left behind
|
|
77
|
-
// by a flat ZIP update (for example
|
|
77
|
+
// by a flat ZIP update (for example v96 when installing v97).
|
|
78
78
|
const stableMatch = name.match(/^nexowatt-stable-v(\d+)\.js$/i);
|
|
79
79
|
if (stableMatch) return Number(stableMatch[1]) !== activeShell;
|
|
80
80
|
const versionMatch = name.match(/-v(\d+)\.(?:js|js\.map|css|css\.map)$/i);
|
|
@@ -21,9 +21,9 @@ for (const marker of [
|
|
|
21
21
|
'Diagnose ist rein lesend',
|
|
22
22
|
'system.adapter.nexowatt-ui.*.alive',
|
|
23
23
|
]) if (!component.includes(marker)) fail(`Komponenten-Vertrag fehlt: ${marker}`);
|
|
24
|
+
if (component.includes('Vollständige EMS-Diagnose öffnen')) fail('Nicht vorhandener Diagnose-Link ist noch aktiv.');
|
|
24
25
|
if ((component.match(/NexoWatt EMS · Live-Diagnose/g) || []).length !== 1) fail('EMS-Überschrift ist doppelt oder fehlt.');
|
|
25
26
|
if (!css.includes('/* nexowatt-ems-overview-v1 */')) fail('Responsive EMS-CSS fehlt.');
|
|
26
|
-
if (component.includes('Vollständige EMS-Diagnose öffnen')) fail('Nicht vorhandener Diagnose-Link darf nicht angeboten werden.');
|
|
27
27
|
if (/setState\(|setForeignState\(|sendToHost\(/.test(component)) fail('EMS-Overview darf keine ioBroker-Schreiboperation enthalten.');
|
|
28
28
|
const assetDir = path.join(root, 'admin', 'assets');
|
|
29
29
|
if (fs.existsSync(assetDir)) {
|
|
@@ -11,7 +11,7 @@ const CANONICAL_NATIVE_DEFAULTS = Object.freeze({
|
|
|
11
11
|
eosHideLegacyAdminFromNonAdmins: true,
|
|
12
12
|
eosHideLegacyBackupFromNonAdmins: true,
|
|
13
13
|
nexowattHideLegacyBackupFromNonAdmins: true,
|
|
14
|
-
eosRequireFirstLoginPassword:
|
|
14
|
+
eosRequireFirstLoginPassword: false,
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const fs=require('node:fs'),path=require('node:path');const root=path.resolve(__dirname,'..');const read=r=>fs.readFileSync(path.join(root,r),'utf8');const fail=m=>{console.error(
|
|
4
|
-
const css=read('adminWww/css/nexowatt-native-shell.css'),
|
|
5
|
-
must(css===
|
|
6
|
-
for(const marker of ['NexoWatt EOS 7.
|
|
7
|
-
must(
|
|
8
|
-
must(
|
|
9
|
-
must(
|
|
10
|
-
must(
|
|
11
|
-
|
|
12
|
-
must(login.includes('minHeight: 0')&&login.includes('maxWidth: 372')&&login.includes("marginTop: 18")&&login.includes('width: 90')&&(login.match(/inputLabel: \{ shrink: true \}/g)||[]).length>=2,'native Login source does not match product contract');
|
|
13
|
-
for(const marker of ['p:"26px 28px 24px"','width:"calc(100% - 48px)"','maxWidth:372','width:90,height:90','marginTop:18','inputLabel:{shrink:!0}'])must(bundle.includes(marker),`productive login bundle marker missing: ${marker}`);
|
|
14
|
-
must(index.includes(`nexowatt-native-shell.css?v=${info.shellCacheTag}`)&&index.includes('nexowatt-stable-v7100.js?v=7100'),'v7100 login assets not active');
|
|
15
|
-
console.log('[NexoWatt EOS login layout] OK (single Paper, static labels, compact responsive geometry)');
|
|
3
|
+
const fs=require('node:fs'),path=require('node:path');const root=path.resolve(__dirname,'..');const read=r=>fs.readFileSync(path.join(root,r),'utf8');const fail=m=>{console.error('[NexoWatt EOS login layout] '+m);process.exit(1)};const must=(v,m)=>{if(!v)fail(m)};
|
|
4
|
+
const css=read('adminWww/css/nexowatt-native-shell.css'),source=read('src-admin/public/css/nexowatt-native-shell.css'),login=read('src-admin/src/login/Login.tsx'),stableLogin=read('adminWww/static/js/nexowatt-stable-login.js'),stable=read('adminWww/static/js/nexowatt-stable-v7101.js'),index=read('adminWww/index.html');
|
|
5
|
+
must(css===source,'CSS mirror drift');
|
|
6
|
+
for(const marker of ['NexoWatt EOS 7.9.98 – stable scrolling','width: min(456px','width: min(400px','overflow: visible !important'])must(css.includes(marker),`base stable login marker missing: ${marker}`);
|
|
7
|
+
must(login.includes('placeholder={`${I18n.t(\'enterLogin\')} *`}')&&login.includes('placeholder={`${I18n.t(\'enterPassword\')} *`}'),'clean placeholder login missing');
|
|
8
|
+
must(!/data-nw-login-card|createElement\(['"]style/.test(stableLogin),'stable login runtime mutates geometry');
|
|
9
|
+
must(stable.includes("VERSION = 'v7101-scroll-standard-password'")&&!stable.includes('first-password'),'stable v7101 runtime incorrect');
|
|
10
|
+
must(index.includes('nexowatt-stable-v7101.js?v=7101'),'v7101 login runtime not active');
|
|
11
|
+
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.10.
|
|
19
|
+
'MERGE_UPDATE_README_V7.10.2.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',
|
|
@@ -67,5 +67,11 @@ if (!pkg.scripts['precheck:eos-package']?.startsWith('npm run sync:eos-version')
|
|
|
67
67
|
if (!pkg.scripts.prepublishOnly?.startsWith('npm run sync:eos-version')) {
|
|
68
68
|
fail('npm publishing does not self-heal stale merged version files');
|
|
69
69
|
}
|
|
70
|
+
if (/\b(?:tsc|tsx)\b|build:(?:backend|frontend)|\bnpx\b|npm\s+(?:i|install|ci)\b/i.test(pkg.scripts.prepublishOnly || '')) {
|
|
71
|
+
fail('npm publishing must not require local development dependencies');
|
|
72
|
+
}
|
|
73
|
+
if (!pkg.scripts.prepack?.includes('nexowatt-prebuilt-release-selftest.cjs')) {
|
|
74
|
+
fail('npm packaging does not verify the sealed prebuilt release');
|
|
75
|
+
}
|
|
70
76
|
|
|
71
77
|
console.log('[NexoWatt EOS merge update selftest] OK (flat overwrite + EMS merge + version repair + guarded verification)');
|
|
@@ -1,49 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const fs
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const bootstrap = read('adminWww/js/eos-role-bootstrap.js');
|
|
14
|
-
const shell = read('adminWww/js/nexowatt-native-shell.js');
|
|
15
|
-
const role = read('adminWww/js/eos-role-ui.js');
|
|
16
|
-
const assist = read('adminWww/js/eos-assistant.js');
|
|
17
|
-
|
|
18
|
-
for (const marker of [
|
|
19
|
-
'--nx-shadow-soft', '--nx-shadow-float', 'backdrop-filter: blur', '#app-paper', '.MuiDialog-paper',
|
|
20
|
-
'.MuiButton-root', '.MuiTableRow-root', ':focus-visible', 'border-radius: 24px', 'linear-gradient',
|
|
21
|
-
'@media (max-width: 720px)', '.eos-activation-steps', '.eos-account-row:hover',
|
|
22
|
-
'main.MuiPaper-root::before', 'width: min(380px, calc(100vw - 52px)) !important', '.eos-assist-header-root', '.eos-overview-hero', '.eos-overview-grid',
|
|
23
|
-
'.eos-account-management-entry', 'pointer-events: auto !important', 'touch-action: manipulation', 'overflow: visible',
|
|
24
|
-
]) if (!css.includes(marker)) fail(`modern styling marker missing: ${marker}`);
|
|
25
|
-
if (sourceCss !== css) fail('modern CSS source/build drift');
|
|
26
|
-
for (const marker of ['eos-account-management.js','nexowatt-native-shell.css','eos-role-bootstrap.js','eos-assistant.js']) {
|
|
27
|
-
if (!index.includes(marker)) fail(`modern UI asset not active: ${marker}`);
|
|
28
|
-
}
|
|
29
|
-
for (const marker of ['NexoWatt EOS', 'Zugänge & Rollen', 'Endkunden-Zugänge', 'ensureEntrySurface']) {
|
|
30
|
-
if (!accountUi.includes(marker)) fail(`modern account surface missing label: ${marker}`);
|
|
31
|
-
}
|
|
32
|
-
for (const marker of ['installIntegratedFirstLogin','v7100 normal-login first activation','eos-passwordless-card','Idempotent writes are essential here']) {
|
|
33
|
-
if (!bootstrap.includes(marker)) fail(`integrated first-activation surface missing: ${marker}`);
|
|
34
|
-
}
|
|
35
|
-
if (/data-eos-account=|selector\.innerHTML/.test(bootstrap)) fail('compact login still injects role buttons or a second panel');
|
|
36
|
-
if (bootstrap.includes("launcher.className = 'eos-passwordless-launcher'")) fail('old bottom first-login launcher is still rendered');
|
|
37
|
-
if (!shell.includes('ensureModernOverview') || !shell.includes("currentTab() !== 'tab-intro'")) fail('current overview is not modernized');
|
|
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
|
-
if (!role.includes('ensureReserveObserver') || !role.includes('state.reserveObserver.observe(paper')) fail('scoped delayed-row reserve filter is missing');
|
|
40
|
-
if (!assist.includes('NEXOWATT_EOS_ASSIST_DISABLED') || !assist.includes('element.remove()')) fail('EOS Assist stable-disable guard is missing');
|
|
41
|
-
|
|
42
|
-
const observerCount = [
|
|
43
|
-
'eos-policy-client.js', 'eos-branding-sanitizer.js', 'nexowatt-native-shell.js', 'eos-native-security.js',
|
|
44
|
-
'eos-basic-settings.js', 'eos-role-ui.js', 'eos-account-management.js', 'eos-assistant.js',
|
|
45
|
-
].reduce((count, file) => count + (read(`adminWww/js/${file}`).match(/new MutationObserver/g) || []).length, 0);
|
|
46
|
-
if (observerCount !== 2) fail(`expected one shared observer plus one scoped reserve-row observer, found ${observerCount}`);
|
|
47
|
-
|
|
48
|
-
if (bad) process.exit(1);
|
|
3
|
+
const fs=require('node:fs'),path=require('node:path');const root=path.resolve(__dirname,'..');const read=r=>fs.readFileSync(path.join(root,r),'utf8');const fail=m=>{console.error('[NexoWatt EOS modern UI] '+m);process.exit(1)};const must=(v,m)=>{if(!v)fail(m)};
|
|
4
|
+
const css=read('adminWww/css/nexowatt-native-shell.css'),sourceCss=read('src-admin/public/css/nexowatt-native-shell.css'),index=read('adminWww/index.html'),boot=read('adminWww/js/eos-role-bootstrap.js'),shell=read('adminWww/js/nexowatt-native-shell.js'),role=read('adminWww/js/eos-role-ui.js'),assist=read('adminWww/js/eos-assistant.js');
|
|
5
|
+
must(css===sourceCss,'CSS source/build drift');
|
|
6
|
+
for(const marker of ['#app-paper','.MuiDialog-paper','.MuiButton-root',':focus-visible','persistent native overview scrolling','eos-overview-edit-controls'])must(css.includes(marker),`CSS marker missing: ${marker}`);
|
|
7
|
+
for(const marker of ['nexowatt-native-shell.css','eos-role-bootstrap.js','eos-auto-update.js','nexowatt-stable-v7101.js'])must(index.includes(marker),`active asset missing: ${marker}`);
|
|
8
|
+
must(!index.includes('eos-account-management.js'),'custom reset asset active');
|
|
9
|
+
must(boot.includes('NEXOWATT_EOS_STANDARD_PASSWORD_MODE')&&!boot.includes('installIntegratedFirstLogin(base);'),'standard login mode missing');
|
|
10
|
+
must(shell.includes('ensureModernOverview'),'modern overview missing');
|
|
11
|
+
must(role.includes('never cover the real Admin Intro')&&role.includes('filterNativeUsersPage'),'role UI contract missing');
|
|
12
|
+
must(assist.includes('NEXOWATT_EOS_ASSIST_DISABLED'),'assist disable guard missing');
|
|
49
13
|
console.log('[NexoWatt EOS modern UI] OK');
|
|
@@ -1,18 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const assert=require('node:assert/strict');
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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); });
|