Compare commits

...
2 Commits
Author SHA1 Message Date
Gmer4Lfe b02857cf04 Put the AI master switch on Settings, so the switch that hides the AI tab is reachable without it
Turning AI off takes the AI tools and their conf gear off the Tools card with it, which
left the raw master.conf editor as the only way back on.
2026-08-07 09:49:41 -04:00
Gmer4Lfe be306cf86c Let one gate decide every AI surface, so switching AI off actually removes all of it
The Tools card adopted the AI scripts on the host check alone, and api/ai.php only
tested AI_ENABLED in front of ask, so a disabled subsystem still had rows to run and
an endpoint that answered.
2026-08-07 09:49:31 -04:00
6 changed files with 109 additions and 17 deletions
+1 -2
View File
@@ -83,8 +83,7 @@ $validTabs = ['monitor', 'scheduler', 'docker', 'watchdog', 'partnership', 'fall
// include/ai.php only ever reads the *local* {HOST}_OLLAMA_URL — there is no Tailscale resolver // include/ai.php only ever reads the *local* {HOST}_OLLAMA_URL — there is no Tailscale resolver
// in the PHP layer the way there is in the shell. On any other host the tab could only render // in the PHP layer the way there is in the shell. On any other host the tab could only render
// and then fail its own health check. // and then fail its own health check.
$_vv_ai = vv_is_ai_host() $_vv_ai = vv_ai_ui_on();
&& strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true';
if ($_vv_ai) $validTabs[] = 'ai'; if ($_vv_ai) $validTabs[] = 'ai';
if (!in_array($tab, $validTabs)) $tab = 'monitor'; if (!in_array($tab, $validTabs)) $tab = 'monitor';
+10 -4
View File
@@ -134,6 +134,16 @@ if (!vv_is_ai_host()) {
exit; exit;
} }
// Master switch, on the same footing as the host gate rather than only in front of ask. With
// AI_ENABLED false the tab is not in the tab list and the scheduler dock is not rendered, so
// nothing in the UI can legitimately reach any action here — including the cheap reads, which
// would otherwise still answer with index and token figures for a subsystem the operator has
// turned off. Not a 404: the switch is a setting, and the message names the setting.
if (!vv_ai_enabled()) {
echo json_encode(['ok' => false, 'error' => 'AI_ENABLED is false — AI features are off']);
exit;
}
// ── stats ───────────────────────────────────────────────────────────────────── // ── stats ─────────────────────────────────────────────────────────────────────
if ($action === 'stats') { if ($action === 'stats') {
echo json_encode(['ok' => true, 'stats' => vv_ai_stats()]); echo json_encode(['ok' => true, 'stats' => vv_ai_stats()]);
@@ -220,10 +230,6 @@ if ($action === 'incident_add') {
if ($action === 'ask') { if ($action === 'ask') {
if (!$isPost) { http_response_code(405); echo json_encode(['ok' => false, 'error' => 'POST only']); exit; } if (!$isPost) { http_response_code(405); echo json_encode(['ok' => false, 'error' => 'POST only']); exit; }
if (!vv_ai_enabled()) {
echo json_encode(['ok' => false, 'error' => 'AI_ENABLED is false — AI features are off']); exit;
}
$cfg = vv_ai_config(); $cfg = vv_ai_config();
if ($cfg['model'] === '') { if ($cfg['model'] === '') {
echo json_encode(['ok' => false, 'error' => 'No generation model configured']); exit; echo json_encode(['ok' => false, 'error' => 'No generation model configured']); exit;
+13
View File
@@ -268,6 +268,19 @@ function vv_is_ai_host(): bool {
return vv_detect_host() === 'host1'; return vv_detect_host() === 'host1';
} }
// Whether the UI may offer anything AI at all: the right host, with the master switch on. Every
// AI surface asks this one question — the AI tab, the assistant dock on the Scheduler, and the
// two AI rows on the Tools card — so AI off means AI gone, not gone from most places.
//
// It lives here rather than in include/ai.php because the pages that need it do not all load
// that file; the Scheduler loads only config.php, and a gate that silently answers false where
// its definition is missing is worse than no gate. Reads AI_ENABLED directly for the same
// reason. Fail-closed on anything but the literal "true", matching the conf's own contract.
function vv_ai_ui_on(): bool {
return vv_is_ai_host()
&& strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true';
}
function vv_read_conf_raw(string $filename): string { function vv_read_conf_raw(string $filename): string {
$path = CONF_DIR . '/' . $filename; $path = CONF_DIR . '/' . $filename;
return file_exists($path) ? file_get_contents($path) : ''; return file_exists($path) ? file_get_contents($path) : '';
+6 -6
View File
@@ -319,12 +319,12 @@ function vv_tools_scripts(): array {
// chain. Moving the file to match the UI would break the grouping that explains it. // chain. Moving the file to match the UI would break the grouping that explains it.
$ADOPTED = ['System_Essentials/server_reboot.sh']; $ADOPTED = ['System_Essentials/server_reboot.sh'];
// The AI tools are adopted only where the index and the model actually live. Elsewhere they // The AI tools are adopted only where the index and the model actually live, and only while
// are two rows that can only ever fail — the retrieval index is not synced between hosts and // the subsystem is switched on. Elsewhere they are two rows that can only ever fail — the
// Ollama runs on one of them. vv_is_ai_host() comes from include/ai.php, which the page loads // retrieval index is not synced between hosts, Ollama runs on one of them, and both scripts
// but this file does not require; absent it, assume not, because showing a tool that cannot // refuse on their own unless AI_ENABLED is true. vv_ai_ui_on() is the same gate the AI tab
// work is worse than omitting one that could. // and the assistant dock use, so AI off means no AI anywhere in the UI, not most of it.
if (function_exists('vv_is_ai_host') && vv_is_ai_host()) { if (vv_ai_ui_on()) {
array_push($ADOPTED, 'AI/ai_index.sh', 'AI/ai_query.sh'); array_push($ADOPTED, 'AI/ai_index.sh', 'AI/ai_query.sh');
} }
+5 -4
View File
@@ -70,10 +70,11 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), [
]); ]);
// The assistant dock renders only where the AI tab itself would: HOST1, with AI_ENABLED true. // The assistant dock renders only where the AI tab itself would: HOST1, with AI_ENABLED true.
// Same two conditions, checked the same way, so the page cannot offer a chat the endpoint will // The same gate function, not a second copy of the condition, so the page cannot offer a chat
// refuse — api/ai.php 404s every action off HOST1 regardless of what this page draws. // the endpoint will refuse — api/ai.php rejects every action on both counts regardless of what
$_vv_ai_on = vv_is_ai_host() // this page draws. With it false the dock markup is never emitted, and every caller into the
&& strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true'; // dock is guarded by vvAiDockOn(), which reads the element's absence.
$_vv_ai_on = vv_ai_ui_on();
// Setup mode — auto-open a conf file and force the editing sequence // Setup mode — auto-open a conf file and force the editing sequence
$vv_setup_conf = preg_match('/^[\w.]+\.conf$/', $_GET['vv_setup'] ?? '') $vv_setup_conf = preg_match('/^[\w.]+\.conf$/', $_GET['vv_setup'] ?? '')
+74 -1
View File
@@ -23,7 +23,7 @@
// Values are escaped on render and on write-back. // Values are escaped on render and on write-back.
// //
// RENDERS // RENDERS
// Storage mode selector, global toggles, general conf fields // Storage mode selector, global toggles, AI master switch, general conf fields
// //
// DEPENDS ON // DEPENDS ON
// api/confform.php conf read/write → include/confform.php // api/confform.php conf read/write → include/confform.php
@@ -42,6 +42,13 @@ $_enableLogging = ($_vars['ENABLE_LOGGING'] ?? 'false') === 'true';
$_discordKey = $_myId . '_DISCORD_WEBHOOK'; $_discordKey = $_myId . '_DISCORD_WEBHOOK';
$_discordHook = $_vars[$_discordKey] ?? ''; $_discordHook = $_vars[$_discordKey] ?? '';
// AI. The card is drawn only on the host that can actually run it — elsewhere AI_ENABLED is a
// switch with nothing behind it, since the model and the index are HOST1's. vv_ai_ui_on() is
// not the test here: it folds the switch into the host check, and this card is how the switch
// gets turned back on. Fail-closed on anything but "true", matching the gate it feeds.
$_aiHost = vv_is_ai_host();
$_aiEnabled = strtolower(trim($_vars['AI_ENABLED'] ?? 'false')) === 'true';
?> ?>
<style> <style>
.vv-set-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:14px 16px;margin-bottom:14px; } .vv-set-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:14px 16px;margin-bottom:14px; }
@@ -167,6 +174,35 @@ $_discordHook = $_vars[$_discordKey] ?? '';
<span id="vv-ntf-fb" style="font-size:11px;"></span> <span id="vv-ntf-fb" style="font-size:11px;"></span>
</div> </div>
<?php if ($_aiHost): ?>
<!-- AI card -->
<div class="vv-set-card">
<div class="vv-set-hdr">AI</div>
<label class="vv-set-tog-wrap" style="margin-bottom:12px;">
<div class="vv-set-tog-track<?= $_aiEnabled ? ' on' : '' ?>" id="vv-set-ai-tog"
onclick="vvSetAiToggle(this)"></div>
<div>
<div class="vv-set-tog-lbl">AI features</div>
<div class="vv-set-tog-sub">Master switch — the AI tab, the Scheduler assistant, and the AI tools on the Tools card</div>
</div>
</label>
<div class="vv-set-info">
Varaverk works exactly as well with this off. Every feature that can lean on AI has a
complete non-AI path, so turning it off removes the AI surfaces from the WebGUI and
nothing else — no script changes behaviour, no schedule changes.
<br>
Off is total: with it off the AI tab is not in the tab bar, the Scheduler's assistant dock
is not drawn, the AI tools are not listed, and
<code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">api/ai.php</code>
refuses every request. This card is the way back on.
</div>
<span id="vv-set-ai-fb" style="font-size:11px;color:#444;"></span>
</div>
<?php endif; ?>
<!-- Unraid API Key card --> <!-- Unraid API Key card -->
<div class="vv-set-card"> <div class="vv-set-card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
@@ -356,6 +392,43 @@ function vvNtfSaveWebhook() {
.catch(() => { btn.disabled = false; btn.textContent = 'Save'; fb.style.color='#ef5350'; fb.textContent='Request failed'; }); .catch(() => { btn.disabled = false; btn.textContent = 'Save'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
} }
// ── AI ────────────────────────────────────────────────────────────────────────
// Reloads on success, unlike the notification toggles, because this one changes the page around
// it: the tab bar and the assistant dock are decided server-side at render, so without a reload
// the switch would read "off" with the AI tab still sitting next to it. The switch is moved
// first and reverted if the write fails, matching the toggles above.
function vvSetAiToggle(track) {
const on = !track.classList.contains('on');
const fb = document.getElementById('vv-set-ai-fb');
track.classList.toggle('on', on);
fb.style.color = '#444';
fb.textContent = 'Saving…';
const fd = new URLSearchParams();
fd.append('id', 'settings');
fd.append('changes', JSON.stringify([
{ file: 'master.conf', key: 'AI_ENABLED', 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);
fb.style.color = '#ef5350';
fb.textContent = d.error || 'Failed';
return;
}
fb.style.color = '#4caf50';
fb.textContent = (on ? 'Enabled' : 'Disabled') + ' — reloading…';
setTimeout(() => location.reload(), 700);
})
.catch(() => {
track.classList.toggle('on', !on);
fb.style.color = '#ef5350';
fb.textContent = 'Request failed';
});
}
// ── Unraid API Keys ─────────────────────────────────────────────────────────── // ── Unraid API Keys ───────────────────────────────────────────────────────────
function vvApiCheck() { function vvApiCheck() {
const btn = document.getElementById('vv-api-check-btn'); const btn = document.getElementById('vv-api-check-btn');