iobroker.eos-admin 7.10.3 → 7.10.4
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.4.md +9 -0
- package/MERGE_UPDATE_README_V7.10.4.md +3 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +3 -3
- package/NEXOWATT_EOS_PREBUILT_MANIFEST.json +12 -9
- package/{PUBLISH_STABLE_V7.10.3.md → PUBLISH_STABLE_V7.10.4.md} +1 -1
- package/README.md +4 -4
- package/README_STABILITY_V7.10.4.md +12 -0
- package/RELEASE_ACCEPTANCE_V7.10.4.md +3 -0
- package/RELEASE_NOTES_V7.10.4.md +11 -0
- package/adminWww/index.html +1 -0
- package/adminWww/nexowatt-role-security.js +336 -0
- package/build/lib/eosRoleSecurity.js +310 -0
- package/build/main.js +5 -0
- package/io-package.json +9 -9
- package/package.json +14 -11
- package/src-admin/README.md +69 -0
- package/src-admin/nexowatt-role-security.js +336 -0
- package/tools/nexowatt-merge-update-selftest.cjs +1 -1
- package/tools/nexowatt-role-security-selftest.cjs +43 -0
- package/tools/nexowatt-stable-v7103-selftest.cjs +2 -2
- package/tools/nexowatt-validate-package.cjs +6 -6
- package/INSTALL_TEST_V7.10.3.md +0 -7
- package/MERGE_UPDATE_README_V7.10.3.md +0 -3
- package/README_STABILITY_V7.10.3.md +0 -5
- package/RELEASE_ACCEPTANCE_V7.10.3.md +0 -3
- package/RELEASE_NOTES_V7.10.3.md +0 -7
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Installationstest 7.10.4
|
|
2
|
+
|
|
3
|
+
1. Version 7.10.4 in einem neuen Arbeitsordner veröffentlichen bzw. installieren.
|
|
4
|
+
2. EOS Admin mindestens 60 Sekunden ohne Start-/Stop-Schleife beobachten.
|
|
5
|
+
3. Als Administrator App-Center, Lizenz, Simulation, Benutzer/Rechte, Expertenmodus, ioBroker Admin und XTerm prüfen.
|
|
6
|
+
4. Als Installateur müssen diese Bereiche vollständig fehlen bzw. bei direktem Aufruf gesperrt sein.
|
|
7
|
+
5. Als Endkunde müssen Datenpunkte sichtbar, aber nicht beschreibbar sein; Benutzer/Rechte und Passwortverwaltung dürfen nicht erreichbar sein.
|
|
8
|
+
6. Backup-Instanz deaktivieren: der Navigationspunkt Sicherung muss verschwinden. Backup-Instanz aktivieren: der Punkt muss wieder erscheinen.
|
|
9
|
+
7. Systemeinstellungen öffnen: die Karte Automatische Adapter-Updates darf nur dort sichtbar sein.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Merge-/Update-Hinweis 7.10.4
|
|
2
|
+
|
|
3
|
+
Den bisherigen Arbeitsordner vollständig ersetzen oder die Repository-ZIP in einen leeren Ordner entpacken. Nicht über eine ältere Version kopieren, weil veraltete Browser-Bundles, Rollen-Skripte oder Paket-Lifecycle-Einträge sonst bestehen bleiben können. Danach zuerst `npm publish --dry-run` ausführen.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "7.10.
|
|
2
|
+
"version": "7.10.4",
|
|
3
3
|
"channel": "stable-latest",
|
|
4
4
|
"baseFrontend": "validated 7.10.2 stable runtime with automatic adapter updates relocated into System Settings",
|
|
5
5
|
"runtimeEntry": "v84",
|
|
6
6
|
"releaseType": "stable-system-settings-auto-update-placement",
|
|
7
7
|
"release": "stable-system-settings-auto-update-placement",
|
|
8
|
-
"label": "v7-10-
|
|
8
|
+
"label": "v7-10-4",
|
|
9
9
|
"notes": [
|
|
10
10
|
"Retains the validated 7.10.2 restart-loop and package-completeness fixes.",
|
|
11
11
|
"The automatic NexoWatt adapter update card is no longer mounted in the normal overview or module pages.",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"shellCacheVersion": 7101,
|
|
23
23
|
"nativeShellVersion": 7101,
|
|
24
24
|
"shellCacheTag": "7101",
|
|
25
|
-
"repositoryEntry": "nexowatt-eos-admin-repository-entry-v7-10-
|
|
25
|
+
"repositoryEntry": "nexowatt-eos-admin-repository-entry-v7-10-4.json",
|
|
26
26
|
"autoUpdateCacheVersion": 7103,
|
|
27
27
|
"autoUpdateCacheTag": "7103"
|
|
28
28
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": 1,
|
|
3
3
|
"package": "iobroker.eos-admin",
|
|
4
|
-
"version": "7.10.
|
|
4
|
+
"version": "7.10.4",
|
|
5
5
|
"algorithm": "sha256",
|
|
6
|
-
"fileCount":
|
|
6
|
+
"fileCount": 1391,
|
|
7
7
|
"trackedRoots": [
|
|
8
8
|
"src",
|
|
9
9
|
"build",
|
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
"adminWww/img/le.png": "ef5ebb7112926f646e781a326b639f56888fafe0891b9b000178091b4f8ed9dd",
|
|
553
553
|
"adminWww/img/logo.svg": "1167118074ff84d8cef82ad80c38609719dacfde9417126e46966c1ec986534b",
|
|
554
554
|
"adminWww/img/no-image.svg": "25fc382f4ad3a36f8a637cd966b9107368e1f11906d706102ad5f37db6f57f34",
|
|
555
|
-
"adminWww/index.html": "
|
|
555
|
+
"adminWww/index.html": "9dcbe4023a66ececd427066d57811c8610d2eb95e4d0a64aab4800a832563f7e",
|
|
556
556
|
"adminWww/js/adapter-settings.js": "fb2e538a3385899f7fd47275ec588051c735d8f778a44b89f40f5eb820a31dbc",
|
|
557
557
|
"adminWww/js/eos-account-management.js": "dbba398605b2391ac1a7ec97cf7eb84da7f929ea16bfb6761750cd700a687fbe",
|
|
558
558
|
"adminWww/js/eos-assistant.js": "be43f49fd5310d2962365035556c37109d4868856e874e1f964c4b2f05bb6beb",
|
|
@@ -740,6 +740,7 @@
|
|
|
740
740
|
"adminWww/logo512.png": "a7e6375e6f4e1f5e09f167af81eca69626e4baf4762a07e41b838bc4786f0519",
|
|
741
741
|
"adminWww/manifest.json": "bfa868e65ffed57405e0091cdb3250a6b9265ba3b0831ca3f14e77acb9f255d7",
|
|
742
742
|
"adminWww/mf-manifest.json": "737e829bad795bdc781cd820ea513d3e09345e23762e4054e1ece0c003160d3c",
|
|
743
|
+
"adminWww/nexowatt-role-security.js": "920868a8dd283b485f90c0946a4ded9cb9fbd1a4f95c5429fea9b1436feb74e1",
|
|
743
744
|
"adminWww/remoteEntry-v84.js": "3836e43036d446c7c549ca1e5b1211a452b50a2206bc197746840cc95e71f95d",
|
|
744
745
|
"adminWww/remoteEntry.js": "9e3c715c9adb33c77f0eeefbd80510f97ebe1b18d8f11d43a2332ce89cd35e20",
|
|
745
746
|
"adminWww/robots.txt": "90d24bc3bf698ac1e173739502298ccca72adf1f564fab05f484b8c48d1cadd2",
|
|
@@ -785,6 +786,7 @@
|
|
|
785
786
|
"build/lib/eosAutoUpdate.js.map": "d5ad4227eb6433921938c97b64ca781cde4c42a77682c1046dea9fda4d77b266",
|
|
786
787
|
"build/lib/eosRequestSecurity.js": "c9193759d0ca2f5221d31688b4ab3b0ec21aa2f4862443c0171e608adb9858a0",
|
|
787
788
|
"build/lib/eosRequestSecurity.js.map": "3e6512682f0d7a1169fcab9d3d167f95e02e38774f7e47688ce0320e1509c4d5",
|
|
789
|
+
"build/lib/eosRoleSecurity.js": "6e5e223c40b43d3eee003f4ab150099babd04f01b9629ccdf9d4d121a7e4f4f7",
|
|
788
790
|
"build/lib/objectFixes.js": "ce095d293991b45bd4bfdb4a256e33a2ba74556868f07040e04b666b0757016d",
|
|
789
791
|
"build/lib/objectFixes.js.map": "27f9d305b48ebc92e3ccd1b7a944bccec366a7a38c63e003bf164f03fe898790",
|
|
790
792
|
"build/lib/testPassword.js": "9d2d4fcbd892db0864fb09db2c2142e3743908d70c53fc644826f4a8f492cd93",
|
|
@@ -795,7 +797,7 @@
|
|
|
795
797
|
"build/lib/utils.js.map": "74238d0d82cc28d41c4f627b73793f6cda26b338ef1bd97916b6417783949e29",
|
|
796
798
|
"build/lib/web.js": "8de94dffdd7c70be6b537c111691c831116338b311f7569739de57f9a32e071f",
|
|
797
799
|
"build/lib/web.js.map": "5fb9f337f3a27c04bb4303cb61ffa5f0da857299a7d2890609eb5f55f24f1789",
|
|
798
|
-
"build/main.js": "
|
|
800
|
+
"build/main.js": "ed9f996f6240d2663a2b641e8965d42aeab75403efe2c2b0a772885f28b6c0b5",
|
|
799
801
|
"build/main.js.map": "1cd3887da9b3d4061c59b6ad497c536c6c9a79938b10bbddf7128a1e7930dfbd",
|
|
800
802
|
"public/404.html": "6c6458ba2bc49b0c27973293e461262bf697607a8e71ab716227553f6d7005eb",
|
|
801
803
|
"public/logTemplate.html": "fc371fc8e4d7ef57c0ca76a405508eced038523464ff65b6cae04dae6144fd54",
|
|
@@ -1323,7 +1325,7 @@
|
|
|
1323
1325
|
"src-admin/src/tabs/Objects.tsx": "834773b07b613b96f58d053496ed1de0e0b8cbf987a3b33314c85e8495a836f6",
|
|
1324
1326
|
"src-admin/src/tabs/Users.tsx": "45ed37e57b66f7e23eb465aef9ad9db55613b40bf44530ab81d6206806de102d",
|
|
1325
1327
|
"src-admin/src/types.d.ts": "3cfac48af5f8416b55b89c0daca78499dbbd402c0b44cc661112b76953ba3344",
|
|
1326
|
-
"src-admin/src/version.json": "
|
|
1328
|
+
"src-admin/src/version.json": "47f62d99d6da117043311a6484e38f40fbfb096eb192478c618c0a3035df8cde",
|
|
1327
1329
|
"src/i18n/de.json": "85d4ecfedf15d78984922b05377063b808ae8c5a75944af323e72a2c3a483d50",
|
|
1328
1330
|
"src/i18n/en.json": "6a0156a1b026903821d395ef2ceef845e90531f323a218f97f5abefaabb46a23",
|
|
1329
1331
|
"src/i18n/es.json": "54369f8044dfa222779849d2f158eb100188a43816d6c42a64575122cb528a30",
|
|
@@ -1378,7 +1380,7 @@
|
|
|
1378
1380
|
"tools/nexowatt-internal-reserve-selftest.cjs": "5d90e42b2f1d4aac1039fce14bf18a952e9c5665203e8caae112737c5dcd265e",
|
|
1379
1381
|
"tools/nexowatt-login-layout-selftest.cjs": "4d1aa1f58a8f117ab7485d883c75603df277043b838dcaa0de3143acb6d885ab",
|
|
1380
1382
|
"tools/nexowatt-manual-write-policy-selftest.cjs": "a41057610972d681120bf35e0321029facc08b098c79da9b131980f80bd3fdce",
|
|
1381
|
-
"tools/nexowatt-merge-update-selftest.cjs": "
|
|
1383
|
+
"tools/nexowatt-merge-update-selftest.cjs": "46d269abc9cfc64299823088824bde1739829d05aa2fdd21cd1528e499bb2381",
|
|
1382
1384
|
"tools/nexowatt-modern-ui-selftest.cjs": "e0caf8244ff5e3431052f9cb5e2b59a64a50c92ca37ec51e286200e1ed37edc0",
|
|
1383
1385
|
"tools/nexowatt-module-popup-selftest.cjs": "31f02e8cf0bfd61d7769e1095c93887a34cfbbd76a9805ef4931cea6dff53ddf",
|
|
1384
1386
|
"tools/nexowatt-native-shell-selftest.cjs": "13aabd3a19f0c0422bbc1c676718685b77c88a526167a7b511fa6c9ab2ac860c",
|
|
@@ -1392,18 +1394,19 @@
|
|
|
1392
1394
|
"tools/nexowatt-release-defaults-selftest.cjs": "3e3c96ca774200461978c546d1a937cbae18d7f11497ed84dda639c2358f6e16",
|
|
1393
1395
|
"tools/nexowatt-repair-eos-admin-update.cjs": "f28c1999b0f75139e582770ad649d17d11f9e909ac49d13c31be5fa5b36253e3",
|
|
1394
1396
|
"tools/nexowatt-role-access-selftest.cjs": "5d88eaeac77ff1460f28caca68a197b4cd820fbc3cf5194ff3bff036d4b20814",
|
|
1397
|
+
"tools/nexowatt-role-security-selftest.cjs": "d98b1eef1bbe7259a9015bcc7bca1b296a1fe4bb8df1a930e2e3f71c2665ddff",
|
|
1395
1398
|
"tools/nexowatt-runtime-cleanup-selftest.cjs": "8c5a5aaf51290436ceef40f7c5a28e602a428558582ee54bb94d57ed70cc3662",
|
|
1396
|
-
"tools/nexowatt-stability-check.cjs": "
|
|
1399
|
+
"tools/nexowatt-stability-check.cjs": "fb968bcaf388cbf2b8a61b7b4cf7b251970e97f914db6f2c90a74d73aa7ce18b",
|
|
1397
1400
|
"tools/nexowatt-stable-v7101-selftest.cjs": "817d09861ef29c52a67f64ec5b8f4ad4654ce7e6486eded786bb16f3c773eaf8",
|
|
1398
1401
|
"tools/nexowatt-stable-v7102-selftest.cjs": "75baab5f8069569ad4896e5018f3c5ccbbda2c2297aca5c5146bce94f9cf795d",
|
|
1399
|
-
"tools/nexowatt-stable-v7103-selftest.cjs": "
|
|
1402
|
+
"tools/nexowatt-stable-v7103-selftest.cjs": "db438580a443547ff7ceba821df940e59ba1e10105248653414a94a81fe3642e",
|
|
1400
1403
|
"tools/nexowatt-stable-v94-selftest.cjs": "5226881b3ad8918c67921ffb37716490f053ec88e7c0f24827a5eda7c75d4019",
|
|
1401
1404
|
"tools/nexowatt-stable-v95-selftest.cjs": "0b15fcd329eb2994e64c407ab04eaafab7c38c4688136c197bf9601e1c35ee07",
|
|
1402
1405
|
"tools/nexowatt-stable-v97-selftest.cjs": "4f50ad6e53b8de7eb045d56657cccd846883eacd8a877d51240d78153ba53d80",
|
|
1403
1406
|
"tools/nexowatt-sync-release-version.cjs": "2478d714533ece21a275071ef6e5467f73c2fa33d0a63d76f408dc048a86c6f6",
|
|
1404
1407
|
"tools/nexowatt-ui-overview-runtime-selftest.cjs": "838a8f44f4ba322424e35ebbb01e4a55bb9cc90b8625033086969114744386b4",
|
|
1405
1408
|
"tools/nexowatt-update-selftest.cjs": "c38c454135f98de683de8fa15fc4b9032063823f0f55fcfc8b675f551fd342f5",
|
|
1406
|
-
"tools/nexowatt-validate-package.cjs": "
|
|
1409
|
+
"tools/nexowatt-validate-package.cjs": "f918335d1add07777ee8998a66f207df7c30479aceec6c401ec10a72a59a98fe",
|
|
1407
1410
|
"tools/nexowatt-value-column-selftest.cjs": "29d0a9bbea05a87dcf8ee09c86cc81acb6c59e1e34801890768f5e342083871a",
|
|
1408
1411
|
"tools/nexowatt-version-sync-selftest.cjs": "65d7c8634b6d5581f337365e13a1a9432c7d47dbf5aebc237886730c32ba5e94",
|
|
1409
1412
|
"tsconfig.build.json": "d3a31263975a13de77b97af9ff246d89931f3abb594047be2739cf11378b97f5",
|
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ Standard-Port: 8081
|
|
|
13
13
|
|
|
14
14
|
Damit überschreibt der Adapter den offiziellen `admin`-Adapter nicht mehr. Die alte ioBroker-Admin-Oberfläche kann parallel vorhanden bleiben, während NexoWatt-Systeme über `eos-admin.0` die EOS-Oberfläche nutzen.
|
|
15
15
|
|
|
16
|
-
## Aktueller Stable-Verkaufskandidat 7.10.
|
|
16
|
+
## Aktueller Stable-Verkaufskandidat 7.10.4
|
|
17
17
|
|
|
18
|
-
Version 7.10.
|
|
18
|
+
Version 7.10.4 übernimmt den validierten und absturzfreien Stand 7.10.2. Die Einstellung **Automatische Adapter-Updates** liegt jetzt ausschließlich in den **Systemeinstellungen** hinter dem Schraubenschlüssel. Auf Übersicht, Module und allen anderen Hauptseiten wird die Karte nicht mehr angezeigt. Die restart-sichere Updateverwaltung, der verzögerte Repository-Abgleich und die vollständige Backend-Paketprüfung bleiben unverändert.
|
|
19
19
|
|
|
20
20
|
Verwaltete Installateur- und Endkundenkonten verwenden das Startpasswort `nexowatt`. Nach der ersten Anmeldung beziehungsweise nach einem Reset muss unmittelbar ein persönliches Passwort vergeben werden. Der Reset und die persönliche Passwortvergabe schreiben das reale ioBroker-Benutzerkennwort; EOS-Metadaten werden davon getrennt aktualisiert.
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ Minimaler Repository-Eintrag:
|
|
|
33
33
|
{
|
|
34
34
|
"eos-admin": {
|
|
35
35
|
"name": "eos-admin",
|
|
36
|
-
"version": "7.10.
|
|
36
|
+
"version": "7.10.4",
|
|
37
37
|
"title": "NexoWatt EOS Admin",
|
|
38
38
|
"desc": {
|
|
39
39
|
"de": "NexoWatt EOS Administrationsoberfläche als eigenständiger Adapter.",
|
|
@@ -141,7 +141,7 @@ npm publish --dry-run --tag latest
|
|
|
141
141
|
npm publish --tag latest
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Die Version muss vor jedem erneuten Publish erhöht werden. Für 7.10.
|
|
144
|
+
Die Version muss vor jedem erneuten Publish erhöht werden. Für 7.10.4 wird direkt aus der vollständig geprüften Repository-ZIP veröffentlicht; `npm publish` benötigt dabei weiterhin weder `tsc` noch `tsx`.
|
|
145
145
|
|
|
146
146
|
## Lizenz
|
|
147
147
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# NexoWatt EOS Admin 7.10.4 – Stabilitäts- und Sicherheitsstand
|
|
2
|
+
|
|
3
|
+
Version 7.10.4 baut auf dem absturz- und publish-sicheren Stand 7.10.3 auf. Die automatische Update-Karte bleibt ausschließlich in den Systemeinstellungen hinter dem Schraubenschlüssel.
|
|
4
|
+
|
|
5
|
+
Zusätzlich gilt eine rollenbasierte Sicherheitsgrenze:
|
|
6
|
+
|
|
7
|
+
- Administrator: vollständiger Zugriff einschließlich App-Center, Lizenz, Simulation, Expertenmodus, Benutzer/Rechte, ioBroker Admin und XTerm.
|
|
8
|
+
- Installateur: technische Bedienoberfläche ohne App-Center, Lizenz, Simulation, Expertenmodus, Benutzer/Rechte, ioBroker Admin und XTerm.
|
|
9
|
+
- Endkunde: Cockpit, Smart Home und Datenpunkte mit Nur-Lesezugriff; keine Schreib-, Rechte-, Passwort- oder Expertenfunktionen.
|
|
10
|
+
- Sicherung: Navigation nur bei installierter und aktivierter Backup-Instanz.
|
|
11
|
+
|
|
12
|
+
Die Sperren werden nicht nur optisch, sondern zusätzlich im EOS-HTTP-Guard und über die Endkunden-ACLs durchgesetzt.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Release Acceptance 7.10.4
|
|
2
|
+
|
|
3
|
+
Akzeptiert, wenn Paketvalidierung, Stabilitätstests, Prebuilt-Manifest, Backend-Runtime, Rollen-Selbsttest und `npm publish --dry-run` erfolgreich sind. Zusätzlich müssen Administrator-, Installateur- und Endkundenrechte im Feldtest der dokumentierten Rollenmatrix entsprechen.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Release Notes 7.10.4
|
|
2
|
+
|
|
3
|
+
- App-Center, Lizenzverwaltung und Simulation auf Administratoren begrenzt.
|
|
4
|
+
- Expertenmodus und Expertenkopf für Installateur und Endkunde entfernt.
|
|
5
|
+
- Benutzer-, Gruppen-, Rechte- und Passwortverwaltung für Nicht-Administratoren gesperrt.
|
|
6
|
+
- ioBroker Admin und XTerm für Installateur und Endkunde ausgeblendet und serverseitig abgefangen.
|
|
7
|
+
- Endkunden-Datenpunkte sichtbar, aber nur lesbar; Schreibaufrufe werden blockiert.
|
|
8
|
+
- Sicherungsnavigation nur bei aktiver Backup-Instanz.
|
|
9
|
+
- Bereits bestehende privilegierte UI-Sitzungen werden bei Nicht-Administratoren nicht weiterverwendet.
|
|
10
|
+
- Update-Karte weiterhin ausschließlich unter Systemeinstellungen.
|
|
11
|
+
- Absturz-, Runtime- und dependency-freier NPM-Publish-Schutz aus 7.10.2/7.10.3 beibehalten.
|
package/adminWww/index.html
CHANGED
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
<div id="root"></div>
|
|
178
178
|
<script defer src="static/js/nexowatt-stable-login.js?v=7101"></script>
|
|
179
179
|
<script defer src="static/js/nexowatt-stable-v7101.js?v=7101"></script>
|
|
180
|
+
<script defer src="./nexowatt-role-security.js?v=7104"></script>
|
|
180
181
|
</body>
|
|
181
182
|
</html>
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const VERSION = "7.10.4";
|
|
5
|
+
const HIDDEN = "data-eos-role-hidden";
|
|
6
|
+
const READONLY = "data-eos-readonly";
|
|
7
|
+
const state = { role: "unknown", backupActive: null, user: "", appliedAt: 0 };
|
|
8
|
+
|
|
9
|
+
const adminOnlyTokens = [
|
|
10
|
+
"app-center", "app center", "appcenter", "lizenz", "license", "simulation", "simulator",
|
|
11
|
+
"expertenmodus", "expert mode", "xterm", "shell-konsole", "shell console", "zugänge & rechte",
|
|
12
|
+
"zugaenge & rechte", "access & rights", "user management", "benutzerverwaltung"
|
|
13
|
+
];
|
|
14
|
+
const backupTokens = ["sicherung", "backup", "backitup"];
|
|
15
|
+
const dataPointTokens = ["datenpunkte", "datapoints", "objects", "objekte"];
|
|
16
|
+
const dangerousWriteTokens = [
|
|
17
|
+
"wert schreiben", "wert setzen", "schreiben", "write value", "set value", "setstate", "speichern",
|
|
18
|
+
"save", "anwenden", "apply", "löschen", "delete", "bearbeiten", "edit", "stift"
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const normalize = value => String(value == null ? "" : value).trim().toLowerCase();
|
|
22
|
+
const textOf = el => normalize([
|
|
23
|
+
el && el.textContent,
|
|
24
|
+
el && el.getAttribute && el.getAttribute("title"),
|
|
25
|
+
el && el.getAttribute && el.getAttribute("aria-label"),
|
|
26
|
+
el && el.getAttribute && el.getAttribute("data-testid"),
|
|
27
|
+
el && el.getAttribute && el.getAttribute("href"),
|
|
28
|
+
el && el.getAttribute && el.getAttribute("to"),
|
|
29
|
+
el && el.id,
|
|
30
|
+
el && el.className && typeof el.className === "string" ? el.className : ""
|
|
31
|
+
].filter(Boolean).join(" "));
|
|
32
|
+
|
|
33
|
+
function classify(value) {
|
|
34
|
+
const text = normalize(value);
|
|
35
|
+
if (!text) return "unknown";
|
|
36
|
+
if (/^(?:system\.user\.)?admin(?:istrator)?$/.test(text) || /(?:^|[.:/_ -])admin(?:istrator)?(?:$|[.:/_ -])/.test(text)) return "admin";
|
|
37
|
+
if (/installer|installateur|inbetriebnahme/.test(text)) return "installer";
|
|
38
|
+
if (/endkunde|customer|smarthome|guest|gast|(?:^|\s)user(?:$|\s)/.test(text)) return "customer";
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function roleFromGlobals() {
|
|
43
|
+
const candidates = [
|
|
44
|
+
window.__NEXOWATT_ROLE__, window.__EOS_ROLE__, window.eosRole,
|
|
45
|
+
window.currentUser && window.currentUser.role,
|
|
46
|
+
window.user && window.user.role,
|
|
47
|
+
document.documentElement.getAttribute("data-role"),
|
|
48
|
+
document.body && document.body.getAttribute("data-role")
|
|
49
|
+
];
|
|
50
|
+
for (const value of candidates) {
|
|
51
|
+
const role = classify(value);
|
|
52
|
+
if (role !== "unknown") return role;
|
|
53
|
+
}
|
|
54
|
+
return "unknown";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function roleFromStorage() {
|
|
58
|
+
const stores = [window.sessionStorage, window.localStorage];
|
|
59
|
+
const keys = ["eos.role", "eosRole", "role", "userRole", "nexowatt.role", "auth.role", "currentRole"];
|
|
60
|
+
for (const store of stores) {
|
|
61
|
+
try {
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
const role = classify(store.getItem(key));
|
|
64
|
+
if (role !== "unknown") return role;
|
|
65
|
+
}
|
|
66
|
+
} catch { /* storage can be disabled */ }
|
|
67
|
+
}
|
|
68
|
+
return "unknown";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function roleFromDom() {
|
|
72
|
+
const selectors = [
|
|
73
|
+
"[data-role]", "[data-user-role]", "[data-current-role]", "[aria-label*='Rolle' i]",
|
|
74
|
+
"[aria-label*='role' i]", "header", "nav", ".MuiAppBar-root"
|
|
75
|
+
];
|
|
76
|
+
for (const selector of selectors) {
|
|
77
|
+
for (const el of document.querySelectorAll(selector)) {
|
|
78
|
+
const value = [
|
|
79
|
+
el.getAttribute && el.getAttribute("data-role"),
|
|
80
|
+
el.getAttribute && el.getAttribute("data-user-role"),
|
|
81
|
+
el.getAttribute && el.getAttribute("data-current-role"),
|
|
82
|
+
el.getAttribute && el.getAttribute("aria-label"),
|
|
83
|
+
el.textContent
|
|
84
|
+
].filter(Boolean).join(" ");
|
|
85
|
+
const role = classify(value);
|
|
86
|
+
if (role !== "unknown") return role;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return "unknown";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findDisplayedUser() {
|
|
93
|
+
const candidates = document.querySelectorAll("header *, nav *, .MuiAppBar-root *");
|
|
94
|
+
for (const el of candidates) {
|
|
95
|
+
const text = normalize(el.textContent);
|
|
96
|
+
if (!text || text.length > 80) continue;
|
|
97
|
+
if (/\b(admin|installer|user|guest|gast|endkunde)\b/.test(text)) return text;
|
|
98
|
+
}
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function fetchSecurityStatus() {
|
|
103
|
+
try {
|
|
104
|
+
const user = encodeURIComponent(findDisplayedUser());
|
|
105
|
+
const response = await fetch(`/api/nexowatt-eos/security/status?user=${user}&v=${VERSION}`, {
|
|
106
|
+
credentials: "same-origin", cache: "no-store", headers: { "Accept": "application/json" }
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) return null;
|
|
109
|
+
return await response.json();
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isAdmin() { return state.role === "admin"; }
|
|
116
|
+
function isCustomer() { return state.role === "customer"; }
|
|
117
|
+
|
|
118
|
+
function hide(el, reason) {
|
|
119
|
+
if (!el || el === document.body || el === document.documentElement) return;
|
|
120
|
+
if (!el.hasAttribute(HIDDEN)) {
|
|
121
|
+
el.setAttribute(HIDDEN, reason || "restricted");
|
|
122
|
+
el.setAttribute("aria-hidden", "true");
|
|
123
|
+
el.style.setProperty("display", "none", "important");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function unhide(el) {
|
|
128
|
+
if (!el || !el.hasAttribute(HIDDEN)) return;
|
|
129
|
+
el.removeAttribute(HIDDEN);
|
|
130
|
+
el.removeAttribute("aria-hidden");
|
|
131
|
+
el.style.removeProperty("display");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function closestAction(el) {
|
|
135
|
+
return el && el.closest && el.closest("a,button,[role='button'],[role='tab'],li,.MuiButtonBase-root") || el;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function includesAny(text, tokens) {
|
|
139
|
+
return tokens.some(token => text.includes(token));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function applyNavigationPolicy() {
|
|
143
|
+
const actions = document.querySelectorAll("a,button,[role='button'],[role='tab'],li,.MuiButtonBase-root");
|
|
144
|
+
for (const raw of actions) {
|
|
145
|
+
const el = closestAction(raw);
|
|
146
|
+
const text = textOf(el);
|
|
147
|
+
if (!text) continue;
|
|
148
|
+
|
|
149
|
+
const adminOnly = includesAny(text, adminOnlyTokens) ||
|
|
150
|
+
(/\badmin\b/.test(text) && /(?:8081|adapter=admin|io.?broker)/.test(text));
|
|
151
|
+
const backup = includesAny(text, backupTokens);
|
|
152
|
+
|
|
153
|
+
if (!isAdmin() && adminOnly) hide(el, "admin-only");
|
|
154
|
+
else if (adminOnly) unhide(el);
|
|
155
|
+
|
|
156
|
+
if (backup && state.backupActive === false) hide(el, "backup-inactive");
|
|
157
|
+
else if (backup && state.backupActive === true && (isAdmin() || !adminOnly)) unhide(el);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!isAdmin()) {
|
|
161
|
+
for (const el of document.querySelectorAll("[data-testid*='expert' i],[class*='expert' i],[id*='expert' i],[title*='Experten' i],[aria-label*='Experten' i],[title*='Expert' i],[aria-label*='Expert' i]")) {
|
|
162
|
+
hide(closestAction(el), "admin-only-expert");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isDataPointPage() {
|
|
168
|
+
const route = normalize(location.pathname + " " + location.hash + " " + document.title);
|
|
169
|
+
if (includesAny(route, dataPointTokens)) return true;
|
|
170
|
+
const selected = document.querySelector("[aria-selected='true'],.Mui-selected,.active");
|
|
171
|
+
return selected ? includesAny(textOf(selected), dataPointTokens) : false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function makeDatapointsReadOnly() {
|
|
175
|
+
const root = document.querySelector("main") || document.body;
|
|
176
|
+
if (!isCustomer() || !isDataPointPage()) {
|
|
177
|
+
root && root.removeAttribute(READONLY);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
root.setAttribute(READONLY, "true");
|
|
181
|
+
for (const el of root.querySelectorAll("input,textarea,select,[contenteditable='true'],button,[role='button']")) {
|
|
182
|
+
const text = textOf(el);
|
|
183
|
+
const isSearch = /filter|suche|search|refresh|aktualisieren|reload|sort|spalte|column|zurück|back/.test(text) ||
|
|
184
|
+
/search/i.test(el.getAttribute && el.getAttribute("type") || "") ||
|
|
185
|
+
/filter|search/i.test(el.getAttribute && el.getAttribute("placeholder") || "");
|
|
186
|
+
const likelyWriter = includesAny(text, dangerousWriteTokens) ||
|
|
187
|
+
/value|wert|write|edit|save|delete|setstate|state-value/i.test(text) ||
|
|
188
|
+
(el.matches("input,textarea,select,[contenteditable='true']") && !isSearch);
|
|
189
|
+
if (likelyWriter && !isSearch) {
|
|
190
|
+
el.setAttribute("aria-disabled", "true");
|
|
191
|
+
el.setAttribute("tabindex", "-1");
|
|
192
|
+
if ("disabled" in el) el.disabled = true;
|
|
193
|
+
if (el.getAttribute("contenteditable") === "true") el.setAttribute("contenteditable", "false");
|
|
194
|
+
el.style.setProperty("pointer-events", "none", "important");
|
|
195
|
+
el.style.setProperty("opacity", "0.62", "important");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function currentRouteRestricted() {
|
|
201
|
+
const route = normalize(location.pathname + " " + location.search + " " + location.hash);
|
|
202
|
+
return includesAny(route, adminOnlyTokens);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function renderForbidden() {
|
|
206
|
+
if (document.getElementById("eos-role-forbidden")) return;
|
|
207
|
+
const overlay = document.createElement("div");
|
|
208
|
+
overlay.id = "eos-role-forbidden";
|
|
209
|
+
overlay.innerHTML = `<div class="eos-role-forbidden-card"><strong>Zugriff gesperrt</strong><span>App-Center, Lizenz, Simulation und Expertenmodus sind ausschließlich für Administratoren freigegeben.</span><button type="button">Zurück zum Cockpit</button></div>`;
|
|
210
|
+
overlay.querySelector("button").addEventListener("click", () => {
|
|
211
|
+
location.hash = "#/";
|
|
212
|
+
try { history.replaceState(null, "", location.pathname); } catch { /* ignore */ }
|
|
213
|
+
overlay.remove();
|
|
214
|
+
});
|
|
215
|
+
document.body.appendChild(overlay);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function guardCurrentRoute() {
|
|
219
|
+
if (!isAdmin() && currentRouteRestricted()) renderForbidden();
|
|
220
|
+
else document.getElementById("eos-role-forbidden")?.remove();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function clearExpertMode() {
|
|
224
|
+
if (isAdmin()) return;
|
|
225
|
+
const keys = ["expertMode", "expert-mode", "eos.expert", "eosExpertMode", "adminExpertMode"];
|
|
226
|
+
for (const store of [window.localStorage, window.sessionStorage]) {
|
|
227
|
+
try { keys.forEach(key => store.removeItem(key)); } catch { /* ignore */ }
|
|
228
|
+
}
|
|
229
|
+
window.__NEXOWATT_EXPERT_MODE__ = false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function scrubPrivilegedUiSessions() {
|
|
233
|
+
if (isAdmin()) return;
|
|
234
|
+
const marker = "eos.nonAdminUiLogout.v7104";
|
|
235
|
+
try {
|
|
236
|
+
if (sessionStorage.getItem(marker)) return;
|
|
237
|
+
sessionStorage.setItem(marker, "1");
|
|
238
|
+
} catch { /* continue */ }
|
|
239
|
+
const protocol = location.protocol === "https:" ? "https:" : "http:";
|
|
240
|
+
const host = location.hostname;
|
|
241
|
+
for (const port of [8082, 8188]) {
|
|
242
|
+
const iframe = document.createElement("iframe");
|
|
243
|
+
iframe.hidden = true;
|
|
244
|
+
iframe.setAttribute("aria-hidden", "true");
|
|
245
|
+
iframe.src = `${protocol}//${host}:${port}/logout?source=eos-role-security&v=${VERSION}`;
|
|
246
|
+
document.body.appendChild(iframe);
|
|
247
|
+
setTimeout(() => iframe.remove(), 12000);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function apply() {
|
|
252
|
+
state.appliedAt = Date.now();
|
|
253
|
+
document.documentElement.setAttribute("data-eos-effective-role", state.role);
|
|
254
|
+
applyNavigationPolicy();
|
|
255
|
+
makeDatapointsReadOnly();
|
|
256
|
+
clearExpertMode();
|
|
257
|
+
guardCurrentRoute();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function installEventGuards() {
|
|
261
|
+
document.addEventListener("click", event => {
|
|
262
|
+
if (isAdmin()) return;
|
|
263
|
+
const action = closestAction(event.target);
|
|
264
|
+
const text = textOf(action);
|
|
265
|
+
if (includesAny(text, adminOnlyTokens) || (/\badmin\b/.test(text) && /(?:8081|adapter=admin|io.?broker)/.test(text))) {
|
|
266
|
+
event.preventDefault(); event.stopImmediatePropagation(); renderForbidden(); return;
|
|
267
|
+
}
|
|
268
|
+
if (isCustomer() && isDataPointPage() && includesAny(text, dangerousWriteTokens)) {
|
|
269
|
+
event.preventDefault(); event.stopImmediatePropagation(); return;
|
|
270
|
+
}
|
|
271
|
+
}, true);
|
|
272
|
+
document.addEventListener("submit", event => {
|
|
273
|
+
if (isCustomer() && isDataPointPage()) { event.preventDefault(); event.stopImmediatePropagation(); }
|
|
274
|
+
}, true);
|
|
275
|
+
for (const name of ["pushState", "replaceState"]) {
|
|
276
|
+
const original = history[name];
|
|
277
|
+
history[name] = function(...args) {
|
|
278
|
+
const result = original.apply(this, args);
|
|
279
|
+
queueMicrotask(apply);
|
|
280
|
+
return result;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
addEventListener("hashchange", apply);
|
|
284
|
+
addEventListener("popstate", apply);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function injectStyles() {
|
|
288
|
+
if (document.getElementById("eos-role-security-style")) return;
|
|
289
|
+
const style = document.createElement("style");
|
|
290
|
+
style.id = "eos-role-security-style";
|
|
291
|
+
style.textContent = `
|
|
292
|
+
[${HIDDEN}] { display:none !important; }
|
|
293
|
+
#eos-role-forbidden { position:fixed; inset:0; z-index:2147483647; display:grid; place-items:center; padding:24px; background:rgba(1,17,28,.96); color:#eefaff; font-family:Arial,sans-serif; }
|
|
294
|
+
.eos-role-forbidden-card { width:min(620px,100%); display:grid; gap:18px; padding:30px; border:1px solid #16d49d; border-radius:18px; background:#072333; box-shadow:0 20px 80px rgba(0,0,0,.45); }
|
|
295
|
+
.eos-role-forbidden-card strong { color:#24e6ad; font-size:28px; }
|
|
296
|
+
.eos-role-forbidden-card span { line-height:1.55; }
|
|
297
|
+
.eos-role-forbidden-card button { justify-self:start; padding:10px 18px; border:1px solid #16d49d; border-radius:10px; color:#021911; background:#24e6ad; font-weight:700; cursor:pointer; }
|
|
298
|
+
[${READONLY}="true"]::before { content:"Nur-Lesezugriff für Endkunden"; position:sticky; top:0; z-index:10; display:block; margin:8px; padding:8px 12px; border:1px solid #1aa57d; border-radius:9px; background:#07342b; color:#cffff0; font:600 13px Arial,sans-serif; }
|
|
299
|
+
`;
|
|
300
|
+
document.head.appendChild(style);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function init() {
|
|
304
|
+
injectStyles();
|
|
305
|
+
let role = roleFromGlobals();
|
|
306
|
+
if (role === "unknown") role = roleFromStorage();
|
|
307
|
+
if (role === "unknown") role = roleFromDom();
|
|
308
|
+
const status = await fetchSecurityStatus();
|
|
309
|
+
if (status) {
|
|
310
|
+
const serverRole = classify(status.role);
|
|
311
|
+
if (serverRole !== "unknown") role = serverRole;
|
|
312
|
+
if (typeof status.backupActive === "boolean") state.backupActive = status.backupActive;
|
|
313
|
+
state.user = status.user || "";
|
|
314
|
+
}
|
|
315
|
+
if (role === "unknown") role = "customer"; // least privilege until authenticated role is known
|
|
316
|
+
state.role = role;
|
|
317
|
+
try { sessionStorage.setItem("eos.role.effective", role); } catch { /* ignore */ }
|
|
318
|
+
installEventGuards();
|
|
319
|
+
apply();
|
|
320
|
+
scrubPrivilegedUiSessions();
|
|
321
|
+
const observer = new MutationObserver(() => {
|
|
322
|
+
clearTimeout(observer._timer);
|
|
323
|
+
observer._timer = setTimeout(apply, 40);
|
|
324
|
+
});
|
|
325
|
+
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "aria-selected", "href"] });
|
|
326
|
+
setInterval(async () => {
|
|
327
|
+
const refreshed = await fetchSecurityStatus();
|
|
328
|
+
if (refreshed && typeof refreshed.backupActive === "boolean" && refreshed.backupActive !== state.backupActive) {
|
|
329
|
+
state.backupActive = refreshed.backupActive; apply();
|
|
330
|
+
}
|
|
331
|
+
}, 10000);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init, { once: true });
|
|
335
|
+
else void init();
|
|
336
|
+
})();
|