Bug fixes, arr cleanup API-driven scan, shared JS formatters
Bug fixes: - fallback.sh: escape sed metacharacters (\ & |) in state_set values - common.sh: parse PID from lock file content correctly (handles pid:metadata format) - unraid_api_key_renew.sh: fix path depth (../../../) and sync registry key to conf when stale - stop.php: only clear pid/status if process is actually dead — D-state survives SIGKILL - array_started.sh: check ARRAY_START_SCRIPTS empty before printing launch header Arr cleanup: - radarr_cleanup.sh / sonarr_cleanup.sh: fetch root folders from arr API instead of reverse-looking up the path map — handles multi-root-folder setups correctly UI: - varaverk.js: extract shared formatters (_relTime, _fmtBytes, _sz, _uptime, _gb, _tb, _n) - arrs.php / partnership.php: use shared formatters, remove duplicates - arrs.php / fallback.php: show error message on fetch failure instead of silent empty - docker.php: disable rename input during request, restore original value on failure - setup.php: abort controller timeout on detect fetch - partnership.php: remove Re-run Phase 2 button opacity dimming
This commit is contained in:
@@ -321,7 +321,9 @@ state_get() {
|
|||||||
state_set() {
|
state_set() {
|
||||||
local key="$1" value="$2"
|
local key="$1" value="$2"
|
||||||
if grep -q "^${key}=" "$FALLBACK_STATE_FILE" 2>/dev/null; then
|
if grep -q "^${key}=" "$FALLBACK_STATE_FILE" 2>/dev/null; then
|
||||||
sed -i "s|^${key}=.*|${key}=${value}|" "$FALLBACK_STATE_FILE"
|
# Escape sed replacement metacharacters: \ first, then & and |
|
||||||
|
local safe="${value//\\/\\\\}"; safe="${safe//&/\\&}"; safe="${safe//|/\\|}"
|
||||||
|
sed -i "s|^${key}=.*|${key}=${safe}|" "$FALLBACK_STATE_FILE"
|
||||||
else
|
else
|
||||||
echo "${key}=${value}" >> "$FALLBACK_STATE_FILE"
|
echo "${key}=${value}" >> "$FALLBACK_STATE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|||||||
+19
-18
@@ -305,24 +305,25 @@ format_bytes() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "━━━ $ICON_SYNC Pre-flight: Radarr Import Scan ━━━"
|
echo "━━━ $ICON_SYNC Pre-flight: Radarr Import Scan ━━━"
|
||||||
|
|
||||||
# Reverse-lookup container path from path map so Radarr gets its own path, not the host path
|
# Fetch root folders from Radarr API and translate container paths to host paths
|
||||||
RADARR_CONTAINER_ROOT=""
|
mapfile -t SCAN_ROOTS < <(
|
||||||
for _cp in "${!ARR_PATH_MAP[@]}"; do
|
radarr_api "rootfolder" | \
|
||||||
if [[ "${ARR_PATH_MAP[$_cp]}" == "$RADARR_MOVIES_ROOT" ]]; then
|
jq -r '.[].path' 2>/dev/null | \
|
||||||
RADARR_CONTAINER_ROOT="$_cp"
|
while IFS= read -r cp; do translate_path "$cp"; done
|
||||||
break
|
)
|
||||||
fi
|
|
||||||
done
|
|
||||||
unset _cp
|
|
||||||
|
|
||||||
if [[ -n "$RADARR_CONTAINER_ROOT" ]]; then
|
if [[ "${#SCAN_ROOTS[@]}" -eq 0 ]]; then
|
||||||
info "Triggering DownloadedMoviesScan on: $RADARR_CONTAINER_ROOT"
|
error "No root folders returned from Radarr API — aborting"
|
||||||
SCAN_PAYLOAD="{\"name\": \"DownloadedMoviesScan\", \"path\": \"$RADARR_CONTAINER_ROOT\"}"
|
notify "Radarr cleanup aborted on $(hostname) — no root folders from API" \
|
||||||
else
|
"Radarr Cleanup" "warning"
|
||||||
info "No path map match — triggering DownloadedMoviesScan (all root folders)"
|
exit 1
|
||||||
SCAN_PAYLOAD='{"name": "DownloadedMoviesScan"}'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
info "Scan targets (${#SCAN_ROOTS[@]}): ${SCAN_ROOTS[*]}"
|
||||||
|
|
||||||
|
info "Triggering DownloadedMoviesScan (all root folders)"
|
||||||
|
SCAN_PAYLOAD='{"name": "DownloadedMoviesScan"}'
|
||||||
|
|
||||||
SCAN_RESPONSE=$(curl -sf --max-time 30 -X POST \
|
SCAN_RESPONSE=$(curl -sf --max-time 30 -X POST \
|
||||||
-H "X-Api-Key: $RADARR_API_KEY" \
|
-H "X-Api-Key: $RADARR_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -487,7 +488,7 @@ while IFS= read -r filepath; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
done < <(
|
done < <(
|
||||||
for host_path in "${ARR_PATH_MAP[@]}" "$RADARR_MOVIES_ROOT"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
||||||
done | sort -u
|
done | sort -u
|
||||||
)
|
)
|
||||||
@@ -532,13 +533,13 @@ if [[ "$DRY_RUN" == false ]]; then
|
|||||||
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||||
|
|
||||||
done < <(
|
done < <(
|
||||||
for host_path in "${ARR_PATH_MAP[@]}" "$RADARR_MOVIES_ROOT"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
||||||
done | sort -u
|
done | sort -u
|
||||||
)
|
)
|
||||||
|
|
||||||
info "Cleaning up empty folders..."
|
info "Cleaning up empty folders..."
|
||||||
for host_path in "${ARR_PATH_MAP[@]}" "$RADARR_MOVIES_ROOT"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
[[ -d "$host_path" ]] && \
|
[[ -d "$host_path" ]] && \
|
||||||
find "$host_path" -mindepth 1 -type d -empty -delete 2>/dev/null
|
find "$host_path" -mindepth 1 -type d -empty -delete 2>/dev/null
|
||||||
done
|
done
|
||||||
|
|||||||
+19
-18
@@ -305,24 +305,25 @@ format_bytes() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "━━━ $ICON_SYNC Pre-flight: Sonarr Import Scan ━━━"
|
echo "━━━ $ICON_SYNC Pre-flight: Sonarr Import Scan ━━━"
|
||||||
|
|
||||||
# Reverse-lookup container path from path map so Sonarr gets its own path, not the host path
|
# Fetch root folders from Sonarr API and translate container paths to host paths
|
||||||
SONARR_CONTAINER_ROOT=""
|
mapfile -t SCAN_ROOTS < <(
|
||||||
for _cp in "${!ARR_PATH_MAP[@]}"; do
|
sonarr_api "rootfolder" | \
|
||||||
if [[ "${ARR_PATH_MAP[$_cp]}" == "$SONARR_TV_ROOT" ]]; then
|
jq -r '.[].path' 2>/dev/null | \
|
||||||
SONARR_CONTAINER_ROOT="$_cp"
|
while IFS= read -r cp; do translate_path "$cp"; done
|
||||||
break
|
)
|
||||||
fi
|
|
||||||
done
|
|
||||||
unset _cp
|
|
||||||
|
|
||||||
if [[ -n "$SONARR_CONTAINER_ROOT" ]]; then
|
if [[ "${#SCAN_ROOTS[@]}" -eq 0 ]]; then
|
||||||
info "Triggering DownloadedEpisodesScan on: $SONARR_CONTAINER_ROOT"
|
error "No root folders returned from Sonarr API — aborting"
|
||||||
SCAN_PAYLOAD="{\"name\": \"DownloadedEpisodesScan\", \"path\": \"$SONARR_CONTAINER_ROOT\"}"
|
notify "Sonarr cleanup aborted on $(hostname) — no root folders from API" \
|
||||||
else
|
"Sonarr Cleanup" "warning"
|
||||||
info "No path map match — triggering DownloadedEpisodesScan (all root folders)"
|
exit 1
|
||||||
SCAN_PAYLOAD='{"name": "DownloadedEpisodesScan"}'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
info "Scan targets (${#SCAN_ROOTS[@]}): ${SCAN_ROOTS[*]}"
|
||||||
|
|
||||||
|
info "Triggering DownloadedEpisodesScan (all root folders)"
|
||||||
|
SCAN_PAYLOAD='{"name": "DownloadedEpisodesScan"}'
|
||||||
|
|
||||||
SCAN_RESPONSE=$(curl -sf --max-time 30 -X POST \
|
SCAN_RESPONSE=$(curl -sf --max-time 30 -X POST \
|
||||||
-H "X-Api-Key: $SONARR_API_KEY" \
|
-H "X-Api-Key: $SONARR_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -486,7 +487,7 @@ while IFS= read -r filepath; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
done < <(
|
done < <(
|
||||||
for host_path in "${ARR_PATH_MAP[@]}" "$SONARR_TV_ROOT"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
||||||
done | sort -u
|
done | sort -u
|
||||||
)
|
)
|
||||||
@@ -531,13 +532,13 @@ if [[ "$DRY_RUN" == false ]]; then
|
|||||||
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||||
|
|
||||||
done < <(
|
done < <(
|
||||||
for host_path in "${ARR_PATH_MAP[@]}" "$SONARR_TV_ROOT"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
||||||
done | sort -u
|
done | sort -u
|
||||||
)
|
)
|
||||||
|
|
||||||
info "Cleaning up empty folders..."
|
info "Cleaning up empty folders..."
|
||||||
for host_path in "${ARR_PATH_MAP[@]}" "$SONARR_TV_ROOT"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
[[ -d "$host_path" ]] && \
|
[[ -d "$host_path" ]] && \
|
||||||
find "$host_path" -mindepth 1 -type d -empty -delete 2>/dev/null
|
find "$host_path" -mindepth 1 -type d -empty -delete 2>/dev/null
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -123,6 +123,13 @@ fi
|
|||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
# ━━━ Launch Scripts ━━━
|
# ━━━ Launch Scripts ━━━
|
||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
|
if [[ ${#ARRAY_START_SCRIPTS[@]} -eq 0 ]]; then
|
||||||
|
notify "Array started on $LOCAL_SERVER_NAME ($MY_ID) but ARRAY_START_SCRIPTS is empty — boot sequence skipped. Check master.conf." \
|
||||||
|
"Array Start" "alert"
|
||||||
|
error "ARRAY_START_SCRIPTS is empty — check master.conf"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━ $ICON_GEAR Array Start — $MY_ID — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
echo "━━━ $ICON_GEAR Array Start — $MY_ID — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
||||||
log "Ecosystem root: $ECOSYSTEM_ROOT"
|
log "Ecosystem root: $ECOSYSTEM_ROOT"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ acquire_lock
|
|||||||
detect_hosts
|
detect_hosts
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
CONF_FILE="$SCRIPT_DIR/../Configurations/${MY_ID,,}.conf"
|
CONF_FILE="$SCRIPT_DIR/../../../Configurations/${MY_ID,,}.conf"
|
||||||
VAR_NAME="${MY_ID}_UNRAID_API_KEY"
|
VAR_NAME="${MY_ID}_UNRAID_API_KEY"
|
||||||
|
|
||||||
# Key name: "Varaverk <hostname>" stripping any unraid- prefix
|
# Key name: "Varaverk <hostname>" stripping any unraid- prefix
|
||||||
@@ -69,8 +69,21 @@ KEY=$(echo "$EXISTING" | jq -r '.key // empty' 2>/dev/null)
|
|||||||
|
|
||||||
if [[ -n "$KEY" ]]; then
|
if [[ -n "$KEY" ]]; then
|
||||||
PREVIEW="${KEY:0:8}...${KEY: -4}"
|
PREVIEW="${KEY:0:8}...${KEY: -4}"
|
||||||
echo "API key valid ✅ — $VAR_NAME = $PREVIEW"
|
# Always sync registry key → conf, even if the key was already there.
|
||||||
log "Key found in registry — no renewal needed"
|
# Conf gets wiped on git pull / conf regeneration without touching the registry.
|
||||||
|
CONF_HAS_KEY=$(grep -oP "(?<=^\s*${VAR_NAME}=\")[^\"]*" "$CONF_FILE" 2>/dev/null || true)
|
||||||
|
if [[ "$CONF_HAS_KEY" == "$KEY" ]]; then
|
||||||
|
echo "API key valid ✅ — $VAR_NAME = $PREVIEW"
|
||||||
|
log "Key in registry and conf — no action needed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
log "Key in registry but conf is stale — syncing..."
|
||||||
|
if grep -q "^\s*${VAR_NAME}\s*=" "$CONF_FILE"; then
|
||||||
|
sed -i "s|^\(\s*${VAR_NAME}\s*=\s*\)\"[^\"]*\"|\1\"${KEY}\"|" "$CONF_FILE"
|
||||||
|
else
|
||||||
|
echo " ${VAR_NAME}=\"${KEY}\"" >> "$CONF_FILE"
|
||||||
|
fi
|
||||||
|
echo "API key synced to conf ✅ — $VAR_NAME = $PREVIEW"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -76,16 +76,17 @@ if (file_exists($namedLock)) {
|
|||||||
if (!in_array(basename($namedLock), $cleared)) $cleared[] = basename($namedLock);
|
if (!in_array(basename($namedLock), $cleared)) $cleared[] = basename($namedLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stat file
|
// Update stat file — only clear pid if actually dead (D-state processes survive SIGKILL)
|
||||||
$now = time();
|
$now = time();
|
||||||
$stat['status'] = 'stopped';
|
$stat['status'] = $dead ? 'stopped' : 'running';
|
||||||
$stat['end'] = $now;
|
$stat['end'] = $dead ? $now : ($stat['end'] ?? null);
|
||||||
$stat['exit'] = -1;
|
$stat['exit'] = $dead ? -1 : ($stat['exit'] ?? null);
|
||||||
unset($stat['pid']);
|
if ($dead) unset($stat['pid']);
|
||||||
file_put_contents($statFile, json_encode($stat));
|
file_put_contents($statFile, json_encode($stat));
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'ok' => true,
|
'ok' => $dead,
|
||||||
'killed' => $dead,
|
'killed' => $dead,
|
||||||
'locks' => $cleared,
|
'locks' => $cleared,
|
||||||
|
'error' => $dead ? null : 'Process still alive after SIGKILL (D-state) — lock may persist',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,6 +1,47 @@
|
|||||||
// Varaverk — shared JS utilities
|
// Varaverk — shared JS utilities
|
||||||
// Page-specific JS lives inline in each page partial.
|
// Page-specific JS lives inline in each page partial.
|
||||||
|
|
||||||
|
// ── Shared formatters ────────────────────────────────────────────────────────
|
||||||
|
const _GB = 1073741824, _MB = 1048576, _TB = 1099511627776;
|
||||||
|
|
||||||
|
// Relative timestamp → "just now" / "5m ago" / "yesterday" / "3d ago"
|
||||||
|
function _relTime(ts) {
|
||||||
|
if (!ts) return '—';
|
||||||
|
const d = Math.floor(Date.now() / 1000) - ts;
|
||||||
|
if (d < 60) return 'just now';
|
||||||
|
if (d < 3600) return Math.floor(d / 60) + 'm ago';
|
||||||
|
if (d < 86400) return Math.floor(d / 3600) + 'h ago';
|
||||||
|
if (d < 172800) return 'yesterday';
|
||||||
|
return Math.floor(d / 86400) + 'd ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes → human-readable size (GB / MB / KB)
|
||||||
|
function _fmtBytes(b) {
|
||||||
|
if (!b) return '—';
|
||||||
|
if (b >= _GB) return (b / _GB).toFixed(1) + ' GB';
|
||||||
|
if (b >= _MB) return (b / _MB).toFixed(0) + ' MB';
|
||||||
|
return (b / 1024).toFixed(0) + ' KB';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes → auto-scale TB / GB / MB
|
||||||
|
function _sz(b) {
|
||||||
|
if (!b) return '—';
|
||||||
|
if (b >= _TB) return (b / _TB).toFixed(1) + ' TB';
|
||||||
|
if (b >= _GB) return (b / _GB).toFixed(1) + ' GB';
|
||||||
|
return (b / _MB).toFixed(0) + ' MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
function _gb(b) { return !b ? '—' : (b / _GB).toFixed(1) + ' GB'; }
|
||||||
|
function _tb(b) { return !b ? '—' : (b / _TB).toFixed(2) + ' TB'; }
|
||||||
|
function _n(v) { return v == null ? '—' : Number(v).toLocaleString(); }
|
||||||
|
|
||||||
|
// Seconds → uptime string: "2d 3h 5m" / "3h 5m" / "5m"
|
||||||
|
function _uptime(sec) {
|
||||||
|
if (!sec) return '—';
|
||||||
|
const d = Math.floor(sec / 86400), h = Math.floor((sec % 86400) / 3600), m = Math.floor((sec % 3600) / 60);
|
||||||
|
return d > 0 ? `${d}d ${h}h ${m}m` : h > 0 ? `${h}h ${m}m` : `${m}m`;
|
||||||
|
}
|
||||||
|
|
||||||
// Flash a status element briefly then fade
|
// Flash a status element briefly then fade
|
||||||
function vvFlashStatus(el, msg, ok) {
|
function vvFlashStatus(el, msg, ok) {
|
||||||
el.textContent = msg;
|
el.textContent = msg;
|
||||||
|
|||||||
@@ -96,26 +96,6 @@
|
|||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
||||||
function _rel(ts) {
|
|
||||||
if (!ts) return '—';
|
|
||||||
const d = Math.floor(Date.now() / 1000) - ts;
|
|
||||||
if (d < 60) return 'just now';
|
|
||||||
if (d < 3600) return Math.floor(d/60) + 'm ago';
|
|
||||||
if (d < 86400) return Math.floor(d/3600) + 'h ago';
|
|
||||||
if (d < 172800)return 'yesterday';
|
|
||||||
return Math.floor(d/86400) + 'd ago';
|
|
||||||
}
|
|
||||||
function _n(v) { return v == null ? '—' : Number(v).toLocaleString(); }
|
|
||||||
function _gb(b) { if (!b) return '—'; return (b / 1073741824).toFixed(1) + ' GB'; }
|
|
||||||
function _tb(b) { if (!b) return '—'; return (b / 1099511627776).toFixed(2) + ' TB'; }
|
|
||||||
function _sz(b) {
|
|
||||||
if (!b) return '—';
|
|
||||||
if (b >= 1099511627776) return (b/1099511627776).toFixed(1) + ' TB';
|
|
||||||
if (b >= 1073741824) return (b/1073741824).toFixed(1) + ' GB';
|
|
||||||
return (b/1048576).toFixed(0) + ' MB';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Arr card ──────────────────────────────────────────────────────────────────
|
// ── Arr card ──────────────────────────────────────────────────────────────────
|
||||||
function _arrCard(arr) {
|
function _arrCard(arr) {
|
||||||
const LABELS = { sonarr:'Sonarr', radarr:'Radarr', lidarr:'Lidarr' };
|
const LABELS = { sonarr:'Sonarr', radarr:'Radarr', lidarr:'Lidarr' };
|
||||||
@@ -209,7 +189,7 @@ function _arrCard(arr) {
|
|||||||
cleanup = `
|
cleanup = `
|
||||||
<div class="vv-arr-meta">
|
<div class="vv-arr-meta">
|
||||||
<span class="vv-arr-meta-lbl">Cleanup</span>
|
<span class="vv-arr-meta-lbl">Cleanup</span>
|
||||||
<span class="vv-arr-meta-val">${_rel(cl.last_run)} ${st}</span>
|
<span class="vv-arr-meta-val">${_relTime(cl.last_run)} ${st}</span>
|
||||||
</div>
|
</div>
|
||||||
${cl.tracked != null ? `<div class="vv-arr-meta-sub" style="${orCol}">
|
${cl.tracked != null ? `<div class="vv-arr-meta-sub" style="${orCol}">
|
||||||
${_n(cl.tracked)} files · ${orph} orphans${junk ? ' · ' + junk + ' junk' : ''}
|
${_n(cl.tracked)} files · ${orph} orphans${junk ? ' · ' + junk + ' junk' : ''}
|
||||||
@@ -223,7 +203,7 @@ function _arrCard(arr) {
|
|||||||
const added = dsc.added != null ? ` <span style="color:#6fcf97">+${dsc.added}</span>` : '';
|
const added = dsc.added != null ? ` <span style="color:#6fcf97">+${dsc.added}</span>` : '';
|
||||||
disc = `<div class="vv-arr-meta">
|
disc = `<div class="vv-arr-meta">
|
||||||
<span class="vv-arr-meta-lbl">Discovery</span>
|
<span class="vv-arr-meta-lbl">Discovery</span>
|
||||||
<span class="vv-arr-meta-val">${_rel(dsc.last_run)}${added}</span>
|
<span class="vv-arr-meta-val">${_relTime(dsc.last_run)}${added}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +275,7 @@ function _syncSection(sync, recovery, settings) {
|
|||||||
const pillTxt = !data.last_run ? 'never run' : ok ? 'ok' : data.status || 'error';
|
const pillTxt = !data.last_run ? 'never run' : ok ? 'ok' : data.status || 'error';
|
||||||
let body = '';
|
let body = '';
|
||||||
if (data.last_run) {
|
if (data.last_run) {
|
||||||
body += `<div style="font-size:11px;color:#555;margin-bottom:10px;">${_rel(data.last_run)}</div>`;
|
body += `<div style="font-size:11px;color:#555;margin-bottom:10px;">${_relTime(data.last_run)}</div>`;
|
||||||
}
|
}
|
||||||
body += `<div class="vv-arr-sr-row">${items}</div>`;
|
body += `<div class="vv-arr-sr-row">${items}</div>`;
|
||||||
if (extra) body += extra;
|
if (extra) body += extra;
|
||||||
@@ -410,7 +390,12 @@ function _render(data) {
|
|||||||
|
|
||||||
function vvArrsLoad() {
|
function vvArrsLoad() {
|
||||||
fetch('/plugins/varaverk/api/arrs.php')
|
fetch('/plugins/varaverk/api/arrs.php')
|
||||||
.then(r => r.json()).then(_render).catch(() => {});
|
.then(r => r.json())
|
||||||
|
.then(_render)
|
||||||
|
.catch(() => {
|
||||||
|
document.getElementById('vv-arrs-grid').innerHTML =
|
||||||
|
'<div style="grid-column:1/-1;color:#555;font-size:12px;padding:24px 0;text-align:center;">Error loading arrs data — check API</div>';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
vvArrsLoad();
|
vvArrsLoad();
|
||||||
|
|||||||
@@ -291,7 +291,11 @@ function _bindEvents() {
|
|||||||
if (!fid || !name) return;
|
if (!fid || !name) return;
|
||||||
const f = (_data.folders||[]).find(x=>x.id===fid);
|
const f = (_data.folders||[]).find(x=>x.id===fid);
|
||||||
if (f && name===f.name) return;
|
if (f && name===f.name) return;
|
||||||
_api({action:'rename_folder',folder_id:fid,name}, r => { if(r.ok) _reload(); else alert('Rename failed: '+(r.error||'?')); });
|
el.disabled = true; el.style.opacity = '0.5';
|
||||||
|
_api({action:'rename_folder',folder_id:fid,name}, r => {
|
||||||
|
el.disabled = false; el.style.opacity = '';
|
||||||
|
if (r.ok) _reload(); else { alert('Rename failed: '+(r.error||'?')); el.value = f?.name ?? name; }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,10 @@ function vvFbLoad() {
|
|||||||
fetch('/plugins/varaverk/api/fallback.php')
|
fetch('/plugins/varaverk/api/fallback.php')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(_render)
|
.then(_render)
|
||||||
.catch(() => {});
|
.catch(() => {
|
||||||
|
document.getElementById('vv-fb-grid').innerHTML =
|
||||||
|
'<div style="grid-column:1/-1;color:#555;font-size:12px;padding:24px 0;text-align:center;">Error loading fallback data — check API</div>';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
vvFbLoad();
|
vvFbLoad();
|
||||||
|
|||||||
@@ -593,24 +593,6 @@ function vvPtSaveSettings(btn) {
|
|||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function _relTime(ts) {
|
|
||||||
if (!ts) return '—';
|
|
||||||
const d = Math.floor(Date.now() / 1000) - ts;
|
|
||||||
if (d < 60) return 'just now';
|
|
||||||
if (d < 3600) return Math.floor(d / 60) + 'm ago';
|
|
||||||
if (d < 86400) return Math.floor(d / 3600) + 'h ago';
|
|
||||||
if (d < 172800) return 'yesterday';
|
|
||||||
return Math.floor(d / 86400) + 'd ago';
|
|
||||||
}
|
|
||||||
|
|
||||||
function _uptime(sec) {
|
|
||||||
if (!sec) return '—';
|
|
||||||
const d = Math.floor(sec / 86400);
|
|
||||||
const h = Math.floor((sec % 86400) / 3600);
|
|
||||||
const m = Math.floor((sec % 3600) / 60);
|
|
||||||
return d > 0 ? `${d}d ${h}h ${m}m` : h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _row(lbl, val) {
|
function _row(lbl, val) {
|
||||||
return `<div class="vv-pt-row"><span class="vv-pt-lbl">${lbl}</span><span class="vv-pt-val">${val}</span></div>`;
|
return `<div class="vv-pt-row"><span class="vv-pt-lbl">${lbl}</span><span class="vv-pt-val">${val}</span></div>`;
|
||||||
}
|
}
|
||||||
@@ -1083,7 +1065,7 @@ function _renderActions(nodes, cfg) {
|
|||||||
</div>
|
</div>
|
||||||
<div style="display:flex;flex-direction:column;gap:5px;align-items:flex-end;">
|
<div style="display:flex;flex-direction:column;gap:5px;align-items:flex-end;">
|
||||||
<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this,'${remote.id}')"
|
<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this,'${remote.id}')"
|
||||||
style="opacity:.35;font-size:10px;" title="Re-run Phase 2">↻ Re-run</button>
|
style="font-size:10px;" title="Re-run Phase 2">↻ Re-run</button>
|
||||||
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
|
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
|
||||||
style="opacity:.4;font-size:10px;">🗑 Keys</button>
|
style="opacity:.4;font-size:10px;">🗑 Keys</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ let _vvRedirect = '?tab=scheduler';
|
|||||||
|
|
||||||
// ── Detection banner ──────────────────────────────────────────────────────────
|
// ── Detection banner ──────────────────────────────────────────────────────────
|
||||||
(function() {
|
(function() {
|
||||||
fetch('/plugins/varaverk/api/setup.php?action=detect&_=' + Date.now())
|
const _ac = new AbortController();
|
||||||
|
setTimeout(() => _ac.abort(), 6000);
|
||||||
|
fetch('/plugins/varaverk/api/setup.php?action=detect&_=' + Date.now(), {signal: _ac.signal})
|
||||||
.then(r => r.json()).then(d => {
|
.then(r => r.json()).then(d => {
|
||||||
const b = document.getElementById('vv-detect-banner');
|
const b = document.getElementById('vv-detect-banner');
|
||||||
if (!d.ok) { b.innerHTML = '<span style="color:#555">Detection unavailable</span>'; return; }
|
if (!d.ok) { b.innerHTML = '<span style="color:#555">Detection unavailable</span>'; return; }
|
||||||
|
|||||||
@@ -1490,8 +1490,9 @@ acquire_rsync_lock() {
|
|||||||
local actual_count=0
|
local actual_count=0
|
||||||
for lf in "$LOCK_DIR"/rsync_*.lock; do
|
for lf in "$LOCK_DIR"/rsync_*.lock; do
|
||||||
[[ -f "$lf" ]] || continue
|
[[ -f "$lf" ]] || continue
|
||||||
local lpid
|
local lpid lf_content
|
||||||
lpid=$(cat "$lf" 2>/dev/null)
|
lf_content=$(cat "$lf" 2>/dev/null)
|
||||||
|
lpid="${lf_content%%:*}"
|
||||||
kill -0 "$lpid" 2>/dev/null && ((actual_count++))
|
kill -0 "$lpid" 2>/dev/null && ((actual_count++))
|
||||||
done
|
done
|
||||||
if [[ "$actual_count" -ne "$current_count" ]]; then
|
if [[ "$actual_count" -ne "$current_count" ]]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user