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.
Files changed (65) hide show
  1. package/INSTALL_TEST_V7.10.2.md +22 -0
  2. package/MERGE_UPDATE_README_V7.10.2.md +11 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +19 -21
  4. package/NEXOWATT_EOS_PREBUILT_MANIFEST.json +1410 -0
  5. package/PUBLISH_STABLE_V7.10.2.md +21 -0
  6. package/README.md +16 -6
  7. package/README_STABILITY_V7.10.2.md +20 -0
  8. package/RELEASE_ACCEPTANCE_V7.10.2.md +13 -0
  9. package/RELEASE_NOTES_V7.10.2.md +11 -0
  10. package/adminWww/assets/Intro-DkwRiz1n-v84.js +1 -1
  11. package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
  12. package/adminWww/css/eos-ems-overview.css +5 -3
  13. package/adminWww/css/nexowatt-native-shell.css +85 -413
  14. package/adminWww/index.html +20 -21
  15. package/adminWww/js/eos-account-management.js +28 -19
  16. package/adminWww/js/eos-assistant.js +1 -1
  17. package/adminWww/js/eos-ems-overview.js +1 -1
  18. package/adminWww/js/eos-role-bootstrap.js +17 -80
  19. package/adminWww/js/eos-role-ui.js +28 -4
  20. package/adminWww/js/nexowatt-native-shell.js +1 -1
  21. package/adminWww/static/js/nexowatt-stable-login.js +43 -70
  22. package/adminWww/static/js/nexowatt-stable-v7101.js +27 -0
  23. package/build/lib/eosAutoUpdate.js +418 -20
  24. package/build/lib/eosAutoUpdate.js.map +1 -0
  25. package/build/lib/eosRequestSecurity.js +4 -1
  26. package/build/lib/eosRequestSecurity.js.map +1 -0
  27. package/build/lib/web.js +161 -254
  28. package/build/lib/web.js.map +1 -1
  29. package/build/main.js +39 -67
  30. package/io-package.json +72 -7
  31. package/package.json +24 -25
  32. package/tools/nexowatt-account-management-selftest.cjs +15 -19
  33. package/tools/nexowatt-account-password-flow-selftest.cjs +15 -0
  34. package/tools/nexowatt-apply-ems-overview.cjs +4 -2
  35. package/tools/nexowatt-auto-update-selftest.cjs +144 -32
  36. package/tools/nexowatt-backend-runtime-selftest.cjs +147 -0
  37. package/tools/nexowatt-clean-legacy-runtime.cjs +1 -1
  38. package/tools/nexowatt-ems-overview-selftest.cjs +1 -1
  39. package/tools/nexowatt-ensure-release-defaults.cjs +1 -1
  40. package/tools/nexowatt-login-layout-selftest.cjs +9 -13
  41. package/tools/nexowatt-merge-update-selftest.cjs +7 -1
  42. package/tools/nexowatt-modern-ui-selftest.cjs +10 -46
  43. package/tools/nexowatt-password-write-selftest.cjs +46 -16
  44. package/tools/nexowatt-prebuilt-release-selftest.cjs +292 -0
  45. package/tools/nexowatt-release-defaults-selftest.cjs +1 -1
  46. package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -8
  47. package/tools/nexowatt-stable-v7102-selftest.cjs +51 -0
  48. package/tools/nexowatt-sync-release-version.cjs +43 -5
  49. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +2 -3
  50. package/tools/nexowatt-validate-package.cjs +54 -29
  51. package/tools/nexowatt-version-sync-selftest.cjs +18 -1
  52. package/INSTALL_TEST_V7.10.0.md +0 -11
  53. package/MERGE_UPDATE_README_V7.10.0.md +0 -5
  54. package/PUBLISH_STABLE_V7.10.0.md +0 -15
  55. package/README_STABILITY_V7.10.0.md +0 -3
  56. package/RELEASE_ACCEPTANCE_V7.10.0.md +0 -11
  57. package/RELEASE_NOTES_V7.10.0.md +0 -17
  58. package/adminWww/js/eos-release-watch.js +0 -77
  59. package/adminWww/static/js/nexowatt-stable-v7100.js +0 -111
  60. package/build/lib/eosPassword.js +0 -72
  61. package/build/lib/nexowattStableAuth.js +0 -74
  62. package/src/lib/eosAutoUpdate.ts +0 -320
  63. package/tools/nexowatt-cache-update-selftest.cjs +0 -12
  64. package/tools/nexowatt-request-security-selftest.cjs +0 -11
  65. package/tools/nexowatt-stable-v7100-selftest.cjs +0 -17
@@ -1,34 +1,146 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- const assert=require('node:assert/strict');
4
- const {NexoWattStableUpdateManager,isNexoWattRepositoryEntry}=require('../build/lib/eosAutoUpdate.js');
5
- const objects=new Map();
6
- objects.set('system.adapter.eos-admin.0',{_id:'system.adapter.eos-admin.0',common:{name:'eos-admin',version:'7.10.0'},native:{eosNexoWattAutoUpdate:true}});
7
- objects.set('system.config',{_id:'system.config',common:{activeRepo:['stable'],adapterAutoUpgrade:{repositories:{stable:false},defaultPolicy:'none'}},native:{}});
8
- objects.set('system.repositories',{_id:'system.repositories',native:{repositories:{stable:{json:{'eos-admin':{name:'eos-admin',version:'7.10.0',meta:'https://github.com/NexoWatt/ioBroker.eos-admin'},'nexowatt-ui':{name:'nexowatt-ui',version:'0.8.198',publisher:'NexoWatt'},'nexowatt-beta':{name:'nexowatt-beta',version:'1.0.0-beta.1',publisher:'NexoWatt'},admin:{name:'admin',version:'7.7.0',publisher:'ioBroker'}}}}}});
9
- objects.set('system.adapter.eos-admin',{_id:'system.adapter.eos-admin',common:{name:'eos-admin',version:'7.10.0',automaticUpgrade:'patch'},native:{}});
10
- objects.set('system.adapter.nexowatt-ui',{_id:'system.adapter.nexowatt-ui',common:{name:'nexowatt-ui',version:'0.8.197'},native:{}});
11
- objects.set('system.adapter.admin',{_id:'system.adapter.admin',common:{name:'admin',version:'7.7.0',automaticUpgrade:'minor'},native:{}});
12
- const copy=v=>JSON.parse(JSON.stringify(v));
13
- const refresh=[];
14
- const adapter={namespace:'eos-admin.0',host:'eos-host',log:{debug(){},info(){},warn(){},error(){}},getForeignObjectAsync:async id=>copy(objects.get(id)||null),setForeignObjectAsync:async(id,obj)=>{objects.set(id,copy(obj));},extendForeignObjectAsync:async(id,part)=>{const base=copy(objects.get(id)||{_id:id,common:{},native:{}});base.native={...(base.native||{}),...(part.native||{})};base.common={...(base.common||{}),...(part.common||{})};objects.set(id,base);},getObjectViewAsync:async()=>({rows:[...objects.entries()].filter(([id])=>/^system\.adapter\.[^.]+$/.test(id)).map(([id,value])=>({id,value:copy(value)}))}),sendToHostAsync:async(...args)=>{refresh.push(args);}};
15
- (async()=>{
16
- assert.equal(isNexoWattRepositoryEntry('nexowatt-ui',{publisher:'NexoWatt'}),true);
17
- assert.equal(isNexoWattRepositoryEntry('admin',{publisher:'ioBroker'}),false);
18
- const manager=new NexoWattStableUpdateManager(adapter);
19
- const enabled=await manager.reconcile('test');
20
- assert.equal(enabled.enabled,true);assert.equal(enabled.repository,'stable');assert.deepEqual(enabled.managedAdapters,['eos-admin','nexowatt-ui']);
21
- assert.equal(objects.get('system.adapter.eos-admin').common.automaticUpgrade,'major');
22
- assert.equal(objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade,'major');
23
- assert.equal(objects.get('system.adapter.admin').common.automaticUpgrade,'minor');
24
- assert.equal(objects.get('system.config').common.adapterAutoUpgrade.repositories.stable,true);
25
- // A manual user change while EOS management is active must not be overwritten on disable.
26
- objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade='minor';
27
- const disabled=await manager.setEnabled(false);assert.equal(disabled.enabled,false);
28
- assert.equal(objects.get('system.adapter.eos-admin').common.automaticUpgrade,'patch');
29
- assert.equal(objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade,'minor');
30
- assert.equal(objects.get('system.adapter.admin').common.automaticUpgrade,'minor');
31
- assert.equal(objects.get('system.config').common.adapterAutoUpgrade.repositories.stable,false);
32
- manager.stop();
33
- console.log('[NexoWatt EOS auto update] OK (native policy, stable-only scope, third-party isolation and restore)');
34
- })().catch(e=>{console.error(e.stack||e);process.exit(1)});
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 v98 when installing v99).
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: true,
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(`[NexoWatt EOS login layout] ${m}`);process.exit(1)};const must=(c,m)=>{if(!c)fail(m)};
4
- const css=read('adminWww/css/nexowatt-native-shell.css'),sourceCss=read('src-admin/public/css/nexowatt-native-shell.css'),login=read('src-admin/src/login/Login.tsx'),bundle=read('adminWww/assets/bootstrap-COulQZax-v84.js'),boot=read('adminWww/js/eos-role-bootstrap.js'),stableLogin=read('adminWww/static/js/nexowatt-stable-login.js'),stable=read('adminWww/static/js/nexowatt-stable-v7100.js'),index=read('adminWww/index.html'),info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));
5
- must(css===sourceCss,'login CSS source/build drift');
6
- for(const marker of ['NexoWatt EOS 7.10.0 – restored clean login geometry','NexoWatt EOS 7.10.0 final – static labels above login fields','width: min(424px, calc(100vw - 20px)) !important','width: min(372px, calc(100vw - 48px)) !important','position: static !important','transform: none !important','display: none !important','overflow: visible !important'])must(css.includes(marker),`login marker missing: ${marker}`);
7
- must(!css.includes('width: min(520px, calc(100vw - 24px))')&&!css.includes('width: min(460px, calc(100vw - 56px))'),'oversized v96 geometry remains');
8
- must(stableLogin.includes('normalizeGeometry')&&stableLogin.includes('MuiInputLabel-shrink')&&stableLogin.includes("removeAttribute('data-nw-login-card')")&&stableLogin.includes("removeAttribute('aria-hidden')"),'login cleanup/static-label runtime missing');
9
- must(!/setAttribute\(['"]data-nw-login-card|createElement\(['"]style/.test(stableLogin),'stable-login runtime creates geometry attributes/styles');
10
- must(!/width:min\(460px|min-height:540px|createElement\(['"]style/.test(stable),'stable runtime injects login geometry');
11
- must(boot.includes("const card = form?.closest?.('.MuiPaper-root')")&&!boot.includes("closest?.('.MuiPaper-root,form,main,section')"),'login Paper ownership incorrect');
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.0.md',
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 = require('fs');
4
- const path = require('path');
5
- const root = path.resolve(__dirname, '..');
6
- const read = rel => fs.readFileSync(path.join(root, rel), 'utf8');
7
- let bad = false;
8
- const fail = msg => { console.error(`[NexoWatt EOS modern UI] ${msg}`); bad = true; };
9
- const css = read('adminWww/css/nexowatt-native-shell.css');
10
- const sourceCss = read('src-admin/public/css/nexowatt-native-shell.css');
11
- const index = read('adminWww/index.html');
12
- const accountUi = read('adminWww/js/eos-account-management.js');
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 {normalizeEosPasswordTarget,setEosUserPasswordWithVerification,verifyEosUserPasswordCredential}=require('../build/lib/eosPassword.js');
5
- function adapter({canonicalFails=false, optionsFail=false, verify=true}={}){
6
- const user={_id:'system.user.user',common:{password:'$old'},native:{}};let accepted='old';const calls=[];
7
- const set=async(target,password,options)=>{calls.push({target,password,options});if(canonicalFails&&target==='system.user.user')throw new Error('canonicalRejected');if(optionsFail&&options)throw new Error('optionsRejected');user.common.password=`$${Date.now()}:${password}`;accepted=password};
8
- return {calls,getForeignObjectAsync:async id=>id==='system.user.user'?user:null,setPasswordAsync:set,checkPasswordAsync:async(target,password)=>[verify&&['system.user.user','user'].includes(target)&&password===accepted,'system.user.user']};
9
- }
10
- (async()=>{
11
- assert.deepEqual(normalizeEosPasswordTarget('user'),{userId:'system.user.user',userName:'user'});
12
- const normal=adapter();const a=await setEosUserPasswordWithVerification(normal,'system.user.user','Strong!2026');assert.equal(a.apiTarget,'system.user.user');
13
- await verifyEosUserPasswordCredential(normal,'system.user.user','Strong!2026');
14
- const legacy=adapter({canonicalFails:true});const b=await setEosUserPasswordWithVerification(legacy,'system.user.user','Legacy!2026');assert.equal(b.apiTarget,'user');
15
- const noOptions=adapter({optionsFail:true});const c=await setEosUserPasswordWithVerification(noOptions,'system.user.user','NoOptions!2026');assert.ok(['system.user.user','user'].includes(c.apiTarget));assert.ok(noOptions.calls.some(call=>call.options===null));
16
- await assert.rejects(()=>setEosUserPasswordWithVerification(adapter({verify:false}),'user','Fail!2026'),/passwordVerificationFailed/);
17
- console.log('[NexoWatt EOS password write v7100] OK');
18
- })().catch(error=>{console.error(error.stack||error);process.exit(1)});
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); });