iobroker.eos-admin 7.9.97 → 7.9.99
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.97.md → INSTALL_TEST_V7.9.99.md} +2 -2
- package/{MERGE_UPDATE_README_V7.9.97.md → MERGE_UPDATE_README_V7.9.99.md} +1 -1
- package/NEXOWATT_EOS_BUILD_INFO.json +21 -20
- package/{PUBLISH_STABLE_V7.9.97.md → PUBLISH_STABLE_V7.9.99.md} +2 -2
- package/{README_STABILITY_V7.9.97.md → README_STABILITY_V7.9.99.md} +1 -1
- package/{RELEASE_ACCEPTANCE_V7.9.97.md → RELEASE_ACCEPTANCE_V7.9.99.md} +1 -1
- package/RELEASE_NOTES_V7.9.99.md +9 -0
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
- package/adminWww/css/eos-ems-overview.css +3 -5
- package/adminWww/css/nexowatt-native-shell.css +32 -1
- package/adminWww/index.html +19 -18
- package/adminWww/js/eos-account-management.js +13 -9
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-ems-overview.js +2 -3
- package/adminWww/js/eos-release-watch.js +77 -0
- package/adminWww/js/eos-role-bootstrap.js +7 -4
- package/adminWww/js/eos-role-ui.js +2 -2
- package/adminWww/js/nexowatt-native-shell.js +1 -1
- package/adminWww/static/js/nexowatt-stable-login.js +3 -3
- package/adminWww/static/js/{nexowatt-stable-v97.js → nexowatt-stable-v99.js} +2 -2
- package/build/lib/eosPassword.js +62 -0
- package/build/lib/eosRequestSecurity.js +31 -0
- package/build/lib/web.js +99 -71
- package/build/main.js +2 -1
- package/io-package.json +5 -5
- package/package.json +18 -12
- package/tools/nexowatt-account-management-selftest.cjs +20 -57
- package/tools/nexowatt-apply-ems-overview.cjs +2 -4
- package/tools/nexowatt-cache-update-selftest.cjs +11 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +1 -1
- package/tools/nexowatt-ems-overview-selftest.cjs +1 -1
- package/tools/nexowatt-login-layout-selftest.cjs +11 -11
- package/tools/nexowatt-merge-update-selftest.cjs +1 -1
- package/tools/nexowatt-modern-ui-selftest.cjs +1 -1
- package/tools/nexowatt-password-write-selftest.cjs +46 -0
- package/tools/nexowatt-request-security-selftest.cjs +11 -0
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -6
- package/tools/nexowatt-stable-v99-selftest.cjs +25 -0
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +3 -1
- package/tools/nexowatt-validate-package.cjs +23 -14
- package/RELEASE_NOTES_V7.9.97.md +0 -19
- package/tools/nexowatt-stable-v97-selftest.cjs +0 -16
package/build/lib/web.js
CHANGED
|
@@ -21,6 +21,8 @@ const session = require("express-session");
|
|
|
21
21
|
const bodyParser = require("body-parser");
|
|
22
22
|
const cookieParser = require("cookie-parser");
|
|
23
23
|
const iobroker_mcp_1 = require("iobroker.mcp");
|
|
24
|
+
const eosPassword_1 = require("./eosPassword");
|
|
25
|
+
const eosRequestSecurity_1 = require("./eosRequestSecurity");
|
|
24
26
|
let AdapterStore;
|
|
25
27
|
/** Content of a socket-io file */
|
|
26
28
|
let socketIoFile;
|
|
@@ -714,22 +716,9 @@ class Web {
|
|
|
714
716
|
return { valid: true, password: value };
|
|
715
717
|
}
|
|
716
718
|
async setEosUserPassword(userId, password) {
|
|
717
|
-
|
|
718
|
-
// session and cannot be supplied by the browser. Use the trusted EOS service context for the
|
|
719
|
-
// actual password write so installer/end-user groups do not need global users.write rights.
|
|
720
|
-
const options = { user: EOS_PASSWORD_SERVICE_USER };
|
|
721
|
-
if (typeof this.adapter.setPasswordAsync === 'function') {
|
|
722
|
-
await this.adapter.setPasswordAsync(userId, password, options);
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
if (typeof this.adapter.setPassword === 'function') {
|
|
726
|
-
await new Promise((resolve, reject) => {
|
|
727
|
-
this.adapter.setPassword?.(userId, password, options, error => (error ? reject(error) : resolve()));
|
|
728
|
-
});
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
throw new Error('passwordApiUnavailable');
|
|
719
|
+
await (0, eosPassword_1.setEosUserPasswordWithVerification)(this.adapter, userId, password, EOS_PASSWORD_SERVICE_USER);
|
|
732
720
|
}
|
|
721
|
+
|
|
733
722
|
async destroyEosRequestSessions(req) {
|
|
734
723
|
const token = this.readAccessTokenFromRequest(req);
|
|
735
724
|
if (!token || typeof this.adapter.destroySession !== 'function') {
|
|
@@ -758,7 +747,11 @@ class Web {
|
|
|
758
747
|
res.status(403).json({ error: 'passwordSetupNotAllowed' });
|
|
759
748
|
return;
|
|
760
749
|
}
|
|
761
|
-
if (
|
|
750
|
+
if (req.headers['x-nexowatt-eos-first-login'] !== '1') {
|
|
751
|
+
res.status(403).json({ error: 'invalidRequest' });
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (!this.isEosSameOriginWrite(req)) {
|
|
762
755
|
res.status(403).json({ error: 'invalidRequestOrigin' });
|
|
763
756
|
return;
|
|
764
757
|
}
|
|
@@ -781,8 +774,8 @@ class Web {
|
|
|
781
774
|
if (!userObject) {
|
|
782
775
|
throw new Error('userObjectUnavailableAfterPasswordChange');
|
|
783
776
|
}
|
|
784
|
-
const native = (userObject.native
|
|
785
|
-
const account = (native.nexowattEosAccount
|
|
777
|
+
const native = { ...(userObject.native || {}) };
|
|
778
|
+
const account = { ...(native.nexowattEosAccount || {}) };
|
|
786
779
|
const now = new Date().toISOString();
|
|
787
780
|
account.passwordInitialized = true;
|
|
788
781
|
account.passwordInitializedAt = Date.now();
|
|
@@ -793,13 +786,18 @@ class Web {
|
|
|
793
786
|
account.firstLoginCompletedAt = now;
|
|
794
787
|
account.forcePasswordChange = false;
|
|
795
788
|
account.passwordlessFirstLoginAllowed = false;
|
|
789
|
+
native.nexowattEosAccount = account;
|
|
796
790
|
native.nexowattPasswordChangeRequired = false;
|
|
797
791
|
native.eosPasswordChangeRequired = false;
|
|
798
792
|
native.nexowattFirstLoginPending = false;
|
|
799
793
|
native.eosFirstLoginRequired = false;
|
|
800
794
|
native.nexowattInitialPasswordApplied = false;
|
|
801
795
|
native.nexowattPasswordChangedAt = now;
|
|
802
|
-
await this.adapter.
|
|
796
|
+
await this.adapter.extendForeignObjectAsync(access.userId, { native }, { user: EOS_PASSWORD_SERVICE_USER });
|
|
797
|
+
const persistedState = await this.getEosFirstLoginPasswordState(access.userId, access.role);
|
|
798
|
+
if (persistedState.required || !persistedState.initialized) {
|
|
799
|
+
throw new Error('passwordMetadataNotPersisted');
|
|
800
|
+
}
|
|
803
801
|
await this.destroyEosRequestSessions(req);
|
|
804
802
|
for (const cookie of ['access_token', 'refresh_token', 'connect.sid']) {
|
|
805
803
|
res.clearCookie(cookie);
|
|
@@ -1027,8 +1025,8 @@ class Web {
|
|
|
1027
1025
|
if (!user) {
|
|
1028
1026
|
throw new Error('userObjectUnavailableAfterPasswordChange');
|
|
1029
1027
|
}
|
|
1030
|
-
const native = (user.native
|
|
1031
|
-
const account = (native.nexowattEosAccount
|
|
1028
|
+
const native = { ...(user.native || {}) };
|
|
1029
|
+
const account = { ...(native.nexowattEosAccount || {}) };
|
|
1032
1030
|
const now = new Date().toISOString();
|
|
1033
1031
|
account.passwordInitialized = true;
|
|
1034
1032
|
account.passwordInitializedAt = Date.now();
|
|
@@ -1040,7 +1038,12 @@ class Web {
|
|
|
1040
1038
|
account.forcePasswordChange = false;
|
|
1041
1039
|
account.passwordlessFirstLoginAllowed = false;
|
|
1042
1040
|
account.passwordlessClaimCompletedAt = now;
|
|
1043
|
-
|
|
1041
|
+
native.nexowattEosAccount = account;
|
|
1042
|
+
native.nexowattPasswordChangeRequired = false;
|
|
1043
|
+
native.eosPasswordChangeRequired = false;
|
|
1044
|
+
native.nexowattFirstLoginPending = false;
|
|
1045
|
+
native.eosFirstLoginRequired = false;
|
|
1046
|
+
await this.adapter.extendForeignObjectAsync(claim.userId, { native }, { user: EOS_PASSWORD_SERVICE_USER });
|
|
1044
1047
|
this.invalidateEosPasswordClaimsForUser(claim.userId);
|
|
1045
1048
|
res.clearCookie('nexowatt_eos_first_login', { path: '/nexowatt/account' });
|
|
1046
1049
|
this.adapter.log.info(`EOS passwordless first activation completed for ${claim.userId}`);
|
|
@@ -1106,11 +1109,16 @@ class Web {
|
|
|
1106
1109
|
async resetEosAccountPassword(req, res) {
|
|
1107
1110
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
1108
1111
|
const access = await this.getEosRequestAccess(req);
|
|
1109
|
-
if (!access.userId
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1112
|
+
if (!access.userId || (access.role !== 'admin' && access.role !== 'installer')) {
|
|
1113
|
+
res.status(403).json({ error: 'permissionError', role: access.role });
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if (req.headers['x-nexowatt-eos-account-reset'] !== '1') {
|
|
1117
|
+
res.status(403).json({ error: 'invalidRequest' });
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (!this.isEosSameOriginWrite(req)) {
|
|
1121
|
+
res.status(403).json({ error: 'invalidRequestOrigin' });
|
|
1114
1122
|
return;
|
|
1115
1123
|
}
|
|
1116
1124
|
const targetUserId = this.normalizeEosUserId(req.body?.user);
|
|
@@ -1118,19 +1126,12 @@ class Web {
|
|
|
1118
1126
|
res.status(400).json({ error: 'invalidTarget' });
|
|
1119
1127
|
return;
|
|
1120
1128
|
}
|
|
1121
|
-
const
|
|
1122
|
-
const
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
? 'enduser'
|
|
1128
|
-
: null;
|
|
1129
|
-
if (targetAccess.role === 'admin'
|
|
1130
|
-
|| !explicitlyManagedRole
|
|
1131
|
-
|| (access.role === 'installer' && explicitlyManagedRole !== 'enduser')) {
|
|
1132
|
-
// Never rely on the secure-default "enduser" classification for a password reset. The
|
|
1133
|
-
// target must be an explicit member of a managed EOS installer/end-user group.
|
|
1129
|
+
const managedAccounts = await this.getEosManagedAccounts(access.role);
|
|
1130
|
+
const managedTarget = managedAccounts.find(account => account.id === targetUserId);
|
|
1131
|
+
const explicitlyManagedRole = managedTarget?.role === 'installer' || managedTarget?.role === 'enduser'
|
|
1132
|
+
? managedTarget.role
|
|
1133
|
+
: null;
|
|
1134
|
+
if (!managedTarget || !explicitlyManagedRole) {
|
|
1134
1135
|
res.status(403).json({ error: 'permissionError' });
|
|
1135
1136
|
return;
|
|
1136
1137
|
}
|
|
@@ -1148,8 +1149,8 @@ class Web {
|
|
|
1148
1149
|
if (!user) {
|
|
1149
1150
|
throw new Error('userObjectUnavailableAfterPasswordReset');
|
|
1150
1151
|
}
|
|
1151
|
-
const native = (user.native
|
|
1152
|
-
const account = (native.nexowattEosAccount
|
|
1152
|
+
const native = { ...(user.native || {}) };
|
|
1153
|
+
const account = { ...(native.nexowattEosAccount || {}) };
|
|
1153
1154
|
const now = new Date().toISOString();
|
|
1154
1155
|
account.passwordInitialized = false;
|
|
1155
1156
|
account.passwordSetupVersion = 0;
|
|
@@ -1159,6 +1160,7 @@ class Web {
|
|
|
1159
1160
|
account.passwordResetAt = now;
|
|
1160
1161
|
account.passwordResetBy = access.userId;
|
|
1161
1162
|
account.passwordResetMode = 'initial-password';
|
|
1163
|
+
native.nexowattEosAccount = account;
|
|
1162
1164
|
native.nexowattPasswordChangeRequired = true;
|
|
1163
1165
|
native.eosPasswordChangeRequired = true;
|
|
1164
1166
|
native.nexowattFirstLoginPending = true;
|
|
@@ -1168,7 +1170,11 @@ class Web {
|
|
|
1168
1170
|
native.nexowattStableInitialCredentialVersion = 1;
|
|
1169
1171
|
delete account.passwordSetAt;
|
|
1170
1172
|
delete account.firstLoginCompletedAt;
|
|
1171
|
-
await this.adapter.
|
|
1173
|
+
await this.adapter.extendForeignObjectAsync(targetUserId, { native }, { user: EOS_PASSWORD_SERVICE_USER });
|
|
1174
|
+
const resetState = await this.getEosFirstLoginPasswordState(targetUserId, explicitlyManagedRole);
|
|
1175
|
+
if (!resetState.required || resetState.initialized) {
|
|
1176
|
+
throw new Error('passwordMetadataNotPersisted');
|
|
1177
|
+
}
|
|
1172
1178
|
this.invalidateEosPasswordClaimsForUser(targetUserId);
|
|
1173
1179
|
this.adapter.log.warn(`EOS account ${targetUserId} was reset to the mandatory initial-password flow by ${access.userId}`);
|
|
1174
1180
|
res.status(200).json({
|
|
@@ -1245,15 +1251,9 @@ class Web {
|
|
|
1245
1251
|
});
|
|
1246
1252
|
}
|
|
1247
1253
|
isEosSameOriginWrite(req) {
|
|
1248
|
-
|
|
1249
|
-
if (!origin) {
|
|
1250
|
-
return true;
|
|
1251
|
-
}
|
|
1252
|
-
const forwardedProto = String(req.headers['x-forwarded-proto'] || '').split(',')[0].trim();
|
|
1253
|
-
const protocol = forwardedProto || (this.settings.secure ? 'https' : 'http');
|
|
1254
|
-
const host = String(req.headers.host || '').trim();
|
|
1255
|
-
return !!host && origin === `${protocol}://${host}`;
|
|
1254
|
+
return (0, eosRequestSecurity_1.isEosSameOriginRequest)(req.headers);
|
|
1256
1255
|
}
|
|
1256
|
+
|
|
1257
1257
|
async saveEosBasicSettings(req, res) {
|
|
1258
1258
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
1259
1259
|
const access = await this.getEosRequestAccess(req);
|
|
@@ -1484,6 +1484,9 @@ class Web {
|
|
|
1484
1484
|
next();
|
|
1485
1485
|
});*/
|
|
1486
1486
|
this.server.app.get('/version', (_req, res) => {
|
|
1487
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
1488
|
+
res.setHeader('Pragma', 'no-cache');
|
|
1489
|
+
res.setHeader('Expires', '0');
|
|
1487
1490
|
res.status(200).send(this.adapter.version);
|
|
1488
1491
|
});
|
|
1489
1492
|
// v36: Repair stale/broken URLs generated by older EOS hard-logout builds.
|
|
@@ -1762,8 +1765,13 @@ class Web {
|
|
|
1762
1765
|
});
|
|
1763
1766
|
this.server.app.post('/nexowatt/account/first-password', (req, res) => {
|
|
1764
1767
|
void this.saveEosFirstLoginPassword(req, res).catch(e => {
|
|
1765
|
-
|
|
1766
|
-
|
|
1768
|
+
const detail = e instanceof Error ? e.message : String(e || '');
|
|
1769
|
+
this.adapter.log.warn(`Cannot initialize EOS first-login password: ${detail}`);
|
|
1770
|
+
const known = new Set([
|
|
1771
|
+
'passwordApiUnavailable', 'passwordNotPersisted', 'passwordVerificationFailed',
|
|
1772
|
+
'passwordMetadataNotPersisted', 'userObjectUnavailableAfterPasswordChange',
|
|
1773
|
+
]);
|
|
1774
|
+
res.status(500).json({ error: known.has(detail) ? detail : 'passwordSetupFailed' });
|
|
1767
1775
|
});
|
|
1768
1776
|
});
|
|
1769
1777
|
this.server.app.get('/nexowatt/account/manage', (req, res) => {
|
|
@@ -1774,8 +1782,13 @@ class Web {
|
|
|
1774
1782
|
});
|
|
1775
1783
|
this.server.app.post('/nexowatt/account/reset', (req, res) => {
|
|
1776
1784
|
void this.resetEosAccountPassword(req, res).catch(e => {
|
|
1777
|
-
|
|
1778
|
-
|
|
1785
|
+
const detail = e instanceof Error ? e.message : String(e || '');
|
|
1786
|
+
this.adapter.log.warn(`Cannot reset EOS account: ${detail}`);
|
|
1787
|
+
const known = new Set([
|
|
1788
|
+
'passwordApiUnavailable', 'passwordNotPersisted', 'passwordVerificationFailed',
|
|
1789
|
+
'passwordMetadataNotPersisted', 'userObjectUnavailableAfterPasswordReset',
|
|
1790
|
+
]);
|
|
1791
|
+
res.status(500).json({ error: known.has(detail) ? detail : 'accountResetFailed' });
|
|
1779
1792
|
});
|
|
1780
1793
|
});
|
|
1781
1794
|
this.server.app.get('/iobroker_check.html', (_req, res) => {
|
|
@@ -1902,7 +1915,29 @@ class Web {
|
|
|
1902
1915
|
res.status(404).send(get404Page(`File ${escapeHtml(fileName)} not found`));
|
|
1903
1916
|
}
|
|
1904
1917
|
});
|
|
1905
|
-
const
|
|
1918
|
+
const isEosCurrentAsset = (pathName) => pathName === '/'
|
|
1919
|
+
|| pathName === '/index.html'
|
|
1920
|
+
|| pathName === '/manifest.json'
|
|
1921
|
+
|| pathName === '/mf-manifest.json'
|
|
1922
|
+
|| pathName === '/version'
|
|
1923
|
+
|| /^\/(?:js|css)\/(?:eos-|nexowatt-)/.test(pathName)
|
|
1924
|
+
|| /^\/static\/js\/nexowatt-stable-v\d+\.js$/.test(pathName)
|
|
1925
|
+
|| /^\/remoteEntry(?:-v\d+)?\.js$/.test(pathName)
|
|
1926
|
+
|| /^\/assets\/(?:bootstrap|hostInit|Intro|index-CQZugZ1z|index-D2ymscJA|iobroker_admin__mf_v__runtimeInit__mf_v__)-[^/]+\.js$/.test(pathName);
|
|
1927
|
+
const applyNoStoreHeaders = (res) => {
|
|
1928
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
1929
|
+
res.setHeader('Pragma', 'no-cache');
|
|
1930
|
+
res.setHeader('Expires', '0');
|
|
1931
|
+
res.setHeader('Surrogate-Control', 'no-store');
|
|
1932
|
+
};
|
|
1933
|
+
const appOptions = {
|
|
1934
|
+
setHeaders: (res, filePath) => {
|
|
1935
|
+
const raw = filePath.startsWith(this.wwwDir) ? filePath.slice(this.wwwDir.length) : filePath;
|
|
1936
|
+
const relativePath = `/${raw.replace(/\\/g, '/').replace(/^\/+/, '')}`;
|
|
1937
|
+
if (isEosCurrentAsset(relativePath))
|
|
1938
|
+
applyNoStoreHeaders(res);
|
|
1939
|
+
},
|
|
1940
|
+
};
|
|
1906
1941
|
if (this.settings.cache) {
|
|
1907
1942
|
appOptions.maxAge = 30_758_400_000;
|
|
1908
1943
|
}
|
|
@@ -2000,31 +2035,24 @@ class Web {
|
|
|
2000
2035
|
this.server.app.get('/empty.html', (_req, res) => {
|
|
2001
2036
|
res.status(200).send('');
|
|
2002
2037
|
});
|
|
2003
|
-
//
|
|
2004
|
-
//
|
|
2038
|
+
// EOS entrypoints, overlays and patched module bundles must never be reused
|
|
2039
|
+
// from a previous release. Hashed untouched leaf assets may remain cacheable.
|
|
2005
2040
|
this.server.app.use((req, res, next) => {
|
|
2006
2041
|
const pathName = req.path || req.url.split('?')[0];
|
|
2007
|
-
if (pathName
|
|
2008
|
-
|
|
2009
|
-
|| pathName === '/mf-manifest.json'
|
|
2010
|
-
|| /^\/remoteEntry(?:-v\d+)?\.js$/.test(pathName)
|
|
2011
|
-
|| /^\/assets\/(?:hostInit|index-CQZugZ1z)-[^/]+\.js$/.test(pathName)) {
|
|
2012
|
-
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
2013
|
-
res.setHeader('Pragma', 'no-cache');
|
|
2014
|
-
res.setHeader('Expires', '0');
|
|
2015
|
-
}
|
|
2042
|
+
if (isEosCurrentAsset(pathName))
|
|
2043
|
+
applyNoStoreHeaders(res);
|
|
2016
2044
|
next();
|
|
2017
2045
|
});
|
|
2018
2046
|
this.server.app.get('/index.html', async (_req, res) => {
|
|
2019
|
-
this.indexHTML
|
|
2047
|
+
this.indexHTML = await this.prepareIndex('/index.html');
|
|
2020
2048
|
res.header('Content-Type', 'text/html');
|
|
2021
|
-
res
|
|
2049
|
+
applyNoStoreHeaders(res);
|
|
2022
2050
|
res.status(200).send(this.indexHTML);
|
|
2023
2051
|
});
|
|
2024
2052
|
this.server.app.get('/', async (_req, res) => {
|
|
2025
|
-
this.indexHTML
|
|
2053
|
+
this.indexHTML = await this.prepareIndex('/index.html');
|
|
2026
2054
|
res.header('Content-Type', 'text/html');
|
|
2027
|
-
res
|
|
2055
|
+
applyNoStoreHeaders(res);
|
|
2028
2056
|
res.status(200).send(this.indexHTML);
|
|
2029
2057
|
});
|
|
2030
2058
|
this.server.app.use('/', express.static(this.wwwDir, appOptions));
|
package/build/main.js
CHANGED
|
@@ -20,6 +20,7 @@ const socket_classes_1 = require("@iobroker/socket-classes");
|
|
|
20
20
|
const ws_server_1 = require("@iobroker/ws-server");
|
|
21
21
|
const translations_1 = require("./lib/translations");
|
|
22
22
|
const web_1 = require("./lib/web");
|
|
23
|
+
const eosPassword_1 = require("./lib/eosPassword");
|
|
23
24
|
const checkLinuxPass_1 = require("./lib/checkLinuxPass");
|
|
24
25
|
const plugin_docker_1 = require("@iobroker/plugin-docker");
|
|
25
26
|
const objectFixes_1 = require("./lib/objectFixes");
|
|
@@ -1152,7 +1153,7 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1152
1153
|
}
|
|
1153
1154
|
if (String(user.common.password || '') === '') {
|
|
1154
1155
|
const bootstrapSecret = `${(0, node_crypto_1.randomBytes)(48).toString('base64url')}!aA1`;
|
|
1155
|
-
await
|
|
1156
|
+
await (0, eosPassword_1.setEosUserPasswordWithVerification)(this, userId, bootstrapSecret, ADMIN_OWNER_USER);
|
|
1156
1157
|
user = (await this.getForeignObjectAsync(userId));
|
|
1157
1158
|
if (!user) {
|
|
1158
1159
|
return;
|
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "eos-admin",
|
|
4
|
-
"version": "7.9.
|
|
4
|
+
"version": "7.9.99",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "NexoWatt EOS Admin",
|
|
7
7
|
"de": "NexoWatt EOS Admin",
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
"icon": "admin.svg",
|
|
330
330
|
"messagebox": true,
|
|
331
331
|
"enabled": true,
|
|
332
|
-
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
332
|
+
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.99/admin/admin.svg",
|
|
333
333
|
"keywords": [
|
|
334
334
|
"NexoWatt",
|
|
335
335
|
"EOS",
|
|
@@ -340,7 +340,7 @@
|
|
|
340
340
|
"licensed"
|
|
341
341
|
],
|
|
342
342
|
"compact": true,
|
|
343
|
-
"readme": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
343
|
+
"readme": "https://unpkg.com/iobroker.eos-admin@7.9.99/README.md",
|
|
344
344
|
"authors": [
|
|
345
345
|
"NexoWatt"
|
|
346
346
|
],
|
|
@@ -408,7 +408,7 @@
|
|
|
408
408
|
"nondeletable": false,
|
|
409
409
|
"allowAdapterUpdate": true,
|
|
410
410
|
"allowAdapterDelete": false,
|
|
411
|
-
"meta": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
411
|
+
"meta": "https://unpkg.com/iobroker.eos-admin@7.9.99/io-package.json",
|
|
412
412
|
"npmPackage": "iobroker.eos-admin",
|
|
413
413
|
"packetName": "iobroker.eos-admin",
|
|
414
414
|
"publisher": "NexoWatt"
|
|
@@ -1117,5 +1117,5 @@
|
|
|
1117
1117
|
}
|
|
1118
1118
|
}
|
|
1119
1119
|
},
|
|
1120
|
-
"version": "7.9.
|
|
1120
|
+
"version": "7.9.99"
|
|
1121
1121
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.eos-admin",
|
|
3
|
-
"description": "NexoWatt EOS Admin – stable
|
|
4
|
-
"version": "7.9.
|
|
3
|
+
"description": "NexoWatt EOS Admin – stable candidate with truthful EMS overview, verified user passwords and deterministic browser updates.",
|
|
4
|
+
"version": "7.9.99",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"bluefox <dogafox@gmail.com>",
|
|
7
7
|
"apollon77",
|
|
@@ -118,19 +118,22 @@
|
|
|
118
118
|
"tools/nexowatt-version-sync-selftest.cjs",
|
|
119
119
|
"tools/nexowatt-merge-update-selftest.cjs",
|
|
120
120
|
"tools/nexowatt-ems-overview-selftest.cjs",
|
|
121
|
-
"README_STABILITY_V7.9.
|
|
122
|
-
"RELEASE_NOTES_V7.9.
|
|
123
|
-
"PUBLISH_STABLE_V7.9.
|
|
124
|
-
"INSTALL_TEST_V7.9.
|
|
125
|
-
"RELEASE_ACCEPTANCE_V7.9.
|
|
126
|
-
"MERGE_UPDATE_README_V7.9.
|
|
121
|
+
"README_STABILITY_V7.9.99.md",
|
|
122
|
+
"RELEASE_NOTES_V7.9.99.md",
|
|
123
|
+
"PUBLISH_STABLE_V7.9.99.md",
|
|
124
|
+
"INSTALL_TEST_V7.9.99.md",
|
|
125
|
+
"RELEASE_ACCEPTANCE_V7.9.99.md",
|
|
126
|
+
"MERGE_UPDATE_README_V7.9.99.md",
|
|
127
127
|
"README_EMS_OVERVIEW_MERGE.md",
|
|
128
128
|
"RC73_EOS_ADMIN_MERGE_VALIDATION.md",
|
|
129
129
|
"VALIDATION_EMS_OVERVIEW_MERGE.md",
|
|
130
130
|
"NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md",
|
|
131
131
|
"tools/nexowatt-apply-ems-overview.cjs",
|
|
132
132
|
"tools/nexowatt-ui-overview-runtime-selftest.cjs",
|
|
133
|
-
"tools/nexowatt-stable-
|
|
133
|
+
"tools/nexowatt-stable-v99-selftest.cjs",
|
|
134
|
+
"tools/nexowatt-password-write-selftest.cjs",
|
|
135
|
+
"tools/nexowatt-request-security-selftest.cjs",
|
|
136
|
+
"tools/nexowatt-cache-update-selftest.cjs"
|
|
134
137
|
],
|
|
135
138
|
"scripts": {
|
|
136
139
|
"npm": "npm i -f && cd src-admin && npm i -f",
|
|
@@ -165,7 +168,7 @@
|
|
|
165
168
|
"nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
|
|
166
169
|
"nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
|
|
167
170
|
"nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
|
|
168
|
-
"check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-first-login-selftest.cjs && node tools/nexowatt-account-management-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && npm run check:eos-stable-
|
|
171
|
+
"check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-first-login-selftest.cjs && node tools/nexowatt-account-management-selftest.cjs && node tools/nexowatt-cache-update-selftest.cjs && node tools/nexowatt-request-security-selftest.cjs && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && npm run check:eos-stable-v99 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs",
|
|
169
172
|
"nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
|
|
170
173
|
"clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
|
|
171
174
|
"precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
|
|
@@ -189,7 +192,10 @@
|
|
|
189
192
|
"sync:eos-version": "node tools/nexowatt-sync-release-version.cjs",
|
|
190
193
|
"test:eos-version-sync": "node tools/nexowatt-version-sync-selftest.cjs",
|
|
191
194
|
"verify:eos-merge": "node tools/nexowatt-merge-update-selftest.cjs",
|
|
192
|
-
"check:eos-stable-
|
|
195
|
+
"check:eos-stable-v99": "node tools/nexowatt-stable-v99-selftest.cjs",
|
|
196
|
+
"test:eos-password-write": "node tools/nexowatt-password-write-selftest.cjs",
|
|
197
|
+
"test:eos-request-security": "node tools/nexowatt-request-security-selftest.cjs",
|
|
198
|
+
"test:eos-cache-update": "node tools/nexowatt-cache-update-selftest.cjs"
|
|
193
199
|
},
|
|
194
200
|
"author": "NexoWatt",
|
|
195
201
|
"private": false,
|
|
@@ -200,7 +206,7 @@
|
|
|
200
206
|
},
|
|
201
207
|
"nexowattReleasePolicy": {
|
|
202
208
|
"distTag": "latest",
|
|
203
|
-
"purpose": "accepted stable NexoWatt EOS sales-product release 7.9.
|
|
209
|
+
"purpose": "accepted stable NexoWatt EOS sales-product release 7.9.99"
|
|
204
210
|
},
|
|
205
211
|
"publisher": "NexoWatt"
|
|
206
212
|
}
|
|
@@ -1,60 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const fs =
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const read
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const webSource = read('src/lib/web.ts');
|
|
14
|
-
const webBuilt = read('build/lib/web.js');
|
|
15
|
-
const sourceUi = read('src-admin/public/js/eos-account-management.js');
|
|
16
|
-
const builtUi = read('adminWww/js/eos-account-management.js');
|
|
17
|
-
const roleUi = read('adminWww/js/eos-role-ui.js');
|
|
18
|
-
const css = read('adminWww/css/nexowatt-native-shell.css');
|
|
19
|
-
const index = read('adminWww/index.html');
|
|
20
|
-
const info = json('NEXOWATT_EOS_BUILD_INFO.json');
|
|
21
|
-
const shellTag = String(info.shellCacheTag || info.shellCacheVersion);
|
|
22
|
-
|
|
23
|
-
for (const code of [mainSource, mainBuilt]) {
|
|
24
|
-
for (const marker of [
|
|
25
|
-
'system.user.installer', 'system.user.guest', 'ensureEosDefaultRoleUsers', 'unknown random bootstrap secret',
|
|
26
|
-
'eosPasswordSetupRequired', 'personal password setup is required',
|
|
27
|
-
]) if (!code.includes(marker)) fail(`main account marker missing: ${marker}`);
|
|
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 json=rel=>JSON.parse(read(rel));
|
|
4
|
+
const fail=m=>{console.error(`[NexoWatt EOS account management] ${m}`);process.exit(1)};const must=(c,m)=>{if(!c)fail(m)};
|
|
5
|
+
const mainSource=read('src/main.ts'),mainBuilt=read('build/main.js'),webSource=read('src/lib/web.ts'),webBuilt=read('build/lib/web.js'),passwordSource=read('src/lib/eosPassword.ts');
|
|
6
|
+
const sourceUi=read('src-admin/public/js/eos-account-management.js'),builtUi=read('adminWww/js/eos-account-management.js'),roleUi=read('adminWww/js/eos-role-ui.js'),css=read('adminWww/css/nexowatt-native-shell.css'),index=read('adminWww/index.html'),info=json('NEXOWATT_EOS_BUILD_INFO.json'),shellTag=String(info.shellCacheTag||info.shellCacheVersion);
|
|
7
|
+
for(const code of [mainSource,mainBuilt])for(const marker of ['system.user.installer','system.user.guest','ensureEosDefaultRoleUsers','unknown random bootstrap secret','personal password setup is required'])must(code.includes(marker),`main marker missing: ${marker}`);
|
|
8
|
+
for(const code of [webSource,webBuilt]){
|
|
9
|
+
for(const marker of ['getEosManagedAccounts','sendEosAccountManagement','resetEosAccountPassword','/nexowatt/account/manage','/nexowatt/account/reset','x-nexowatt-eos-account-reset','canResetInstaller','canResetEndUser',"targetUserId === 'system.user.admin'",'targetUserId === access.userId','const managedAccounts = await this.getEosManagedAccounts(access.role)','const managedTarget = managedAccounts.find(account => account.id === targetUserId)',"managedTarget?.role === 'installer'","managedTarget?.role === 'enduser'",'passwordResetAt','passwordResetBy',"passwordResetMode = 'initial-password'","setEosUserPassword(targetUserId, 'nexowatt')",'passwordlessFirstLoginAllowed = false','initialPasswordRequired: true','accountDisabled','passwordMetadataNotPersisted']) must(code.includes(marker),`web marker missing: ${marker}`);
|
|
10
|
+
must(!code.includes('const explicitInstaller = targetAccess.groups.some'),'obsolete explicit installer re-classifier remains');
|
|
11
|
+
must(!code.includes('const explicitEndUser = targetAccess.groups.some'),'obsolete explicit end-user re-classifier remains');
|
|
12
|
+
must(!code.includes('setForeignObjectAsync(targetUserId, user)'),'reset still writes stale full user object');
|
|
28
13
|
}
|
|
29
|
-
for
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (!code.includes('const explicitEndUser = targetAccess.groups.some')) fail('reset target is not restricted to explicit end-user membership');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (const marker of [
|
|
43
|
-
'v91-account-management-under-access-rights', "['admin', 'installer']", 'nexowatt/account/manage',
|
|
44
|
-
'nexowatt/account/reset', 'X-NexoWatt-EOS-Account-Reset', 'data.canResetInstaller === true',
|
|
45
|
-
"account.role === 'enduser'", 'Admin/Service', 'Installateure', 'NEXOWATT_EOS_ACCOUNT_MANAGEMENT',
|
|
46
|
-
'ensureEntrySurface', "currentRoute() !== 'tab-users'", 'eos-account-management-entry',
|
|
47
|
-
]) if (!sourceUi.includes(marker)) fail(`account UI marker missing: ${marker}`);
|
|
48
|
-
if (sourceUi.includes("launcher.className = 'eos-account-management-launcher'")) fail('standalone account launcher is still created');
|
|
49
|
-
if (sourceUi.includes('new MutationObserver')) fail('account management adds a second broad MutationObserver');
|
|
50
|
-
if (sourceUi !== builtUi) fail('account management source/build drift');
|
|
51
|
-
if (!roleUi.includes("route === 'tab-users'")) fail('installer cannot reach Zugänge & Rechte');
|
|
52
|
-
if (roleUi.includes('NEXOWATT_EOS_ACCOUNT_MANAGEMENT.open')) fail('role overview opens account management outside Zugänge & Rechte');
|
|
53
|
-
if (!index.includes(`eos-account-management.js?v=${shellTag}`)) fail('account management asset is not active with the current cache tag');
|
|
54
|
-
for (const marker of [
|
|
55
|
-
'.eos-account-management-entry', '.eos-account-management-overlay', '.eos-account-management-dialog',
|
|
56
|
-
'.eos-account-row', '.eos-account-reset', 'html.eos-account-page-installer',
|
|
57
|
-
]) if (!css.includes(marker)) fail(`account management CSS marker missing: ${marker}`);
|
|
58
|
-
|
|
59
|
-
if (bad) process.exit(1);
|
|
60
|
-
console.log('[NexoWatt EOS account management] OK');
|
|
14
|
+
for(const marker of ['setPasswordAsync(userName','checkPasswordAsync(userName','passwordNotPersisted','passwordVerificationFailed'])must(passwordSource.includes(marker),`password helper marker missing: ${marker}`);
|
|
15
|
+
for(const marker of ["VERSION = 'v99-account-management-password-write'","['admin', 'installer']",'nexowatt/account/manage','nexowatt/account/reset','X-NexoWatt-EOS-Account-Reset','data.canResetInstaller === true',"account.role === 'enduser'",'Admin/Service','Installateure','NEXOWATT_EOS_ACCOUNT_MANAGEMENT','ensureEntrySurface',"currentRoute() !== 'tab-users'",'eos-account-management-entry','Startpasswort „nexowatt“','passwordVerificationFailed'])must(sourceUi.includes(marker),`UI marker missing: ${marker}`);
|
|
16
|
+
must(sourceUi===builtUi,'account management source/build drift');
|
|
17
|
+
must(!sourceUi.includes("launcher.className = 'eos-account-management-launcher'"),'standalone account launcher remains');
|
|
18
|
+
must(!sourceUi.includes('new MutationObserver'),'account management adds a broad observer');
|
|
19
|
+
must(roleUi.includes("route === 'tab-users'"),'installer cannot reach Zugänge & Rechte');
|
|
20
|
+
must(!roleUi.includes('NEXOWATT_EOS_ACCOUNT_MANAGEMENT.open'),'overview opens account management outside Zugänge & Rechte');
|
|
21
|
+
must(index.includes(`eos-account-management.js?v=${shellTag}`),'account management cache tag stale');
|
|
22
|
+
for(const marker of ['.eos-account-management-entry','.eos-account-management-overlay','.eos-account-management-dialog','.eos-account-row','.eos-account-reset','html.eos-account-page-installer'])must(css.includes(marker),`CSS marker missing: ${marker}`);
|
|
23
|
+
console.log('[NexoWatt EOS account management] OK (managed-list authorization and real password reset)');
|
|
@@ -52,7 +52,7 @@ if (!css.includes(cssMarker)) {
|
|
|
52
52
|
overflow: hidden;
|
|
53
53
|
}
|
|
54
54
|
.eos-ems-overview-card * { box-sizing: border-box; }
|
|
55
|
-
.eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:
|
|
55
|
+
.eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:flex-start; gap:20px; }
|
|
56
56
|
.eos-ems-overview-header h2 { margin:5px 0 6px; font-size:clamp(1.2rem,2vw,1.65rem); color:#f5fbff; }
|
|
57
57
|
.eos-ems-overview-header p { margin:0; max-width:760px; color:#9eb2c2; line-height:1.45; }
|
|
58
58
|
.eos-ems-overview-eyebrow { color:#49e39e; font-size:.72rem; font-weight:900; letter-spacing:.13em; text-transform:uppercase; }
|
|
@@ -83,9 +83,7 @@ if (!css.includes(cssMarker)) {
|
|
|
83
83
|
.eos-ems-overview-event time { color:#6f8799; font-size:.68rem; font-variant-numeric:tabular-nums; }
|
|
84
84
|
.eos-ems-overview-event div { display:grid; gap:2px; min-width:0; }
|
|
85
85
|
.eos-ems-overview-empty { color:#718797; font-size:.76rem; }
|
|
86
|
-
.eos-ems-overview-footer { display:flex; justify-content:
|
|
87
|
-
.eos-ems-overview-footer a { color:#5ee0c2; font-weight:800; text-decoration:none; }
|
|
88
|
-
.eos-ems-overview-footer a:hover,.eos-ems-overview-footer a:focus-visible { color:#9af2dd; text-decoration:underline; }
|
|
86
|
+
.eos-ems-overview-footer { display:flex; justify-content:flex-start; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
|
|
89
87
|
@media (max-width: 1100px) {
|
|
90
88
|
.eos-ems-overview-card { flex-basis:100%; max-width:none; }
|
|
91
89
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
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');const fail=m=>{console.error(`[NexoWatt EOS cache update] ${m}`);process.exit(1)};const must=(c,m)=>{if(!c)fail(m)};
|
|
5
|
+
const source=read('src/lib/web.ts'),built=read('build/lib/web.js'),index=read('adminWww/index.html'),dev=read('src-admin/index.html'),watch=read('adminWww/js/eos-release-watch.js');
|
|
6
|
+
for(const code of [source,built])for(const marker of ['isEosCurrentAsset','applyNoStoreHeaders','Surrogate-Control','this.indexHTML = await this.prepareIndex'])must(code.includes(marker),`server marker missing: ${marker}`);
|
|
7
|
+
for(const marker of ["const RELEASE = '7.9.99'",'nexowatt.eos.loadedRelease','cache: \'no-store\'','window.location.replace','navigator.serviceWorker?.getRegistrations'])must(watch.includes(marker),`watcher marker missing: ${marker}`);
|
|
8
|
+
for(const html of [index,dev])for(const marker of ['eos-release-watch.js?v=99','nexowatt-stable-v99.js?v=99','nexowatt-native-shell.css?v=99'])must(html.includes(marker),`index marker missing: ${marker}`);
|
|
9
|
+
must(index.includes('hostInit-v84.js?v=99')&&index.includes('index-CQZugZ1z-v84.js?v=99'),'productive module entry cache tags missing');
|
|
10
|
+
must(!/\?v=(?:97|98)(?:["'])/.test(index),'stale product cache tag remains');
|
|
11
|
+
console.log('[NexoWatt EOS cache update] OK (no-store entrypoints and release watcher)');
|
|
@@ -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 v98 when installing v99).
|
|
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);
|
|
@@ -19,11 +19,11 @@ for (const marker of [
|
|
|
19
19
|
'document.visibilityState',
|
|
20
20
|
'window.setInterval(() => void load(), 5_000)',
|
|
21
21
|
'Diagnose ist rein lesend',
|
|
22
|
-
'Vollständige EMS-Diagnose öffnen',
|
|
23
22
|
'system.adapter.nexowatt-ui.*.alive',
|
|
24
23
|
]) if (!component.includes(marker)) fail(`Komponenten-Vertrag fehlt: ${marker}`);
|
|
25
24
|
if ((component.match(/NexoWatt EMS · Live-Diagnose/g) || []).length !== 1) fail('EMS-Überschrift ist doppelt oder fehlt.');
|
|
26
25
|
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)) {
|