Settings: add Notifications card and Unraid API Key card
This commit is contained in:
@@ -95,4 +95,37 @@ if ($action === 'detect' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Unraid API key status ─────────────────────────────────────────────────────
|
||||
if ($action === 'api_status') {
|
||||
require_once dirname(__DIR__) . '/include/unraid_api.php';
|
||||
$data = vv_api_data();
|
||||
$status = vv_api_get_status();
|
||||
$vars = vv_conf_vars();
|
||||
$myHost = vv_detect_host();
|
||||
$keyVar = strtoupper($myHost) . '_UNRAID_API_KEY';
|
||||
$key = $vars[$keyVar] ?? '';
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'key_present' => !empty($key),
|
||||
'key_preview' => $key ? substr($key, 0, 8) . '...' . substr($key, -4) : null,
|
||||
'api_ok' => !empty($key) && !$status['key_missing'] && $status['available'],
|
||||
'key_missing' => $status['key_missing'],
|
||||
'fallbacks' => $status['fallbacks'],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Renew Unraid API key ──────────────────────────────────────────────────────
|
||||
if ($action === 'renew_apikey' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$script = SCRIPTS_DIR . '/unRAID_Essentials/unraid_api_key_renew.sh';
|
||||
if (!file_exists($script)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'unraid_api_key_renew.sh not found']); exit;
|
||||
}
|
||||
set_time_limit(30);
|
||||
$output = []; $exit = 0;
|
||||
exec('bash ' . escapeshellarg($script) . ' 2>&1', $output, $exit);
|
||||
echo json_encode(['ok' => $exit === 0, 'exit' => $exit, 'output' => implode("\n", $output)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => false, 'error' => 'Unknown action']);
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
<?php
|
||||
$_myHost = vv_detect_host();
|
||||
$_vars = vv_conf_vars();
|
||||
$_confKey = strtoupper($_myHost) . '_STORAGE_MODE_INTERNAL';
|
||||
$_myHost = vv_detect_host();
|
||||
$_vars = vv_conf_vars();
|
||||
$_myId = strtoupper($_myHost);
|
||||
|
||||
// Storage
|
||||
$_confKey = $_myId . '_STORAGE_MODE_INTERNAL';
|
||||
$_confVal = $_vars[$_confKey] ?? null;
|
||||
|
||||
// Notifications
|
||||
$_notifyUnraid = ($_vars['NOTIFY_UNRAID'] ?? 'true') !== 'false';
|
||||
$_enableLogging = ($_vars['ENABLE_LOGGING'] ?? 'false') === 'true';
|
||||
$_discordKey = $_myId . '_DISCORD_WEBHOOK';
|
||||
$_discordHook = $_vars[$_discordKey] ?? '';
|
||||
|
||||
// API key
|
||||
$_apiKeyVar = $_myId . '_UNRAID_API_KEY';
|
||||
$_apiKey = $_vars[$_apiKeyVar] ?? '';
|
||||
$_apiPreview = $_apiKey ? substr($_apiKey, 0, 8) . '...' . substr($_apiKey, -4) : '';
|
||||
?>
|
||||
<style>
|
||||
.vv-set-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:14px 16px;margin-bottom:14px; }
|
||||
@@ -31,6 +45,19 @@ $_confVal = $_vars[$_confKey] ?? null;
|
||||
.vv-set-info { font-size:11px;color:#444;line-height:1.6;margin-bottom:10px; }
|
||||
.vv-set-warn-box { background:#1a1200;border:1px solid #3a2800;border-radius:4px;
|
||||
padding:8px 12px;font-size:11px;color:#ffb74d;margin-top:8px;display:none; }
|
||||
.vv-set-tog-wrap { display:flex;align-items:center;gap:10px;cursor:pointer;user-select:none; }
|
||||
.vv-set-tog-track{ width:32px;height:18px;border-radius:9px;background:#222;border:1px solid #333;
|
||||
position:relative;transition:background .15s,border-color .15s;flex-shrink:0; }
|
||||
.vv-set-tog-track.on { background:#1a3a1a;border-color:#2d5a2d; }
|
||||
.vv-set-tog-track::after { content:'';position:absolute;top:2px;left:2px;width:12px;height:12px;
|
||||
border-radius:50%;background:#555;transition:left .15s,background .15s; }
|
||||
.vv-set-tog-track.on::after { left:16px;background:#4caf50; }
|
||||
.vv-set-tog-lbl { font-size:12px;color:#888; }
|
||||
.vv-set-tog-sub { font-size:10px;color:#3a3a3a;margin-top:1px; }
|
||||
.vv-set-inp { background:#0d0d0d;border:1px solid #2a2a2a;border-radius:3px;color:#888;
|
||||
font-size:12px;padding:5px 8px;outline:none;width:100%;box-sizing:border-box;
|
||||
font-family:monospace; }
|
||||
.vv-set-inp:focus{ border-color:#444; }
|
||||
</style>
|
||||
|
||||
<div style="max-width:740px;margin:0 auto;">
|
||||
@@ -73,6 +100,78 @@ $_confVal = $_vars[$_confKey] ?? null;
|
||||
<pre class="vv-set-out" id="vv-stor-out"></pre>
|
||||
</div>
|
||||
|
||||
<!-- Notifications card -->
|
||||
<div class="vv-set-card">
|
||||
<div class="vv-set-hdr">Notifications</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:10px;margin-bottom:14px;">
|
||||
<label class="vv-set-tog-wrap">
|
||||
<div class="vv-set-tog-track<?= $_notifyUnraid ? ' on' : '' ?>" id="vv-ntf-unraid"
|
||||
onclick="vvNtfToggle(this,'NOTIFY_UNRAID')"></div>
|
||||
<div>
|
||||
<div class="vv-set-tog-lbl">Unraid native notifications</div>
|
||||
<div class="vv-set-tog-sub">Appears in the Unraid bell icon — job completions, warnings, errors</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="vv-set-tog-wrap">
|
||||
<div class="vv-set-tog-track<?= $_enableLogging ? ' on' : '' ?>" id="vv-ntf-logging"
|
||||
onclick="vvNtfToggle(this,'ENABLE_LOGGING')"></div>
|
||||
<div>
|
||||
<div class="vv-set-tog-lbl">Verbose logging</div>
|
||||
<div class="vv-set-tog-sub">Adds detailed [LOG] lines to script output — useful for debugging scheduled runs</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<hr class="vv-set-sep">
|
||||
|
||||
<div style="margin-bottom:6px;">
|
||||
<div style="font-size:11px;color:#555;margin-bottom:5px;">
|
||||
Discord webhook
|
||||
<span style="color:#2a2a2a;font-size:10px;margin-left:4px;"><?= htmlspecialchars($_discordKey) ?></span>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;align-items:center;">
|
||||
<input class="vv-set-inp" id="vv-ntf-discord" type="text"
|
||||
placeholder="https://discord.com/api/webhooks/…"
|
||||
value="<?= htmlspecialchars($_discordHook) ?>">
|
||||
<button class="vv-set-btn primary" onclick="vvNtfSaveWebhook()" id="vv-ntf-save"
|
||||
style="white-space:nowrap;flex-shrink:0;">Save</button>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#2a2a2a;margin-top:4px;">Leave blank to disable Discord notifications for this host</div>
|
||||
</div>
|
||||
<span id="vv-ntf-fb" style="font-size:11px;"></span>
|
||||
</div>
|
||||
|
||||
<!-- Unraid API Key card -->
|
||||
<div class="vv-set-card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<span class="vv-set-hdr" style="margin-bottom:0;">Unraid API Key</span>
|
||||
<span id="vv-api-badge" class="vv-set-badge"><?= $_apiKey ? 'present' : 'missing' ?></span>
|
||||
</div>
|
||||
|
||||
<div id="vv-api-info" style="font-size:11px;color:#3a3a3a;margin-bottom:12px;">
|
||||
<?php if ($_apiPreview): ?>
|
||||
Key: <code style="color:#4a6a4a;font-size:10px;"><?= htmlspecialchars($_apiPreview) ?></code>
|
||||
· Var: <code style="color:#3a3a3a;font-size:10px;"><?= htmlspecialchars($_apiKeyVar) ?></code>
|
||||
<?php else: ?>
|
||||
No key found in <?= htmlspecialchars($_myHost) ?>.conf — click Renew to register one.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="font-size:11px;color:#444;margin-bottom:12px;line-height:1.6;">
|
||||
The Unraid API key is ephemeral — it clears on OS updates and service restarts.
|
||||
<code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">unraid_api_key_renew.sh</code>
|
||||
runs at array start to auto-recover, but if the monitor page shows API errors, renew here.
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<button class="vv-set-btn" onclick="vvApiCheck()" id="vv-api-check-btn">Check status</button>
|
||||
<button class="vv-set-btn primary" onclick="vvApiRenew()" id="vv-api-renew-btn">Renew key</button>
|
||||
<span id="vv-api-fb" style="font-size:11px;color:#444;"></span>
|
||||
</div>
|
||||
<pre class="vv-set-out" id="vv-api-out"></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -198,4 +297,86 @@ function vvStorMigrate() {
|
||||
}
|
||||
|
||||
vvStorLoad();
|
||||
|
||||
// ── Notifications ─────────────────────────────────────────────────────────────
|
||||
function vvNtfToggle(track, key) {
|
||||
const on = !track.classList.contains('on');
|
||||
track.classList.toggle('on', on);
|
||||
const file = (key === 'NOTIFY_UNRAID' || key === 'ENABLE_LOGGING') ? 'master.conf'
|
||||
: '<?= htmlspecialchars($_myHost) ?>.conf';
|
||||
const fd = new FormData();
|
||||
fd.append('id', 'settings');
|
||||
fd.append('changes', JSON.stringify([{ file, key, value: on ? 'true' : 'false', type: 'scalar' }]));
|
||||
fetch('/plugins/varaverk/api/confform.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => { if (!d.ok) track.classList.toggle('on', !on); })
|
||||
.catch(() => track.classList.toggle('on', !on));
|
||||
}
|
||||
|
||||
function vvNtfSaveWebhook() {
|
||||
const inp = document.getElementById('vv-ntf-discord');
|
||||
const fb = document.getElementById('vv-ntf-fb');
|
||||
const btn = document.getElementById('vv-ntf-save');
|
||||
btn.disabled = true; btn.textContent = 'Saving…'; fb.textContent = '';
|
||||
const fd = new FormData();
|
||||
fd.append('id', 'settings');
|
||||
fd.append('changes', JSON.stringify([{
|
||||
file: '<?= htmlspecialchars($_myHost) ?>.conf',
|
||||
key: '<?= htmlspecialchars($_discordKey) ?>',
|
||||
value: inp.value.trim(),
|
||||
type: 'scalar',
|
||||
}]));
|
||||
fetch('/plugins/varaverk/api/confform.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false; btn.textContent = 'Save';
|
||||
fb.style.color = d.ok ? '#4caf50' : '#ef5350';
|
||||
fb.textContent = d.ok ? 'Saved ✓' : (d.error || 'Failed');
|
||||
if (d.ok) setTimeout(() => { fb.textContent = ''; }, 3000);
|
||||
})
|
||||
.catch(() => { btn.disabled = false; btn.textContent = 'Save'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
||||
}
|
||||
|
||||
// ── Unraid API Key ────────────────────────────────────────────────────────────
|
||||
function vvApiCheck() {
|
||||
const btn = document.getElementById('vv-api-check-btn');
|
||||
const fb = document.getElementById('vv-api-fb');
|
||||
const badge= document.getElementById('vv-api-badge');
|
||||
btn.disabled = true; btn.textContent = 'Checking…'; fb.textContent = '';
|
||||
fetch('/plugins/varaverk/api/storage.php?action=api_status&_=' + Date.now())
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false; btn.textContent = 'Check status';
|
||||
if (!d.ok) { fb.style.color='#ef5350'; fb.textContent='Request failed'; return; }
|
||||
badge.textContent = d.api_ok ? 'ok' : d.key_missing ? 'missing' : 'error';
|
||||
badge.className = 'vv-set-badge ' + (d.api_ok ? 'internal' : 'flash');
|
||||
fb.style.color = d.api_ok ? '#4caf50' : '#ef5350';
|
||||
fb.textContent = d.api_ok ? 'API responding ✓'
|
||||
: d.key_missing ? 'Key missing from conf — click Renew'
|
||||
: 'API unreachable — ' + (d.fallbacks?.length ? d.fallbacks.join(', ') + ' using fallback' : 'check Unraid API service');
|
||||
})
|
||||
.catch(() => { btn.disabled = false; btn.textContent = 'Check status'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
||||
}
|
||||
|
||||
function vvApiRenew() {
|
||||
const btn = document.getElementById('vv-api-renew-btn');
|
||||
const out = document.getElementById('vv-api-out');
|
||||
const fb = document.getElementById('vv-api-fb');
|
||||
const badge= document.getElementById('vv-api-badge');
|
||||
btn.disabled = true; btn.textContent = 'Renewing…'; fb.textContent = '';
|
||||
out.textContent = ''; out.style.display = '';
|
||||
const fd = new FormData(); fd.append('action', 'renew_apikey');
|
||||
fetch('/plugins/varaverk/api/storage.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false; btn.textContent = 'Renew key';
|
||||
out.textContent = d.output || '(no output)';
|
||||
out.scrollTop = out.scrollHeight;
|
||||
badge.textContent = d.ok ? 'ok' : 'error';
|
||||
badge.className = 'vv-set-badge ' + (d.ok ? 'internal' : 'flash');
|
||||
fb.style.color = d.ok ? '#4caf50' : '#ef5350';
|
||||
fb.textContent = d.ok ? 'Key renewed ✓' : 'Renewal failed — see output';
|
||||
})
|
||||
.catch(() => { btn.disabled = false; btn.textContent = 'Renew key'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user