varaverk: add Partner card — known hosts, owner tags, Tailscale online status
This commit is contained in:
@@ -4,6 +4,7 @@ require_once dirname(__DIR__) . '/include/monitor.php';
|
|||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'fallback' => vv_fallback_state(),
|
'fallback' => vv_fallback_state(),
|
||||||
|
'partner' => vv_partner_state(),
|
||||||
'resources' => vv_system_resources(),
|
'resources' => vv_system_resources(),
|
||||||
'cpu' => vv_cpu_per_core(),
|
'cpu' => vv_cpu_per_core(),
|
||||||
'mem' => vv_memory_breakdown(),
|
'mem' => vv_memory_breakdown(),
|
||||||
|
|||||||
@@ -249,6 +249,45 @@ function vv_network_stats(): array {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vv_partner_state(): array {
|
||||||
|
$vars = vv_conf_vars();
|
||||||
|
$myName = trim(shell_exec('hostname -s') ?: '');
|
||||||
|
|
||||||
|
// Parse Tailscale peer status once
|
||||||
|
$tsData = json_decode(shell_exec('tailscale status --json 2>/dev/null') ?: '{}', true) ?? [];
|
||||||
|
$tsPeers = [];
|
||||||
|
foreach ($tsData['Peer'] ?? [] as $peer) {
|
||||||
|
// DNSName is "hostname.tailnet.ts.net." — take the first label (full, not truncated)
|
||||||
|
$dns = $peer['DNSName'] ?? '';
|
||||||
|
$h = $dns ? strtolower(explode('.', $dns)[0]) : strtolower($peer['HostName'] ?? '');
|
||||||
|
if ($h) $tsPeers[$h] = (bool)($peer['Online'] ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hosts = [];
|
||||||
|
foreach (['HOST1', 'HOST2', 'HOST3', 'HOST4'] as $id) {
|
||||||
|
$hostname = $vars[$id] ?? '';
|
||||||
|
if (!$hostname) continue;
|
||||||
|
$isMe = strcasecmp($hostname, $myName) === 0;
|
||||||
|
$isOwner = strcasecmp($id, $vars['PARTNERSHIP_OWNER_HOST'] ?? '') === 0;
|
||||||
|
$online = $isMe ? true : ($tsPeers[strtolower($hostname)] ?? null);
|
||||||
|
$hosts[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'hostname' => $hostname,
|
||||||
|
'owner' => $vars[$id . '_OWNER'] ?? '',
|
||||||
|
'is_me' => $isMe,
|
||||||
|
'is_owner' => $isOwner,
|
||||||
|
'online' => $online,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'enabled' => ($vars['PARTNERSHIP_ENABLED'] ?? 'false') === 'true',
|
||||||
|
'owner_host' => $vars['PARTNERSHIP_OWNER_HOST'] ?? '',
|
||||||
|
'sync_min' => (int)($vars['PARTNERSHIP_SYNC_INTERVAL'] ?? 15),
|
||||||
|
'hosts' => $hosts,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function vv_fallback_state(): array {
|
function vv_fallback_state(): array {
|
||||||
// State file written by fallback.sh
|
// State file written by fallback.sh
|
||||||
$stateFile = '/tmp/fallback_state.db';
|
$stateFile = '/tmp/fallback_state.db';
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
<div id="vv-fallback-desc"></div>
|
<div id="vv-fallback-desc"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="vv-card" id="vv-partner" style="flex:2;min-width:220px;">
|
||||||
|
<h3>Partner</h3>
|
||||||
|
<div id="vv-partner-body">Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="vv-card" id="vv-cpu" style="flex:2;min-width:240px;">
|
<div class="vv-card" id="vv-cpu" style="flex:2;min-width:240px;">
|
||||||
<h3>CPU</h3>
|
<h3>CPU</h3>
|
||||||
<div id="vv-cpu-body">Loading...</div>
|
<div id="vv-cpu-body">Loading...</div>
|
||||||
@@ -318,6 +323,34 @@ function vvPollMonitor() {
|
|||||||
}
|
}
|
||||||
document.getElementById('vv-fallback-desc').innerHTML = descHtml;
|
document.getElementById('vv-fallback-desc').innerHTML = descHtml;
|
||||||
|
|
||||||
|
// ── Partner ─────────────────────────────────────────────────────────────
|
||||||
|
const pt = d.partner ?? {};
|
||||||
|
const ptHosts = pt.hosts ?? [];
|
||||||
|
const ptStatus = pt.enabled
|
||||||
|
? `<span style="color:#4caf50;">enabled</span> · sync every ${pt.sync_min}min`
|
||||||
|
: `<span style="color:#555;">disabled</span>`;
|
||||||
|
let ptHtml = `<div style="font-size:10px;color:#666;margin-bottom:10px;">${ptStatus}</div>`;
|
||||||
|
ptHosts.forEach(h => {
|
||||||
|
const dot = h.online === null ? '#555' : h.online ? '#4caf50' : '#f44336';
|
||||||
|
const label = h.online === null ? 'unknown' : h.online ? 'online' : 'offline';
|
||||||
|
const tags = [
|
||||||
|
h.is_me ? `<span style="background:#1a3a1a;color:#4caf50;font-size:8px;padding:1px 5px;border-radius:3px;margin-left:4px;">US</span>` : '',
|
||||||
|
h.is_owner ? `<span style="background:#1a2a3a;color:#4a9eff;font-size:8px;padding:1px 5px;border-radius:3px;margin-left:4px;">OWNER</span>` : '',
|
||||||
|
].join('');
|
||||||
|
ptHtml += `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
||||||
|
<div>
|
||||||
|
<span style="font-size:10px;color:#555;margin-right:4px;">${h.id}</span>
|
||||||
|
<span style="font-size:12px;color:#ccc;font-weight:500;">${h.owner || h.hostname}</span>
|
||||||
|
${tags}
|
||||||
|
<div style="font-size:10px;color:#666;margin-top:1px;">${h.hostname}</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align:right;">
|
||||||
|
<span style="color:${dot};font-size:10px;">● ${label}</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
document.getElementById('vv-partner-body').innerHTML = ptHtml;
|
||||||
|
|
||||||
// ── CPU ─────────────────────────────────────────────────────────────────
|
// ── CPU ─────────────────────────────────────────────────────────────────
|
||||||
const cpu = d.cpu ?? {};
|
const cpu = d.cpu ?? {};
|
||||||
document.getElementById('vv-cpu-body').innerHTML = vvRenderCpu(cpu);
|
document.getElementById('vv-cpu-body').innerHTML = vvRenderCpu(cpu);
|
||||||
|
|||||||
Reference in New Issue
Block a user