iobroker.eos-admin 7.9.95 → 7.9.96
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.96.md +11 -0
- package/MERGE_UPDATE_README_V7.9.96.md +5 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +18 -23
- package/NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md +44 -0
- package/PUBLISH_STABLE_V7.9.96.md +10 -0
- package/RC73_EOS_ADMIN_MERGE_VALIDATION.md +78 -0
- package/README_EMS_OVERVIEW_MERGE.md +45 -0
- package/README_STABILITY_V7.9.96.md +11 -0
- package/RELEASE_ACCEPTANCE_V7.9.96.md +10 -0
- package/RELEASE_NOTES_V7.9.96.md +12 -0
- package/VALIDATION_EMS_OVERVIEW_MERGE.md +35 -0
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
- package/adminWww/assets/index-D2ymscJA-v84.js +1 -1
- package/adminWww/css/eos-ems-overview.css +62 -0
- package/adminWww/css/nexowatt-native-shell.css +111 -13
- package/adminWww/index.html +15 -13
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-ems-overview.js +140 -0
- package/adminWww/js/eos-role-bootstrap.js +4 -4
- package/adminWww/js/eos-role-ui.js +34 -38
- package/adminWww/js/nexowatt-native-shell.js +11 -6
- package/adminWww/static/js/nexowatt-stable-login.js +12 -12
- package/adminWww/static/js/nexowatt-stable-v96.js +82 -0
- package/io-package.json +10 -5
- package/package.json +38 -12
- package/tools/nexowatt-account-management-selftest.cjs +60 -0
- package/tools/nexowatt-apply-ems-overview.cjs +115 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +151 -0
- package/tools/nexowatt-ems-overview-selftest.cjs +35 -0
- package/tools/nexowatt-ensure-release-defaults.cjs +72 -0
- package/tools/nexowatt-generate-eos-admin-repo-entry.cjs +37 -0
- package/tools/nexowatt-generate-repo-entry.cjs +3 -0
- package/tools/nexowatt-internal-reserve-selftest.cjs +55 -0
- package/tools/nexowatt-login-layout-selftest.cjs +83 -0
- package/tools/nexowatt-merge-update-selftest.cjs +69 -0
- package/tools/nexowatt-modern-ui-selftest.cjs +49 -0
- package/tools/nexowatt-patch-repo.cjs +54 -0
- package/tools/nexowatt-publish-channel-guard.cjs +58 -0
- package/tools/nexowatt-release-defaults-selftest.cjs +29 -0
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +47 -0
- package/tools/nexowatt-stable-v96-selftest.cjs +13 -0
- package/tools/nexowatt-sync-release-version.cjs +181 -0
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +16 -0
- package/tools/nexowatt-validate-package.cjs +277 -0
- package/tools/nexowatt-version-sync-selftest.cjs +86 -0
- package/INSTALL_TEST_V7.9.95.md +0 -15
- package/NEXOWATT_EOS_STABLE_7.9.94.md +0 -16
- package/PUBLISH_STABLE_V7.9.95.md +0 -16
- package/README_STABILITY_V7.9.95.md +0 -12
- package/RELEASE_ACCEPTANCE_V7.9.95.md +0 -11
- package/RELEASE_NOTES_V7.9.95.md +0 -12
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* nexowatt-ems-overview-v1 – current runtime and future React build */
|
|
2
|
+
.eos-ems-overview-card {
|
|
3
|
+
--eos-ems-status-color: #48b9ff;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
flex: 1 1 720px;
|
|
6
|
+
min-width: min(100%, 420px);
|
|
7
|
+
max-width: 1180px;
|
|
8
|
+
min-height: 390px;
|
|
9
|
+
margin: 10px;
|
|
10
|
+
padding: 22px;
|
|
11
|
+
border: 1px solid color-mix(in srgb, var(--eos-ems-status-color) 38%, rgba(255,255,255,.08));
|
|
12
|
+
border-radius: 20px;
|
|
13
|
+
background: linear-gradient(145deg, rgba(4,24,35,.97), rgba(3,16,29,.98));
|
|
14
|
+
color: #ecf8ff;
|
|
15
|
+
box-shadow: 0 18px 55px rgba(0,0,0,.26), inset 0 0 0 1px rgba(255,255,255,.025);
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
#eos-ems-overview-runtime { width: calc(100% - 32px); max-width: none; margin: 0 16px 20px; }
|
|
19
|
+
.eos-ems-overview-card * { box-sizing: border-box; }
|
|
20
|
+
.eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:space-between; gap:20px; }
|
|
21
|
+
.eos-ems-overview-header h2 { margin:5px 0 6px; font-size:clamp(1.2rem,2vw,1.65rem); color:#f5fbff; }
|
|
22
|
+
.eos-ems-overview-header p { margin:0; max-width:760px; color:#9eb2c2; line-height:1.45; }
|
|
23
|
+
.eos-ems-overview-eyebrow { color:#49e39e; font-size:.72rem; font-weight:900; letter-spacing:.13em; text-transform:uppercase; }
|
|
24
|
+
.eos-ems-overview-state { min-width:126px; display:grid; grid-template-columns:auto 1fr; align-items:center; gap:3px 8px; padding:9px 12px; border:1px solid rgba(255,255,255,.09); border-radius:999px; background:rgba(255,255,255,.035); }
|
|
25
|
+
.eos-ems-overview-state-dot { width:9px; height:9px; border-radius:50%; background:var(--eos-ems-status-color); box-shadow:0 0 15px var(--eos-ems-status-color); }
|
|
26
|
+
.eos-ems-overview-state strong { color:var(--eos-ems-status-color); }
|
|
27
|
+
.eos-ems-overview-state small { grid-column:2; color:#8095a6; }
|
|
28
|
+
.eos-ems-overview-metrics { display:grid; grid-template-columns:repeat(4,minmax(130px,1fr)); gap:10px; margin-top:19px; }
|
|
29
|
+
.eos-ems-overview-metric { min-width:0; padding:12px; border:1px solid rgba(255,255,255,.075); border-radius:14px; background:rgba(255,255,255,.025); }
|
|
30
|
+
.eos-ems-overview-metric span,.eos-ems-overview-metric small { display:block; color:#879cad; font-size:.72rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
31
|
+
.eos-ems-overview-metric strong { display:block; margin:4px 0 2px; color:#f4fbff; font-size:1rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
32
|
+
.eos-ems-overview-budget { height:7px; margin:12px 0 10px; border-radius:999px; background:rgba(255,255,255,.06); overflow:hidden; }
|
|
33
|
+
.eos-ems-overview-budget span { display:block; height:100%; border-radius:inherit; background:linear-gradient(90deg,#01bc69,var(--eos-ems-status-color)); box-shadow:0 0 15px color-mix(in srgb,var(--eos-ems-status-color) 50%,transparent); transition:width .25s ease; }
|
|
34
|
+
.eos-ems-overview-tags { display:flex; flex-wrap:wrap; gap:7px; margin-bottom:14px; }
|
|
35
|
+
.eos-ems-overview-tags span { padding:5px 9px; border:1px solid rgba(72,185,255,.18); border-radius:999px; background:rgba(72,185,255,.07); color:#b9deef; font-size:.72rem; font-weight:750; }
|
|
36
|
+
.eos-ems-overview-tags span.is-warning { border-color:rgba(255,189,89,.3); background:rgba(255,189,89,.09); color:#ffd484; }
|
|
37
|
+
.eos-ems-overview-columns { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
38
|
+
.eos-ems-overview-panel { min-width:0; padding:14px; border:1px solid rgba(255,255,255,.065); border-radius:16px; background:rgba(0,0,0,.12); }
|
|
39
|
+
.eos-ems-overview-panel h3 { margin:0 0 10px; color:#dfeef6; font-size:.9rem; }
|
|
40
|
+
.eos-ems-overview-decision,.eos-ems-overview-event { position:relative; display:grid; gap:2px; margin-top:7px; padding:8px 9px 8px 12px; border-radius:10px; background:rgba(255,255,255,.025); overflow:hidden; }
|
|
41
|
+
.eos-ems-overview-decision::before,.eos-ems-overview-event::before { content:''; position:absolute; inset:0 auto 0 0; width:3px; background:#48b9ff; }
|
|
42
|
+
.eos-ems-overview-decision--warning::before,.eos-ems-overview-event--warning::before { background:#ffbd59; }
|
|
43
|
+
.eos-ems-overview-decision--error::before,.eos-ems-overview-event--error::before { background:#ff5f72; }
|
|
44
|
+
.eos-ems-overview-decision strong,.eos-ems-overview-event strong { color:#e7f4fa; font-size:.78rem; }
|
|
45
|
+
.eos-ems-overview-decision span,.eos-ems-overview-event span { color:#91a5b5; font-size:.72rem; line-height:1.35; }
|
|
46
|
+
.eos-ems-overview-decision small { color:#6fcba1; font-size:.68rem; }
|
|
47
|
+
.eos-ems-overview-event { grid-template-columns:64px 1fr; align-items:start; }
|
|
48
|
+
.eos-ems-overview-event time { color:#6f8799; font-size:.68rem; font-variant-numeric:tabular-nums; }
|
|
49
|
+
.eos-ems-overview-event div { display:grid; gap:2px; min-width:0; }
|
|
50
|
+
.eos-ems-overview-empty { color:#718797; font-size:.76rem; }
|
|
51
|
+
.eos-ems-overview-footer { display:flex; justify-content:space-between; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
|
|
52
|
+
.eos-ems-overview-footer a { color:#5ee0c2; font-weight:800; text-decoration:none; }
|
|
53
|
+
.eos-ems-overview-footer a:hover,.eos-ems-overview-footer a:focus-visible { color:#9af2dd; text-decoration:underline; }
|
|
54
|
+
@media (max-width: 1100px) { .eos-ems-overview-card { flex-basis:100%; max-width:none; } }
|
|
55
|
+
@media (max-width: 760px) {
|
|
56
|
+
#eos-ems-overview-runtime,.eos-ems-overview-card { min-width:0; width:calc(100% - 16px); margin:8px; padding:16px; }
|
|
57
|
+
.eos-ems-overview-header { display:grid; }
|
|
58
|
+
.eos-ems-overview-state { width:max-content; }
|
|
59
|
+
.eos-ems-overview-metrics { grid-template-columns:1fr 1fr; }
|
|
60
|
+
.eos-ems-overview-columns { grid-template-columns:1fr; }
|
|
61
|
+
.eos-ems-overview-footer { display:grid; }
|
|
62
|
+
}
|
|
@@ -75,8 +75,8 @@ html.eos-login main.MuiPaper-root::before {
|
|
|
75
75
|
left: 50%;
|
|
76
76
|
top: 50%;
|
|
77
77
|
box-sizing: border-box;
|
|
78
|
-
width: min(
|
|
79
|
-
height: min(
|
|
78
|
+
width: min(520px, calc(100vw - 24px)) !important;
|
|
79
|
+
height: min(640px, calc(100dvh - 24px)) !important;
|
|
80
80
|
transform: translate(-50%, -50%);
|
|
81
81
|
border: 1px solid rgba(0, 229, 138, .34);
|
|
82
82
|
border-radius: 26px;
|
|
@@ -90,13 +90,13 @@ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password) {
|
|
|
90
90
|
position: relative;
|
|
91
91
|
z-index: 1;
|
|
92
92
|
box-sizing: border-box !important;
|
|
93
|
-
width: min(
|
|
94
|
-
height:
|
|
95
|
-
min-height:
|
|
96
|
-
max-width:
|
|
93
|
+
width: min(460px, calc(100vw - 56px)) !important;
|
|
94
|
+
height: auto !important;
|
|
95
|
+
min-height: 540px !important;
|
|
96
|
+
max-width: 460px !important;
|
|
97
97
|
max-height: none !important;
|
|
98
|
-
padding:
|
|
99
|
-
overflow:
|
|
98
|
+
padding: 34px 36px 32px !important;
|
|
99
|
+
overflow: visible !important;
|
|
100
100
|
border-radius: 24px !important;
|
|
101
101
|
color: var(--nx-text) !important;
|
|
102
102
|
background: linear-gradient(180deg, rgba(14, 36, 56, .97), rgba(4, 15, 27, .96)) !important;
|
|
@@ -886,12 +886,12 @@ html.eos-login .eos-login-card-modern {
|
|
|
886
886
|
/* Compact product login: the form remains the proven inner card while the
|
|
887
887
|
decorative rear frame keeps a fixed, restrained footprint. */
|
|
888
888
|
box-sizing: border-box !important;
|
|
889
|
-
width: min(
|
|
890
|
-
height:
|
|
891
|
-
min-height:
|
|
892
|
-
max-width:
|
|
889
|
+
width: min(460px, calc(100vw - 56px)) !important;
|
|
890
|
+
height: auto !important;
|
|
891
|
+
min-height: 540px !important;
|
|
892
|
+
max-width: 460px !important;
|
|
893
893
|
max-height: none !important;
|
|
894
|
-
overflow:
|
|
894
|
+
overflow: visible !important;
|
|
895
895
|
}
|
|
896
896
|
.eos-login-role-selector {
|
|
897
897
|
display: none !important;
|
|
@@ -1291,3 +1291,101 @@ html.eos-route-intro #app-paper [class*="card"] {
|
|
|
1291
1291
|
.eos-account-management-entry { margin: 9px; padding: 19px; border-radius: 18px; }
|
|
1292
1292
|
.eos-assist-header-root .eos-assist-panel { right: 8px !important; width: calc(100vw - 16px); }
|
|
1293
1293
|
}
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
/* NexoWatt EOS 7.9.96 – spacious login card without inner scrollbars. */
|
|
1297
|
+
html.eos-login,
|
|
1298
|
+
html.eos-login body {
|
|
1299
|
+
min-height: 100%;
|
|
1300
|
+
margin: 0 !important;
|
|
1301
|
+
overflow-x: hidden !important;
|
|
1302
|
+
overflow-y: auto !important;
|
|
1303
|
+
}
|
|
1304
|
+
html.eos-login #root {
|
|
1305
|
+
min-height: 100dvh;
|
|
1306
|
+
box-sizing: border-box;
|
|
1307
|
+
padding: 24px 12px;
|
|
1308
|
+
}
|
|
1309
|
+
html.eos-login main.MuiPaper-root {
|
|
1310
|
+
box-sizing: border-box !important;
|
|
1311
|
+
min-height: calc(100dvh - 48px) !important;
|
|
1312
|
+
padding: 28px 0 !important;
|
|
1313
|
+
overflow: visible !important;
|
|
1314
|
+
}
|
|
1315
|
+
html.eos-login main.MuiPaper-root::before {
|
|
1316
|
+
width: min(520px, calc(100vw - 24px)) !important;
|
|
1317
|
+
height: min(640px, calc(100dvh - 24px)) !important;
|
|
1318
|
+
min-height: 590px !important;
|
|
1319
|
+
}
|
|
1320
|
+
html.eos-login main.MuiPaper-root > .eos-login-card-modern,
|
|
1321
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username),
|
|
1322
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password),
|
|
1323
|
+
html.eos-login [data-nw-login-card="true"] {
|
|
1324
|
+
box-sizing: border-box !important;
|
|
1325
|
+
width: min(460px, calc(100vw - 56px)) !important;
|
|
1326
|
+
min-width: 0 !important;
|
|
1327
|
+
min-height: 540px !important;
|
|
1328
|
+
height: auto !important;
|
|
1329
|
+
max-width: 460px !important;
|
|
1330
|
+
max-height: none !important;
|
|
1331
|
+
padding: 34px 36px 32px !important;
|
|
1332
|
+
overflow: visible !important;
|
|
1333
|
+
}
|
|
1334
|
+
html.eos-login main.MuiPaper-root > .eos-login-card-modern *,
|
|
1335
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username) *,
|
|
1336
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password) * {
|
|
1337
|
+
box-sizing: border-box;
|
|
1338
|
+
max-width: 100%;
|
|
1339
|
+
}
|
|
1340
|
+
html.eos-login .MuiAlert-root,
|
|
1341
|
+
html.eos-login [role="alert"],
|
|
1342
|
+
html.eos-login .eos-login-first-status,
|
|
1343
|
+
html.eos-login .eos-login-first-hint {
|
|
1344
|
+
position: static !important;
|
|
1345
|
+
width: 100% !important;
|
|
1346
|
+
max-width: 100% !important;
|
|
1347
|
+
max-height: none !important;
|
|
1348
|
+
overflow: visible !important;
|
|
1349
|
+
white-space: normal !important;
|
|
1350
|
+
overflow-wrap: anywhere !important;
|
|
1351
|
+
word-break: normal !important;
|
|
1352
|
+
flex-shrink: 0 !important;
|
|
1353
|
+
}
|
|
1354
|
+
html.eos-login input,
|
|
1355
|
+
html.eos-login button,
|
|
1356
|
+
html.eos-login form {
|
|
1357
|
+
max-width: 100% !important;
|
|
1358
|
+
}
|
|
1359
|
+
@media (max-width: 560px) {
|
|
1360
|
+
html.eos-login #root { padding: 10px 8px; }
|
|
1361
|
+
html.eos-login main.MuiPaper-root { min-height: calc(100dvh - 20px) !important; padding: 14px 0 !important; }
|
|
1362
|
+
html.eos-login main.MuiPaper-root::before {
|
|
1363
|
+
width: calc(100vw - 16px) !important;
|
|
1364
|
+
min-height: 0 !important;
|
|
1365
|
+
height: calc(100% - 4px) !important;
|
|
1366
|
+
border-radius: 20px !important;
|
|
1367
|
+
}
|
|
1368
|
+
html.eos-login main.MuiPaper-root > .eos-login-card-modern,
|
|
1369
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username),
|
|
1370
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password),
|
|
1371
|
+
html.eos-login [data-nw-login-card="true"] {
|
|
1372
|
+
width: calc(100vw - 38px) !important;
|
|
1373
|
+
max-width: none !important;
|
|
1374
|
+
min-height: 0 !important;
|
|
1375
|
+
padding: 26px 22px !important;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
@media (max-height: 690px) {
|
|
1379
|
+
html.eos-login main.MuiPaper-root::before { min-height: 0 !important; height: calc(100% - 12px) !important; }
|
|
1380
|
+
html.eos-login main.MuiPaper-root > .eos-login-card-modern,
|
|
1381
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username),
|
|
1382
|
+
html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password),
|
|
1383
|
+
html.eos-login [data-nw-login-card="true"] { min-height: 0 !important; }
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
/* 7.9.96: native Intro owns the overview for every EOS role. */
|
|
1388
|
+
#eos-role-safe-overview,
|
|
1389
|
+
.eos-role-safe-overview { display: none !important; }
|
|
1390
|
+
html.eos-safe-overview-active #app-paper > :not(.MuiSnackbar-root):not([role="presentation"]) { pointer-events: auto !important; }
|
|
1391
|
+
.eos-intro-admin-edit-hidden { display: none !important; }
|
package/adminWww/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
|
|
9
9
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
10
10
|
<meta http-equiv="Expires" content="0" />
|
|
11
|
-
<script src="./js/eos-manual-write-policy.js?v=
|
|
11
|
+
<script src="./js/eos-manual-write-policy.js?v=96"></script>
|
|
12
12
|
<script type="module" src="./assets/hostInit-v84.js?v=84"></script>
|
|
13
13
|
|
|
14
14
|
<meta charset="utf-8" />
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
rel="stylesheet"
|
|
37
37
|
href="css/leaflet.css"
|
|
38
38
|
/>
|
|
39
|
-
<link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=
|
|
39
|
+
<link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=96" />
|
|
40
|
+
<link rel="stylesheet" href="./css/eos-ems-overview.css?v=96" />
|
|
40
41
|
<link
|
|
41
42
|
rel="manifest"
|
|
42
43
|
href="manifest.json"
|
|
@@ -158,20 +159,21 @@
|
|
|
158
159
|
</script>
|
|
159
160
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
|
|
160
161
|
<link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
|
|
161
|
-
<script defer src="./js/eos-role-bootstrap.js?v=
|
|
162
|
-
<script defer src="./js/eos-policy-client.js?v=
|
|
163
|
-
<script defer src="./js/eos-branding-sanitizer.js?v=
|
|
164
|
-
<script defer src="./js/nexowatt-native-shell.js?v=
|
|
165
|
-
<script defer src="./js/eos-native-security.js?v=
|
|
166
|
-
<script defer src="./js/eos-basic-settings.js?v=
|
|
167
|
-
<script defer src="./js/eos-role-ui.js?v=
|
|
168
|
-
<script defer src="./js/eos-
|
|
169
|
-
<script defer src="./js/eos-
|
|
162
|
+
<script defer src="./js/eos-role-bootstrap.js?v=96" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=84"></script>
|
|
163
|
+
<script defer src="./js/eos-policy-client.js?v=96"></script>
|
|
164
|
+
<script defer src="./js/eos-branding-sanitizer.js?v=96"></script>
|
|
165
|
+
<script defer src="./js/nexowatt-native-shell.js?v=96"></script>
|
|
166
|
+
<script defer src="./js/eos-native-security.js?v=96"></script>
|
|
167
|
+
<script defer src="./js/eos-basic-settings.js?v=96"></script>
|
|
168
|
+
<script defer src="./js/eos-role-ui.js?v=96"></script>
|
|
169
|
+
<script defer src="./js/eos-ems-overview.js?v=96"></script>
|
|
170
|
+
<script defer src="./js/eos-account-management.js?v=96"></script>
|
|
171
|
+
<script defer src="./js/eos-assistant.js?v=96"></script>
|
|
170
172
|
</head>
|
|
171
173
|
<body>
|
|
172
174
|
<noscript>Für NexoWatt EOS muss JavaScript aktiviert sein.</noscript>
|
|
173
175
|
<div id="root"></div>
|
|
174
|
-
<script defer src="static/js/nexowatt-stable-login.js?v=
|
|
175
|
-
<script defer src="static/js/nexowatt-stable-
|
|
176
|
+
<script defer src="static/js/nexowatt-stable-login.js?v=96"></script>
|
|
177
|
+
<script defer src="static/js/nexowatt-stable-v96.js?v=96"></script>
|
|
176
178
|
</body>
|
|
177
179
|
</html>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
|
-
// NexoWatt EOS stable 7.9.
|
|
3
|
+
// NexoWatt EOS stable 7.9.96: EOS Assist remains disabled until the
|
|
4
4
|
// read-only system assistant is rebuilt and accepted separately.
|
|
5
5
|
window.NEXOWATT_EOS_ASSIST_DISABLED = true;
|
|
6
6
|
document.documentElement.classList.add('eos-assist-disabled');
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
const VERSION = 'v96-nexowatt-ems-overview-runtime';
|
|
4
|
+
const CONTRACT = 'nexowatt-ems-overview-v1';
|
|
5
|
+
const ROOT_ID = 'eos-ems-overview-runtime';
|
|
6
|
+
const POLL_MS = 5000;
|
|
7
|
+
const STALE_MS = 20000;
|
|
8
|
+
const COLORS = { ok: '#01bc69', info: '#48b9ff', warning: '#ffbd59', error: '#ff5f72' };
|
|
9
|
+
let timer = 0;
|
|
10
|
+
let inFlight = false;
|
|
11
|
+
let destroyed = false;
|
|
12
|
+
|
|
13
|
+
const esc = value => String(value ?? '').replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character]));
|
|
14
|
+
const text = (value, fallback = '') => String(value ?? fallback).replace(/\s+/g, ' ').trim() || fallback;
|
|
15
|
+
const num = (value, fallback = 0) => Number.isFinite(Number(value)) ? Number(value) : fallback;
|
|
16
|
+
const parse = (value, fallback) => {
|
|
17
|
+
if (value && typeof value === 'object') return value;
|
|
18
|
+
try { return JSON.parse(String(value ?? '')); } catch (_) { return fallback; }
|
|
19
|
+
};
|
|
20
|
+
const severity = value => {
|
|
21
|
+
const normalized = String(value || '').toLowerCase();
|
|
22
|
+
if (normalized === 'error') return 'error';
|
|
23
|
+
if (normalized === 'warning' || normalized === 'warn') return 'warning';
|
|
24
|
+
if (normalized === 'ok') return 'ok';
|
|
25
|
+
return 'info';
|
|
26
|
+
};
|
|
27
|
+
const currentTab = () => String(location.hash || '').match(/tab-[a-z0-9_-]+/i)?.[0]?.toLowerCase() || 'tab-intro';
|
|
28
|
+
const currentRole = () => String(window.NEXOWATT_EOS_ACCESS_ROLE || 'admin').toLowerCase();
|
|
29
|
+
const technical = () => ['admin', 'service', 'installer', 'installateur'].includes(currentRole());
|
|
30
|
+
const formatPower = value => {
|
|
31
|
+
const watts = num(value); const absolute = Math.abs(watts); const sign = watts < 0 ? '−' : '';
|
|
32
|
+
return absolute >= 1000 ? `${sign}${(absolute / 1000).toLocaleString('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 2 })} kW` : `${sign}${Math.round(absolute).toLocaleString('de-DE')} W`;
|
|
33
|
+
};
|
|
34
|
+
const formatAge = timestamp => {
|
|
35
|
+
const value = num(timestamp); if (!value) return 'noch kein Regeltick';
|
|
36
|
+
const seconds = Math.max(0, Math.round((Date.now() - value) / 1000));
|
|
37
|
+
if (seconds < 2) return 'gerade eben';
|
|
38
|
+
if (seconds < 60) return `vor ${seconds} s`;
|
|
39
|
+
return `vor ${Math.round(seconds / 60)} min`;
|
|
40
|
+
};
|
|
41
|
+
const formatClock = timestamp => {
|
|
42
|
+
const value = num(timestamp); if (!value) return '—';
|
|
43
|
+
try { return new Date(value).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch (_) { return '—'; }
|
|
44
|
+
};
|
|
45
|
+
const getSocket = () => window.NEXOWATT_EOS_ADMIN_SOCKET || window.NEXOWATT_EOS_SOCKET || null;
|
|
46
|
+
const stateVal = (states, id) => states?.[id]?.val;
|
|
47
|
+
|
|
48
|
+
const normalizeInstances = (states, aliveStates) => {
|
|
49
|
+
const prefixes = new Set();
|
|
50
|
+
Object.keys(states || {}).forEach(id => { const match = id.match(/^(nexowatt-ui\.\d+)\.info\.adminOverview\./); if (match) prefixes.add(match[1]); });
|
|
51
|
+
return Array.from(prefixes).map(instance => {
|
|
52
|
+
const summaryId = `${instance}.info.adminOverview.summaryJson`;
|
|
53
|
+
const summary = parse(stateVal(states, summaryId), {});
|
|
54
|
+
const events = parse(stateVal(states, `${instance}.info.adminOverview.eventsJson`), []);
|
|
55
|
+
const updatedAt = Math.max(num(summary.updatedAt), num(summary.generatedAt), num(stateVal(states, `${instance}.info.adminOverview.updatedAt`)), num(states?.[summaryId]?.ts));
|
|
56
|
+
const rawAlive = stateVal(aliveStates, `system.adapter.${instance}.alive`);
|
|
57
|
+
const alive = rawAlive == null ? null : (rawAlive === true || rawAlive === 1 || ['true', '1', 'online', 'active'].includes(String(rawAlive).toLowerCase()));
|
|
58
|
+
return { instance, alive, summary: { ...summary, status: severity(summary.status || stateVal(states, `${instance}.info.adminOverview.status`)), headline: text(summary.headline || stateVal(states, `${instance}.info.adminOverview.headline`), 'EMS-Diagnose wird aufgebaut'), reason: text(summary.reason || stateVal(states, `${instance}.info.adminOverview.reason`), ''), binding: text(summary.binding || stateVal(states, `${instance}.info.adminOverview.binding`), 'none') }, events: Array.isArray(events) ? events : [], updatedAt };
|
|
59
|
+
}).sort((a, b) => Number(b.alive === true) - Number(a.alive === true) || b.updatedAt - a.updatedAt || a.instance.localeCompare(b.instance));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const ensureRoot = () => {
|
|
63
|
+
const reactCard = document.querySelector(`[data-nexowatt-contract="${CONTRACT}"]:not(#${ROOT_ID})`);
|
|
64
|
+
if (reactCard) { document.getElementById(ROOT_ID)?.remove(); return null; }
|
|
65
|
+
const paper = document.getElementById('app-paper');
|
|
66
|
+
if (!paper || currentTab() !== 'tab-intro') return null;
|
|
67
|
+
let root = document.getElementById(ROOT_ID);
|
|
68
|
+
if (!root) {
|
|
69
|
+
root = document.createElement('section');
|
|
70
|
+
root.id = ROOT_ID;
|
|
71
|
+
root.className = 'eos-ems-overview-card';
|
|
72
|
+
root.dataset.nexowattContract = CONTRACT;
|
|
73
|
+
}
|
|
74
|
+
const hero = document.getElementById('eos-native-overview-hero');
|
|
75
|
+
if (hero?.parentElement === paper) paper.insertBefore(root, hero.nextSibling); else paper.insertBefore(root, paper.firstChild || null);
|
|
76
|
+
return root;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const unavailable = message => {
|
|
80
|
+
const root = ensureRoot(); if (!root) return;
|
|
81
|
+
root.style.setProperty('--eos-ems-status-color', COLORS.info);
|
|
82
|
+
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS</span><h2>Live-Diagnose</h2><p>${esc(message)}</p></div><div class="eos-ems-overview-state"><span class="eos-ems-overview-state-dot"></span><strong>Nicht verfügbar</strong><small>read-only</small></div></header><footer class="eos-ems-overview-footer"><span>Die Kachel liest ausschließlich info.adminOverview.*.</span><span>Keine EMS-Regelung wurde verändert.</span></footer>`;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const fallbackDecisions = summary => {
|
|
86
|
+
const result = []; const charging = summary.charging || {}; const storage = summary.storage || {};
|
|
87
|
+
if (charging.available) result.push({ severity: num(charging.faultCount) > 0 ? 'error' : num(charging.waitingCount) > 0 ? 'info' : 'ok', title: `${num(charging.activeCount)} lädt · ${num(charging.waitingCount)} wartet · ${num(charging.faultCount)} gestört`, reason: text(charging.limiterText || charging.status, 'Lademanagement aktiv'), details: `Ist ${formatPower(charging.actualW)} · Soll ${formatPower(charging.targetW)}` });
|
|
88
|
+
if (storage.available) result.push({ severity: storage.writeOk === false ? 'error' : 'ok', title: `Speicher ${text(storage.topology, 'aktiv')}${storage.socPct != null ? ` · SoC ${Math.round(num(storage.socPct))} %` : ''}`, reason: text(storage.reason, 'Speicherregelung aktiv'), details: `Ist ${formatPower(storage.actualW)} · Soll ${formatPower(storage.targetW)}` });
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const render = data => {
|
|
93
|
+
const root = ensureRoot(); if (!root) return;
|
|
94
|
+
const summary = data.summary || {}; const budget = summary.budget || {}; const charging = summary.charging || {}; const storage = summary.storage || {}; const ems = summary.ems || {}; const para14a = summary.para14a || {}; const tariff = summary.tariff || {}; const forecast = summary.forecast || {}; const peak = summary.peakShaving || {};
|
|
95
|
+
const stale = data.alive === false || !data.updatedAt || Date.now() - data.updatedAt > STALE_MS;
|
|
96
|
+
const status = stale ? 'error' : severity(summary.status); const statusColor = COLORS[status]; root.style.setProperty('--eos-ems-status-color', statusColor);
|
|
97
|
+
const totalBudget = Math.max(0, num(budget.totalW)); const remaining = Math.max(0, num(budget.remainingW)); const percent = totalBudget > 0 ? Math.max(0, Math.min(100, remaining / totalBudget * 100)) : 0;
|
|
98
|
+
const isTechnical = technical(); const decisions = (Array.isArray(summary.currentDecisions) ? summary.currentDecisions : fallbackDecisions(summary)).slice(0, isTechnical ? 5 : 2); const events = isTechnical ? data.events.slice(0, 6) : [];
|
|
99
|
+
const tags = [];
|
|
100
|
+
if (text(summary.binding, 'none') !== 'none') tags.push({ label: `Bindend: ${text(budget.bindingText || summary.binding)}`, warning: true });
|
|
101
|
+
if (para14a.communicationFallbackActive) tags.push({ label: `§14a-Fallback ${formatPower(para14a.fallbackCapW)}`, warning: true }); else if (para14a.binding) tags.push({ label: '§14a begrenzt', warning: true });
|
|
102
|
+
if (peak.active) tags.push({ label: `Peak-Shaving: ${text(peak.status, 'aktiv')}`, warning: true });
|
|
103
|
+
if (tariff.available) tags.push({ label: `Tarif: ${text(tariff.state, 'aktiv')}${tariff.priceEurPerKwh != null ? ` · ${num(tariff.priceEurPerKwh).toFixed(3)} €/kWh` : ''}` });
|
|
104
|
+
if (forecast.available) tags.push({ label: `Prognose: ${text(forecast.source, 'aktiv')}${forecast.fresh === false ? ' · veraltet' : ''}`, warning: forecast.fresh === false });
|
|
105
|
+
if (ems.safetyActive || ems.safetyEmergencyStop || ems.safetyValid === false) tags.push({ label: 'EOS Safety aktiv', warning: true });
|
|
106
|
+
const detailsPort = Math.max(1, num(summary.details?.port, 8188)); const detailsPath = text(summary.details?.path, '/ems-apps.html?tab=status'); const detailsUrl = `${location.protocol}//${location.hostname}:${detailsPort}${detailsPath.startsWith('/') ? detailsPath : `/${detailsPath}`}`;
|
|
107
|
+
const metrics = `<div class="eos-ems-overview-metric"><span>EMS-Budget</span><strong>${totalBudget > 0 ? esc(formatPower(totalBudget)) : 'nicht begrenzt'}</strong><small>Rest ${esc(formatPower(remaining))}</small></div><div class="eos-ems-overview-metric"><span>Lademanagement</span><strong>${esc(formatPower(charging.actualW))} / ${esc(formatPower(charging.targetW))}</strong><small>${num(charging.activeCount)} lädt · ${num(charging.waitingCount)} wartet</small></div>${storage.available ? `<div class="eos-ems-overview-metric"><span>Speicher ${esc(text(storage.topology))}</span><strong>${esc(formatPower(storage.actualW))} / ${esc(formatPower(storage.targetW))}</strong><small>${storage.socPct != null ? `SoC ${Math.round(num(storage.socPct))} %` : esc(text(storage.reason, 'aktiv'))}</small></div>` : `<div class="eos-ems-overview-metric"><span>PV-Budget</span><strong>${esc(formatPower(budget.pvBudgetW))}</strong><small>PV-Rest ${esc(formatPower(budget.remainingPvW))}</small></div>`}<div class="eos-ems-overview-metric"><span>Letzter Regeltick</span><strong>${esc(formatAge(ems.lastTickTs || data.updatedAt))}</strong><small>${isTechnical ? `Zyklus ${num(ems.cycleMs)} ms` : esc(text(ems.decision, 'EMS aktiv'))}</small></div>`;
|
|
108
|
+
const decisionsHtml = decisions.length ? decisions.map(item => `<div class="eos-ems-overview-decision eos-ems-overview-decision--${severity(item.severity)}"><strong>${esc(text(item.title, 'EMS-Entscheidung'))}</strong><span>${esc(text(item.reason, 'Keine zusätzliche Begrenzung'))}</span>${isTechnical && item.details ? `<small>${esc(item.details)}</small>` : ''}</div>`).join('') : '<div class="eos-ems-overview-empty">Keine aktiven Regelentscheidungen. EOS arbeitet mit den aktuellen Messwerten.</div>';
|
|
109
|
+
const eventsHtml = isTechnical ? (events.length ? events.map(item => `<div class="eos-ems-overview-event eos-ems-overview-event--${severity(item.severity)}"><time>${esc(formatClock(item.ts))}</time><div><strong>${esc(text(item.title, 'EMS'))}</strong><span>${esc(text(item.message || item.reason, 'Zustand aktualisiert'))}</span></div></div>`).join('') : '<div class="eos-ems-overview-empty">Noch keine Zustandsänderung im begrenzten Ereignispuffer.</div>') : `<div class="eos-ems-overview-decision eos-ems-overview-decision--info"><strong>${esc(text(summary.headline, 'EMS aktiv'))}</strong><span>${esc(text(summary.reason, 'EOS optimiert die Anlage innerhalb aller Sicherheitsgrenzen.'))}</span></div>`;
|
|
110
|
+
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS · Live-Diagnose</span><h2>${esc(stale ? 'EMS-Diagnose ist nicht aktuell' : text(summary.headline, 'EMS arbeitet normal'))}</h2><p>${esc(stale ? 'Adapter oder Diagnosewerte sind älter als 20 Sekunden. Die Werte werden als veraltet markiert.' : text(summary.reason, 'EMS arbeitet innerhalb aller aktiven Grenzen.'))}</p></div><div class="eos-ems-overview-state"><span class="eos-ems-overview-state-dot"></span><strong>${stale ? 'Offline / veraltet' : ({ok:'Normal',info:'Information',warning:'Begrenzt',error:'Störung'}[status])}</strong><small>${esc(data.instance)} · ${esc(formatAge(data.updatedAt))}</small></div></header><div class="eos-ems-overview-metrics">${metrics}</div>${totalBudget > 0 ? `<div class="eos-ems-overview-budget" title="${percent.toFixed(1)} % Restbudget"><span style="width:${percent}%"></span></div>` : ''}${tags.length ? `<div class="eos-ems-overview-tags">${tags.map(tag => `<span class="${tag.warning ? 'is-warning' : ''}">${esc(tag.label)}</span>`).join('')}</div>` : ''}<div class="eos-ems-overview-columns"><div class="eos-ems-overview-panel"><h3>Aktuelle Regelentscheidungen</h3>${decisionsHtml}</div><div class="eos-ems-overview-panel"><h3>${isTechnical ? 'Letzte EMS-Ereignisse' : 'Systemerklärung'}</h3>${eventsHtml}</div></div><footer class="eos-ems-overview-footer"><span>Diagnose ist rein lesend. Keine Kachel besitzt Schreibhoheit.</span><a href="${esc(detailsUrl)}" target="_blank" rel="noreferrer">Vollständige EMS-Diagnose öffnen ↗</a></footer>`;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const load = async () => {
|
|
114
|
+
if (destroyed || inFlight || document.visibilityState === 'hidden' || currentTab() !== 'tab-intro') return;
|
|
115
|
+
const socket = getSocket();
|
|
116
|
+
if (!socket?.getForeignStates) { unavailable('Die Admin-Verbindung wird noch aufgebaut.'); return; }
|
|
117
|
+
inFlight = true;
|
|
118
|
+
try {
|
|
119
|
+
const [states, alive] = await Promise.all([socket.getForeignStates('nexowatt-ui.*.info.adminOverview.*'), socket.getForeignStates('system.adapter.nexowatt-ui.*.alive')]);
|
|
120
|
+
const instances = normalizeInstances(states || {}, alive || {});
|
|
121
|
+
if (instances.length) render(instances[0]); else unavailable('NexoWatt UI ist nicht installiert, offline oder der Diagnosevertrag info.adminOverview.* fehlt.');
|
|
122
|
+
} catch (error) { unavailable(`EMS-Diagnose konnte nicht gelesen werden: ${text(error?.message || error, 'unbekannter Fehler')}`); }
|
|
123
|
+
finally { inFlight = false; }
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const apply = () => {
|
|
127
|
+
document.getElementById('eos-role-safe-overview')?.remove();
|
|
128
|
+
document.documentElement.classList.remove('eos-safe-overview-active');
|
|
129
|
+
if (currentTab() !== 'tab-intro') { document.getElementById(ROOT_ID)?.remove(); return; }
|
|
130
|
+
ensureRoot(); void load();
|
|
131
|
+
};
|
|
132
|
+
const start = () => {
|
|
133
|
+
apply();
|
|
134
|
+
timer = window.setInterval(apply, POLL_MS);
|
|
135
|
+
window.addEventListener('hashchange', apply);
|
|
136
|
+
document.addEventListener('visibilitychange', apply);
|
|
137
|
+
};
|
|
138
|
+
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', start, { once: true }) : start();
|
|
139
|
+
window.NEXOWATT_EOS_EMS_OVERVIEW = Object.freeze({ version: VERSION, contract: CONTRACT, refresh: apply, destroy() { destroyed = true; if (timer) clearInterval(timer); document.getElementById(ROOT_ID)?.remove(); } });
|
|
140
|
+
})();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v96-first-login-password-native-overview';
|
|
5
5
|
const script = document.currentScript || document.querySelector('script[src*="eos-role-bootstrap.js"]');
|
|
6
6
|
const entry = script?.dataset?.eosEntry || '';
|
|
7
7
|
let launched = false;
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
overlay.querySelector('.eos-first-login-submit').textContent = t.submit;
|
|
173
173
|
overlay.querySelector('.eos-first-login-logout').textContent = t.logout;
|
|
174
174
|
const form = overlay.querySelector('form');
|
|
175
|
-
form.elements.username.value =
|
|
175
|
+
form.elements.username.value = userName;
|
|
176
176
|
const password = form.elements.password;
|
|
177
177
|
const repeat = form.elements.passwordRepeat;
|
|
178
178
|
const show = form.elements.show;
|
|
@@ -500,7 +500,7 @@
|
|
|
500
500
|
const { username, password, card, submit } = loginElements();
|
|
501
501
|
if (!username || !password || !card) return;
|
|
502
502
|
card.classList.add('eos-login-card-modern');
|
|
503
|
-
//
|
|
503
|
+
// v96 compact normal-login first activation: do not add role buttons or a second panel.
|
|
504
504
|
// The account is entered in the existing Loginname field; a blank password is accepted only
|
|
505
505
|
// after the backend confirms that this exact managed account is eligible.
|
|
506
506
|
card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove());
|
|
@@ -631,7 +631,7 @@
|
|
|
631
631
|
|
|
632
632
|
const contextUrl = new URL('nexowatt/security/context', base).href;
|
|
633
633
|
const controller = new AbortController();
|
|
634
|
-
const timeout = window.setTimeout(() => controller.abort(),
|
|
634
|
+
const timeout = window.setTimeout(() => controller.abort(), 8000);
|
|
635
635
|
|
|
636
636
|
window.NEXOWATT_EOS_BOOTSTRAP_READY = fetch(contextUrl, {
|
|
637
637
|
credentials: 'same-origin', cache: 'no-store', headers: { Accept: 'application/json' }, signal: controller.signal,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v96-native-overview-ems-live-reserve-filter';
|
|
5
5
|
window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
|
|
6
6
|
|
|
7
7
|
const state = {
|
|
@@ -251,47 +251,43 @@
|
|
|
251
251
|
if (/permissionerror|cannot get data/i.test(node.textContent || '')) node.classList.add('eos-role-hidden');
|
|
252
252
|
});
|
|
253
253
|
};
|
|
254
|
-
const
|
|
255
|
-
if (isLoginView() ||
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
const updateNativeOverviewRole = () => {
|
|
255
|
+
if (isLoginView() || currentRoute() !== 'tab-intro') return;
|
|
256
|
+
const config = state.role === 'admin'
|
|
257
|
+
? { label: 'Admin / Service', text: 'Systemstatus, Module, Dienste und Anlagenzugänge in einer modernen Serviceübersicht.' }
|
|
258
|
+
: state.role === 'installer'
|
|
259
|
+
? { label: 'Installateur', text: 'Inbetriebnahme, Fehlersuche, Geräte- und EMS-Diagnose – mit deinen freigegebenen Installateurrechten.' }
|
|
260
|
+
: { label: 'Endkunde', text: 'Energie, Laden, Gebäude und die aktuellen EMS-Entscheidungen auf einen Blick.' };
|
|
261
|
+
const hero = document.getElementById('eos-native-overview-hero');
|
|
262
|
+
const description = hero?.querySelector('p');
|
|
263
|
+
const role = hero?.querySelector('.eos-overview-role');
|
|
264
|
+
if (description) description.textContent = config.text;
|
|
265
|
+
if (role) {
|
|
266
|
+
role.innerHTML = '<span class="eos-overview-status-dot"></span>';
|
|
267
|
+
role.append(document.createTextNode(config.label));
|
|
268
|
+
role.setAttribute('data-nexowatt-overview-role', state.role);
|
|
258
269
|
}
|
|
270
|
+
};
|
|
271
|
+
const hideIntroEditControls = () => {
|
|
259
272
|
const paper = appPaper();
|
|
260
273
|
if (!paper) return;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
overview = document.createElement('section');
|
|
266
|
-
overview.id = 'eos-role-safe-overview';
|
|
267
|
-
overview.className = 'eos-role-safe-overview';
|
|
268
|
-
paper.appendChild(overview);
|
|
269
|
-
}
|
|
270
|
-
const installer = state.role === 'installer';
|
|
271
|
-
const actions = actionDefinitions(state.role).filter(([, , tab]) => tab === 'basic-settings' || isRouteAllowed(state.role, tab));
|
|
272
|
-
overview.innerHTML = `
|
|
273
|
-
<header class="eos-overview-hero eos-role-overview-hero">
|
|
274
|
-
<div><span class="eos-overview-eyebrow">NexoWatt EOS</span><h1>Übersicht</h1><p>${installer ? 'Inbetriebnahme, Fehlersuche und Anlagenkonfiguration – passend zu deinen Installateurrechten.' : 'Deine freigegebenen Energie-, Smart-Home- und Sicherungsfunktionen.'}</p></div>
|
|
275
|
-
<div class="eos-overview-role"><span class="eos-overview-status-dot"></span>${installer ? 'Installateur' : 'Gast / Endkunde'}</div>
|
|
276
|
-
</header>
|
|
277
|
-
<div class="eos-overview-grid">${actions.map(([label, description, tab, icon]) => `
|
|
278
|
-
<a class="eos-overview-action" data-eos-role-tab="${tab}" href="${tab === 'basic-settings' ? '#tab-intro' : `#${tab}`}">
|
|
279
|
-
<span class="eos-overview-action-icon" aria-hidden="true">${iconFor(icon)}</span>
|
|
280
|
-
<span><strong>${label}</strong><small>${description}</small></span><i aria-hidden="true">›</i>
|
|
281
|
-
</a>`).join('')}</div>`;
|
|
282
|
-
overview.querySelectorAll('a[data-eos-role-tab]').forEach(link => {
|
|
283
|
-
link.addEventListener('click', event => {
|
|
284
|
-
event.preventDefault();
|
|
285
|
-
event.stopPropagation();
|
|
286
|
-
const targetTab = link.getAttribute('data-eos-role-tab') || '';
|
|
287
|
-
if (targetTab) navigateToTab(targetTab, link.textContent || '');
|
|
288
|
-
});
|
|
289
|
-
link.addEventListener('keydown', event => {
|
|
290
|
-
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
291
|
-
event.preventDefault();
|
|
292
|
-
navigateToTab(link.getAttribute('data-eos-role-tab') || '', link.textContent || '');
|
|
293
|
-
});
|
|
274
|
+
const admin = state.role === 'admin';
|
|
275
|
+
paper.querySelectorAll(':scope > .MuiFab-root,:scope > button.MuiFab-root').forEach(button => {
|
|
276
|
+
button.classList.toggle('eos-intro-admin-edit-hidden', !admin);
|
|
277
|
+
if (!admin) button.setAttribute('aria-hidden', 'true'); else button.removeAttribute('aria-hidden');
|
|
294
278
|
});
|
|
279
|
+
};
|
|
280
|
+
const ensureSafeOverview = () => safe(() => {
|
|
281
|
+
// 7.9.96: never cover the real Admin Intro with a second tile surface.
|
|
282
|
+
// Installer and end-user use the native card component; security remains
|
|
283
|
+
// enforced by route/instance filters and backend permissions.
|
|
284
|
+
removeSafeOverview();
|
|
285
|
+
if (isLoginView() || state.role === 'unknown' || currentRoute() !== 'tab-intro') return;
|
|
286
|
+
suppressPermissionErrors();
|
|
287
|
+
updateNativeOverviewRole();
|
|
288
|
+
hideIntroEditControls();
|
|
289
|
+
window.NEXOWATT_NATIVE_SHELL?.refresh?.();
|
|
290
|
+
window.NEXOWATT_EOS_EMS_OVERVIEW?.refresh?.();
|
|
295
291
|
});
|
|
296
292
|
|
|
297
293
|
const hideOfficialReserveSurfaces = () => safe(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v96-nexowatt-native-shell-native-overview';
|
|
5
5
|
const previous = window.NEXOWATT_NATIVE_SHELL;
|
|
6
6
|
if (previous?.version === VERSION) return;
|
|
7
7
|
previous?.destroy?.();
|
|
@@ -148,21 +148,26 @@
|
|
|
148
148
|
|
|
149
149
|
const ensureModernOverview = () => {
|
|
150
150
|
const paper = document.getElementById('app-paper');
|
|
151
|
-
const role = window.NEXOWATT_EOS_ACCESS_ROLE || 'unknown';
|
|
152
|
-
if (!paper || isLogin() || currentTab() !== 'tab-intro' || role
|
|
151
|
+
const role = String(window.NEXOWATT_EOS_ACCESS_ROLE || 'unknown').toLowerCase();
|
|
152
|
+
if (!paper || isLogin() || currentTab() !== 'tab-intro' || role === 'unknown') {
|
|
153
153
|
document.getElementById('eos-native-overview-hero')?.remove();
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
|
+
const config = role === 'admin'
|
|
157
|
+
? { label: 'Admin / Service', text: 'Systemstatus, Module, Dienste und Anlagenzugänge in einer modernen Serviceübersicht.' }
|
|
158
|
+
: role === 'installer' || role === 'installateur'
|
|
159
|
+
? { label: 'Installateur', text: 'Inbetriebnahme, Fehlersuche, Geräte- und EMS-Diagnose – mit deinen freigegebenen Installateurrechten.' }
|
|
160
|
+
: { label: 'Endkunde', text: 'Energie, Laden, Gebäude und die aktuellen EMS-Entscheidungen auf einen Blick.' };
|
|
156
161
|
let hero = document.getElementById('eos-native-overview-hero');
|
|
157
162
|
if (!hero) {
|
|
158
163
|
hero = document.createElement('section');
|
|
159
164
|
hero.id = 'eos-native-overview-hero';
|
|
160
165
|
hero.className = 'eos-overview-hero eos-native-overview-hero';
|
|
161
|
-
hero.innerHTML = `
|
|
162
|
-
<div><span class="eos-overview-eyebrow">NexoWatt EOS</span><h1>Übersicht</h1><p>Systemstatus, Module, Dienste und Anlagenzugänge in einer modernen Serviceübersicht.</p></div>
|
|
163
|
-
<div class="eos-overview-role"><span class="eos-overview-status-dot"></span>Admin / Service</div>`;
|
|
164
166
|
paper.insertBefore(hero, paper.firstChild || null);
|
|
165
167
|
}
|
|
168
|
+
hero.innerHTML = `
|
|
169
|
+
<div><span class="eos-overview-eyebrow">NexoWatt EOS</span><h1>Übersicht</h1><p>${config.text}</p></div>
|
|
170
|
+
<div class="eos-overview-role" data-nexowatt-overview-role="${role}"><span class="eos-overview-status-dot"></span>${config.label}</div>`;
|
|
166
171
|
};
|
|
167
172
|
|
|
168
173
|
const apply = () => {
|