iobroker.script-restore 0.0.9 → 0.0.11
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/README.de.md +148 -0
- package/README.md +13 -23
- package/admin/jsonConfig.json +4 -4
- package/admin/tab_m.html +157 -3
- package/build/main.js +67 -0
- package/build/main.js.map +2 -2
- package/io-package.json +27 -40
- package/package.json +3 -3
- package/admin/words.js +0 -444
package/README.de.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# ioBroker.script-restore
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.script-restore)
|
|
6
|
+
[](https://www.npmjs.com/package/iobroker.script-restore)
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
[](https://nodei.co/npm/iobroker.script-restore/)
|
|
10
|
+
|
|
11
|
+
**Tests:** 
|
|
12
|
+
|
|
13
|
+
## script-restore Adapter für ioBroker
|
|
14
|
+
|
|
15
|
+
Einzelne Skripte aus ioBroker-Backup-Archiven durchsuchen und wiederherstellen — ohne das gesamte Backup einspielen zu müssen.
|
|
16
|
+
|
|
17
|
+
## Beschreibung
|
|
18
|
+
|
|
19
|
+
Der script-restore Adapter fügt dem ioBroker-Admin-Interface einen Tab hinzu, über den Backup-Archive geöffnet und alle enthaltenen JavaScript-, TypeScript-, Blockly- und Rules-Skripte durchsucht werden können. Der Quellcode jedes Skripts kann einzeln angezeigt, heruntergeladen oder kopiert werden.
|
|
20
|
+
|
|
21
|
+
Das Archiv wird vollständig im Browser geparst — beim Durchsuchen werden keine Dateien auf die Festplatte geschrieben.
|
|
22
|
+
|
|
23
|
+
## Funktionen
|
|
24
|
+
|
|
25
|
+
- Backup-Archive direkt im ioBroker-Admin-Tab durchsuchen
|
|
26
|
+
- Lokale Backup-Dateien aus dem Backup-Verzeichnis laden (Standard: `/opt/iobroker/backups`)
|
|
27
|
+
- Archivdateien direkt vom Computer hochladen
|
|
28
|
+
- Unterstützte Formate: `.tar.gz`, `.tar`, `.json`, `.jsonl`
|
|
29
|
+
- Baumansicht aller Skripte nach Ordner sortiert
|
|
30
|
+
- Skripte nach Typ filtern: JS, TypeScript, Blockly, Rules
|
|
31
|
+
- Volltextsuche über Skriptnamen, Pfade und Quellcode
|
|
32
|
+
- Quellcode anzeigen (JS/TS/Blockly/Rules)
|
|
33
|
+
- Quellcode in die Zwischenablage kopieren oder als Datei herunterladen
|
|
34
|
+
- Vollständig browserbasiertes Parsen — kein Server-Roundtrip bei Uploads
|
|
35
|
+
- Mehrere Skripte mit Strg+Klick auswählen und als ZIP herunterladen
|
|
36
|
+
- Optionale Quellen: Lokal, FTP, SMB, HTTP, SFTP, WebDAV
|
|
37
|
+
- **Skripte direkt in ioBroker laden** mit konfigurierbarem Suffix (Standard: `_rcvr`) — bestehende Skripte werden nie überschrieben
|
|
38
|
+
|
|
39
|
+
## Konfiguration
|
|
40
|
+
|
|
41
|
+
| Einstellung | Beschreibung | Standard |
|
|
42
|
+
|-------------|--------------|----------|
|
|
43
|
+
| Backup-Pfad | Verzeichnis mit ioBroker-Backup-Dateien | `/opt/iobroker/backups` |
|
|
44
|
+
|
|
45
|
+
## Verwendung
|
|
46
|
+
|
|
47
|
+
### Lokale Backup-Datei laden
|
|
48
|
+
|
|
49
|
+
1. Den Tab **Script Restore** im ioBroker-Admin öffnen
|
|
50
|
+
2. Auf das Dropdown **Lokale Dateien** klicken
|
|
51
|
+
3. Eine Backup-Datei aus der Liste auswählen — die Skripte werden automatisch geladen
|
|
52
|
+
|
|
53
|
+
### Backup-Datei hochladen
|
|
54
|
+
|
|
55
|
+
1. Den Tab **Script Restore** im ioBroker-Admin öffnen
|
|
56
|
+
2. Auf **Archiv hochladen** klicken und eine Datei vom Computer auswählen
|
|
57
|
+
3. Das Archiv wird im Browser geparst und alle Skripte werden angezeigt
|
|
58
|
+
|
|
59
|
+
### Skripte ansehen und herunterladen
|
|
60
|
+
|
|
61
|
+
- Ein Skript im Baum anklicken, um den Quellcode anzuzeigen
|
|
62
|
+
- **Kopieren**-Schaltfläche nutzen, um den Quellcode in die Zwischenablage zu kopieren
|
|
63
|
+
- **Herunterladen**-Schaltfläche nutzen, um das Skript als Datei zu speichern
|
|
64
|
+
|
|
65
|
+
## Unterstützte Backup-Formate
|
|
66
|
+
|
|
67
|
+
| Format | Beschreibung |
|
|
68
|
+
|--------|--------------|
|
|
69
|
+
| `.tar.gz` | Standard-ioBroker-Backup (`iobroker_YYYY-MM-DD-HH-mm_SS_backupiobroker.tar.gz`) |
|
|
70
|
+
| `.tar` | Unkomprimiertes Tar-Archiv |
|
|
71
|
+
| `.json` | JavaScript-Adapter Skript-Export |
|
|
72
|
+
| `.jsonl` | ioBroker-Objekte-Export (JSON Lines) |
|
|
73
|
+
|
|
74
|
+
## Changelog
|
|
75
|
+
|
|
76
|
+
<!--
|
|
77
|
+
Placeholder for the next version (at the beginning of the line):
|
|
78
|
+
### **WORK IN PROGRESS**
|
|
79
|
+
-->
|
|
80
|
+
### **WORK IN PROGRESS**
|
|
81
|
+
* (ipod86) Typ-Filter (JS/TS/Blockly/Rules) in der Skript-Sidebar hinzugefügt
|
|
82
|
+
* (ipod86) Direktes Laden in ioBroker mit Suffix-Eingabe und Bestätigungs-Modal hinzugefügt
|
|
83
|
+
* (ipod86) Veraltete admin/words.js und .prettierignore entfernt
|
|
84
|
+
|
|
85
|
+
### 0.0.10 (2026-04-08)
|
|
86
|
+
* (ipod86) jsonConfig: responsive Größen lg/xl für backupPath korrigiert (E5509)
|
|
87
|
+
* (ipod86) News-Einträge auf 7 begrenzt (W1032)
|
|
88
|
+
* (ipod86) Dependabot npm cooldown von 7 Tagen hinzugefügt (W8915)
|
|
89
|
+
|
|
90
|
+
### 0.0.9 (2026-04-08)
|
|
91
|
+
* (ipod86) jsonConfig: responsive Größenattribute ergänzt (E5507)
|
|
92
|
+
* (ipod86) i18n-Übersetzungsdateien hinzugefügt (W5022)
|
|
93
|
+
* (ipod86) veraltete index_m.html und style.css entfernt (W5047)
|
|
94
|
+
* (ipod86) ungültiges copyToField-Attribut entfernt (W5512)
|
|
95
|
+
|
|
96
|
+
### 0.0.8 (2026-04-08)
|
|
97
|
+
* (ipod86) Einstellungs-UI zu jsonConfig (admin 5+) migriert — behebt S5022
|
|
98
|
+
* (ipod86) `node:fs` statt `fs` verwendet — behebt S5043
|
|
99
|
+
* (ipod86) Dependabot-Zeitplan von monatlich auf wöchentlich geändert — behebt S8906
|
|
100
|
+
* (ipod86) Automerge-Workflow in automerge-dependabot.yml umbenannt — behebt S8911
|
|
101
|
+
|
|
102
|
+
### 0.0.7 (2026-04-08)
|
|
103
|
+
* (ipod86) HTTP-URL-Laden ohne Protokoll-Präfix korrigiert (https:// wird automatisch ergänzt)
|
|
104
|
+
* (ipod86) localStorage-Speicherung des zuletzt geladenen Backups entfernt
|
|
105
|
+
|
|
106
|
+
### 0.0.6 (2026-04-08)
|
|
107
|
+
* (ipod86) HTTP, SFTP und WebDAV als optionale Backup-Quellen hinzugefügt
|
|
108
|
+
* (ipod86) Mehrfachauswahl von Skripten mit Strg+Klick und ZIP-Download
|
|
109
|
+
* (ipod86) Zuletzt geladenes Backup im Browser merken (localStorage)
|
|
110
|
+
* (ipod86) Lokalen Backup-Pfad vom backitup-Adapter automatisch erkennen
|
|
111
|
+
|
|
112
|
+
### 0.0.5 (2026-04-08)
|
|
113
|
+
* (ipod86) FTP und SMB als optionale Backup-Quellen mit Verbindungstest hinzugefügt
|
|
114
|
+
* (ipod86) Lokale Backup-Quelle optional gemacht (in Einstellungen aktivierbar)
|
|
115
|
+
* (ipod86) SMB-Versionshinweis (nur SMB2) in den Einstellungen ergänzt
|
|
116
|
+
|
|
117
|
+
### 0.0.4 (2026-04-06)
|
|
118
|
+
* (ipod86) Dunkles-Theme-Erkennung verbessert: Live-Umschaltung via MutationObserver und Storage-Events
|
|
119
|
+
|
|
120
|
+
### 0.0.3 (2026-04-06)
|
|
121
|
+
* (ipod86) Dunkles Theme für Admin-Tab-UI hinzugefügt
|
|
122
|
+
|
|
123
|
+
### 0.0.1 (2026-04-06)
|
|
124
|
+
* (ipod86) Erstveröffentlichung
|
|
125
|
+
|
|
126
|
+
## Lizenz
|
|
127
|
+
|
|
128
|
+
MIT License
|
|
129
|
+
|
|
130
|
+
Copyright (c) 2026 ipod86 <david@graef.email>
|
|
131
|
+
|
|
132
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
133
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
134
|
+
in the Software without restriction, including without limitation the rights
|
|
135
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
136
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
137
|
+
furnished to do so, subject to the following conditions:
|
|
138
|
+
|
|
139
|
+
The above copyright notice and this permission notice shall be included in all
|
|
140
|
+
copies or substantial portions of the Software.
|
|
141
|
+
|
|
142
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
143
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
144
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
145
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
146
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
147
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
148
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -20,8 +20,6 @@ The script-restore adapter adds a tab to the ioBroker admin interface that lets
|
|
|
20
20
|
|
|
21
21
|
The archive is parsed entirely in the browser — no files are written to disk during browsing.
|
|
22
22
|
|
|
23
|
-
**Note:** This adapter does not automatically restore scripts into ioBroker. It only allows you to view and download them so you can manually re-import them via the JavaScript adapter.
|
|
24
|
-
|
|
25
23
|
## Features
|
|
26
24
|
|
|
27
25
|
- Browse backup archives directly from the ioBroker admin tab
|
|
@@ -29,10 +27,12 @@ The archive is parsed entirely in the browser — no files are written to disk d
|
|
|
29
27
|
- Upload archive files directly from your computer
|
|
30
28
|
- Supported formats: `.tar.gz`, `.tar`, `.json`, `.jsonl`
|
|
31
29
|
- Tree view of all scripts organized by folder
|
|
32
|
-
-
|
|
30
|
+
- Filter scripts by type: JS, TypeScript, Blockly, Rules
|
|
31
|
+
- Full-text search across script names, paths and source code
|
|
33
32
|
- View source code (JS/TS/Blockly/Rules)
|
|
34
33
|
- Copy source code to clipboard or download as file
|
|
35
34
|
- Fully browser-based parsing — no server roundtrip for uploads
|
|
35
|
+
- **Restore scripts directly into ioBroker** with a configurable suffix (default: `_rcvr`) — existing scripts are never overwritten
|
|
36
36
|
|
|
37
37
|
## Configuration
|
|
38
38
|
|
|
@@ -75,6 +75,16 @@ The archive is parsed entirely in the browser — no files are written to disk d
|
|
|
75
75
|
Placeholder for the next version (at the beginning of the line):
|
|
76
76
|
### **WORK IN PROGRESS**
|
|
77
77
|
-->
|
|
78
|
+
### 0.0.11 (2026-04-13)
|
|
79
|
+
* (ipod86) add type filter (JS/TS/Blockly/Rules) in script sidebar
|
|
80
|
+
* (ipod86) add direct restore into ioBroker with suffix input and confirm modal
|
|
81
|
+
* (ipod86) remove obsolete admin/words.js and .prettierignore
|
|
82
|
+
|
|
83
|
+
### 0.0.10 (2026-04-08)
|
|
84
|
+
* (ipod86) fix jsonConfig responsive sizes lg/xl for backupPath (E5509)
|
|
85
|
+
* (ipod86) trim news entries to 7 (W1032)
|
|
86
|
+
* (ipod86) add Dependabot npm cooldown of 7 days (W8915)
|
|
87
|
+
|
|
78
88
|
### 0.0.9 (2026-04-08)
|
|
79
89
|
* (ipod86) fix jsonConfig: add responsive size attributes (E5507)
|
|
80
90
|
* (ipod86) add i18n translation files (W5022)
|
|
@@ -91,26 +101,6 @@ The archive is parsed entirely in the browser — no files are written to disk d
|
|
|
91
101
|
* (ipod86) fix HTTP URL loading without protocol prefix (auto-prepend https://)
|
|
92
102
|
* (ipod86) remove localStorage persistence for last loaded backup
|
|
93
103
|
|
|
94
|
-
### 0.0.6 (2026-04-08)
|
|
95
|
-
* (ipod86) add HTTP, SFTP and WebDAV as optional backup sources
|
|
96
|
-
* (ipod86) multi-select scripts with Ctrl+click and download as ZIP
|
|
97
|
-
* (ipod86) remember last loaded backup in browser (localStorage)
|
|
98
|
-
* (ipod86) auto-detect local backup path from backitup adapter
|
|
99
|
-
|
|
100
|
-
### 0.0.5 (2026-04-08)
|
|
101
|
-
* (ipod86) add FTP and SMB as optional backup sources with connection test button
|
|
102
|
-
* (ipod86) make local backup source optional (enable/disable in settings)
|
|
103
|
-
* (ipod86) add SMB version info (SMB2 only) in settings
|
|
104
|
-
|
|
105
|
-
### 0.0.4 (2026-04-06)
|
|
106
|
-
* (ipod86) improve dark theme detection: live switching via MutationObserver and storage events
|
|
107
|
-
|
|
108
|
-
### 0.0.3 (2026-04-06)
|
|
109
|
-
* (ipod86) add dark theme support for admin tab UI
|
|
110
|
-
|
|
111
|
-
### 0.0.1 (2026-04-06)
|
|
112
|
-
* (ipod86) initial release
|
|
113
|
-
|
|
114
104
|
## License
|
|
115
105
|
MIT License
|
|
116
106
|
|
package/admin/jsonConfig.json
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"xs": 12,
|
|
28
28
|
"sm": 9,
|
|
29
29
|
"md": 9,
|
|
30
|
-
"lg":
|
|
31
|
-
"xl":
|
|
30
|
+
"lg": 9,
|
|
31
|
+
"xl": 9
|
|
32
32
|
},
|
|
33
33
|
"_suggestBtn": {
|
|
34
34
|
"type": "sendTo",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"xs": 12,
|
|
39
39
|
"sm": 3,
|
|
40
40
|
"md": 3,
|
|
41
|
-
"lg":
|
|
42
|
-
"xl":
|
|
41
|
+
"lg": 3,
|
|
42
|
+
"xl": 3
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
},
|
package/admin/tab_m.html
CHANGED
|
@@ -297,6 +297,66 @@
|
|
|
297
297
|
body.dark .progress-inner { background: rgba(26,26,46,0.95); }
|
|
298
298
|
body.dark #loaderText { color: #c0c0d0; }
|
|
299
299
|
|
|
300
|
+
/* Type Filter Bar */
|
|
301
|
+
.type-filter-bar {
|
|
302
|
+
display: flex; gap: 4px; padding: 5px 8px;
|
|
303
|
+
border-bottom: 1px solid var(--border); flex-shrink: 0; flex-wrap: wrap;
|
|
304
|
+
}
|
|
305
|
+
.btn-type {
|
|
306
|
+
background: transparent; border: 1px solid #ced4da; border-radius: 3px;
|
|
307
|
+
padding: 2px 8px; cursor: pointer; font-size: 0.8rem; color: #495057;
|
|
308
|
+
transition: 0.15s; font-family: var(--font-main);
|
|
309
|
+
}
|
|
310
|
+
.btn-type:hover { background: #f0f0f0; }
|
|
311
|
+
.btn-type.active { background: #e9ecef; border-color: #999; font-weight: 600; }
|
|
312
|
+
.btn-type .type-badge { margin: 0; pointer-events: none; }
|
|
313
|
+
.restore-suffix-input {
|
|
314
|
+
width: 70px; padding: 2px 6px; border: 1px solid #555;
|
|
315
|
+
background: #1e1e1e; color: #d4d4d4; border-radius: 3px; font-size: 0.85rem;
|
|
316
|
+
font-family: var(--font-main);
|
|
317
|
+
}
|
|
318
|
+
body.dark .type-filter-bar { border-bottom-color: #3a3a5c; }
|
|
319
|
+
body.dark .btn-type { color: #c0c0d0; border-color: #4a4a6a; }
|
|
320
|
+
body.dark .btn-type:hover { background: #3a3a5c; }
|
|
321
|
+
body.dark .btn-type.active { background: #3a3a5c; border-color: #7070a0; }
|
|
322
|
+
|
|
323
|
+
/* Restore Modal */
|
|
324
|
+
.restore-modal-overlay {
|
|
325
|
+
display: none; position: fixed; inset: 0;
|
|
326
|
+
background: rgba(0,0,0,0.5); z-index: 10000;
|
|
327
|
+
align-items: center; justify-content: center;
|
|
328
|
+
}
|
|
329
|
+
.restore-modal-overlay.open { display: flex; }
|
|
330
|
+
.restore-modal {
|
|
331
|
+
background: white; border-radius: 8px; padding: 24px;
|
|
332
|
+
min-width: 360px; max-width: 520px; width: 90%;
|
|
333
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.25); font-family: var(--font-main);
|
|
334
|
+
}
|
|
335
|
+
.restore-modal h3 { margin: 0 0 16px; font-size: 1.1rem; color: #111; }
|
|
336
|
+
.restore-modal-meta { background: #f8f9fa; border-radius: 4px; padding: 10px 12px; margin-bottom: 16px; font-size: 0.85rem; }
|
|
337
|
+
.restore-modal-meta div { color: #555; margin-bottom: 4px; }
|
|
338
|
+
.restore-modal-meta div:last-child { margin-bottom: 0; }
|
|
339
|
+
.restore-modal-meta strong { color: #111; }
|
|
340
|
+
.restore-modal label { display: block; font-size: 0.85rem; color: #555; margin-bottom: 6px; }
|
|
341
|
+
.restore-modal input[type=text] {
|
|
342
|
+
width: 100%; padding: 8px 10px; border: 1px solid #ced4da; border-radius: 4px;
|
|
343
|
+
font-size: 0.95rem; font-family: var(--font-main); box-sizing: border-box;
|
|
344
|
+
}
|
|
345
|
+
.restore-modal input[type=text]:focus { outline: none; border-color: var(--primary); }
|
|
346
|
+
.restore-modal-preview {
|
|
347
|
+
margin-top: 10px; font-size: 0.8rem; color: #6c757d;
|
|
348
|
+
font-family: 'Consolas', monospace; word-break: break-all;
|
|
349
|
+
}
|
|
350
|
+
.restore-modal-footer { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
|
|
351
|
+
body.dark .restore-modal { background: #252537; }
|
|
352
|
+
body.dark .restore-modal h3 { color: #e0e0e0; }
|
|
353
|
+
body.dark .restore-modal-meta { background: #1a1a2e; }
|
|
354
|
+
body.dark .restore-modal-meta div { color: #aaa; }
|
|
355
|
+
body.dark .restore-modal-meta strong { color: #e0e0e0; }
|
|
356
|
+
body.dark .restore-modal label { color: #aaa; }
|
|
357
|
+
body.dark .restore-modal input[type=text] { background: #1a1a2e; border-color: #4a4a6a; color: #e0e0e0; }
|
|
358
|
+
body.dark .restore-modal-preview { color: #7a7aaa; }
|
|
359
|
+
|
|
300
360
|
/* Responsive */
|
|
301
361
|
@media (max-width: 768px) {
|
|
302
362
|
.toolbar { padding: 6px 8px; }
|
|
@@ -316,6 +376,26 @@
|
|
|
316
376
|
<div id="loaderText">Lade Backup...</div>
|
|
317
377
|
</div>
|
|
318
378
|
|
|
379
|
+
<!-- Restore Modal -->
|
|
380
|
+
<div class="restore-modal-overlay" id="restoreModalOverlay" onclick="closeRestoreModal(event)">
|
|
381
|
+
<div class="restore-modal" onclick="event.stopPropagation()">
|
|
382
|
+
<h3>In ioBroker laden</h3>
|
|
383
|
+
<div class="restore-modal-meta">
|
|
384
|
+
<div>Skript: <strong id="rmScriptName"></strong></div>
|
|
385
|
+
<div>Pfad: <strong id="rmScriptPath"></strong></div>
|
|
386
|
+
<div>Typ: <strong id="rmScriptType"></strong></div>
|
|
387
|
+
</div>
|
|
388
|
+
<label for="rmSuffix">Suffix (wird an den Skriptnamen angehängt)</label>
|
|
389
|
+
<input type="text" id="rmSuffix" value="_rcvr" oninput="updateRestorePreview()">
|
|
390
|
+
<div class="restore-modal-preview" id="rmPreview"></div>
|
|
391
|
+
<div class="restore-modal-footer">
|
|
392
|
+
<span id="rmMsg" style="flex:1;font-size:0.85rem;align-self:center;"></span>
|
|
393
|
+
<button class="btn btn-outline" onclick="closeRestoreModal()">Abbrechen</button>
|
|
394
|
+
<button class="btn btn-outline-success" id="rmConfirmBtn" onclick="confirmRestoreScript()">Laden</button>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
319
399
|
<div class="toolbar">
|
|
320
400
|
<div class="toolbar-left">
|
|
321
401
|
<label class="file-input-label">
|
|
@@ -367,6 +447,13 @@
|
|
|
367
447
|
<input type="text" id="q" placeholder="Suche in Namen, Ordner & Code...">
|
|
368
448
|
<button class="btn-icon" id="expandToggleBtn" onclick="toggleExpandAll()" title="Alle Ordner aufklappen">📂</button>
|
|
369
449
|
</div>
|
|
450
|
+
<div class="type-filter-bar">
|
|
451
|
+
<button class="btn-type active" onclick="setTypeFilter(this, '')">Alle</button>
|
|
452
|
+
<button class="btn-type" onclick="setTypeFilter(this, 'JS')"><span class="type-badge badge-JS">JS</span></button>
|
|
453
|
+
<button class="btn-type" onclick="setTypeFilter(this, 'TypeScript')"><span class="type-badge badge-TypeScript">TS</span></button>
|
|
454
|
+
<button class="btn-type" onclick="setTypeFilter(this, 'Blockly')"><span class="type-badge badge-Blockly">Blockly</span></button>
|
|
455
|
+
<button class="btn-type" onclick="setTypeFilter(this, 'Rules')"><span class="type-badge badge-Rules">Rules</span></button>
|
|
456
|
+
</div>
|
|
370
457
|
<div id="list" class="script-list"></div>
|
|
371
458
|
</div>
|
|
372
459
|
|
|
@@ -376,7 +463,8 @@
|
|
|
376
463
|
<div class="action-bar" id="actionBar">
|
|
377
464
|
<div class="action-bar-inner">
|
|
378
465
|
<div class="btn-group" id="viewSwitcher"></div>
|
|
379
|
-
<div style="display:flex;gap:
|
|
466
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
467
|
+
<button onclick="openRestoreModal()" class="btn btn-outline-success" id="restoreBtn">In ioBroker laden</button>
|
|
380
468
|
<button onclick="copyCode(this)" class="btn btn-outline-light">Code Kopieren</button>
|
|
381
469
|
<button onclick="downloadActive()" class="btn btn-primary" id="dlBtn">Download</button>
|
|
382
470
|
</div>
|
|
@@ -578,6 +666,7 @@
|
|
|
578
666
|
let cur = { index: -1 };
|
|
579
667
|
const openFolders = new Set();
|
|
580
668
|
let isAllExpanded = false;
|
|
669
|
+
let activeTypeFilter = '';
|
|
581
670
|
|
|
582
671
|
function escapeHTML(str) {
|
|
583
672
|
if (!str) return '';
|
|
@@ -1023,13 +1112,15 @@
|
|
|
1023
1112
|
const toggleBtn = document.getElementById('expandToggleBtn');
|
|
1024
1113
|
const tree = buildTree(scriptsData);
|
|
1025
1114
|
|
|
1026
|
-
if (q) {
|
|
1115
|
+
if (q || activeTypeFilter) {
|
|
1027
1116
|
toggleBtn.style.display = 'none';
|
|
1028
1117
|
const filter = (node, path) => {
|
|
1029
1118
|
if (!node.isDir) {
|
|
1030
|
-
|
|
1119
|
+
const typeOk = !activeTypeFilter || node.script.type === activeTypeFilter;
|
|
1120
|
+
const qOk = !q || node.script.name.toLowerCase().includes(q) ||
|
|
1031
1121
|
node.script.path.toLowerCase().includes(q) ||
|
|
1032
1122
|
(node.script.source && node.script.source.toLowerCase().includes(q));
|
|
1123
|
+
return typeOk && qOk;
|
|
1033
1124
|
}
|
|
1034
1125
|
let has = false;
|
|
1035
1126
|
for (const k in node.children) {
|
|
@@ -1144,6 +1235,69 @@
|
|
|
1144
1235
|
}
|
|
1145
1236
|
|
|
1146
1237
|
document.getElementById('q').addEventListener('keyup', renderList);
|
|
1238
|
+
|
|
1239
|
+
// === Type Filter ===
|
|
1240
|
+
function setTypeFilter(btn, type) {
|
|
1241
|
+
activeTypeFilter = type;
|
|
1242
|
+
document.querySelectorAll('.btn-type').forEach(b => b.classList.remove('active'));
|
|
1243
|
+
btn.classList.add('active');
|
|
1244
|
+
renderList();
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// === Restore Modal ===
|
|
1248
|
+
function openRestoreModal() {
|
|
1249
|
+
if (cur.index < 0) return;
|
|
1250
|
+
const s = scriptsData[cur.index];
|
|
1251
|
+
document.getElementById('rmScriptName').textContent = s.name;
|
|
1252
|
+
document.getElementById('rmScriptPath').textContent = s.path;
|
|
1253
|
+
document.getElementById('rmScriptType').textContent = s.type;
|
|
1254
|
+
document.getElementById('rmSuffix').value = '_rcvr';
|
|
1255
|
+
document.getElementById('rmMsg').textContent = '';
|
|
1256
|
+
document.getElementById('rmMsg').style.color = '';
|
|
1257
|
+
document.getElementById('rmConfirmBtn').disabled = false;
|
|
1258
|
+
updateRestorePreview();
|
|
1259
|
+
document.getElementById('restoreModalOverlay').classList.add('open');
|
|
1260
|
+
setTimeout(() => document.getElementById('rmSuffix').focus(), 50);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function closeRestoreModal(e) {
|
|
1264
|
+
if (e && e.target !== document.getElementById('restoreModalOverlay')) return;
|
|
1265
|
+
document.getElementById('restoreModalOverlay').classList.remove('open');
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function updateRestorePreview() {
|
|
1269
|
+
if (cur.index < 0) return;
|
|
1270
|
+
const s = scriptsData[cur.index];
|
|
1271
|
+
const suffix = document.getElementById('rmSuffix').value.trim();
|
|
1272
|
+
const parts = s.path.split('.');
|
|
1273
|
+
parts[parts.length - 1] = parts[parts.length - 1] + suffix;
|
|
1274
|
+
document.getElementById('rmPreview').textContent = '→ script.js.' + parts.join('.');
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function confirmRestoreScript() {
|
|
1278
|
+
if (cur.index < 0) return;
|
|
1279
|
+
const s = scriptsData[cur.index];
|
|
1280
|
+
const suffix = document.getElementById('rmSuffix').value.trim() || '_rcvr';
|
|
1281
|
+
const msgEl = document.getElementById('rmMsg');
|
|
1282
|
+
const confirmBtn = document.getElementById('rmConfirmBtn');
|
|
1283
|
+
msgEl.style.color = '#aaa';
|
|
1284
|
+
msgEl.textContent = '⏳ Laden...';
|
|
1285
|
+
confirmBtn.disabled = true;
|
|
1286
|
+
sendTo('restoreScript', { path: s.path, name: s.name, type: s.type, source: s.source, suffix }, function(result) {
|
|
1287
|
+
confirmBtn.disabled = false;
|
|
1288
|
+
if (result && result.error) {
|
|
1289
|
+
msgEl.style.color = '#dc3545';
|
|
1290
|
+
msgEl.textContent = '✗ ' + result.error;
|
|
1291
|
+
} else if (result && result.success) {
|
|
1292
|
+
msgEl.style.color = '#198754';
|
|
1293
|
+
msgEl.textContent = '✓ Geladen!';
|
|
1294
|
+
setTimeout(() => document.getElementById('restoreModalOverlay').classList.remove('open'), 1200);
|
|
1295
|
+
} else {
|
|
1296
|
+
msgEl.style.color = '#dc3545';
|
|
1297
|
+
msgEl.textContent = '✗ Unbekannter Fehler';
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1147
1301
|
</script>
|
|
1148
1302
|
</body>
|
|
1149
1303
|
</html>
|
package/build/main.js
CHANGED
|
@@ -122,6 +122,9 @@ class ScriptRestore extends utils.Adapter {
|
|
|
122
122
|
case "parseSmbFile":
|
|
123
123
|
await this.handleParseSmbFile(obj);
|
|
124
124
|
break;
|
|
125
|
+
case "restoreScript":
|
|
126
|
+
await this.handleRestoreScript(obj);
|
|
127
|
+
break;
|
|
125
128
|
default:
|
|
126
129
|
this.sendTo(obj.from, obj.command, { error: "Unknown command" }, obj.callback);
|
|
127
130
|
}
|
|
@@ -685,6 +688,70 @@ class ScriptRestore extends utils.Adapter {
|
|
|
685
688
|
this.sendTo(obj.from, obj.command, { error: e.message }, obj.callback);
|
|
686
689
|
}
|
|
687
690
|
}
|
|
691
|
+
// ─── Restore to ioBroker ─────────────────────────────────────────────────
|
|
692
|
+
async handleRestoreScript(obj) {
|
|
693
|
+
const msg = obj.message;
|
|
694
|
+
const suffix = msg.suffix || "_rcvr";
|
|
695
|
+
const parts = msg.path.split(".");
|
|
696
|
+
parts[parts.length - 1] = parts[parts.length - 1] + suffix;
|
|
697
|
+
const newScriptPath = parts.join(".");
|
|
698
|
+
const newId = `script.js.${newScriptPath}`;
|
|
699
|
+
const newName = msg.name + suffix;
|
|
700
|
+
let existing;
|
|
701
|
+
try {
|
|
702
|
+
existing = await this.getForeignObjectAsync(newId);
|
|
703
|
+
} catch {
|
|
704
|
+
existing = null;
|
|
705
|
+
}
|
|
706
|
+
if (existing) {
|
|
707
|
+
this.sendTo(obj.from, obj.command, { error: `Skript existiert bereits: ${newId}` }, obj.callback);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const engineTypeMap = {
|
|
711
|
+
TypeScript: "TypeScript/ts",
|
|
712
|
+
Blockly: "Blockly",
|
|
713
|
+
Rules: "Rules",
|
|
714
|
+
JS: "JavaScript/js"
|
|
715
|
+
};
|
|
716
|
+
const engineType = engineTypeMap[msg.type] || "JavaScript/js";
|
|
717
|
+
try {
|
|
718
|
+
await this.ensureScriptFolders(newId);
|
|
719
|
+
await this.setForeignObjectAsync(newId, {
|
|
720
|
+
type: "script",
|
|
721
|
+
common: {
|
|
722
|
+
name: newName,
|
|
723
|
+
engineType,
|
|
724
|
+
engine: "system.adapter.javascript.0",
|
|
725
|
+
source: msg.source || "",
|
|
726
|
+
enabled: false,
|
|
727
|
+
debug: false,
|
|
728
|
+
verbose: false
|
|
729
|
+
},
|
|
730
|
+
native: {}
|
|
731
|
+
});
|
|
732
|
+
this.log.info(`Script restored: ${newId}`);
|
|
733
|
+
this.sendTo(obj.from, obj.command, { success: true, id: newId }, obj.callback);
|
|
734
|
+
} catch (e) {
|
|
735
|
+
this.sendTo(obj.from, obj.command, { error: e.message }, obj.callback);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async ensureScriptFolders(scriptId) {
|
|
739
|
+
const parts = scriptId.split(".");
|
|
740
|
+
for (let i = 2; i < parts.length - 1; i++) {
|
|
741
|
+
const folderId = parts.slice(0, i + 1).join(".");
|
|
742
|
+
try {
|
|
743
|
+
const existing = await this.getForeignObjectAsync(folderId);
|
|
744
|
+
if (!existing) {
|
|
745
|
+
await this.setForeignObjectAsync(folderId, {
|
|
746
|
+
type: "folder",
|
|
747
|
+
common: { name: parts[i] },
|
|
748
|
+
native: {}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
688
755
|
}
|
|
689
756
|
if (require.main !== module) {
|
|
690
757
|
module.exports = (options) => new ScriptRestore(options);
|