Plugin: common.php library, watchdog expansion, monitor + scheduler improvements
PHP architecture: - Extract common.php from monitor.php — shared system functions (vv_system_info, vv_memory_breakdown, vv_remote_hosts_stats, disk/GPU/UPS/network/docker/parity, etc.) now live in one place; monitor.php and watchdog.php both require common.php - Add unraid_api.php as explicit include (was implicit via config.php chain) - confform.php: add missing require_once config.php (implicit dep made explicit) - Delete orphaned pages/docs.php and pages/config.php (absorbed into scheduler) Watchdog page: - Add Storage watchdog card (growth + log strikes, baseline age, suppress ceilings) - Add Network watchdog card (NPM strikes, DDNS domain/container, NPM URL) - One host per row layout — all 5 watchdog cards equally spaced via inner grid - Watchdog now uses Unraid API for local system stats; remote nodes with API key but no SSH get system info from vv_remote_hosts_stats() with api_only flag - SSH bundle: /proc/meminfo passed as raw section instead of awk-parsed header fields — fixes RAM showing 0 on remote hosts where awk quoting was unreliable - vv_wd_local_system() rewritten as thin wrapper over vv_system_info() + vv_memory_breakdown() Monitor page: - Watchdog card: add stability strikes, storage watchdog strikes, network NPM status, live system stats (rootfs/log/tmp %, RAM free, load, CPU temp, zombies, NIC, sshd) - Row height: switch from max-height on cards to minmax(0, calc(...)) on grid track — all cards in a row now fill to the tallest card's height correctly (fixes Pools card being shorter than neighbours) Scheduler page: - Add Tools section above Custom Scripts — lists Tools/*.sh with run/dry-run/cron/log - vv_tools_scripts() function in scheduler.php include Tools: - Add docker_prune_images.sh — removes dangling Docker images; --dry-run and --status modes
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
// Unraid GraphQL API — single-request master fetch + per-function fallback tracking.
|
||||
// All API-first functions call vv_api_data() then fall back to local reads on null.
|
||||
//
|
||||
// Confirmed schema (introspected 2026-05-29):
|
||||
// InfoOs: hostname, uptime (String), release — no version/uptime-as-int
|
||||
// InfoCpu: brand, threads, cores — no physicalCores/currentLoad
|
||||
// InfoMemory: layout only — NO usage fields; memory usage stays as local read
|
||||
// ArrayDisk: single list for all types (DATA/PARITY/CACHE); fields: name, device,
|
||||
// type (ArrayDiskType enum), status (ArrayDiskStatus enum), size (BigInt),
|
||||
// fsSize, fsFree, fsUsed (BigInt), temp, transport (String), rotational,
|
||||
// isSpinning — no free/mounted
|
||||
// VmDomain: name, state (VmState enum) — no memory/vcpus
|
||||
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
// ── Fallback tracking ─────────────────────────────────────────────────────────
|
||||
|
||||
function &_vv_api_fallbacks(): array { static $f = []; return $f; }
|
||||
|
||||
function vv_api_record_fallback(string $fn): void {
|
||||
$f = &_vv_api_fallbacks();
|
||||
$f[] = $fn;
|
||||
}
|
||||
|
||||
function vv_api_get_status(): array {
|
||||
$fallbacks = array_unique(_vv_api_fallbacks());
|
||||
return [
|
||||
'available' => empty($fallbacks),
|
||||
'fallbacks' => $fallbacks,
|
||||
];
|
||||
}
|
||||
|
||||
// ── Master fetch ──────────────────────────────────────────────────────────────
|
||||
|
||||
// Single combined query — one HTTP round-trip, cached for the request lifetime.
|
||||
// Returns null if API is unreachable, key missing, or any query error occurs.
|
||||
function vv_api_data(): ?array {
|
||||
static $cache = null, $fetched = false;
|
||||
if ($fetched) return $cache;
|
||||
$fetched = true;
|
||||
|
||||
$hostId = vv_detect_host();
|
||||
$vars = vv_conf_vars();
|
||||
$key = $vars[strtoupper($hostId) . '_UNRAID_API_KEY'] ?? '';
|
||||
if (!$key) { $cache = null; return null; }
|
||||
|
||||
// Fields verified against live schema introspection (2026-05-29).
|
||||
// array.parities / .disks / .caches are SEPARATE lists — .disks is DATA only.
|
||||
// metrics.cpu.percentTotal and metrics.memory.* provide real-time utilisation.
|
||||
$gql = <<<'GQL'
|
||||
{
|
||||
info {
|
||||
os { hostname uptime release }
|
||||
cpu { brand threads cores }
|
||||
}
|
||||
metrics {
|
||||
cpu { percentTotal }
|
||||
memory { percentTotal total used available swapTotal swapUsed }
|
||||
}
|
||||
array {
|
||||
state
|
||||
parities { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
|
||||
disks { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
|
||||
caches { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
|
||||
}
|
||||
vms {
|
||||
domains { name state }
|
||||
}
|
||||
}
|
||||
GQL;
|
||||
|
||||
$cache = vv_unraid_api_query($hostId, $gql, 5, $key);
|
||||
return $cache;
|
||||
}
|
||||
|
||||
// ── Disk data helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
// API size fields (BigInt) are in bytes on this schema.
|
||||
// Heuristic: if raw > 100 billion → bytes; else → KB (covers both possible encodings).
|
||||
function _vv_api_bytes_to_gb(float $raw): float {
|
||||
return $raw > 100_000_000_000
|
||||
? round($raw / (1024 ** 3), 1)
|
||||
: round($raw / (1024 ** 2), 1);
|
||||
}
|
||||
|
||||
// Map ArrayDiskType enum → role string used by the rest of the plugin.
|
||||
// Unraid 6.9 used CACHE; 6.10+ renamed pools to POOL. FLASH is the USB boot drive (skip).
|
||||
// Anything unrecognised (not DATA/PARITY*/FLASH) is treated as a pool.
|
||||
function _vv_api_disk_role(string $type): string {
|
||||
$t = strtoupper($type);
|
||||
if (str_contains($t, 'PARITY')) return 'parity';
|
||||
if ($t === 'DATA') return 'data';
|
||||
if ($t === 'FLASH') return 'flash'; // USB boot — excluded from both views
|
||||
return 'cache'; // CACHE, POOL, or future variants
|
||||
}
|
||||
|
||||
// Build a normalised disk entry from API data, matching the shape vv_disk_entry() produces.
|
||||
// $role may be overridden; if empty it is derived from the disk's type field.
|
||||
function vv_api_disk_entry(array $d, string $role = ''): ?array {
|
||||
if (!$role) $role = _vv_api_disk_role((string)($d['type'] ?? 'DATA'));
|
||||
|
||||
if ($role === 'parity') {
|
||||
// Parity disks have no filesystem — use raw size only.
|
||||
$sizeRaw = (float)($d['size'] ?? 0);
|
||||
if ($sizeRaw <= 0) return null;
|
||||
$sizeGb = _vv_api_bytes_to_gb($sizeRaw);
|
||||
$usedGb = 0.0;
|
||||
$pct = null;
|
||||
} else {
|
||||
// Data/cache disks: prefer fsSize/fsUsed; fall back to size if unmounted.
|
||||
$sizeRaw = (float)($d['fsSize'] ?? $d['size'] ?? 0);
|
||||
if ($sizeRaw <= 0) return null;
|
||||
$usedRaw = (float)($d['fsUsed'] ?? 0);
|
||||
$sizeGb = _vv_api_bytes_to_gb($sizeRaw);
|
||||
$usedGb = _vv_api_bytes_to_gb($usedRaw);
|
||||
$pct = $sizeGb > 0 ? round($usedGb / $sizeGb * 100, 1) : null;
|
||||
}
|
||||
|
||||
$temp = isset($d['temp']) && is_numeric($d['temp']) ? (int)$d['temp'] : null;
|
||||
$spinning = (bool)($d['isSpinning'] ?? true);
|
||||
$transport = $d['transport'] ?? (str_contains(strtolower($d['device'] ?? ''), 'nvme') ? 'nvme' : 'ata');
|
||||
|
||||
return [
|
||||
'name' => $d['name'] ?? '',
|
||||
'device' => $d['device'] ?? '',
|
||||
'role' => $role,
|
||||
'size_gb' => $sizeGb,
|
||||
'used_gb' => $usedGb,
|
||||
'pct' => $pct,
|
||||
'temp' => $temp,
|
||||
'transport' => strtolower($transport),
|
||||
'mounted' => $spinning,
|
||||
'status' => $d['status'] ?? 'DISK_OK',
|
||||
];
|
||||
}
|
||||
|
||||
// ── Confirmed schema (Unraid 7.2.5, introspected 2026-05-29) ─────────────────
|
||||
// Adding a new host: add HOSTn="hostname" to master.conf and HOSTn_UNRAID_API_KEY
|
||||
// to hostn.conf, then run Deployment/deploy.sh. No schema work needed.
|
||||
//
|
||||
// If a future Unraid version renames a field, the affected function falls back
|
||||
// to local reads and the api banner lists the fallback — fix by updating the GQL.
|
||||
Reference in New Issue
Block a user