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 ADDED
@@ -0,0 +1,148 @@
1
+ ![Logo](admin/script-restore.svg)
2
+
3
+ # ioBroker.script-restore
4
+
5
+ [![NPM version](https://img.shields.io/npm/v/iobroker.script-restore.svg)](https://www.npmjs.com/package/iobroker.script-restore)
6
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.script-restore.svg)](https://www.npmjs.com/package/iobroker.script-restore)
7
+ ![Number of Installations](https://iobroker.live/badges/script-restore-installed.svg)
8
+ ![Current version in stable repository](https://iobroker.live/badges/script-restore-stable.svg)
9
+ [![NPM](https://nodei.co/npm/iobroker.script-restore.png?downloads=true)](https://nodei.co/npm/iobroker.script-restore/)
10
+
11
+ **Tests:** ![Test and Release](https://github.com/ipod86/ioBroker.script-restore/workflows/Test%20and%20Release/badge.svg)
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
- - Search across all script names
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
 
@@ -27,8 +27,8 @@
27
27
  "xs": 12,
28
28
  "sm": 9,
29
29
  "md": 9,
30
- "lg": 10,
31
- "xl": 10
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": 2,
42
- "xl": 2
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 &amp; 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:6px;">
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
- return node.script.name.toLowerCase().includes(q) ||
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);