Platform-agnostic refactor: eliminate OS-specific hardcodes from core scripts
All bash scripts are now platform-neutral. Unraid-specific paths, commands, and service checks moved to Plugin/unraid/adapter.sh. Core scripts call platform_*() functions exclusively — no direct OS paths in runtime logic. New adapter functions: platform_storage_path, platform_webui_install_path, platform_scripts_dir_probe_cmd, platform_setup_db_path, platform_storage_healthy, platform_is_service_enabled, platform_get_temp_thresholds, platform_disk_states_path, platform_rebuild_container, platform_push_conf, platform_push_setup_state, platform_get_templates_dir, platform_send_os_notification. Partnership services stack (Emby/Jellyfin/Seerr/SeerrFin) added as third onboarding stack alongside auth and arr stacks.
This commit is contained in:
@@ -214,15 +214,16 @@ deploy_xml_stack() {
|
||||
cleanup_deployed_stack_on_remote() {
|
||||
local remote_ip="$1" ssh_key="$2"
|
||||
local -a xml_names=()
|
||||
[[ ${#PARTNERSHIP_AUTH_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_AUTH_STACK[@]}")
|
||||
[[ ${#PARTNERSHIP_ARR_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_ARR_STACK[@]}")
|
||||
[[ ${#PARTNERSHIP_AUTH_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_AUTH_STACK[@]}")
|
||||
[[ ${#PARTNERSHIP_ARR_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_ARR_STACK[@]}")
|
||||
[[ ${#PARTNERSHIP_SERVICES_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_SERVICES_STACK[@]}")
|
||||
|
||||
if [[ ${#xml_names[@]} -eq 0 ]]; then
|
||||
log "No auth/arr stack arrays configured — skipping deployed stack cleanup"
|
||||
log "No auth/arr/services stack arrays configured — skipping deployed stack cleanup"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Removing owner-deployed containers (auth/arr stacks) from $MIRROR..."
|
||||
log "Removing owner-deployed containers (auth/arr/services stacks) from $MIRROR..."
|
||||
for xml_name in "${xml_names[@]}"; do
|
||||
[[ -z "$xml_name" ]] && continue
|
||||
local xml_file="${TEMPLATES_DIR}/${xml_name}"
|
||||
@@ -289,15 +290,21 @@ cleanup_deployed_stack_locally() {
|
||||
"source '$SCRIPTS_ROOT/load_config.sh' 2>/dev/null
|
||||
detect_hosts 2>/dev/null
|
||||
printf '%s\n' \"\${PARTNERSHIP_ARR_STACK[@]:-}\"" 2>/dev/null | grep -v '^$')
|
||||
xml_names=("${auth_arr[@]}" "${arr_arr[@]}")
|
||||
local -a svc_arr
|
||||
mapfile -t svc_arr < <(timeout "$SSH_TIMEOUT" ssh -i "$ssh_key" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$owner_ip" \
|
||||
"source '$SCRIPTS_ROOT/load_config.sh' 2>/dev/null
|
||||
detect_hosts 2>/dev/null
|
||||
printf '%s\n' \"\${PARTNERSHIP_SERVICES_STACK[@]:-}\"" 2>/dev/null | grep -v '^$')
|
||||
xml_names=("${auth_arr[@]}" "${arr_arr[@]}" "${svc_arr[@]}")
|
||||
fi
|
||||
|
||||
if [[ ${#xml_names[@]} -eq 0 ]]; then
|
||||
log "Could not read deployed stack from owner — skipping auth/arr cleanup"
|
||||
log "Could not read deployed stack from owner — skipping auth/arr/services cleanup"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Removing owner-deployed containers (auth/arr stacks) locally..."
|
||||
log "Removing owner-deployed containers (auth/arr/services stacks) locally..."
|
||||
for xml_name in "${xml_names[@]}"; do
|
||||
[[ -z "$xml_name" ]] && continue
|
||||
local xml_file="${TEMPLATES_DIR}/${xml_name}"
|
||||
@@ -336,3 +343,74 @@ cleanup_deployed_stack_locally() {
|
||||
done
|
||||
}
|
||||
|
||||
# ==============================================================================================
|
||||
# ── Reconfigure a container's WebUI on the remote server ─────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
reconfigure_webui() {
|
||||
local container="$1" port="$2" target_ip="$3"
|
||||
local ssh_key="$4" remote_ip="$5" label="${6:-remote}"
|
||||
|
||||
log "Reconfiguring $container WebUI → ${target_ip}:${port} on $label..."
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would reconfigure $container WebUI to http://${target_ip}:${port}/"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local template
|
||||
template=$(timeout "$SSH_TIMEOUT" ssh -i "$ssh_key" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" root@"$remote_ip" \
|
||||
"grep -rl '<WebUI>' '$TEMPLATES_DIR/' 2>/dev/null | \
|
||||
xargs grep -l '\"$container\"' 2>/dev/null | head -1" 2>/dev/null)
|
||||
|
||||
if [[ -z "$template" ]]; then
|
||||
warn "$container template not found on $label — WebUI needs manual reconfiguration"
|
||||
return 1
|
||||
fi
|
||||
|
||||
timeout "$SSH_TIMEOUT" ssh -i "$ssh_key" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" root@"$remote_ip" \
|
||||
"sed -i 's|<WebUI>.*</WebUI>|<WebUI>http://${target_ip}:${port}/</WebUI>|g' '$template'" \
|
||||
2>/dev/null && \
|
||||
log "$container → http://${target_ip}:${port}/ ✅" || {
|
||||
error "Failed to reconfigure $container WebUI on $label"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# ==============================================================================================
|
||||
# ── Reconfigure local auth WebUIs to target IP ───────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
reconfigure_local_webuis() {
|
||||
local target_ip="$1"
|
||||
log "Reconfiguring local auth WebUIs → ${target_ip}..."
|
||||
|
||||
local failures=0
|
||||
for entry in "${PARTNERSHIP_AUTH_WEBUIS[@]}"; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
local container="${entry%%|*}"
|
||||
local port="${entry##*|}"
|
||||
|
||||
local template
|
||||
template=$(grep -rl '<WebUI>' "$TEMPLATES_DIR/" 2>/dev/null | \
|
||||
xargs grep -l "\"$container\"" 2>/dev/null | head -1)
|
||||
|
||||
if [[ -z "$template" ]]; then
|
||||
warn "$container template not found locally"
|
||||
(( failures++ ))
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would reconfigure $container → http://${target_ip}:${port}/"
|
||||
continue
|
||||
fi
|
||||
|
||||
sed -i "s|<WebUI>.*</WebUI>|<WebUI>http://${target_ip}:${port}/</WebUI>|g" \
|
||||
"$template" 2>/dev/null && \
|
||||
log "$container → http://${target_ip}:${port}/ ✅" || \
|
||||
{ error "Failed to reconfigure $container"; (( failures++ )); }
|
||||
done
|
||||
return $failures
|
||||
}
|
||||
|
||||
|
||||
+121
-9
@@ -17,8 +17,9 @@
|
||||
# ── FUNCTIONS ─────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_require_cmd — verify a platform command exists and is executable
|
||||
# platform_storage_healthy — array mounted and shfs active on /mnt/user
|
||||
# platform_get_disk_states — raw disks.ini content from emhttp state
|
||||
# platform_get_temp_thresholds — disk warn/crit °C from dynamix.cfg
|
||||
# platform_disk_states_path — path to the platform disk state file
|
||||
# platform_get_disk_states — raw disk state content from platform
|
||||
# platform_get_temp_thresholds — disk warn/crit °C from platform config
|
||||
# platform_is_maintenance_running — parity check/sync in progress
|
||||
# platform_is_service_enabled — docker or libvirt enabled in boot config
|
||||
# platform_restart_service — restart a named service via rc.d
|
||||
@@ -27,6 +28,14 @@
|
||||
# platform_is_mover_running — unRAID mover process check
|
||||
# platform_stop_user_scripts — kill all user.scripts background processes
|
||||
# platform_send_os_notification — native unRAID notify (dynamix)
|
||||
# platform_storage_path — root path for user shares/storage (e.g. /mnt/user)
|
||||
# platform_webui_install_path — where the platform serves the WebGUI plugin files from
|
||||
# platform_scripts_dir_probe_cmd — shell command to run on a remote to discover its SCRIPTS_DIR
|
||||
# platform_get_templates_dir — path to Unraid CA docker templates-user directory
|
||||
# platform_setup_db_path — path to the persistent Varaverk setup/wizard state database
|
||||
# platform_rebuild_container — rebuild a container from its stored XML template
|
||||
# platform_push_conf — push master.conf to all listed hosts via WebGUI PHP
|
||||
# platform_push_setup_state — push wizard setup state to WebGUI PHP
|
||||
# ==============================================================================================
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
@@ -79,23 +88,30 @@ platform_storage_healthy() {
|
||||
# Writes raw /var/local/emhttp/disks.ini to stdout.
|
||||
# Returns 1 if the file is absent (array not started or emhttp not running).
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_disk_states_path() {
|
||||
echo "/var/local/emhttp/disks.ini"
|
||||
}
|
||||
|
||||
platform_get_disk_states() {
|
||||
local disks_ini="/var/local/emhttp/disks.ini"
|
||||
local disks_ini
|
||||
disks_ini=$(platform_disk_states_path)
|
||||
[[ -f "$disks_ini" ]] || return 1
|
||||
cat "$disks_ini"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_get_temp_thresholds
|
||||
# Writes two space-separated values to stdout: WARN_TEMP CRIT_TEMP (°C integers).
|
||||
# Falls back to 45 55 if dynamix.cfg is absent or the keys are missing.
|
||||
# Writes four space-separated values to stdout: HDD_HOT HDD_MAX SSD_HOT SSD_MAX (°C integers).
|
||||
# Falls back to 45/55/60/70 if dynamix.cfg is absent or keys are missing.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_get_temp_thresholds() {
|
||||
local cfg="/boot/config/plugins/dynamix/dynamix.cfg"
|
||||
local warn crit
|
||||
warn=$(grep -m1 '^diskWarn=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
|
||||
crit=$(grep -m1 '^diskCrit=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
|
||||
echo "${warn:-45} ${crit:-55}"
|
||||
local hdd_hot hdd_max ssd_hot ssd_max
|
||||
hdd_hot=$(grep -m1 '^hot=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
|
||||
hdd_max=$(grep -m1 '^max=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
|
||||
ssd_hot=$(grep -m1 '^hotssd=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
|
||||
ssd_max=$(grep -m1 '^maxssd=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
|
||||
echo "${hdd_hot:-45} ${hdd_max:-55} ${ssd_hot:-60} ${ssd_max:-70}"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
@@ -200,6 +216,39 @@ platform_stop_user_scripts() {
|
||||
# severity: normal | warning | alert (default: normal)
|
||||
# Returns 1 if the notify script is absent.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# ── Platform path defaults ─────────────────────────────────────────────────────────────────────
|
||||
# Set here so master.conf and core scripts never contain OS-specific path literals.
|
||||
# host*.conf may override any of these after the adapter is sourced.
|
||||
DOCKER_APPDATA_BASE="${DOCKER_APPDATA_BASE:-/mnt/user/appdata}"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_storage_path
|
||||
# Writes the root path where user shares and storage are accessible to stdout.
|
||||
# Used by array health checks and disk verification.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_storage_path() {
|
||||
echo "/mnt/user"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_webui_install_path
|
||||
# Writes the path where the platform serves WebGUI plugin files from.
|
||||
# On Unraid the PHP WebGUI is served from /boot/, separate from SCRIPTS_DIR.
|
||||
# Returns 1 (empty) if the platform serves directly from SCRIPTS_DIR (no sync needed).
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_webui_install_path() {
|
||||
echo "/boot/config/plugins/varaverk"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_scripts_dir_probe_cmd
|
||||
# Writes a shell command suitable for running on a remote via SSH to discover
|
||||
# that remote's SCRIPTS_DIR. Each platform exposes its install path differently.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_scripts_dir_probe_cmd() {
|
||||
echo "grep -m1 'SCRIPTS_DIR' /boot/config/plugins/varaverk/varaverk.cfg 2>/dev/null | cut -d= -f2 | tr -d '\"'"
|
||||
}
|
||||
|
||||
platform_send_os_notification() {
|
||||
local message="$1"
|
||||
local subject="${2:-Varaverk}"
|
||||
@@ -209,3 +258,66 @@ platform_send_os_notification() {
|
||||
[[ -x "$notify_script" ]] || return 1
|
||||
"$notify_script" -e "Varaverk" -s "$subject" -d "$message" -i "$severity" 2>/dev/null
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_get_templates_dir
|
||||
# Writes the path to the Unraid CA docker templates-user directory to stdout.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_get_templates_dir() {
|
||||
echo "/boot/config/plugins/dockerMan/templates-user"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_setup_db_path
|
||||
# Writes the path to the persistent Varaverk setup/wizard state database.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_setup_db_path() {
|
||||
echo "/boot/config/varaverk_setup.db"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_rebuild_container <container_name>
|
||||
# Rebuilds a container from its stored Unraid CA XML template (stops old, recreates on new
|
||||
# image digest, prunes old image). Returns 1 if the rebuild script is absent or fails.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_rebuild_container() {
|
||||
local container="$1"
|
||||
local rebuild_script="/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container"
|
||||
[[ -x "$rebuild_script" ]] || return 1
|
||||
"$rebuild_script" "$container" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_push_conf
|
||||
# Pushes master.conf to all listed hosts via the WebGUI PHP API.
|
||||
# Writes per-host push results to stdout. Returns 1 if any host failed.
|
||||
# Returns 0 silently if php is unavailable (caller should warn manually).
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_push_conf() {
|
||||
command -v php &>/dev/null || return 0
|
||||
local rc=0
|
||||
php -r "
|
||||
require_once '/usr/local/emhttp/plugins/varaverk/include/config.php';
|
||||
\$results = vv_push_master_conf();
|
||||
if (empty(\$results)) { echo 'no remote hosts'; exit(0); }
|
||||
\$failed = 0;
|
||||
foreach (\$results as \$r) {
|
||||
echo \$r['host'] . ': ' . (\$r['ok'] ? 'pushed' : 'FAILED — ' . \$r['error']) . PHP_EOL;
|
||||
if (!\$r['ok']) \$failed++;
|
||||
}
|
||||
exit(\$failed > 0 ? 1 : 0);
|
||||
" 2>/dev/null
|
||||
rc=$?
|
||||
return $rc
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# platform_push_setup_state
|
||||
# Pushes the Varaverk wizard setup state to all partners via the WebGUI PHP API.
|
||||
# No-op if php is unavailable. Always returns 0.
|
||||
# ──────────────────────────────────────────────────────────────────────────────────────────────
|
||||
platform_push_setup_state() {
|
||||
command -v php &>/dev/null || return 0
|
||||
php -r "require_once '/usr/local/emhttp/plugins/varaverk/include/config.php'; vv_push_setup_state();" 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -404,9 +404,13 @@ function vv_parity_status(): array {
|
||||
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);
|
||||
$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);
|
||||
@@ -452,6 +456,8 @@ function vv_parity_status(): array {
|
||||
$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,
|
||||
|
||||
@@ -561,20 +561,23 @@ function vvDiskRow(disk) {
|
||||
const tempColor = vvTempColor(tempC, disk.transport);
|
||||
const tempStr = tempC !== null ? `${tempC}°` : '—';
|
||||
const isParity = disk.role === 'parity';
|
||||
const nameColor = isParity ? '#6a8faf' : '#aaa';
|
||||
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 `<div style="margin-bottom:7px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;font-size:11px;margin-bottom:3px;">
|
||||
<span style="color:${nameColor};display:flex;align-items:center;">${disk.name}${spinLabel}${vvIoChip(disk.device)}</span>
|
||||
<span style="color:${nameColor};display:flex;align-items:center;">${disk.name}${spinLabel}${failLabel}${vvIoChip(disk.device)}</span>
|
||||
${right}
|
||||
<span style="color:${tempColor};font-size:10px;margin-left:6px;flex-shrink:0;">${tempStr}</span>
|
||||
</div>
|
||||
@@ -968,10 +971,8 @@ function vvPollMonitor() {
|
||||
// ── 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';
|
||||
const valid = par.valid !== false;
|
||||
const inProg = par.in_progress;
|
||||
|
||||
function vvRelTime(ts) {
|
||||
if (!ts) return '';
|
||||
@@ -999,15 +1000,21 @@ function vvPollMonitor() {
|
||||
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';
|
||||
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 = `<div class="vv-banner ${parBannerCls}">
|
||||
<span>${valid ? '✓ Valid' : '✗ INVALID'}</span>
|
||||
${emulLabel}${missingLabel}
|
||||
${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}
|
||||
</div>`;
|
||||
|
||||
@@ -1027,6 +1034,7 @@ function vvPollMonitor() {
|
||||
<span style="color:${exitColor};">${par.exit_label ?? '—'}</span>
|
||||
<span style="color:#555;">Errors</span>
|
||||
<span style="color:${errColor};">${par.errors ?? 0}</span>
|
||||
${numDisabled > 0 ? `<span style="color:#555;">Emulating</span><span style="color:#ff9800;">${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : ''}
|
||||
<span style="color:#555;">Next check</span>
|
||||
<span style="color:#888;">${nextDate}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user