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
package/build/main.js
CHANGED
|
@@ -20,7 +20,6 @@ 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");
|
|
24
23
|
const checkLinuxPass_1 = require("./lib/checkLinuxPass");
|
|
25
24
|
const plugin_docker_1 = require("@iobroker/plugin-docker");
|
|
26
25
|
const objectFixes_1 = require("./lib/objectFixes");
|
|
@@ -197,43 +196,27 @@ async function ensureEosStableInitialAccounts(adapter) {
|
|
|
197
196
|
let object = await adapter.getForeignObjectAsync(id);
|
|
198
197
|
let created = false;
|
|
199
198
|
if (!object) {
|
|
200
|
-
await adapter.setForeignObjectAsync(id, {
|
|
201
|
-
type: 'user',
|
|
202
|
-
common: { name: account.user, enabled: true, password: '' },
|
|
203
|
-
native: {},
|
|
204
|
-
}, { user: 'system.user.admin' });
|
|
199
|
+
await adapter.setForeignObjectAsync(id, { type: 'user', common: { name: account.user, enabled: true, password: '' }, native: {} }, { user: 'system.user.admin' });
|
|
205
200
|
object = await adapter.getForeignObjectAsync(id);
|
|
206
201
|
created = true;
|
|
207
202
|
}
|
|
208
|
-
if (!object) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const eosNative = object.native || {};
|
|
213
|
-
const resetRequested = eosNative.nexowattResetRequested === true || eosNative.eosResetRequested === true;
|
|
214
|
-
const passwordMissing = !object.common?.password;
|
|
215
|
-
const pendingFirstLogin = eosNative.nexowattPasswordChangeRequired === true || eosNative.eosPasswordChangeRequired === true || eosNative.nexowattFirstLoginPending === true || eosNative.eosFirstLoginRequired === true;
|
|
216
|
-
const stableCredentialMissing = eosNative.nexowattStableInitialCredentialVersion !== 1;
|
|
217
|
-
if (created || passwordMissing || resetRequested || (pendingFirstLogin && stableCredentialMissing)) {
|
|
218
|
-
await (0, eosPassword_1.setEosUserPasswordWithVerification)(adapter, id, EOS_STABLE_INITIAL_PASSWORD, 'system.user.admin');
|
|
219
|
-
await adapter.extendForeignObjectAsync(id, {
|
|
220
|
-
native: {
|
|
221
|
-
...eosNative,
|
|
222
|
-
nexowattManaged: true,
|
|
223
|
-
nexowattRole: account.role,
|
|
224
|
-
nexowattPasswordChangeRequired: true,
|
|
225
|
-
eosPasswordChangeRequired: true,
|
|
226
|
-
nexowattFirstLoginPending: true,
|
|
227
|
-
eosFirstLoginRequired: true,
|
|
228
|
-
nexowattInitialPasswordApplied: true,
|
|
229
|
-
nexowattInitialPasswordVersion: 1,
|
|
230
|
-
nexowattStableInitialCredentialVersion: 1,
|
|
231
|
-
nexowattResetRequested: false,
|
|
232
|
-
eosResetRequested: false,
|
|
233
|
-
},
|
|
234
|
-
}, { user: 'system.user.admin' });
|
|
235
|
-
adapter.log.info(`Prepared EOS ${account.role} account ${account.user} for mandatory first password change`);
|
|
203
|
+
if (!object) { adapter.log.error(`Cannot prepare EOS account ${account.user}`); continue; }
|
|
204
|
+
if (created || !String(object.common?.password || '').trim()) {
|
|
205
|
+
await adapter.setPasswordAsync(account.user, EOS_STABLE_INITIAL_PASSWORD, { user: 'system.user.admin' });
|
|
206
|
+
object = await adapter.getForeignObjectAsync(id);
|
|
236
207
|
}
|
|
208
|
+
if (!object) continue;
|
|
209
|
+
const previous = object.native || {};
|
|
210
|
+
const previousAccount = previous.nexowattEosAccount || {};
|
|
211
|
+
await adapter.extendForeignObjectAsync(id, { native: {
|
|
212
|
+
...previous, nexowattManaged: true, nexowattRole: account.role,
|
|
213
|
+
nexowattPasswordChangeRequired: false, eosPasswordChangeRequired: false,
|
|
214
|
+
nexowattFirstLoginPending: false, eosFirstLoginRequired: false,
|
|
215
|
+
nexowattResetRequested: false, eosResetRequested: false,
|
|
216
|
+
nexowattEosAccount: { ...previousAccount, passwordInitialized: true, passwordSetupVersion: 1,
|
|
217
|
+
passwordInitializationVersion: 1, forcePasswordChange: false,
|
|
218
|
+
passwordlessFirstLoginAllowed: false, standardPasswordMode: true },
|
|
219
|
+
} }, { user: 'system.user.admin' });
|
|
237
220
|
await ensureEosStableGroupMembership(adapter, account.user, account.role);
|
|
238
221
|
}
|
|
239
222
|
}
|
|
@@ -964,7 +947,7 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
964
947
|
object: { ...full },
|
|
965
948
|
state: { ...full },
|
|
966
949
|
file: { ...full },
|
|
967
|
-
users: {
|
|
950
|
+
users: { list: true, read: true, write: false, create: false, delete: false },
|
|
968
951
|
other: { execute: false, http: true, sendto: true },
|
|
969
952
|
};
|
|
970
953
|
}
|
|
@@ -972,7 +955,7 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
972
955
|
object: { list: true, read: true, write: true, create: true, delete: true },
|
|
973
956
|
state: { list: true, read: true, write: true, create: false, delete: false },
|
|
974
957
|
file: { list: true, read: true, write: false, create: false, delete: false },
|
|
975
|
-
users: {
|
|
958
|
+
users: { list: true, read: true, write: false, create: false, delete: false },
|
|
976
959
|
other: { execute: false, http: true, sendto: true },
|
|
977
960
|
};
|
|
978
961
|
}
|
|
@@ -1119,10 +1102,10 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1119
1102
|
},
|
|
1120
1103
|
native: {
|
|
1121
1104
|
nexowattEosAccount: {
|
|
1122
|
-
passwordInitialized:
|
|
1123
|
-
passwordSetupVersion:
|
|
1124
|
-
forcePasswordChange:
|
|
1125
|
-
passwordlessFirstLoginAllowed:
|
|
1105
|
+
passwordInitialized: true,
|
|
1106
|
+
passwordSetupVersion: 1,
|
|
1107
|
+
forcePasswordChange: false,
|
|
1108
|
+
passwordlessFirstLoginAllowed: false,
|
|
1126
1109
|
provisionedBy: 'NexoWatt EOS',
|
|
1127
1110
|
provisionedAt: new Date().toISOString(),
|
|
1128
1111
|
},
|
|
@@ -1153,7 +1136,7 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1153
1136
|
}
|
|
1154
1137
|
if (String(user.common.password || '') === '') {
|
|
1155
1138
|
const bootstrapSecret = `${(0, node_crypto_1.randomBytes)(48).toString('base64url')}!aA1`;
|
|
1156
|
-
await (
|
|
1139
|
+
await this.setPasswordAsync(userId.replace(/^system\.user\./, ''), bootstrapSecret, { user: ADMIN_OWNER_USER });
|
|
1157
1140
|
user = (await this.getForeignObjectAsync(userId));
|
|
1158
1141
|
if (!user) {
|
|
1159
1142
|
return;
|
|
@@ -1198,11 +1181,9 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1198
1181
|
const endUsers = ['system.user.guest', 'system.user.user', 'system.user.endkunde', 'system.user.kunde'];
|
|
1199
1182
|
for (const userId of installerUsers) {
|
|
1200
1183
|
await this.addUserToEosGroup(userId, installerGroup);
|
|
1201
|
-
await this.prepareEosPasswordlessFirstLogin(userId);
|
|
1202
1184
|
}
|
|
1203
1185
|
for (const userId of endUsers) {
|
|
1204
1186
|
await this.addUserToEosGroup(userId, endUserGroup);
|
|
1205
|
-
await this.prepareEosPasswordlessFirstLogin(userId);
|
|
1206
1187
|
}
|
|
1207
1188
|
}
|
|
1208
1189
|
async refreshEosRoleCache() {
|
|
@@ -1211,9 +1192,7 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1211
1192
|
const priorities = { enduser: 1, installer: 2, admin: 3 };
|
|
1212
1193
|
const assign = (userId, role) => {
|
|
1213
1194
|
const current = next.get(userId);
|
|
1214
|
-
if (!current || priorities[role] > priorities[current])
|
|
1215
|
-
next.set(userId, role);
|
|
1216
|
-
}
|
|
1195
|
+
if (!current || priorities[role] > priorities[current]) next.set(userId, role);
|
|
1217
1196
|
};
|
|
1218
1197
|
for (const [roleName, groupIds] of [
|
|
1219
1198
|
['admin', this.getEosRoleGroupIds('service')],
|
|
@@ -1222,29 +1201,15 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1222
1201
|
['admin', ['system.group.administrator']],
|
|
1223
1202
|
]) {
|
|
1224
1203
|
for (const groupId of groupIds) {
|
|
1225
|
-
const group =
|
|
1226
|
-
for (const member of group?.common?.members || [])
|
|
1227
|
-
assign(member, roleName);
|
|
1228
|
-
}
|
|
1204
|
+
const group = await this.getForeignObjectAsync(groupId);
|
|
1205
|
+
for (const member of group?.common?.members || []) assign(member, roleName);
|
|
1229
1206
|
}
|
|
1230
1207
|
}
|
|
1231
1208
|
this.eosRoleCache.clear();
|
|
1232
1209
|
this.eosPasswordSetupRequired.clear();
|
|
1233
|
-
for (const [userId, role] of next.entries())
|
|
1234
|
-
this.eosRoleCache.set(userId, role);
|
|
1235
|
-
if (role === 'admin') {
|
|
1236
|
-
continue;
|
|
1237
|
-
}
|
|
1238
|
-
const user = (await this.getForeignObjectAsync(userId));
|
|
1239
|
-
const native = (user?.native || {});
|
|
1240
|
-
const account = (native.nexowattEosAccount || {});
|
|
1241
|
-
const initialized = account.passwordInitialized === true
|
|
1242
|
-
|| Number(account.passwordSetupVersion || account.passwordInitializationVersion || 0) >= 1;
|
|
1243
|
-
if (account.forcePasswordChange === true || !initialized) {
|
|
1244
|
-
this.eosPasswordSetupRequired.add(userId);
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1210
|
+
for (const [userId, role] of next.entries()) this.eosRoleCache.set(userId, role);
|
|
1247
1211
|
}
|
|
1212
|
+
|
|
1248
1213
|
getEosCachedRole(userId) {
|
|
1249
1214
|
if (userId === 'system.user.admin') {
|
|
1250
1215
|
return 'admin';
|
|
@@ -1279,9 +1244,6 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1279
1244
|
if (role === 'admin') {
|
|
1280
1245
|
return original(socketClient, command, callback, ...args);
|
|
1281
1246
|
}
|
|
1282
|
-
if (userId && this.eosPasswordSetupRequired.has(userId)) {
|
|
1283
|
-
return deny(socketClient, command, callback, 'personal password setup is required');
|
|
1284
|
-
}
|
|
1285
1247
|
if (command === 'cmdExec') {
|
|
1286
1248
|
if (role !== 'installer') {
|
|
1287
1249
|
return deny(socketClient, command, callback, 'raw command execution is Service-only');
|
|
@@ -1304,6 +1266,16 @@ class Admin extends adapter_core_1.Adapter {
|
|
|
1304
1266
|
if (role === 'enduser' && ['readLogs', 'sendToHost'].includes(command)) {
|
|
1305
1267
|
return deny(socketClient, command, callback, 'technical diagnostics are installer/service-only');
|
|
1306
1268
|
}
|
|
1269
|
+
if (command === 'changePassword') {
|
|
1270
|
+
const targetRaw = String(args[0] || '').trim();
|
|
1271
|
+
const targetUser = targetRaw.startsWith('system.user.') ? targetRaw : `system.user.${targetRaw}`;
|
|
1272
|
+
const selfUser = String(userId || '');
|
|
1273
|
+
const allowed = role === 'installer'
|
|
1274
|
+
? new Set([selfUser, 'system.user.installer', 'system.user.user', 'system.user.guest']).has(targetUser)
|
|
1275
|
+
: targetUser === selfUser;
|
|
1276
|
+
if (!allowed) return deny(socketClient, command, callback, 'password target outside EOS role scope');
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1307
1279
|
if (['addUser', 'delUser', 'addGroup', 'delGroup'].includes(command)) {
|
|
1308
1280
|
return deny(socketClient, command, callback, 'account administration is Service-only');
|
|
1309
1281
|
}
|
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "eos-admin",
|
|
4
|
-
"version": "7.10.
|
|
4
|
+
"version": "7.10.2",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "NexoWatt EOS Admin",
|
|
7
7
|
"de": "NexoWatt EOS Admin",
|
|
@@ -18,6 +18,21 @@
|
|
|
18
18
|
"connectionType": "local",
|
|
19
19
|
"dataSource": "push",
|
|
20
20
|
"news": {
|
|
21
|
+
"7.10.2": {
|
|
22
|
+
"de": "Stable-Hotfix 7.10.2 beseitigt die EOS-Admin-Startschleife: Stable-Update-Status wird nicht mehr in die laufende Instanzkonfiguration geschrieben, der erste Abgleich startet verzögert und das Release prüft die vollständige Backend-Modulkette einschließlich eosRequestSecurity.",
|
|
23
|
+
"en": "Stable hotfix 7.10.2 removes the EOS Admin restart loop: stable-update status is no longer written to the running instance configuration, first reconciliation is delayed, and the release validates the complete backend module chain including eosRequestSecurity.",
|
|
24
|
+
"nl": "Stable-hotfix 7.10.2 verhelpt de EOS Admin-herstartlus: de status van stabiele updates wordt niet meer naar de configuratie van de actieve instantie geschreven, de eerste synchronisatie start vertraagd en de release controleert de volledige backendmoduleketen inclusief eosRequestSecurity."
|
|
25
|
+
},
|
|
26
|
+
"7.10.1": {
|
|
27
|
+
"de": "Stable-Kandidat 7.10.1 basiert wieder auf 7.9.98: native Passwortverwaltung ohne erzwungene Erstpasswortseite, dauerhafter Cockpit-Scrollbereich, Stift im unteren Kartenbereich und abschaltbare NexoWatt-Stable-Updates.",
|
|
28
|
+
"en": "Stable candidate 7.10.1 returns to the 7.9.98 baseline: native password management without forced first-password page, persistent cockpit scrolling, in-grid edit control and optional NexoWatt stable updates.",
|
|
29
|
+
"nl": "Stable-kandidaat 7.10.1 keert terug naar de 7.9.98-basis: native wachtwoordbeheer zonder verplichte eerste-wachtwoordpagina, blijvend cockpit-scrollen, bewerken in het onderste kaartgebied en uitschakelbare NexoWatt-stabiele updates."
|
|
30
|
+
},
|
|
31
|
+
"7.9.98": {
|
|
32
|
+
"de": "Stable-Kandidat 7.9.98 ergänzt den vertikalen Cockpit-Scrollbereich, bereinigt die Login-Felder und korrigiert Passwort-Reset sowie persönliche Erstpasswortvergabe über die echte ioBroker-Passwort-API.",
|
|
33
|
+
"en": "Stable candidate 7.9.98 adds vertical cockpit scrolling, cleans up login fields and fixes password reset plus first personal password setup through the real ioBroker password API.",
|
|
34
|
+
"nl": "Stable-kandidaat 7.9.98 voegt verticaal scrollen in het cockpit toe, herstelt de loginvelden en corrigeert wachtwoordreset en het eerste persoonlijke wachtwoord via de echte ioBroker-wachtwoord-API."
|
|
35
|
+
},
|
|
21
36
|
"7.9.95": {
|
|
22
37
|
"de": "Stable 7.9.95 macht den Release-Ordner mergesicher: Die flache Update-ZIP kann direkt über einen bestehenden Arbeitsordner entpackt werden. Vor Paketprüfung und npm-Veröffentlichung werden package.json, io-package.json, beide Lockdateien, die Frontend-Version, Build-Informationen und der aktuelle Repository-Eintrag automatisch auf dieselbe Version gebracht. Dadurch wird insbesondere ein stehen gebliebenes io-package.json aus 7.9.92/7.9.94 automatisch repariert. Die Funktionen aus 7.9.94 bleiben unverändert.",
|
|
23
38
|
"en": "Stable 7.9.95 makes the release folder merge-safe: the flat update ZIP can be extracted directly over an existing working directory. Before package validation and npm publishing, package.json, io-package.json, both lockfiles, the frontend version, build information and the current repository entry are synchronized automatically. In particular, a stale io-package.json from 7.9.92/7.9.94 is repaired automatically. Functional behavior from 7.9.94 remains unchanged.",
|
|
@@ -329,7 +344,7 @@
|
|
|
329
344
|
"icon": "admin.svg",
|
|
330
345
|
"messagebox": true,
|
|
331
346
|
"enabled": true,
|
|
332
|
-
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.
|
|
347
|
+
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.2/admin/admin.svg",
|
|
333
348
|
"keywords": [
|
|
334
349
|
"NexoWatt",
|
|
335
350
|
"EOS",
|
|
@@ -340,7 +355,7 @@
|
|
|
340
355
|
"licensed"
|
|
341
356
|
],
|
|
342
357
|
"compact": true,
|
|
343
|
-
"readme": "https://unpkg.com/iobroker.eos-admin@7.10.
|
|
358
|
+
"readme": "https://unpkg.com/iobroker.eos-admin@7.10.2/README.md",
|
|
344
359
|
"authors": [
|
|
345
360
|
"NexoWatt"
|
|
346
361
|
],
|
|
@@ -408,7 +423,7 @@
|
|
|
408
423
|
"nondeletable": false,
|
|
409
424
|
"allowAdapterUpdate": true,
|
|
410
425
|
"allowAdapterDelete": false,
|
|
411
|
-
"meta": "https://unpkg.com/iobroker.eos-admin@7.10.
|
|
426
|
+
"meta": "https://unpkg.com/iobroker.eos-admin@7.10.2/io-package.json",
|
|
412
427
|
"npmPackage": "iobroker.eos-admin",
|
|
413
428
|
"packetName": "iobroker.eos-admin",
|
|
414
429
|
"publisher": "NexoWatt"
|
|
@@ -538,7 +553,7 @@
|
|
|
538
553
|
"enabled": true
|
|
539
554
|
}
|
|
540
555
|
],
|
|
541
|
-
"eosRequireFirstLoginPassword":
|
|
556
|
+
"eosRequireFirstLoginPassword": false,
|
|
542
557
|
"eosFirstLoginPasswordMinLength": 10,
|
|
543
558
|
"eosAutoAssignDefaultRoleUsers": true,
|
|
544
559
|
"eosPasswordlessFirstLogin": false,
|
|
@@ -547,7 +562,7 @@
|
|
|
547
562
|
"eosHideLegacyBackupFromNonAdmins": true,
|
|
548
563
|
"nexowattHideLegacyBackupFromNonAdmins": true,
|
|
549
564
|
"eosAllowPasswordlessFirstLogin": false,
|
|
550
|
-
"eosRequireFirstLoginPasswordChange":
|
|
565
|
+
"eosRequireFirstLoginPasswordChange": false,
|
|
551
566
|
"eosAutoAssignStandardAccounts": true,
|
|
552
567
|
"eosAssistEnabled": false,
|
|
553
568
|
"eosNexoWattAutoUpdate": true
|
|
@@ -838,6 +853,56 @@
|
|
|
838
853
|
},
|
|
839
854
|
"native": {}
|
|
840
855
|
},
|
|
856
|
+
{
|
|
857
|
+
"_id": "info.nexowattStableUpdatesEnabled",
|
|
858
|
+
"type": "state",
|
|
859
|
+
"common": {
|
|
860
|
+
"name": {
|
|
861
|
+
"en": "NexoWatt stable automatic updates enabled",
|
|
862
|
+
"de": "Automatische NexoWatt-Stable-Updates aktiviert",
|
|
863
|
+
"ru": "Автоматические стабильные обновления NexoWatt включены",
|
|
864
|
+
"pt": "Atualizações estáveis automáticas da NexoWatt ativadas",
|
|
865
|
+
"nl": "Automatische stabiele NexoWatt-updates ingeschakeld",
|
|
866
|
+
"fr": "Mises à jour stables automatiques NexoWatt activées",
|
|
867
|
+
"it": "Aggiornamenti stabili automatici NexoWatt abilitati",
|
|
868
|
+
"es": "Actualizaciones estables automáticas de NexoWatt activadas",
|
|
869
|
+
"pl": "Automatyczne stabilne aktualizacje NexoWatt włączone",
|
|
870
|
+
"uk": "Автоматичні стабільні оновлення NexoWatt увімкнено",
|
|
871
|
+
"zh-cn": "已启用 NexoWatt 稳定版自动更新"
|
|
872
|
+
},
|
|
873
|
+
"role": "switch.enable",
|
|
874
|
+
"type": "boolean",
|
|
875
|
+
"read": true,
|
|
876
|
+
"write": false,
|
|
877
|
+
"def": true
|
|
878
|
+
},
|
|
879
|
+
"native": {}
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
"_id": "info.nexowattStableUpdatesState",
|
|
883
|
+
"type": "state",
|
|
884
|
+
"common": {
|
|
885
|
+
"name": {
|
|
886
|
+
"en": "NexoWatt stable automatic update runtime state",
|
|
887
|
+
"de": "Laufzeitstatus der automatischen NexoWatt-Stable-Updates",
|
|
888
|
+
"ru": "Состояние автоматических стабильных обновлений NexoWatt",
|
|
889
|
+
"pt": "Estado das atualizações estáveis automáticas da NexoWatt",
|
|
890
|
+
"nl": "Runtime-status van automatische stabiele NexoWatt-updates",
|
|
891
|
+
"fr": "État des mises à jour stables automatiques NexoWatt",
|
|
892
|
+
"it": "Stato degli aggiornamenti stabili automatici NexoWatt",
|
|
893
|
+
"es": "Estado de las actualizaciones estables automáticas de NexoWatt",
|
|
894
|
+
"pl": "Stan automatycznych stabilnych aktualizacji NexoWatt",
|
|
895
|
+
"uk": "Стан автоматичних стабільних оновлень NexoWatt",
|
|
896
|
+
"zh-cn": "NexoWatt 稳定版自动更新运行状态"
|
|
897
|
+
},
|
|
898
|
+
"role": "json",
|
|
899
|
+
"type": "json",
|
|
900
|
+
"read": true,
|
|
901
|
+
"write": false,
|
|
902
|
+
"def": "{}"
|
|
903
|
+
},
|
|
904
|
+
"native": {}
|
|
905
|
+
},
|
|
841
906
|
{
|
|
842
907
|
"_id": "info.newsFeed",
|
|
843
908
|
"type": "state",
|
|
@@ -1118,5 +1183,5 @@
|
|
|
1118
1183
|
}
|
|
1119
1184
|
}
|
|
1120
1185
|
},
|
|
1121
|
-
"version": "7.10.
|
|
1186
|
+
"version": "7.10.2"
|
|
1122
1187
|
}
|
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.10.
|
|
3
|
+
"description": "NexoWatt EOS Admin – repaired stable baseline with restart-safe automatic updates, native account passwords and persistent cockpit scrolling.",
|
|
4
|
+
"version": "7.10.2",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"bluefox <dogafox@gmail.com>",
|
|
7
7
|
"apollon77",
|
|
@@ -118,27 +118,27 @@
|
|
|
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.10.0.md",
|
|
122
|
-
"RELEASE_NOTES_V7.10.0.md",
|
|
123
|
-
"PUBLISH_STABLE_V7.10.0.md",
|
|
124
|
-
"INSTALL_TEST_V7.10.0.md",
|
|
125
|
-
"RELEASE_ACCEPTANCE_V7.10.0.md",
|
|
126
|
-
"MERGE_UPDATE_README_V7.10.0.md",
|
|
127
121
|
"README_EMS_OVERVIEW_MERGE.md",
|
|
128
122
|
"RC73_EOS_ADMIN_MERGE_VALIDATION.md",
|
|
129
123
|
"VALIDATION_EMS_OVERVIEW_MERGE.md",
|
|
130
124
|
"NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md",
|
|
131
125
|
"tools/nexowatt-apply-ems-overview.cjs",
|
|
132
126
|
"tools/nexowatt-ui-overview-runtime-selftest.cjs",
|
|
133
|
-
"tools/nexowatt-stable-v7100-selftest.cjs",
|
|
134
127
|
"tools/nexowatt-password-write-selftest.cjs",
|
|
135
|
-
"tools/nexowatt-
|
|
136
|
-
"tools/nexowatt-
|
|
137
|
-
"src/lib/eosAutoUpdate.ts",
|
|
138
|
-
"build/lib/eosAutoUpdate.js",
|
|
139
|
-
"adminWww/js/eos-auto-update.js",
|
|
128
|
+
"tools/nexowatt-account-password-flow-selftest.cjs",
|
|
129
|
+
"tools/nexowatt-auto-update-selftest.cjs",
|
|
140
130
|
"adminWww/css/eos-auto-update.css",
|
|
141
|
-
"
|
|
131
|
+
"adminWww/js/eos-auto-update.js",
|
|
132
|
+
"tools/nexowatt-backend-runtime-selftest.cjs",
|
|
133
|
+
"NEXOWATT_EOS_PREBUILT_MANIFEST.json",
|
|
134
|
+
"tools/nexowatt-prebuilt-release-selftest.cjs",
|
|
135
|
+
"tools/nexowatt-stable-v7102-selftest.cjs",
|
|
136
|
+
"README_STABILITY_V7.10.2.md",
|
|
137
|
+
"RELEASE_NOTES_V7.10.2.md",
|
|
138
|
+
"PUBLISH_STABLE_V7.10.2.md",
|
|
139
|
+
"INSTALL_TEST_V7.10.2.md",
|
|
140
|
+
"RELEASE_ACCEPTANCE_V7.10.2.md",
|
|
141
|
+
"MERGE_UPDATE_README_V7.10.2.md"
|
|
142
142
|
],
|
|
143
143
|
"scripts": {
|
|
144
144
|
"npm": "npm i -f && cd src-admin && npm i -f",
|
|
@@ -149,8 +149,8 @@
|
|
|
149
149
|
"test:integration": "mocha test/integration --exit && npm run test:gui && npm run test:rule && npm run test:unit",
|
|
150
150
|
"test:rule": "mocha test/rule --exit",
|
|
151
151
|
"test:gui": "mocha test/*.gui.js --exit",
|
|
152
|
-
"prepublishOnly": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run check:eos-publish-channel && npm run check:eos-package && npm run check:eos-stability",
|
|
153
|
-
"build": "npm run build:frontend && npm run build:backend && npm run clean:eos-runtime",
|
|
152
|
+
"prepublishOnly": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime && npm run check:eos-publish-channel && npm run check:eos-package && npm run check:eos-stability",
|
|
153
|
+
"build": "npm run build:frontend && npm run build:backend && npm run clean:eos-runtime && npm run seal:eos-prebuilt-release",
|
|
154
154
|
"build:backend": "tsc -p tsconfig.build.json && tsx tasks.mts --backend-i18n",
|
|
155
155
|
"build:frontend": "tsx tasks.mts",
|
|
156
156
|
"clean": "rimraf src-admin/build",
|
|
@@ -173,12 +173,12 @@
|
|
|
173
173
|
"nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
|
|
174
174
|
"nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
|
|
175
175
|
"nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
|
|
176
|
-
"check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-
|
|
176
|
+
"check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-backend-runtime-selftest.cjs && node tools/nexowatt-prebuilt-release-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-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 && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-account-password-flow-selftest.cjs && npm run check:eos-stable-v7102 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs && node tools/nexowatt-auto-update-selftest.cjs",
|
|
177
177
|
"nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
|
|
178
178
|
"clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
|
|
179
179
|
"precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
|
|
180
180
|
"precheck:eos-stability": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
|
|
181
|
-
"prepack": "node tools/nexowatt-sync-release-version.cjs --quiet && node tools/nexowatt-clean-legacy-runtime.cjs --quiet",
|
|
181
|
+
"prepack": "node tools/nexowatt-sync-release-version.cjs --quiet && node tools/nexowatt-ensure-release-defaults.cjs && node tools/nexowatt-clean-legacy-runtime.cjs --quiet && node tools/nexowatt-prebuilt-release-selftest.cjs",
|
|
182
182
|
"check:eos-esm": "node tools/nexowatt-esm-syntax-selftest.cjs",
|
|
183
183
|
"check:eos-imports": "node tools/nexowatt-import-integrity-selftest.cjs",
|
|
184
184
|
"check:eos-release": "npm run check:eos-package && npm run check:eos-stability && npm pack --dry-run",
|
|
@@ -197,11 +197,10 @@
|
|
|
197
197
|
"sync:eos-version": "node tools/nexowatt-sync-release-version.cjs",
|
|
198
198
|
"test:eos-version-sync": "node tools/nexowatt-version-sync-selftest.cjs",
|
|
199
199
|
"verify:eos-merge": "node tools/nexowatt-merge-update-selftest.cjs",
|
|
200
|
-
"
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"check:eos-stable-v7100": "node tools/nexowatt-stable-v7100-selftest.cjs"
|
|
200
|
+
"check:eos-backend-runtime": "node tools/nexowatt-backend-runtime-selftest.cjs",
|
|
201
|
+
"check:eos-stable-v7102": "node tools/nexowatt-stable-v7102-selftest.cjs",
|
|
202
|
+
"check:eos-prebuilt-release": "node tools/nexowatt-prebuilt-release-selftest.cjs",
|
|
203
|
+
"seal:eos-prebuilt-release": "node tools/nexowatt-prebuilt-release-selftest.cjs --write"
|
|
205
204
|
},
|
|
206
205
|
"author": "NexoWatt",
|
|
207
206
|
"private": false,
|
|
@@ -212,7 +211,7 @@
|
|
|
212
211
|
},
|
|
213
212
|
"nexowattReleasePolicy": {
|
|
214
213
|
"distTag": "latest",
|
|
215
|
-
"purpose": "accepted stable NexoWatt EOS sales-product
|
|
214
|
+
"purpose": "accepted stable NexoWatt EOS sales-product candidate 7.10.2"
|
|
216
215
|
},
|
|
217
216
|
"publisher": "NexoWatt"
|
|
218
217
|
}
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const fs=require('
|
|
4
|
-
const
|
|
5
|
-
const mainSource
|
|
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}`);
|
|
3
|
+
const fs=require('fs');const path=require('path');const root=path.resolve(__dirname,'..');const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const json=rel=>JSON.parse(read(rel));let bad=false;const fail=msg=>{console.error(`[NexoWatt EOS account management] ${msg}`);bad=true;};
|
|
4
|
+
const mainSource=read('src/main.ts'),mainBuilt=read('build/main.js'),webSource=read('src/lib/web.ts'),webBuilt=read('build/lib/web.js'),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);
|
|
5
|
+
for(const code of [mainSource,mainBuilt])for(const marker of ['system.user.installer','system.user.guest','ensureEosDefaultRoleUsers','unknown random bootstrap secret','eosPasswordSetupRequired','personal password setup is required'])if(!code.includes(marker))fail(`main marker missing: ${marker}`);
|
|
8
6
|
for(const code of [webSource,webBuilt]){
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
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',"access.role === 'installer' && explicitlyManagedRole !== 'enduser'",'passwordResetAt','passwordResetBy',"passwordResetMode = 'initial-password'","setEosUserPassword(targetUserId, 'nexowatt')",'passwordlessFirstLoginAllowed = false','initialPasswordRequired: true','accountDisabled','getEosPasswordUserName','setPasswordAsync(userName, password','checkPasswordAsync(userName, password','updateEosAccountMetadata','extendForeignObjectAsync','invalidRequestOrigin'])if(!code.includes(marker))fail(`web marker missing: ${marker}`);
|
|
8
|
+
if(!code.includes('const explicitInstaller = targetAccess.groups.some')||!code.includes('const explicitEndUser = targetAccess.groups.some'))fail('explicit managed-group guard missing');
|
|
9
|
+
const reset=code.slice(code.indexOf('resetEosAccountPassword'),code.indexOf('getEosHistoryInstances'));
|
|
10
|
+
if(reset.includes('setForeignObjectAsync(targetUserId'))fail('reset replaces the complete user object after password write');
|
|
13
11
|
}
|
|
14
|
-
for(const marker of ['
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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)');
|
|
12
|
+
for(const marker of ['v98-account-management-real-password-write',"['admin', 'installer']",'nexowatt/account/manage','nexowatt/account/reset','X-NexoWatt-EOS-Account-Reset','data.canResetInstaller === true',"account.role === 'enduser'",'Startpasswort „nexowatt“','accountErrorText','invalidRequestOrigin','credentials: \'include\'','NEXOWATT_EOS_ACCOUNT_MANAGEMENT','ensureEntrySurface',"currentRoute() !== 'tab-users'",'eos-account-management-entry'])if(!sourceUi.includes(marker))fail(`account UI marker missing: ${marker}`);
|
|
13
|
+
if(sourceUi.includes("launcher.className = 'eos-account-management-launcher'"))fail('standalone account launcher remains');
|
|
14
|
+
if(sourceUi.includes('new MutationObserver'))fail('account management adds a second broad MutationObserver');
|
|
15
|
+
if(sourceUi!==builtUi)fail('account UI source/build drift');
|
|
16
|
+
if(!roleUi.includes("route === 'tab-users'"))fail('installer cannot reach access page');
|
|
17
|
+
if(!index.includes(`eos-account-management.js?v=${shellTag}`))fail('account asset cache tag mismatch');
|
|
18
|
+
for(const marker of ['.eos-account-management-entry','.eos-account-management-overlay','.eos-account-management-dialog','.eos-account-row','.eos-account-reset'])if(!css.includes(marker))fail(`account CSS missing: ${marker}`);
|
|
19
|
+
if(bad)process.exit(1);console.log('[NexoWatt EOS account management] OK');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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 standard password flow] '+m);process.exit(1)};const must=(v,m)=>{if(!v)fail(m)};
|
|
4
|
+
const source=read('src/main.ts'),built=read('build/main.js'),boot=read('adminWww/js/eos-role-bootstrap.js'),role=read('adminWww/js/eos-role-ui.js'),index=read('adminWww/index.html'),io=JSON.parse(read('io-package.json'));
|
|
5
|
+
for(const code of [source,built]){
|
|
6
|
+
must(code.includes("EOS_STABLE_INITIAL_PASSWORD = 'nexowatt'"),'initial password constant missing');
|
|
7
|
+
must(code.includes("if (created || !String(object.common?.password || '').trim())")||code.includes("if (created || !String(object.common?.password || '').trim())"),'password is not limited to new/passwordless accounts');
|
|
8
|
+
for(const marker of ['nexowattPasswordChangeRequired: false','eosPasswordChangeRequired: false','nexowattFirstLoginPending: false','eosFirstLoginRequired: false','forcePasswordChange: false','standardPasswordMode: true']) must(code.includes(marker),`legacy first-login flag is not cleared: ${marker}`);
|
|
9
|
+
must(code.includes("command === 'changePassword'")&&code.includes('password target outside EOS role scope'),'native password command role guard missing');
|
|
10
|
+
}
|
|
11
|
+
must(io.native.eosRequireFirstLoginPassword===false&&io.native.eosRequireFirstLoginPasswordChange===false,'forced first-password defaults remain');
|
|
12
|
+
must(!boot.includes('installIntegratedFirstLogin(base);')&&!boot.includes('showFirstLoginPassword(resolved, base);'),'forced password surface remains active');
|
|
13
|
+
must(!index.includes('eos-account-management.js'),'custom reset runtime remains active');
|
|
14
|
+
must(role.includes('filterNativeUsersPage')&&role.includes("route === 'tab-users'"),'role-scoped native Users page missing');
|
|
15
|
+
console.log('[NexoWatt EOS standard password flow] OK (nexowatt only on creation, native password editor, no forced reset flow)');
|
|
@@ -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:space-between; 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,7 +83,9 @@ 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:
|
|
86
|
+
.eos-ems-overview-footer { display:flex; justify-content:space-between; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
|
|
87
|
+
.eos-ems-overview-footer a { color:#5ee0c2; font-weight:800; text-decoration:none; }
|
|
88
|
+
.eos-ems-overview-footer a:hover,.eos-ems-overview-footer a:focus-visible { color:#9af2dd; text-decoration:underline; }
|
|
87
89
|
@media (max-width: 1100px) {
|
|
88
90
|
.eos-ems-overview-card { flex-basis:100%; max-width:none; }
|
|
89
91
|
}
|