[disk10]
  idx=10
  name=disk10
  device=sdl
  id=TOSHIBA_MG07ACA12TEY_Z0N0A001F9SG
  size=11718885324
  sectors=23437770752
  sector_size=512
  transport=ata
  rotational=1
  discard=0
  removable=0
  spundown=0
  status=DISK_DSBL
  format=GPT: 4KiB-aligned
  temp=32
  numReads=929452563
  numWrites=401757821
  numErrors=290
  type=Data
  color=red-on
  spindownDelay=-1
  spinupGroup=
  idSb=TOSHIBA_MG07ACA12TEY_Z0N0A001F9SG
  sizeSb=11718885324
  deviceSb=md10p1
  luksState=0
  fsType=zfs
  fsStatus=Mounted
  fsMountpoint=/mnt/disk10
  autotrim=off
  compression=off
  warning=
  critical=
  exportable=no
  comment=
  fsProfile=
  fsWidth=1
  fsGroups=1
  state=4
  slots=1
  devices=1
  devicesSb=1
  fsColor=green-on
  fsEmpty=no
  fsSize=11576279040
  fsFree=2592848916
  fsUsed=8983430124


Let me check what the Unraid API returns for disk status, and look at the GraphQL query shape.


Bash

grep -n "status\|parities\|disks\|DISK_" /boot/config/plugins/varaverk/Plugin/unraid/include/unraid_api.php | head -60

Check API query fields for disk status


12://   ArrayDisk:  used for parities/disks/caches (ArrayParity/ArrayCache named types removed in 7.3)
13://               fields: name, device, type (ArrayDiskType enum), status (ArrayDiskStatus enum),
33:function vv_api_get_status(): array {
62:    // array.parities / .disks / .caches are SEPARATE lists — .disks is DATA only.
77:    parities { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
78:    disks    { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
79:    caches   { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
118:        // Parity disks have no filesystem — use raw size only.
125:        // Data/cache disks: prefer fsSize/fsUsed; fall back to size if unmounted.
148:        'status'    => $d['status'] ?? 'DISK_OK',
157://   array.{disks,caches,parities}.{fsSize,fsUsed,temp}, vms.domains.
165:    $disks  = $d['array']['disks']    ?? [];
167:    $pars   = $d['array']['parities'] ?? [];
169:    foreach (array_merge($disks, $caches) as $dk) {
176:        array_merge(array_column($disks,'temp'), array_column($caches,'temp'), array_column($pars,'temp')),


1	<?php
2	// Unraid GraphQL API — single-request master fetch + per-function fallback tracking.
3	// All API-first functions call vv_api_data() then fall back to local reads on null.
4	//
5	// Confirmed schema (introspected 2026-05-31, Unraid 7.3):
6	//   InfoOs:     hostname, uptime (String), release, kernel (NEW 7.3), arch (NEW 7.3)
7	//               — also new but unused: fqdn, build, codename, platform, distro, serial, uefi
8	//               — no version/uptime-as-int
9	//   InfoCpu:    brand, threads, cores — also new but unused: manufacturer, model, speed,
10	//               speedmin, speedmax, processors, socket, topology, packages
11	//   InfoMemory: layout only — NO usage fields; memory usage via metrics.memory
12	//   ArrayDisk:  used for parities/disks/caches (ArrayParity/ArrayCache named types removed in 7.3)
13	//               fields: name, device, type (ArrayDiskType enum), status (ArrayDiskStatus enum),
14	//               size (BigInt), fsSize, fsFree, fsUsed (BigInt), temp, transport (String),
15	//               rotational, isSpinning, fsType (String) — also new: numReads, numWrites,
16	//               numErrors (BigInt), idx, warning, critical, color, exportable, comment, format
17	//   MemoryUtilization: percentTotal, total, used, available, swapTotal, swapUsed —
18	//               also new: free, active, buffcache, swapFree, percentSwapTotal
19	//   VmDomain:   name, state (VmState enum) — no memory/vcpus
20
21	require_once __DIR__ . '/config.php';
22
23	// ── Fallback tracking ─────────────────────────────────────────────────────────
24
25	function &_vv_api_fallbacks(): array { static $f = []; return $f; }
26	function &_vv_api_key_missing(): bool { static $m = false; return $m; }
27
28	function vv_api_record_fallback(string $fn): void {
29	    $f   = &_vv_api_fallbacks();
30	    $f[] = $fn;
31	}
32
33	function vv_api_get_status(): array {
34	    $fallbacks = array_unique(_vv_api_fallbacks());
35	    return [
36	        'available'   => empty($fallbacks),
37	        'key_missing' => _vv_api_key_missing(),
38	        'fallbacks'   => $fallbacks,
39	    ];
40	}
41
42	// ── Master fetch ──────────────────────────────────────────────────────────────
43
44	// Single combined query — one HTTP round-trip, cached for the request lifetime.
45	// Returns null if API is unreachable, key missing, or any query error occurs.
46	function vv_api_data(): ?array {
47	    static $cache = null, $fetched = false;
48	    if ($fetched) return $cache;
49	    $fetched = true;
50
51	    $hostId = vv_detect_host();
52	    $vars   = vv_conf_vars();
53	    $key    = $vars[strtoupper($hostId) . '_UNRAID_API_KEY'] ?? '';
54	    if (!$key) {
55	        $m = &_vv_api_key_missing();
56	        $m = true;
57	        $cache = null;
58	        return null;
59	    }
60
61	    // Fields verified against live schema introspection (2026-05-31, Unraid 7.3).
62	    // array.parities / .disks / .caches are SEPARATE lists — .disks is DATA only.
63	    // ArrayParity/ArrayCache are no longer named types in 7.3 but the query structure is unchanged.
64	    // metrics.cpu.percentTotal and metrics.memory.* provide real-time utilisation.
65	    $gql = <<<'GQL'
66	{
67	  info {
68	    os  { hostname uptime release kernel }
69	    cpu { brand threads cores }
70	  }
71	  metrics {
72	    cpu    { percentTotal }
73	    memory { percentTotal total used available swapTotal swapUsed }
74	  }
75	  array {
76	    state
77	    parities { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
78	    disks    { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
79	    caches   { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
80	  }
81	  vms {
82	    domains { name state }
83	  }
84	}
85	GQL;
86
87	    $cache = vv_unraid_api_query($hostId, $gql, 5, $key);
88	    return $cache;
89	}
90
91	// ── Disk data helpers ─────────────────────────────────────────────────────────
92
93	// API size fields (BigInt) are in bytes on this schema.
94	// Heuristic: if raw > 100 billion → bytes; else → KB (covers both possible encodings).
95	function _vv_api_bytes_to_gb(float $raw): float {
96	    return $raw > 100_000_000_000
97	        ? round($raw / (1024 ** 3), 1)
98	        : round($raw / (1024 ** 2), 1);
99	}
100
101	// Map ArrayDiskType enum → role string used by the rest of the plugin.
102	// Unraid 6.9 used CACHE; 6.10+ renamed pools to POOL. FLASH is the USB boot drive (skip).
103	// Anything unrecognised (not DATA/PARITY*/FLASH) is treated as a pool.
104	function _vv_api_disk_role(string $type): string {
105	    $t = strtoupper($type);
106	    if (str_contains($t, 'PARITY')) return 'parity';
107	    if ($t === 'DATA')               return 'data';
108	    if ($t === 'FLASH')              return 'flash';   // USB boot — excluded from both views
109	    return 'cache';                                     // CACHE, POOL, or future variants
110	}
111
112	// Build a normalised disk entry from API data, matching the shape vv_disk_entry() produces.
113	// $role may be overridden; if empty it is derived from the disk's type field.
114	function vv_api_disk_entry(array $d, string $role = ''): ?array {
115	    if (!$role) $role = _vv_api_disk_role((string)($d['type'] ?? 'DATA'));

<?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-31, Unraid 7.3):
//   InfoOs:     hostname, uptime (String), release, kernel (NEW 7.3), arch (NEW 7.3)
//               — also new but unused: fqdn, build, codename, platform, distro, serial, uefi
//               — no version/uptime-as-int
//   InfoCpu:    brand, threads, cores — also new but unused: manufacturer, model, speed,
//               speedmin, speedmax, processors, socket, topology, packages
//   InfoMemory: layout only — NO usage fields; memory usage via metrics.memory
//   ArrayDisk:  used for parities/disks/caches (ArrayParity/ArrayCache named types removed in 7.3)
//               fields: name, device, type (ArrayDiskType enum), status (ArrayDiskStatus enum),
//               size (BigInt), fsSize, fsFree, fsUsed (BigInt), temp, transport (String),
//               rotational, isSpinning, fsType (String) — also new: numReads, numWrites,
//               numErrors (BigInt), idx, warning, critical, color, exportable, comment, format
//   MemoryUtilization: percentTotal, total, used, available, swapTotal, swapUsed —
//               also new: free, active, buffcache, swapFree, percentSwapTotal
//   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_key_missing(): bool { static $m = false; return $m; }

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),
        'key_missing' => _vv_api_key_missing(),
        '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) {
        $m = &_vv_api_key_missing();
        $m = true;
        $cache = null;
        return null;
    }

    // Fields verified against live schema introspection (2026-05-31, Unraid 7.3).
    // array.parities / .disks / .caches are SEPARATE lists — .disks is DATA only.
    // ArrayParity/ArrayCache are no longer named types in 7.3 but the query structure is unchanged.
    // metrics.cpu.percentTotal and metrics.memory.* provide real-time utilisation.
    $gql = <<<'GQL'
{
  info {
    os  { hostname uptime release kernel }
    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'));


so right now disk 10 is gone, i have a replacement coming sorta. but in monitor page parity is shown as invalid. parity is still valid and disk is being emulated. we need to turn the bar red on disks…


Fix parity status display for failed disks


Now I have everything I need. Two fixes to make:
1. `vv_parity_status()` — `$isValid` incorrectly uses `mdNumInvalid` which flags emulated disks as invalid parity; expose `num_disabled`
2. `vvDiskRow()` — bar color ignores `status`, so `DISK_DSBL` disks look green

Let me read the exact lines first.


401	function vv_parity_status(): array {
402	    $var = [];
403	    foreach (@file('/var/local/emhttp/var.ini') ?: [] as $line) {
404	        if (preg_match('/^(\w+)="([^"]*)"/', $line, $m)) $var[$m[1]] = $m[2];
405	    }
406
407	    $isValid    = ($var['mdNumInvalid'] ?? '0') === '0';
408	    $exitCode   = (int)($var['sbSyncExit'] ?? 0);
409	    $errors     = (int)($var['sbSyncErrs'] ?? 0);
410	    $inProgress = ($var['mdResync'] ?? '0') !== '0';
411	    $resyncPos  = (int)($var['mdResyncPos'] ?? 0);
412	    $resyncSize = (int)($var['mdResyncSize'] ?? 1);
413	    $resyncPct  = $resyncSize > 0 ? round($resyncPos / $resyncSize * 100, 1) : 0;
414
415	    // Last check from log
416	    $lastDate = null; $lastDuration = 0; $lastSpeed = 0; $lastErrors = 0; $lastExit = 0;
417	    $logFile  = '/boot/config/parity-checks.log';
418	    if (file_exists($logFile)) {
419	        $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
420	        if ($lines) {
421	            $p = explode('|', trim(end($lines)));
422	            $lastDate     = trim($p[0] ?? '');
423	            $lastDuration = (int)($p[1] ?? 0);
424	            $lastSpeed    = (int)($p[2] ?? 0);
425	            $lastExit     = (int)($p[3] ?? 0);
426	            $lastErrors   = (int)($p[4] ?? 0);
427	        }
428	    }
429
430	    // Parse last date string to timestamp
431	    $lastTs = $lastDate ? strtotime($lastDate) : null;
432
433	    // Next scheduled check from cron
434	    $nextTs   = null;
435	    $cronFile = '/boot/config/plugins/dynamix/parity-check.cron';
436	    foreach (@file($cronFile) ?: [] as $line) {
437	        $line = trim($line);
438	        if ($line === '' || $line[0] === '#') continue;
439	        if (!str_contains($line, 'mdcmd')) continue;
440	        $p = preg_split('/\s+/', $line);
441	        // cron: min hour dom month dow command...
442	        if (count($p) >= 5 && is_numeric($p[0]) && is_numeric($p[1]) && is_numeric($p[2])) {
443	            $next = new DateTime('now');
444	            $next->setTime((int)$p[1], (int)$p[0], 0);
445	            $next->setDate((int)$next->format('Y'), (int)$next->format('n'), (int)$p[2]);
446	            if ($next->getTimestamp() <= time()) $next->modify('+1 month');
447	            $nextTs = $next->getTimestamp();
448	        }
449	        break;
450	    }
451
452	    $exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
453	    return [
454	        'valid'         => $isValid,
455	        'in_progress'   => $inProgress,
456	        'resync_pct'    => $resyncPct,
457	        'exit_code'     => $exitCode,
458	        'exit_label'    => $exitMap[(string)$lastExit] ?? 'Unknown',
459	        'errors'        => $lastErrors,
460	        'last_date'     => $lastDate,
461	        'last_ts'       => $lastTs,
462	        'last_duration' => $lastDuration,
463	        'last_speed_mb' => $lastSpeed > 0 ? round($lastSpeed / 1048576, 1) : null,
464	        'next_ts'       => $nextTs,
465	    ];
466	}

function vv_parity_status(): array {
    $var = [];
    foreach (@file('/var/local/emhttp/var.ini') ?: [] as $line) {
        if (preg_match('/^(\w+)="([^"]*)"/', $line, $m)) $var[$m[1]] = $m[2];
    }

    $isValid    = ($var['mdNumInvalid'] ?? '0') === '0';
    $exitCode   = (int)($var['sbSyncExit'] ?? 0);
    $errors     = (int)($var['sbSyncErrs'] ?? 0);
    $inProgress = ($var['mdResync'] ?? '0') !== '0';
    $resyncPos  = (int)($var['mdResyncPos'] ?? 0);
    $resyncSize = (int)($var['mdResyncSize'] ?? 1);
    $resyncPct  = $resyncSize > 0 ? round($resyncPos / $resyncSize * 100, 1) : 0;

    // Last check from log
    $lastDate = null; $lastDuration = 0; $lastSpeed = 0; $lastErrors = 0; $lastExit = 0;
    $logFile  = '/boot/config/parity-checks.log';
    if (file_exists($logFile)) {
        $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
        if ($lines) {
            $p = explode('|', trim(end($lines)));
            $lastDate     = trim($p[0] ?? '');
            $lastDuration = (int)($p[1] ?? 0);
            $lastSpeed    = (int)($p[2] ?? 0);
            $lastExit     = (int)($p[3] ?? 0);
            $lastErrors   = (int)($p[4] ?? 0);
        }
    }

    // Parse last date string to timestamp
    $lastTs = $lastDate ? strtotime($lastDate) : null;

    // Next scheduled check from cron
    $nextTs   = null;
    $cronFile = '/boot/config/plugins/dynamix/parity-check.cron';
    foreach (@file($cronFile) ?: [] as $line) {
        $line = trim($line);
        if ($line === '' || $line[0] === '#') continue;
        if (!str_contains($line, 'mdcmd')) continue;
        $p = preg_split('/\s+/', $line);
        // cron: min hour dom month dow command...
        if (count($p) >= 5 && is_numeric($p[0]) && is_numeric($p[1]) && is_numeric($p[2])) {
            $next = new DateTime('now');
            $next->setTime((int)$p[1], (int)$p[0], 0);
            $next->setDate((int)$next->format('Y'), (int)$next->format('n'), (int)$p[2]);
            if ($next->getTimestamp() <= time()) $next->modify('+1 month');
            $nextTs = $next->getTimestamp();
        }
        break;
    }

    $exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
    return [
        'valid'         => $isValid,
        'in_progress'   => $inProgress,
        'resync_pct'    => $resyncPct,
        'exit_code'     => $exitCode,
        'exit_label'    => $exitMap[(string)$lastExit] ?? 'Unknown',
        'errors'        => $lastErrors,
        'last_date'     => $lastDate,
        'last_ts'       => $lastTs,
        'last_duration' => $lastDuration,
        'last_speed_mb' => $lastSpeed > 0 ? round($lastSpeed / 1048576, 1) : null,
        'next_ts'       => $nextTs,
    ];
}


Edit

    $isValid    = ($var['mdNumInvalid'] ?? '0') === '0';
    $exitCode   = (int)($var['sbSyncExit'] ?? 0);
    $errors     = (int)($var['sbSyncErrs'] ?? 0);

    $numDisabled = (int)($var['mdNumDisabled'] ?? 0);
    $numMissing  = (int)($var['mdNumMissing']  ?? 0);
    $exitCode    = (int)($var['sbSyncExit'] ?? 0);
    $errors      = (int)($var['sbSyncErrs'] ?? 0);
    // Emulated (DISK_DSBL) disks are protected by parity and don't make parity invalid.
    // True invalidity: sync errors on the last check, or unprotectable missing slots.
    $isValid     = $errors === 0 && $numMissing === 0;


The file /boot/config/plugins/varaverk/Plugin/unraid/include/common.php has been updated successfully. (file state is current in your context — no need to Read it back)

    $isValid    = ($var['mdNumInvalid'] ?? '0') === '0';
    $exitCode   = (int)($var['sbSyncExit'] ?? 0);
    $errors     = (int)($var['sbSyncErrs'] ?? 0);

    $numDisabled = (int)($var['mdNumDisabled'] ?? 0);
    $numMissing  = (int)($var['mdNumMissing']  ?? 0);
    $exitCode    = (int)($var['sbSyncExit'] ?? 0);
    $errors      = (int)($var['sbSyncErrs'] ?? 0);
    // Emulated (DISK_DSBL) disks are protected by parity and don't make parity invalid.
    // True invalidity: sync errors on the last check, or unprotectable missing slots.
    $isValid     = $errors === 0 && $numMissing === 0;

-    $isValid    = ($var['mdNumInvalid'] ?? '0') === '0';

-    $exitCode   = (int)($var['sbSyncExit'] ?? 0);

-    $errors     = (int)($var['sbSyncErrs'] ?? 0);

+    $numDisabled = (int)($var['mdNumDisabled'] ?? 0);

+    $numMissing  = (int)($var['mdNumMissing']  ?? 0);

+    $exitCode    = (int)($var['sbSyncExit'] ?? 0);

+    $errors      = (int)($var['sbSyncErrs'] ?? 0);

+    // Emulated (DISK_DSBL) disks are protected by parity and don't make parity invalid.

+    // True invalidity: sync errors on the last check, or unprotectable missing slots.

+    $isValid     = $errors === 0 && $numMissing === 0;

$inProgress = ($var['mdResync'] ?? '0') !== '0';

$resyncPos  = (int)($var['mdResyncPos'] ?? 0);

$resyncSize = (int)($var['mdResyncSize'] ?? 1);


    $exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
    return [
        'valid'         => $isValid,
        'in_progress'   => $inProgress,
        'resync_pct'    => $resyncPct,
        'exit_code'     => $exitCode,
        'exit_label'    => $exitMap[(string)$lastExit] ?? 'Unknown',
        'errors'        => $lastErrors,
        'last_date'     => $lastDate,
        'last_ts'       => $lastTs,
        'last_duration' => $lastDuration,
        'last_speed_mb' => $lastSpeed > 0 ? round($lastSpeed / 1048576, 1) : null,
        'next_ts'       => $nextTs,
    ];

    $exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
    return [
        'valid'         => $isValid,
        'num_disabled'  => $numDisabled,
        'num_missing'   => $numMissing,
        'in_progress'   => $inProgress,
        'resync_pct'    => $resyncPct,
        'exit_code'     => $exitCode,
        'exit_label'    => $exitMap[(string)$lastExit] ?? 'Unknown',
        'errors'        => $lastErrors,
        'last_date'     => $lastDate,
        'last_ts'       => $lastTs,
        'last_duration' => $lastDuration,
        'last_speed_mb' => $lastSpeed > 0 ? round($lastSpeed / 1048576, 1) : null,
        'next_ts'       => $nextTs,
    ];


    $exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
    return [
        'valid'         => $isValid,
        'in_progress'   => $inProgress,
        'resync_pct'    => $resyncPct,
        'exit_code'     => $exitCode,
        'exit_label'    => $exitMap[(string)$lastExit] ?? 'Unknown',
        'errors'        => $lastErrors,
        'last_date'     => $lastDate,
        'last_ts'       => $lastTs,
        'last_duration' => $lastDuration,
        'last_speed_mb' => $lastSpeed > 0 ? round($lastSpeed / 1048576, 1) : null,
        'next_ts'       => $nextTs,
    ];

    $exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
    return [
        'valid'         => $isValid,
        'num_disabled'  => $numDisabled,
        'num_missing'   => $numMissing,
        'in_progress'   => $inProgress,
        'resync_pct'    => $resyncPct,
        'exit_code'     => $exitCode,
        'exit_label'    => $exitMap[(string)$lastExit] ?? 'Unknown',
        'errors'        => $lastErrors,
        'last_date'     => $lastDate,
        'last_ts'       => $lastTs,
        'last_duration' => $lastDuration,
        'last_speed_mb' => $lastSpeed > 0 ? round($lastSpeed / 1048576, 1) : null,
        'next_ts'       => $nextTs,
    ];

+        'num_disabled'  => $numDisabled,

+        'num_missing'   => $numMissing,


Now fix the frontend. First `vvDiskRow` for the red bar + emulated label, then the parity section for the degraded banner.


559	function vvDiskRow(disk) {
560	  const tempC     = disk.temp;
561	  const tempColor = vvTempColor(tempC, disk.transport);
562	  const tempStr   = tempC !== null ? `${tempC}°` : '—';
563	  const isParity  = disk.role === 'parity';
564	  const nameColor = isParity ? '#6a8faf' : '#aaa';
565	  const pct       = disk.pct ?? 0;
566	  const barColor  = isParity ? '#1e3a5a'
567	    : pct >= vvThresholds.util_crit ? '#f44336'
568	    : pct >= vvThresholds.util_warn ? '#ff9800'
569	    : '#4caf50';
570	  const barWidth  = isParity ? '100' : pct;
571	  const spinLabel = (!isParity && !disk.mounted) ? `<span style="color:#555;font-size:9px;margin-left:4px;">↓</span>` : '';
572	  const right     = isParity
573	    ? `<span style="color:#444;font-size:10px;">${vvFmt(disk.size_gb)}</span>`
574	    : `<span style="color:#555;font-size:10px;">${vvFmt(disk.used_gb)} / ${vvFmt(disk.size_gb)}</span>`;
575	  return `

576

577	      ${disk.name}${spinLabel}${vvIoChip(disk.device)}
578	      ${right}
579	      ${tempStr}
580

581

582

583

584
`;
585	}
586

function vvDiskRow(disk) {
  const tempC     = disk.temp;
  const tempColor = vvTempColor(tempC, disk.transport);
  const tempStr   = tempC !== null ? `${tempC}°` : '—';
  const isParity  = disk.role === 'parity';
  const nameColor = isParity ? '#6a8faf' : '#aaa';
  const pct       = disk.pct ?? 0;
  const barColor  = isParity ? '#1e3a5a'
    : pct >= vvThresholds.util_crit ? '#f44336'
    : pct >= vvThresholds.util_warn ? '#ff9800'
    : '#4caf50';
  const barWidth  = isParity ? '100' : pct;
  const spinLabel = (!isParity && !disk.mounted) ? `<span style="color:#555;font-size:9px;margin-left:4px;">↓</span>` : '';
  const right     = isParity
    ? `<span style="color:#444;font-size:10px;">${vvFmt(disk.size_gb)}</span>`
    : `<span style="color:#555;font-size:10px;">${vvFmt(disk.used_gb)} / ${vvFmt(disk.size_gb)}</span>`;
  return `

      ${disk.name}${spinLabel}${vvIoChip(disk.device)}
      ${right}
      ${tempStr}

`;
}


function vvDiskRow(disk) {
  const tempC     = disk.temp;
  const tempColor = vvTempColor(tempC, disk.transport);
  const tempStr   = tempC !== null ? `${tempC}°` : '—';
  const isParity  = disk.role === 'parity';
  const nameColor = isParity ? '#6a8faf' : '#aaa';
  const pct       = disk.pct ?? 0;
  const barColor  = isParity ? '#1e3a5a'
    : pct >= vvThresholds.util_crit ? '#f44336'
    : pct >= vvThresholds.util_warn ? '#ff9800'
    : '#4caf50';
  const barWidth  = isParity ? '100' : pct;
  const spinLabel = (!isParity && !disk.mounted) ? `<span style="color:#555;font-size:9px;margin-left:4px;">↓</span>` : '';
  const right     = isParity
    ? `<span style="color:#444;font-size:10px;">${vvFmt(disk.size_gb)}</span>`
    : `<span style="color:#555;font-size:10px;">${vvFmt(disk.used_gb)} / ${vvFmt(disk.size_gb)}</span>`;
  return `

      ${disk.name}${spinLabel}${vvIoChip(disk.device)}
      ${right}
      ${tempStr}

`;
}

function vvDiskRow(disk) {
  const tempC     = disk.temp;
  const tempColor = vvTempColor(tempC, disk.transport);
  const tempStr   = tempC !== null ? `${tempC}°` : '—';
  const isParity  = disk.role === 'parity';
  const failed    = !isParity && disk.status && disk.status !== 'DISK_OK';
  const nameColor = isParity ? '#6a8faf' : failed ? '#f44336' : '#aaa';
  const pct       = disk.pct ?? 0;
  const barColor  = isParity ? '#1e3a5a'
    : failed ? '#f44336'
    : pct >= vvThresholds.util_crit ? '#f44336'
    : pct >= vvThresholds.util_warn ? '#ff9800'
    : '#4caf50';
  const barWidth  = isParity ? '100' : pct;
  const spinLabel = (!isParity && !disk.mounted) ? `<span style="color:#555;font-size:9px;margin-left:4px;">↓</span>` : '';
  const failLabel = failed ? `<span style="color:#f44336;font-size:9px;margin-left:4px;">${disk.status === 'DISK_DSBL' ? 'emulated' : disk.status.replace('DISK_','').toLowerCase()}</span>` : '';
  const right     = isParity
    ? `<span style="color:#444;font-size:10px;">${vvFmt(disk.size_gb)}</span>`
    : `<span style="color:#555;font-size:10px;">${vvFmt(disk.used_gb)} / ${vvFmt(disk.size_gb)}</span>`;
  return `

      ${disk.name}${spinLabel}${failLabel}${vvIoChip(disk.device)}
      ${right}
      ${tempStr}

`;
}


The file /boot/config/plugins/varaverk/Plugin/unraid/pages/monitor.php has been updated successfully. (file state is current in your context — no need to Read it back)

function vvDiskRow(disk) {
  const tempC     = disk.temp;
  const tempColor = vvTempColor(tempC, disk.transport);
  const tempStr   = tempC !== null ? `${tempC}°` : '—';
  const isParity  = disk.role === 'parity';
  const nameColor = isParity ? '#6a8faf' : '#aaa';
  const pct       = disk.pct ?? 0;
  const barColor  = isParity ? '#1e3a5a'
    : pct >= vvThresholds.util_crit ? '#f44336'
    : pct >= vvThresholds.util_warn ? '#ff9800'
    : '#4caf50';
  const barWidth  = isParity ? '100' : pct;
  const spinLabel = (!isParity && !disk.mounted) ? `<span style="color:#555;font-size:9px;margin-left:4px;">↓</span>` : '';
  const right     = isParity
    ? `<span style="color:#444;font-size:10px;">${vvFmt(disk.size_gb)}</span>`
    : `<span style="color:#555;font-size:10px;">${vvFmt(disk.used_gb)} / ${vvFmt(disk.size_gb)}</span>`;
  return `

      ${disk.name}${spinLabel}${vvIoChip(disk.device)}
      ${right}
      ${tempStr}

`;
}

function vvDiskRow(disk) {
  const tempC     = disk.temp;
  const tempColor = vvTempColor(tempC, disk.transport);
  const tempStr   = tempC !== null ? `${tempC}°` : '—';
  const isParity  = disk.role === 'parity';
  const failed    = !isParity && disk.status && disk.status !== 'DISK_OK';
  const nameColor = isParity ? '#6a8faf' : failed ? '#f44336' : '#aaa';
  const pct       = disk.pct ?? 0;
  const barColor  = isParity ? '#1e3a5a'
    : failed ? '#f44336'
    : pct >= vvThresholds.util_crit ? '#f44336'
    : pct >= vvThresholds.util_warn ? '#ff9800'
    : '#4caf50';
  const barWidth  = isParity ? '100' : pct;
  const spinLabel = (!isParity && !disk.mounted) ? `<span style="color:#555;font-size:9px;margin-left:4px;">↓</span>` : '';
  const failLabel = failed ? `<span style="color:#f44336;font-size:9px;margin-left:4px;">${disk.status === 'DISK_DSBL' ? 'emulated' : disk.status.replace('DISK_','').toLowerCase()}</span>` : '';
  const right     = isParity
    ? `<span style="color:#444;font-size:10px;">${vvFmt(disk.size_gb)}</span>`
    : `<span style="color:#555;font-size:10px;">${vvFmt(disk.used_gb)} / ${vvFmt(disk.size_gb)}</span>`;
  return `

      ${disk.name}${spinLabel}${failLabel}${vvIoChip(disk.device)}
      ${right}
      ${tempStr}

`;
}

-  const nameColor = isParity ? '#6a8faf' : '#aaa';

+  const failed    = !isParity && disk.status && disk.status !== 'DISK_OK';

+  const nameColor = isParity ? '#6a8faf' : failed ? '#f44336' : '#aaa';

+  const failLabel = failed ? `<span style="color:#f44336;font-size:9px;margin-left:4px;">${disk.status === 'DISK_DSBL' ? 'emulated' : disk.status.replace('DISK_','').toLowerCase()}</span>` : '';

-      ${disk.name}${spinLabel}${vvIoChip(disk.device)}

+      ${disk.name}${spinLabel}${failLabel}${vvIoChip(disk.device)}


Now fix the parity banner to show "Emulating N disk(s)" in orange instead of "INVALID" in red.


968	        document.getElementById('vv-ups-body').innerHTML = '
No UPS detected
';
969	      }
970
971	      // ── Parity ──────────────────────────────────────────────────────────────
972	      const par = d.parity ?? {};
973	      (function() {
974	        const valid      = par.valid !== false;
975	        const inProg     = par.in_progress;
976	        const validColor = valid ? '#4caf50' : '#f44336';
977	        const validLabel = valid ? 'Parity is valid' : 'Parity is INVALID';
978
979	        function vvRelTime(ts) {
980	          if (!ts) return '';
981	          const diff = Math.floor(Date.now() / 1000) - ts;
982	          const d = Math.floor(diff / 86400), h = Math.floor((diff % 86400) / 3600), m = Math.floor((diff % 3600) / 60);
983	          const parts = [];
984	          if (d) parts.push(d + ' day' + (d !== 1 ? 's' : ''));
985	          if (h) parts.push(h + ' hour' + (h !== 1 ? 's' : ''));
986	          if (!d && m) parts.push(m + ' minute' + (m !== 1 ? 's' : ''));
987	          return parts.join(', ') + ' ago';
988	        }
989	        function vvDueIn(ts) {
990	          if (!ts) return '—';
991	          const diff = ts - Math.floor(Date.now() / 1000);
992	          if (diff <= 0) return 'overdue';
993	          const d = Math.floor(diff / 86400), h = Math.floor((diff % 86400) / 3600), m = Math.floor((diff % 3600) / 60);
994	          const parts = [];
995	          if (d) parts.push(d + ' day' + (d !== 1 ? 's' : ''));
996	          if (h) parts.push(h + ' hour' + (h !== 1 ? 's' : ''));
997	          if (!d && m) parts.push(m + ' minute' + (m !== 1 ? 's' : ''));
998	          return 'Due in: ' + parts.join(', ');
999	        }
1000	        function vvFmtDate(ts) {
1001	          if (!ts) return '—';
1002	          return new Date(ts * 1000).toLocaleString([], {weekday:'short',day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
1003	        }
1004
1005	        const exitColor  = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
1006	        const errColor   = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
1007	        const speedStr   = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
1008	        const nextDate   = vvFmtDate(par.next_ts);
1009	        const dueIn      = vvDueIn(par.next_ts);
1010	        const parBannerCls = !valid ? 'vv-banner-err' : (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';
1011
1012	        let html = `

1013	          ${valid ? '✓ Valid' : '✗ INVALID'}
1014	          ${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}
1015
`;
1016
1017	        if (inProg) {
1018	          const pct = par.resync_pct ?? 0;
1019	          html += `
Check in progress — ${pct}%

1020

1021

1022
`;
1023	        }
1024
1025	        html += `
1026

1027	            Last check
1028	            ${par.last_date ?? '—'}${speedStr}
1029	            Status
1030	            ${par.exit_label ?? '—'}
1031	            Errors
1032	            ${par.errors ?? 0}
1033	            Next check
1034	            ${nextDate}
1035

1036
${dueIn}
`;
1037

document.getElementById('vv-ups-body').innerHTML = '
No UPS detected
';
      }

      // ── Parity ──────────────────────────────────────────────────────────────
      const par = d.parity ?? {};
      (function() {
        const valid      = par.valid !== false;
        const inProg     = par.in_progress;
        const validColor = valid ? '#4caf50' : '#f44336';
        const validLabel = valid ? 'Parity is valid' : 'Parity is INVALID';

        function vvRelTime(ts) {
          if (!ts) return '';
          const diff = Math.floor(Date.now() / 1000) - ts;
          const d = Math.floor(diff / 86400), h = Math.floor((diff % 86400) / 3600), m = Math.floor((diff % 3600) / 60);
          const parts = [];
          if (d) parts.push(d + ' day' + (d !== 1 ? 's' : ''));
          if (h) parts.push(h + ' hour' + (h !== 1 ? 's' : ''));
          if (!d && m) parts.push(m + ' minute' + (m !== 1 ? 's' : ''));
          return parts.join(', ') + ' ago';
        }
        function vvDueIn(ts) {
          if (!ts) return '—';
          const diff = ts - Math.floor(Date.now() / 1000);
          if (diff <= 0) return 'overdue';
          const d = Math.floor(diff / 86400), h = Math.floor((diff % 86400) / 3600), m = Math.floor((diff % 3600) / 60);
          const parts = [];
          if (d) parts.push(d + ' day' + (d !== 1 ? 's' : ''));
          if (h) parts.push(h + ' hour' + (h !== 1 ? 's' : ''));
          if (!d && m) parts.push(m + ' minute' + (m !== 1 ? 's' : ''));
          return 'Due in: ' + parts.join(', ');
        }
        function vvFmtDate(ts) {
          if (!ts) return '—';
          return new Date(ts * 1000).toLocaleString([], {weekday:'short',day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
        }

        const exitColor  = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
        const errColor   = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
        const speedStr   = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
        const nextDate   = vvFmtDate(par.next_ts);
        const dueIn      = vvDueIn(par.next_ts);
        const parBannerCls = !valid ? 'vv-banner-err' : (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';

        let html = `

          ${valid ? '✓ Valid' : '✗ INVALID'}
          ${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}

`;

        if (inProg) {
          const pct = par.resync_pct ?? 0;
          html += `
Check in progress — ${pct}%

`;
        }

        html += `

            Last check
            ${par.last_date ?? '—'}${speedStr}
            Status
            ${par.exit_label ?? '—'}
            Errors
            ${par.errors ?? 0}
            Next check
            ${nextDate}

${dueIn}
`;


const exitColor  = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
        const errColor   = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
        const speedStr   = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
        const nextDate   = vvFmtDate(par.next_ts);
        const dueIn      = vvDueIn(par.next_ts);
        const parBannerCls = !valid ? 'vv-banner-err' : (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';

        let html = `

          ${valid ? '✓ Valid' : '✗ INVALID'}
          ${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}

`;

        if (inProg) {
          const pct = par.resync_pct ?? 0;
          html += `
Check in progress — ${pct}%

`;
        }

        html += `

            Last check
            ${par.last_date ?? '—'}${speedStr}
            Status
            ${par.exit_label ?? '—'}
            Errors
            ${par.errors ?? 0}
            Next check
            ${nextDate}

${dueIn}
`;

const numDisabled  = par.num_disabled ?? 0;
        const numMissing   = par.num_missing  ?? 0;
        const degraded     = numDisabled > 0 || numMissing > 0;
        const exitColor    = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
        const errColor     = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
        const speedStr     = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
        const nextDate     = vvFmtDate(par.next_ts);
        const dueIn        = vvDueIn(par.next_ts);
        const parBannerCls = !valid ? 'vv-banner-err' : degraded || (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';
        const emulLabel    = numDisabled > 0 ? `<span style="font-size:11px;">Emulating ${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : '';
        const missingLabel = numMissing  > 0 ? `<span style="font-size:11px;color:#f44336;">${numMissing} slot${numMissing !== 1 ? 's' : ''} missing</span>` : '';

        let html = `

          ${valid ? '✓ Valid' : '✗ INVALID'}
          ${emulLabel}${missingLabel}
          ${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}

`;

        if (inProg) {
          const pct = par.resync_pct ?? 0;
          html += `
Check in progress — ${pct}%

`;
        }

        html += `

            Last check
            ${par.last_date ?? '—'}${speedStr}
            Status
            ${par.exit_label ?? '—'}
            Errors
            ${par.errors ?? 0}
            ${numDisabled > 0 ? `<span style="color:#555;">Emulating</span><span style="color:#ff9800;">${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : ''}
            Next check
            ${nextDate}

${dueIn}
`;


const exitColor  = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
        const errColor   = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
        const speedStr   = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
        const nextDate   = vvFmtDate(par.next_ts);
        const dueIn      = vvDueIn(par.next_ts);
        const parBannerCls = !valid ? 'vv-banner-err' : (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';

        let html = `

          ${valid ? '✓ Valid' : '✗ INVALID'}
          ${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}

`;

        if (inProg) {
          const pct = par.resync_pct ?? 0;
          html += `
Check in progress — ${pct}%

`;
        }

        html += `

            Last check
            ${par.last_date ?? '—'}${speedStr}
            Status
            ${par.exit_label ?? '—'}
            Errors
            ${par.errors ?? 0}
            Next check
            ${nextDate}

${dueIn}
`;

const numDisabled  = par.num_disabled ?? 0;
        const numMissing   = par.num_missing  ?? 0;
        const degraded     = numDisabled > 0 || numMissing > 0;
        const exitColor    = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
        const errColor     = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
        const speedStr     = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
        const nextDate     = vvFmtDate(par.next_ts);
        const dueIn        = vvDueIn(par.next_ts);
        const parBannerCls = !valid ? 'vv-banner-err' : degraded || (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';
        const emulLabel    = numDisabled > 0 ? `<span style="font-size:11px;">Emulating ${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : '';
        const missingLabel = numMissing  > 0 ? `<span style="font-size:11px;color:#f44336;">${numMissing} slot${numMissing !== 1 ? 's' : ''} missing</span>` : '';

        let html = `

          ${valid ? '✓ Valid' : '✗ INVALID'}
          ${emulLabel}${missingLabel}
          ${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}

`;

        if (inProg) {
          const pct = par.resync_pct ?? 0;
          html += `
Check in progress — ${pct}%

`;
        }

        html += `

            Last check
            ${par.last_date ?? '—'}${speedStr}
            Status
            ${par.exit_label ?? '—'}
            Errors
            ${par.errors ?? 0}
            ${numDisabled > 0 ? `<span style="color:#555;">Emulating</span><span style="color:#ff9800;">${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : ''}
            Next check
            ${nextDate}

${dueIn}
`;

return new Date(ts * 1000).toLocaleString([], {weekday:'short',day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});

-        const exitColor  = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';

-        const speedStr   = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';

-        const nextDate   = vvFmtDate(par.next_ts);

-        const dueIn      = vvDueIn(par.next_ts);

-        const parBannerCls = !valid ? 'vv-banner-err' : (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';

+        const numDisabled  = par.num_disabled ?? 0;

+        const numMissing   = par.num_missing  ?? 0;

+        const degraded     = numDisabled > 0 || numMissing > 0;

+        const exitColor    = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';

+        const speedStr     = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';

+        const nextDate     = vvFmtDate(par.next_ts);

+        const dueIn        = vvDueIn(par.next_ts);

+        const parBannerCls = !valid ? 'vv-banner-err' : degraded || (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';

+        const emulLabel    = numDisabled > 0 ? `<span style="font-size:11px;">Emulating ${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : '';

+        const missingLabel = numMissing  > 0 ? `<span style="font-size:11px;color:#f44336;">${numMissing} slot${numMissing !== 1 ? 's' : ''} missing</span>` : '';

let html = `<div class="vv-banner ${parBannerCls}">

+            ${numDisabled > 0 ? `<span style="color:#555;">Emulating</span><span style="color:#ff9800;">${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : ''}


Now let me also remove the now-unused `validColor` and `validLabel` variables to keep things clean.


        const valid      = par.valid !== false;
        const inProg     = par.in_progress;
        const validColor = valid ? '#4caf50' : '#f44336';
        const validLabel = valid ? 'Parity is valid' : 'Parity is INVALID';

        const valid  = par.valid !== false;
        const inProg = par.in_progress;


        const valid      = par.valid !== false;
        const inProg     = par.in_progress;
        const validColor = valid ? '#4caf50' : '#f44336';
        const validLabel = valid ? 'Parity is valid' : 'Parity is INVALID';

// ── Parity ──────────────────────────────────────────────────────────────

-        const valid      = par.valid !== false;

-        const inProg     = par.in_progress;

-        const validColor = valid ? '#4caf50' : '#f44336';

-        const validLabel = valid ? 'Parity is valid' : 'Parity is INVALID';

+        const valid  = par.valid !== false;

+        const inProg = par.in_progress;


Let me do a quick sanity check on the PHP backend to make sure it's valid.


Bash

php -l /boot/config/plugins/varaverk/Plugin/unraid/include/common.php 2>&1

Lint common.php


No syntax errors detected in /boot/config/plugins/varaverk/Plugin/unraid/include/common.php


Good. Here's what changed:

**Backend (`include/common.php`):**
- `$isValid` now derives from `sbSyncErrs === 0 && mdNumMissing === 0` instead of `mdNumInvalid === 0`. Emulated disks (`DISK_DSBL`) are protected by parity — they don't make parity invalid.
- Two new fields in the response: `num_disabled` (count of emulated disks) and `num_missing` (count of truly unprotected missing slots).

**Frontend (`pages/monitor.php`):**
- **Parity banner**: stays green (✓ Valid) when degraded but protected, shows "Emulating N disks" in the orange warning banner. Only goes red INVALID for actual sync errors or unprotected missing slots. Adds an "Emulating: N disk(s)" row in the detail grid.
- **Disk bars**: if `status !== 'DISK_OK'`, the bar turns red and the disk name gets a small `emulated` label (or the raw status suffix lowercased for anything else). So disk10 will show a red bar with "emulated" next to its name.


much better. ty


Glad it's working right. Good luck with the rebuild when the replacement arrives.


well this disk never liked being worked hard when around 80%. so i got another to move music to and leave this disk as one of the tv show disks until it accually dies


Smart. Keep it lighter and let it coast until it gives up on its own — no point stressing it during the replacement window.

When you do eventually swap it out, the emulated state means the rebuild should go clean as long as nothing else fails in the meantime. Keep an eye on that error count (290 reads errors showing in disks.ini) — if that starts climbing fast, move the timeline up.


i have daul parity, im ok for another failure


Even better — two drives could go and you'd still be whole. 290 errors on a 12TB drive that's already being babied is pretty manageable then. Run it until it screams.


it only does it when its 80% or so full and extream writes. outside of that its fine


That tracks — could be a marginal head or platters that struggle under sustained high-density writes. At lighter loads it's fine, move it to TV shows, keep it under that threshold, and it'll probably outlive your patience with it.


Fixed the monitor page to show parity as valid with an "Emulating 1 disk" warning instead of INVALID, and made failed disk bars turn red. Disk 10 is being moved to TV shows to keep it under 80% load. (disable recaps in /config)