Add Varaverk plugin scaffold (PHP, unRAID native)
Plugin lives in Plugin/ — invisible to User Script Plugin. dev_install.sh symlinks into /usr/local/emhttp/plugins/varaverk/ for development. Pages: Monitor (5s poll), Scheduler (orchs + Advanced children, toggle + cron), Config (raw editor, host-aware file access), Docs (markdown + live $VAR substitution). API endpoints: monitor.php (JSON), scheduler.php (writes schedule.json + cron.d), config.php (writes conf files with host permission check). Includes: config.php (parser, host detection, var map), scheduler.php (job tree, cron.d rebuild), monitor.php (docker, GPU, resources, fallback, transcode), docs.php (file tree, var substitution, Parsedown renderer).
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
$files = vv_get_conf_files();
|
||||
$active = $_GET['conf'] ?? ($files[0] ?? '');
|
||||
if (!in_array($active, $files)) $active = $files[0] ?? '';
|
||||
?>
|
||||
|
||||
<div id="vv-config">
|
||||
|
||||
<!-- File selector -->
|
||||
<div id="vv-conf-tabs">
|
||||
<?php foreach ($files as $f): ?>
|
||||
<a href="?tab=config&conf=<?= urlencode($f) ?>"
|
||||
class="vv-conf-tab<?= $f === $active ? ' active' : '' ?>">
|
||||
<?= htmlspecialchars($f) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($active): ?>
|
||||
<form id="vv-conf-form">
|
||||
<input type="hidden" name="file" value="<?= htmlspecialchars($active) ?>">
|
||||
<textarea id="vv-conf-editor" name="content" spellcheck="false"><?=
|
||||
htmlspecialchars(vv_read_conf_raw($active))
|
||||
?></textarea>
|
||||
<div id="vv-conf-actions">
|
||||
<button type="button" onclick="vvSaveConf()">Save</button>
|
||||
<span id="vv-conf-status"></span>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<p>No configuration files found.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function vvSaveConf() {
|
||||
const form = document.getElementById('vv-conf-form');
|
||||
const file = form.querySelector('[name=file]').value;
|
||||
const content = form.querySelector('[name=content]').value;
|
||||
const status = document.getElementById('vv-conf-status');
|
||||
|
||||
status.textContent = 'Saving...';
|
||||
fetch('/plugins/varaverk/api/config.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({file, content})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => { status.textContent = d.ok ? '✓ Saved' : '✗ ' + (d.error ?? 'Error'); })
|
||||
.catch(() => { status.textContent = '✗ Request failed'; });
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__) . '/include/docs.php';
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
$tree = vv_docs_tree();
|
||||
$vars = vv_conf_vars();
|
||||
$active = $_GET['doc'] ?? '';
|
||||
|
||||
// Validate: must be a .md file within SCRIPTS_DIR
|
||||
$active = preg_match('/^[a-zA-Z0-9_\-\/]+\.md$/', $active) ? $active : '';
|
||||
if ($active && !file_exists(SCRIPTS_DIR . '/' . $active)) $active = '';
|
||||
?>
|
||||
|
||||
<div id="vv-docs">
|
||||
|
||||
<div id="vv-docs-sidebar">
|
||||
<h3>Documents</h3>
|
||||
<ul>
|
||||
<?php foreach ($tree as $rel): ?>
|
||||
<li>
|
||||
<a href="?tab=docs&doc=<?= urlencode($rel) ?>"
|
||||
class="<?= $rel === $active ? 'active' : '' ?>">
|
||||
<?= htmlspecialchars($rel) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="vv-docs-content">
|
||||
<?php if ($active): ?>
|
||||
<div class="vv-doc-body">
|
||||
<?= vv_docs_render($active, $vars) ?>
|
||||
</div>
|
||||
<p class="vv-doc-hint">
|
||||
Values shown in <code class="vv-live-var">green</code> are live from your conf files.
|
||||
<code class="vv-unknown-var">Orange</code> means the variable was not found.
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p>Select a document from the sidebar.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php require_once dirname(__DIR__) . '/include/monitor.php'; ?>
|
||||
|
||||
<div id="vv-monitor">
|
||||
|
||||
<div class="vv-row">
|
||||
<div class="vv-card" id="vv-fallback">
|
||||
<h3>Fallback State</h3>
|
||||
<div class="vv-state-badge" id="vv-fallback-state">—</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-resources">
|
||||
<h3>Resources</h3>
|
||||
<div id="vv-resource-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-gpu-card">
|
||||
<h3>GPU</h3>
|
||||
<div id="vv-gpu-body">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-row">
|
||||
<div class="vv-card vv-wide" id="vv-transcode">
|
||||
<h3>Transcode</h3>
|
||||
<div id="vv-transcode-body">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-row">
|
||||
<div class="vv-card vv-wide" id="vv-docker">
|
||||
<h3>Containers</h3>
|
||||
<table id="vv-docker-table">
|
||||
<thead><tr><th>Name</th><th>Status</th><th>Image</th></tr></thead>
|
||||
<tbody id="vv-docker-body"><tr><td colspan="3">Loading...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Poll monitor API every 5 seconds
|
||||
function vvPollMonitor() {
|
||||
fetch('/plugins/varaverk/api/monitor.php')
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
// Fallback state
|
||||
const fb = d.fallback ?? {};
|
||||
const badge = document.getElementById('vv-fallback-state');
|
||||
badge.textContent = fb.state ?? '—';
|
||||
badge.className = 'vv-state-badge vv-state-' + (fb.state ?? 'unknown').toLowerCase();
|
||||
|
||||
// Resources
|
||||
const res = d.resources ?? {};
|
||||
document.getElementById('vv-resource-body').innerHTML =
|
||||
`<p>RAM: ${res.ram_free_mb ?? '—'} MB free / ${res.ram_total_mb ?? '—'} MB</p>
|
||||
<p>CPU: ${res.cpu_percent ?? '—'}%</p>
|
||||
<p>Ramdisk: ${res.ramdisk?.used_mb ?? '—'} MB / ${res.ramdisk?.size_mb ?? '—'} MB</p>`;
|
||||
|
||||
// GPU
|
||||
const gpu = d.gpu ?? {};
|
||||
if (gpu.available) {
|
||||
document.getElementById('vv-gpu-body').innerHTML =
|
||||
`<p>${gpu.name}</p>
|
||||
<p>VRAM: ${gpu.memory_used} / ${gpu.memory_total} MB</p>
|
||||
<p>Util: ${gpu.utilization}% Temp: ${gpu.temperature}°C</p>`;
|
||||
} else {
|
||||
document.getElementById('vv-gpu-body').innerHTML = '<p>No GPU detected</p>';
|
||||
}
|
||||
|
||||
// Docker
|
||||
const containers = d.containers ?? [];
|
||||
const tbody = document.getElementById('vv-docker-body');
|
||||
tbody.innerHTML = containers.length
|
||||
? containers.map(c =>
|
||||
`<tr><td>${c.name}</td><td class="vv-status-${c.status.startsWith('Up') ? 'up' : 'down'}">${c.status}</td><td>${c.image}</td></tr>`
|
||||
).join('')
|
||||
: '<tr><td colspan="3">No running containers</td></tr>';
|
||||
})
|
||||
.catch(() => {}); // silent on poll failure
|
||||
}
|
||||
|
||||
vvPollMonitor();
|
||||
setInterval(vvPollMonitor, 5000);
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__) . '/include/scheduler.php';
|
||||
$tree = vv_job_tree();
|
||||
?>
|
||||
|
||||
<div id="vv-scheduler">
|
||||
|
||||
<p class="vv-hint">Orchestrators run their child scripts in the correct order.
|
||||
Disable an orchestrator and enable individual scripts below it to run them
|
||||
in isolation for debugging or testing.</p>
|
||||
|
||||
<?php foreach ($tree as $orch): ?>
|
||||
<div class="vv-job vv-orch" data-id="<?= htmlspecialchars($orch['id']) ?>">
|
||||
<div class="vv-job-row">
|
||||
<label class="vv-toggle">
|
||||
<input type="checkbox" class="vv-enabled"
|
||||
<?= $orch['enabled'] ? 'checked' : '' ?>
|
||||
onchange="vvSaveJob(this)">
|
||||
<span class="vv-slider"></span>
|
||||
</label>
|
||||
<span class="vv-job-label"><?= htmlspecialchars($orch['label']) ?></span>
|
||||
<input type="text" class="vv-cron" value="<?= htmlspecialchars($orch['cron']) ?>"
|
||||
placeholder="cron expression" onblur="vvSaveJob(this)">
|
||||
<span class="vv-job-status" id="vv-status-<?= md5($orch['id']) ?>"></span>
|
||||
<?php if (!empty($orch['children'])): ?>
|
||||
<button class="vv-advanced-toggle" onclick="vvToggleAdvanced(this)">Advanced ▸</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($orch['children'])): ?>
|
||||
<div class="vv-children" style="display:none;">
|
||||
<?php foreach ($orch['children'] as $child): ?>
|
||||
<div class="vv-job vv-script" data-id="<?= htmlspecialchars($child['id']) ?>">
|
||||
<div class="vv-job-row">
|
||||
<label class="vv-toggle">
|
||||
<input type="checkbox" class="vv-enabled"
|
||||
<?= $child['enabled'] ? 'checked' : '' ?>
|
||||
onchange="vvSaveJob(this)">
|
||||
<span class="vv-slider"></span>
|
||||
</label>
|
||||
<span class="vv-job-label"><?= htmlspecialchars($child['label']) ?></span>
|
||||
<input type="text" class="vv-cron" value="<?= htmlspecialchars($child['cron']) ?>"
|
||||
placeholder="cron expression" onblur="vvSaveJob(this)">
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function vvSaveJob(el) {
|
||||
const row = el.closest('.vv-job');
|
||||
const id = row.dataset.id;
|
||||
const enabled = row.querySelector('.vv-enabled').checked;
|
||||
const cron = row.querySelector('.vv-cron').value.trim();
|
||||
|
||||
fetch('/plugins/varaverk/api/scheduler.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id, enabled, cron})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
const statusEl = document.getElementById('vv-status-' + btoa(id).replace(/=/g,''));
|
||||
if (statusEl) statusEl.textContent = d.ok ? '✓' : '✗ ' + (d.error ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
function vvToggleAdvanced(btn) {
|
||||
const children = btn.closest('.vv-job').querySelector('.vv-children');
|
||||
const visible = children.style.display !== 'none';
|
||||
children.style.display = visible ? 'none' : 'block';
|
||||
btn.textContent = visible ? 'Advanced ▸' : 'Advanced ▾';
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user