iobroker.script-restore 0.0.4 → 0.0.6
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.md +17 -3
- package/admin/index_m.html +344 -6
- package/admin/tab_m.html +174 -33
- package/admin/words.js +398 -0
- package/build/main.js +448 -10
- package/build/main.js.map +3 -3
- package/io-package.json +92 -14
- package/package.json +7 -2
package/admin/tab_m.html
CHANGED
|
@@ -148,6 +148,7 @@
|
|
|
148
148
|
|
|
149
149
|
.script-item:hover { background-color: #f0f7ff; }
|
|
150
150
|
.script-item.active { background-color: #e7f1ff; border-left: 3px solid var(--primary); padding-left: calc(12px - 3px); }
|
|
151
|
+
.script-item.selected { background-color: #fff3cd; border-left: 3px solid #ffc107; padding-left: calc(12px - 3px); }
|
|
151
152
|
.tree-script.active { padding-left: calc(32px - 3px); }
|
|
152
153
|
.tree-children .tree-children .tree-script.active { padding-left: calc(50px - 3px); }
|
|
153
154
|
.tree-children .tree-children .tree-children .tree-script.active { padding-left: calc(68px - 3px); }
|
|
@@ -225,7 +226,7 @@
|
|
|
225
226
|
@keyframes spin { 100% { transform: rotate(360deg); } }
|
|
226
227
|
#loaderText { color: #495057; font-size: 0.95rem; }
|
|
227
228
|
|
|
228
|
-
/*
|
|
229
|
+
/* Files dropdown */
|
|
229
230
|
.dropdown-wrapper { position: relative; }
|
|
230
231
|
.dropdown-menu {
|
|
231
232
|
display: none; position: absolute; top: 100%; left: 0; z-index: 1000;
|
|
@@ -322,12 +323,41 @@
|
|
|
322
323
|
<input type="file" id="fileInput" accept=".tar,.gz,.tar.gz,.json,.jsonl">
|
|
323
324
|
</label>
|
|
324
325
|
<div class="dropdown-wrapper" id="localDropdown">
|
|
325
|
-
<button class="btn btn-outline" onclick="
|
|
326
|
+
<button class="btn btn-outline" onclick="toggleDropdown('local')">
|
|
326
327
|
🗂️ Lokale Backups ▾
|
|
327
328
|
</button>
|
|
328
329
|
<div class="dropdown-menu" id="localMenu"></div>
|
|
329
330
|
</div>
|
|
330
|
-
<
|
|
331
|
+
<div class="dropdown-wrapper" id="ftpDropdown" style="display:none;">
|
|
332
|
+
<button class="btn btn-outline" onclick="toggleDropdown('ftp')">
|
|
333
|
+
🌐 FTP Backups ▾
|
|
334
|
+
</button>
|
|
335
|
+
<div class="dropdown-menu" id="ftpMenu"></div>
|
|
336
|
+
</div>
|
|
337
|
+
<div class="dropdown-wrapper" id="smbDropdown" style="display:none;">
|
|
338
|
+
<button class="btn btn-outline" onclick="toggleDropdown('smb')">
|
|
339
|
+
🗄️ SMB Backups ▾
|
|
340
|
+
</button>
|
|
341
|
+
<div class="dropdown-menu" id="smbMenu"></div>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="dropdown-wrapper" id="sftpDropdown" style="display:none;">
|
|
344
|
+
<button class="btn btn-outline" onclick="toggleDropdown('sftp')">
|
|
345
|
+
🔒 SFTP Backups ▾
|
|
346
|
+
</button>
|
|
347
|
+
<div class="dropdown-menu" id="sftpMenu"></div>
|
|
348
|
+
</div>
|
|
349
|
+
<div class="dropdown-wrapper" id="webdavDropdown" style="display:none;">
|
|
350
|
+
<button class="btn btn-outline" onclick="toggleDropdown('webdav')">
|
|
351
|
+
☁️ WebDAV Backups ▾
|
|
352
|
+
</button>
|
|
353
|
+
<div class="dropdown-menu" id="webdavMenu"></div>
|
|
354
|
+
</div>
|
|
355
|
+
<div id="httpInputWrapper" style="display:none; display:flex; align-items:center; gap:6px;">
|
|
356
|
+
<input type="text" id="httpUrlInput" placeholder="https://..." style="padding:0.35rem 0.6rem; border:1px solid #ced4da; border-radius:4px; font-size:0.875rem; min-width:260px; font-family:inherit;">
|
|
357
|
+
<button class="btn btn-outline" onclick="loadHttpUrl()">🌐 URL laden</button>
|
|
358
|
+
</div>
|
|
359
|
+
<button id="zipBtn" class="btn btn-outline" onclick="downloadZip()" style="display:none;">📦 ZIP</button>
|
|
360
|
+
<span class="status-msg" id="statusMsg">Backup laden oder Quelle wählen</span>
|
|
331
361
|
</div>
|
|
332
362
|
</div>
|
|
333
363
|
|
|
@@ -675,7 +705,7 @@
|
|
|
675
705
|
const scripts = parseJsonContent(reader.result, file.name);
|
|
676
706
|
hideLoader();
|
|
677
707
|
loadScripts(scripts);
|
|
678
|
-
setStatus(scripts.length + ' Skripte geladen aus: ' + file.name, 'success');
|
|
708
|
+
saveLastBackup('Upload', file.name); setStatus(scripts.length + ' Skripte geladen aus: ' + file.name, 'success');
|
|
679
709
|
} catch(e) {
|
|
680
710
|
hideLoader();
|
|
681
711
|
setStatus('Fehler beim Parsen: ' + e.message, 'error');
|
|
@@ -698,7 +728,7 @@
|
|
|
698
728
|
const scripts = await parseArchiveInBrowser(archiveReader.result, file.name); // result is ArrayBuffer
|
|
699
729
|
hideLoader();
|
|
700
730
|
loadScripts(scripts);
|
|
701
|
-
setStatus(scripts.length + ' Skripte geladen aus: ' + file.name, 'success');
|
|
731
|
+
saveLastBackup('Upload', file.name); setStatus(scripts.length + ' Skripte geladen aus: ' + file.name, 'success');
|
|
702
732
|
} catch(e) {
|
|
703
733
|
hideLoader();
|
|
704
734
|
setStatus('Fehler: ' + e.message, 'error');
|
|
@@ -712,58 +742,111 @@
|
|
|
712
742
|
this.value = '';
|
|
713
743
|
});
|
|
714
744
|
|
|
715
|
-
// ===
|
|
716
|
-
|
|
745
|
+
// === localStorage: last loaded backup ===
|
|
746
|
+
const LS_KEY = 'scriptRestore_lastBackup';
|
|
747
|
+
function saveLastBackup(source, label) {
|
|
748
|
+
try { localStorage.setItem(LS_KEY, JSON.stringify({ source, label })); } catch {}
|
|
749
|
+
}
|
|
750
|
+
function restoreLastBackup() {
|
|
751
|
+
try {
|
|
752
|
+
const d = JSON.parse(localStorage.getItem(LS_KEY) || 'null');
|
|
753
|
+
if (d) setStatus('Zuletzt geladen: [' + escapeHTML(d.source) + '] ' + escapeHTML(d.label), '');
|
|
754
|
+
} catch {}
|
|
755
|
+
}
|
|
756
|
+
restoreLastBackup();
|
|
757
|
+
|
|
758
|
+
// === Source Config ===
|
|
759
|
+
function loadSourceConfig(attempt) {
|
|
760
|
+
attempt = attempt || 0;
|
|
761
|
+
sendTo('getSourceConfig', {}, function(result) {
|
|
762
|
+
if (!result || result.error) {
|
|
763
|
+
if (attempt < 10) setTimeout(function() { loadSourceConfig(attempt + 1); }, 500);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (result.localEnabled === false) document.getElementById('localDropdown').style.display = 'none';
|
|
767
|
+
if (result.ftpEnabled) document.getElementById('ftpDropdown').style.display = '';
|
|
768
|
+
if (result.smbEnabled) document.getElementById('smbDropdown').style.display = '';
|
|
769
|
+
if (result.sftpEnabled) document.getElementById('sftpDropdown').style.display = '';
|
|
770
|
+
if (result.webdavEnabled) document.getElementById('webdavDropdown').style.display = '';
|
|
771
|
+
if (result.httpEnabled) document.getElementById('httpInputWrapper').style.display = 'flex';
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
setTimeout(function() { loadSourceConfig(0); }, 300);
|
|
775
|
+
|
|
776
|
+
// === Dropdowns (local / ftp / smb / sftp / webdav) ===
|
|
777
|
+
const dropdownState = { local: false, ftp: false, smb: false, sftp: false, webdav: false };
|
|
778
|
+
const dropdownConfig = {
|
|
779
|
+
local: { listCmd: 'listLocalFiles', parseCmd: 'parseLocalFile', menuId: 'localMenu', wrapperId: 'localDropdown' },
|
|
780
|
+
ftp: { listCmd: 'listFtpFiles', parseCmd: 'parseFtpFile', menuId: 'ftpMenu', wrapperId: 'ftpDropdown' },
|
|
781
|
+
smb: { listCmd: 'listSmbFiles', parseCmd: 'parseSmbFile', menuId: 'smbMenu', wrapperId: 'smbDropdown' },
|
|
782
|
+
sftp: { listCmd: 'listSftpFiles', parseCmd: 'parseSftpFile', menuId: 'sftpMenu', wrapperId: 'sftpDropdown' },
|
|
783
|
+
webdav: { listCmd: 'listWebdavFiles', parseCmd: 'parseWebdavFile', menuId: 'webdavMenu', wrapperId: 'webdavDropdown' },
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
function toggleDropdown(src) {
|
|
787
|
+
const cfg = dropdownConfig[src];
|
|
788
|
+
const menu = document.getElementById(cfg.menuId);
|
|
789
|
+
const isOpen = dropdownState[src];
|
|
790
|
+
|
|
791
|
+
// Close all other open dropdowns first
|
|
792
|
+
Object.keys(dropdownState).forEach(k => {
|
|
793
|
+
if (k !== src && dropdownState[k]) {
|
|
794
|
+
dropdownState[k] = false;
|
|
795
|
+
document.getElementById(dropdownConfig[k].menuId).classList.remove('open');
|
|
796
|
+
}
|
|
797
|
+
});
|
|
717
798
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
localMenuOpen = !localMenuOpen;
|
|
721
|
-
if (localMenuOpen) {
|
|
799
|
+
dropdownState[src] = !isOpen;
|
|
800
|
+
if (dropdownState[src]) {
|
|
722
801
|
menu.classList.add('open');
|
|
723
802
|
menu.innerHTML = '<div class="dropdown-loading">⏳ Lade Dateiliste...</div>';
|
|
724
|
-
sendTo(
|
|
803
|
+
sendTo(cfg.listCmd, {}, function(result) {
|
|
725
804
|
if (result && result.error) {
|
|
726
805
|
menu.innerHTML = '<div class="dropdown-empty">⚠️ ' + escapeHTML(result.error) + '</div>';
|
|
727
806
|
} else if (result && result.files && result.files.length > 0) {
|
|
728
807
|
menu.innerHTML = result.files.map(f =>
|
|
729
|
-
'<div class="dropdown-item" data-file="' + escapeHTML(f) + '">' +
|
|
730
|
-
escapeHTML(f) + '</div>'
|
|
808
|
+
'<div class="dropdown-item" data-file="' + escapeHTML(f) + '">' + escapeHTML(f) + '</div>'
|
|
731
809
|
).join('');
|
|
732
810
|
menu.querySelectorAll('.dropdown-item').forEach(el => {
|
|
733
|
-
el.addEventListener('click', function() {
|
|
811
|
+
el.addEventListener('click', function() { loadRemoteFile(src, this.dataset.file); });
|
|
734
812
|
});
|
|
735
813
|
} else {
|
|
736
814
|
menu.innerHTML = '<div class="dropdown-empty">Keine Dateien gefunden in:<br>' + escapeHTML((result && result.path) || '') + '</div>';
|
|
737
815
|
}
|
|
738
816
|
});
|
|
739
|
-
|
|
740
|
-
setTimeout(() => document.addEventListener('click', closeLocalMenuOutside), 0);
|
|
817
|
+
setTimeout(() => document.addEventListener('click', closeDropdownOutside), 0);
|
|
741
818
|
} else {
|
|
742
819
|
menu.classList.remove('open');
|
|
743
|
-
document.removeEventListener('click', closeLocalMenuOutside);
|
|
744
820
|
}
|
|
745
821
|
}
|
|
746
822
|
|
|
747
|
-
function
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
823
|
+
function closeDropdownOutside(e) {
|
|
824
|
+
let anyOpen = false;
|
|
825
|
+
Object.keys(dropdownState).forEach(src => {
|
|
826
|
+
if (!dropdownState[src]) return;
|
|
827
|
+
const wrapper = document.getElementById(dropdownConfig[src].wrapperId);
|
|
828
|
+
if (!wrapper.contains(e.target)) {
|
|
829
|
+
dropdownState[src] = false;
|
|
830
|
+
document.getElementById(dropdownConfig[src].menuId).classList.remove('open');
|
|
831
|
+
} else {
|
|
832
|
+
anyOpen = true;
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
if (!anyOpen) document.removeEventListener('click', closeDropdownOutside);
|
|
754
836
|
}
|
|
755
837
|
|
|
756
|
-
function
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
838
|
+
function loadRemoteFile(src, filename) {
|
|
839
|
+
const cfg = dropdownConfig[src];
|
|
840
|
+
document.getElementById(cfg.menuId).classList.remove('open');
|
|
841
|
+
dropdownState[src] = false;
|
|
760
842
|
showLoaderSpinner('Lade und verarbeite ' + filename + '...');
|
|
761
|
-
sendTo(
|
|
843
|
+
sendTo(cfg.parseCmd, { filename: filename }, function(result) {
|
|
762
844
|
hideLoader();
|
|
763
845
|
if (result && result.error) {
|
|
764
846
|
setStatus('Fehler: ' + result.error, 'error');
|
|
765
847
|
} else if (result && result.scripts) {
|
|
766
848
|
loadScripts(result.scripts);
|
|
849
|
+
saveLastBackup(src.toUpperCase(), filename);
|
|
767
850
|
setStatus(result.scripts.length + ' Skripte geladen aus: ' + filename, 'success');
|
|
768
851
|
} else {
|
|
769
852
|
setStatus('Keine Skripte gefunden.', 'error');
|
|
@@ -771,9 +854,61 @@
|
|
|
771
854
|
});
|
|
772
855
|
}
|
|
773
856
|
|
|
857
|
+
// === HTTP URL ===
|
|
858
|
+
function loadHttpUrl() {
|
|
859
|
+
const url = document.getElementById('httpUrlInput').value.trim();
|
|
860
|
+
if (!url) return;
|
|
861
|
+
const filename = url.split('/').pop() || 'backup';
|
|
862
|
+
showLoaderSpinner('Lade URL...');
|
|
863
|
+
sendTo('parseHttpUrl', { url }, function(result) {
|
|
864
|
+
hideLoader();
|
|
865
|
+
if (result && result.error) {
|
|
866
|
+
setStatus('Fehler: ' + result.error, 'error');
|
|
867
|
+
} else if (result && result.scripts) {
|
|
868
|
+
loadScripts(result.scripts);
|
|
869
|
+
saveLastBackup('HTTP', filename);
|
|
870
|
+
setStatus(result.scripts.length + ' Skripte geladen von URL', 'success');
|
|
871
|
+
} else {
|
|
872
|
+
setStatus('Keine Skripte gefunden.', 'error');
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// === Multi-Select & ZIP ===
|
|
878
|
+
let selectedIndices = new Set();
|
|
879
|
+
|
|
880
|
+
function toggleSelect(idx, el) {
|
|
881
|
+
if (selectedIndices.has(idx)) {
|
|
882
|
+
selectedIndices.delete(idx);
|
|
883
|
+
el.classList.remove('selected');
|
|
884
|
+
} else {
|
|
885
|
+
selectedIndices.add(idx);
|
|
886
|
+
el.classList.add('selected');
|
|
887
|
+
}
|
|
888
|
+
document.getElementById('zipBtn').style.display = selectedIndices.size > 1 ? '' : 'none';
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async function downloadZip() {
|
|
892
|
+
if (selectedIndices.size < 2) return;
|
|
893
|
+
const { default: JSZip } = await import('https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js');
|
|
894
|
+
const zip = new JSZip();
|
|
895
|
+
selectedIndices.forEach(idx => {
|
|
896
|
+
const s = scriptsData[idx];
|
|
897
|
+
const ext = s.type === 'TypeScript' ? '.ts' : '.js';
|
|
898
|
+
zip.file(s.path.replace(/\./g, '/') + ext, s.source || '');
|
|
899
|
+
});
|
|
900
|
+
const blob = await zip.generateAsync({ type: 'blob' });
|
|
901
|
+
const a = document.createElement('a');
|
|
902
|
+
a.href = URL.createObjectURL(blob);
|
|
903
|
+
a.download = 'scripts.zip';
|
|
904
|
+
a.click();
|
|
905
|
+
}
|
|
906
|
+
|
|
774
907
|
function loadScripts(scripts) {
|
|
775
908
|
scriptsData = scripts;
|
|
776
909
|
cur = { index: -1 };
|
|
910
|
+
selectedIndices.clear();
|
|
911
|
+
document.getElementById('zipBtn').style.display = 'none';
|
|
777
912
|
openFolders.clear();
|
|
778
913
|
isAllExpanded = false;
|
|
779
914
|
document.getElementById('expandToggleBtn').innerHTML = '📂';
|
|
@@ -781,7 +916,7 @@
|
|
|
781
916
|
document.getElementById('actionBar').style.display = 'none';
|
|
782
917
|
document.getElementById('codeContainer').className = 'code-empty';
|
|
783
918
|
document.getElementById('codeContainer').innerHTML = scripts.length > 0
|
|
784
|
-
? '// Skript im Baum links auswählen
|
|
919
|
+
? '// Skript im Baum links auswählen… oder mehrere mit Strg+Klick für ZIP'
|
|
785
920
|
: '<span style="color:#dc3545">Keine Skripte in diesem Backup gefunden.</span>';
|
|
786
921
|
}
|
|
787
922
|
|
|
@@ -878,9 +1013,15 @@
|
|
|
878
1013
|
function createScriptNode(s, idx) {
|
|
879
1014
|
const badgeText = s.type === 'TypeScript' ? 'TS' : (s.type === 'Blockly' ? 'Blockly' : (s.type === 'Rules' ? 'RULES' : 'JS'));
|
|
880
1015
|
const div = document.createElement('div');
|
|
881
|
-
div.className = 'script-item' + (cur.index === idx ? ' active' : '');
|
|
1016
|
+
div.className = 'script-item' + (cur.index === idx ? ' active' : '') + (selectedIndices.has(idx) ? ' selected' : '');
|
|
882
1017
|
div.dataset.index = idx;
|
|
883
|
-
div.onclick = () =>
|
|
1018
|
+
div.onclick = (e) => {
|
|
1019
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1020
|
+
toggleSelect(idx, div);
|
|
1021
|
+
} else {
|
|
1022
|
+
selectScript(idx);
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
884
1025
|
div.innerHTML = '<div class="script-name" title="' + escapeHTML(s.path) + '">📄 ' + escapeHTML(s.name) + '</div>' +
|
|
885
1026
|
'<span class="type-badge badge-' + s.type + '">' + badgeText + '</span>';
|
|
886
1027
|
return div;
|