Move Custom Scripts out of the repo and add an Import Script picker
Custom Scripts (the Scheduler page's inline editor) used to save into the git-tracked Custom/ folder, so anything saved there would end up on GitHub. They now live in /boot/config/plugins/user.scripts/Varaverk/Scripts, same folder family as Unraid's own User Scripts plugin. Import Script lets you browse the whole server and move an existing script in instead of only creating new ones inline — always a move, never a copy, so no stray duplicate is left where it came from.
This commit is contained in:
@@ -272,7 +272,7 @@ $runningScripts = array_unique($runningScripts);
|
||||
</div>
|
||||
<div class="vv-children" id="vv-custom-children" style="display:none;">
|
||||
<?php if (empty($customs)): ?>
|
||||
<p class="vv-custom-empty">No custom scripts yet — click <strong>+ Add Script</strong> to create one.</p>
|
||||
<p class="vv-custom-empty">No custom scripts yet — click <strong>+ Create Script</strong> to create one.</p>
|
||||
<?php else: ?>
|
||||
|
||||
<?php // ── Folder groups ──────────────────────────────────────────────
|
||||
@@ -362,7 +362,8 @@ $runningScripts = array_unique($runningScripts);
|
||||
</div><!-- /#vv-sched-cards -->
|
||||
|
||||
<div class="vv-sched-footer">
|
||||
<button class="vv-save-btn vv-add-script-btn" onclick="vvAddScript()">+ Add Script</button>
|
||||
<button class="vv-save-btn vv-add-script-btn" onclick="vvAddScript()">+ Create Script</button>
|
||||
<button class="vv-save-btn vv-import-script-btn" onclick="vvImportScriptOpen()" title="Move an existing script from anywhere on the server into Custom Scripts">+ Import Script</button>
|
||||
<button class="vv-save-btn vv-new-folder-btn" onclick="vvNewFolder()" title="Create a folder in Custom Scripts">+ Folder</button>
|
||||
<button id="vv-arrange-btn" class="vv-save-btn" onclick="vvToggleArrange()" title="Drag scripts between orchestrators">Arrange</button>
|
||||
<button id="vv-arrange-save-btn" class="vv-save-btn vv-arrange-save-btn" onclick="vvSaveArrange()" style="display:none">Save Arrangement</button>
|
||||
@@ -1044,6 +1045,9 @@ function vvLH() {
|
||||
let vvSetupConf = <?= json_encode($vv_setup_conf) ?>;
|
||||
const vvLocalHostConf = <?= json_encode($_vv_local_host_conf) ?>;
|
||||
|
||||
// Where Custom Scripts (Create + Import) actually live — shown in the Import Script dialog
|
||||
window.__vvCustomScriptsDir = <?= json_encode(CUSTOM_SCRIPTS_DIR) ?>;
|
||||
|
||||
if (vvSetupConf) {
|
||||
// Auto-open the setup conf file once the page is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -1718,6 +1722,191 @@ function vvDeleteScript() {
|
||||
.catch(() => { btn.disabled = false; btn.textContent = '\u{1F5D1} Delete'; });
|
||||
}
|
||||
|
||||
// ── Import Script — browse the whole server, move a chosen .sh into Custom Scripts ──
|
||||
|
||||
let _vvImpSelected = null; // full path of the currently-selected file, or null
|
||||
|
||||
function vvImportScriptOpen() {
|
||||
let modal = document.getElementById('vv-import-modal');
|
||||
if (!modal) modal = _vvImpBuildModal();
|
||||
modal.style.display = 'flex';
|
||||
_vvImpSelected = null;
|
||||
_vvImpUpdateFooter();
|
||||
_vvImpBrowse('/');
|
||||
}
|
||||
|
||||
function vvImportScriptClose() {
|
||||
const modal = document.getElementById('vv-import-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
_vvImpSelected = null;
|
||||
}
|
||||
|
||||
function _vvImpBuildModal() {
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'vv-import-modal';
|
||||
modal.style.cssText = 'display:none;position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.6);'
|
||||
+ 'align-items:center;justify-content:center;';
|
||||
modal.addEventListener('mousedown', e => { if (e.target === modal) vvImportScriptClose(); });
|
||||
|
||||
const box = document.createElement('div');
|
||||
box.style.cssText = 'background:#111;border:1px solid #222;border-radius:6px;width:560px;max-width:92vw;'
|
||||
+ 'max-height:80vh;display:flex;flex-direction:column;overflow:hidden;';
|
||||
|
||||
box.innerHTML = `
|
||||
<div style="padding:10px 14px;border-bottom:1px solid #1e1e1e;display:flex;align-items:center;justify-content:space-between;">
|
||||
<span style="font-size:13px;font-weight:bold;color:#ccc;">Import Script</span>
|
||||
<button onclick="vvImportScriptClose()" style="background:none;border:none;color:#666;cursor:pointer;font-size:14px;">✕</button>
|
||||
</div>
|
||||
<div style="padding:10px 14px;border-bottom:1px solid #1e1e1e;">
|
||||
<div style="font-size:10px;color:#555;margin-bottom:6px;">
|
||||
Moves the selected script into Custom Scripts (<code>${_vvImpEsc(window.__vvCustomScriptsDir || '')}</code>).
|
||||
The original is removed once the copy is verified.
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;align-items:center;">
|
||||
<input id="vv-imp-path" type="text" value="/" style="flex:1;min-width:0;background:#0a0a0a;border:1px solid #222;
|
||||
color:#aaa;border-radius:3px;padding:4px 8px;font-size:11px;font-family:monospace;"
|
||||
onkeydown="if(event.key==='Enter')_vvImpBrowse(document.getElementById('vv-imp-path').value.trim()||'/')">
|
||||
<button onclick="_vvImpNavUp()" title="Parent" style="background:#111;border:1px solid #222;color:#555;
|
||||
border-radius:3px;padding:4px 8px;cursor:pointer;font-size:12px;flex-shrink:0;">↑</button>
|
||||
<button onclick="_vvImpBrowse(document.getElementById('vv-imp-path').value.trim()||'/')" style="background:#111;
|
||||
border:1px solid #222;color:#4a9eff;border-radius:3px;padding:4px 9px;cursor:pointer;font-size:11px;
|
||||
white-space:nowrap;flex-shrink:0;">↻ Browse</button>
|
||||
</div>
|
||||
<span id="vv-imp-err" style="font-size:9px;color:#ef5350;display:none;margin-top:4px;"></span>
|
||||
</div>
|
||||
<div id="vv-imp-list" style="flex:1;overflow-y:auto;background:#080808;min-height:220px;"></div>
|
||||
<div style="padding:10px 14px;border-top:1px solid #1e1e1e;display:flex;align-items:center;justify-content:space-between;gap:10px;">
|
||||
<span id="vv-imp-selected" style="font-size:10px;color:#555;font-family:monospace;flex:1;min-width:0;
|
||||
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></span>
|
||||
<div style="display:flex;gap:6px;flex-shrink:0;">
|
||||
<button onclick="vvImportScriptClose()" class="vv-btn-sm">Cancel</button>
|
||||
<button id="vv-imp-do-btn" class="vv-btn-sm vv-save-script-btn-style" disabled onclick="_vvImpDoImport()">Import</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.appendChild(box);
|
||||
document.body.appendChild(modal);
|
||||
return modal;
|
||||
}
|
||||
|
||||
function _vvImpEsc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function _vvImpBrowse(path) {
|
||||
const listEl = document.getElementById('vv-imp-list');
|
||||
const errEl = document.getElementById('vv-imp-err');
|
||||
const pathEl = document.getElementById('vv-imp-path');
|
||||
errEl.style.display = 'none';
|
||||
listEl.innerHTML = '<div style="padding:10px;color:#444;font-size:11px;">Loading…</div>';
|
||||
|
||||
fetch(`/plugins/varaverk/api/import_script.php?action=browse&path=${encodeURIComponent(path)}&_=${Date.now()}`)
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (!d.ok) {
|
||||
listEl.innerHTML = '';
|
||||
errEl.textContent = d.error || 'Browse failed';
|
||||
errEl.style.display = '';
|
||||
return;
|
||||
}
|
||||
pathEl.value = d.path;
|
||||
_vvImpRender(d);
|
||||
})
|
||||
.catch(e => {
|
||||
listEl.innerHTML = '';
|
||||
errEl.textContent = 'Request failed: ' + e;
|
||||
errEl.style.display = '';
|
||||
});
|
||||
}
|
||||
|
||||
function _vvImpNavUp() {
|
||||
const cur = (document.getElementById('vv-imp-path').value || '/').replace(/\/$/, '') || '/';
|
||||
const up = cur === '/' ? '/' : (cur.substring(0, cur.lastIndexOf('/')) || '/');
|
||||
_vvImpBrowse(up);
|
||||
}
|
||||
|
||||
function _vvImpRender(d) {
|
||||
const listEl = document.getElementById('vv-imp-list');
|
||||
let html = '';
|
||||
|
||||
if (d.parent !== null) {
|
||||
const pname = d.parent === '/' ? '/' : (d.parent.replace(/^.*\//, '') || d.parent) + '/';
|
||||
html += `<div class="vv-imp-row" onclick="_vvImpBrowse(${JSON.stringify(d.parent)})"
|
||||
style="padding:5px 12px;font-size:11px;color:#444;font-style:italic;cursor:pointer;font-family:monospace;">↑ ${pname}</div>`;
|
||||
}
|
||||
|
||||
for (const dir of d.dirs) {
|
||||
const name = dir.replace(/^.*\//, '') || dir;
|
||||
html += `<div class="vv-imp-row" onclick="_vvImpBrowse(${JSON.stringify(dir)})" title="${_vvImpEsc(dir)}"
|
||||
style="padding:5px 12px;font-size:11px;color:#666;cursor:pointer;font-family:monospace;">▶ ${_vvImpEsc(name)}</div>`;
|
||||
}
|
||||
|
||||
for (const file of d.files) {
|
||||
const name = file.replace(/^.*\//, '') || file;
|
||||
const sel = file === _vvImpSelected;
|
||||
html += `<div class="vv-imp-row vv-imp-file${sel ? ' vv-imp-file-sel' : ''}" data-path="${_vvImpEsc(file)}"
|
||||
onclick="_vvImpSelectFile(${JSON.stringify(file)})" title="${_vvImpEsc(file)}"
|
||||
style="padding:5px 12px;font-size:11px;cursor:pointer;font-family:monospace;
|
||||
color:${sel ? '#4caf50' : '#4a9eff'};background:${sel ? '#0f1f0f' : 'transparent'};">📄 ${_vvImpEsc(name)}</div>`;
|
||||
}
|
||||
|
||||
if (!d.dirs.length && !d.files.length) {
|
||||
html += '<div style="padding:10px 12px;color:#333;font-size:11px;">— empty —</div>';
|
||||
}
|
||||
|
||||
listEl.innerHTML = html;
|
||||
}
|
||||
|
||||
function _vvImpSelectFile(path) {
|
||||
_vvImpSelected = path;
|
||||
_vvImpUpdateFooter();
|
||||
// Re-render just the highlight without a re-fetch.
|
||||
document.querySelectorAll('#vv-imp-list .vv-imp-file').forEach(el => {
|
||||
const isSel = el.dataset.path === path;
|
||||
el.classList.toggle('vv-imp-file-sel', isSel);
|
||||
el.style.color = isSel ? '#4caf50' : '#4a9eff';
|
||||
el.style.background = isSel ? '#0f1f0f' : 'transparent';
|
||||
});
|
||||
}
|
||||
|
||||
function _vvImpUpdateFooter() {
|
||||
const sel = document.getElementById('vv-imp-selected');
|
||||
const btn = document.getElementById('vv-imp-do-btn');
|
||||
if (!sel || !btn) return;
|
||||
sel.textContent = _vvImpSelected || '';
|
||||
btn.disabled = !_vvImpSelected;
|
||||
}
|
||||
|
||||
function _vvImpDoImport() {
|
||||
if (!_vvImpSelected) return;
|
||||
const dest = (window.__vvCustomScriptsDir || 'Custom Scripts') + '/' + _vvImpSelected.replace(/^.*\//, '');
|
||||
if (!confirm('Move\n ' + _vvImpSelected + '\n→ ' + dest + '\n\nThe original will be deleted once the copy is verified. Continue?')) return;
|
||||
|
||||
const btn = document.getElementById('vv-imp-do-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Importing…';
|
||||
|
||||
vvPost('/plugins/varaverk/api/import_script.php', {action: 'import', path: _vvImpSelected})
|
||||
.then(d => {
|
||||
if (!d.ok) {
|
||||
alert('Import failed: ' + (d.error || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Import';
|
||||
return;
|
||||
}
|
||||
if (d.warning) alert(d.warning);
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(e => {
|
||||
alert('Import failed: ' + e);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Import';
|
||||
});
|
||||
}
|
||||
|
||||
function vvShowConfMode(title) {
|
||||
document.getElementById('vv-suggestions').style.display = 'none';
|
||||
document.getElementById('vv-log-pre').style.display = 'none';
|
||||
|
||||
Reference in New Issue
Block a user