Merge claude/2

This commit is contained in:
librelad 2026-07-06 18:19:58 +01:00
commit f4ba7ffe51
3 changed files with 93 additions and 65 deletions

View File

@ -2,54 +2,74 @@
webuiGenerateBackupAppStatus() webuiGenerateBackupAppStatus()
{ {
local app_name="$1" local app_name="${1:-}"
local output_dir="$containers_dir/libreportal/frontend/data/backup/generated/apps" local output_dir="$containers_dir/libreportal/frontend/data/backup/generated/apps"
runFileOp mkdir -p "$output_dir" runFileOp mkdir -p "$output_dir"
if [[ -z "$app_name" ]]; then # Resolve the app list: a single app (direct call — e.g. right after that
if [[ -f "$docker_dir/$db_file" ]]; then # app's backup) or every installed app (routine WebUI refresh).
while IFS= read -r a; do local apps=()
[[ -n "$a" ]] && webuiGenerateBackupAppStatus "$a" if [[ -n "$app_name" ]]; then
done < <(runInstallOp sqlite3 "$docker_dir/$db_file" "SELECT name FROM apps WHERE status = 1;" 2>/dev/null) apps=("$app_name")
fi elif [[ -f "$docker_dir/$db_file" ]]; then
return 0 while IFS= read -r a; do
[[ -n "$a" ]] && apps+=("$a")
done < <(runInstallOp sqlite3 "$docker_dir/$db_file" "SELECT name FROM apps WHERE status = 1;" 2>/dev/null)
fi fi
[[ ${#apps[@]} -eq 0 ]] && return 0
local output_file="$output_dir/${app_name}.json" # Fetch each enabled location's snapshot list ONCE, then filter per app
local temp_file="${output_file}.tmp.$$" # in-process. A single `restic snapshots --json` returns the whole repo,
# so this replaces the old O(apps x locations) restic spawns with one
local content="{" # restic call per location. jq reproduces restic's --tag/--host filtering.
content+="\"app\":\"$app_name\"," local host="${CFG_INSTALL_NAME}"
content+="\"generated_at\":\"$(date -Iseconds)\"," local loc_idxs=()
content+="\"host\":\"${CFG_INSTALL_NAME:-libreportal}\"," declare -A loc_name loc_snaps
content+="\"snapshots\":["
local first=true
local idx local idx
while IFS= read -r idx; do while IFS= read -r idx; do
[[ -z "$idx" ]] && continue [[ -z "$idx" ]] && continue
local snaps loc_idxs+=("$idx")
snaps=$(engineSnapshotsJson "$idx" "$app_name" "$CFG_INSTALL_NAME" 2>/dev/null) loc_name["$idx"]=$(resticLocationName "$idx")
[[ -z "$snaps" || "$snaps" == "[]" ]] && continue local raw
local count latest_id latest_time raw=$(engineSnapshotsJson "$idx" 2>/dev/null)
count=$(echo "$snaps" | grep -o '"short_id"' | wc -l | tr -d ' ') [[ -z "$raw" ]] && raw="[]"
latest_id=$(echo "$snaps" | grep -o '"short_id":"[^"]*"' | tail -1 | cut -d'"' -f4) loc_snaps["$idx"]="$raw"
latest_time=$(echo "$snaps" | grep -o '"time":"[^"]*"' | tail -1 | cut -d'"' -f4)
local name_esc
name_esc=$(printf '%s' "$(resticLocationName "$idx")" | sed 's/\\/\\\\/g; s/"/\\"/g')
$first || content+=","
first=false
content+="{"
content+="\"location_idx\":$idx,"
content+="\"location_name\":\"$name_esc\","
content+="\"count\":$count,"
content+="\"latest_id\":\"$latest_id\","
content+="\"latest_time\":\"$latest_time\""
content+="}"
done < <(resticEnabledLocations) done < <(resticEnabledLocations)
content+="]}"
echo "$content" | runFileWrite "$output_file" local app
for app in "${apps[@]}"; do
local content="{"
content+="\"app\":\"$app\","
content+="\"generated_at\":\"$(date -Iseconds)\","
content+="\"host\":\"${host:-libreportal}\","
content+="\"snapshots\":["
local first=true
for idx in "${loc_idxs[@]}"; do
local count latest_id latest_time
IFS=$'\t' read -r count latest_id latest_time <<<"$(printf '%s' "${loc_snaps[$idx]}" | jq -r \
--arg tag "app=$app" --arg host "$host" '
[ .[] | select($host=="" or .hostname==$host)
| select((.tags // []) | index($tag)) ] as $f
| ($f[-1] // {}) as $l
| "\($f|length)\t\($l.short_id // "")\t\($l.time // "")"' 2>/dev/null)"
[[ -z "$count" || "$count" == "0" ]] && continue
local name_esc
name_esc=$(printf '%s' "${loc_name[$idx]}" | sed 's/\\/\\\\/g; s/"/\\"/g')
$first || content+=","
first=false
content+="{"
content+="\"location_idx\":$idx,"
content+="\"location_name\":\"$name_esc\","
content+="\"count\":$count,"
content+="\"latest_id\":\"$latest_id\","
content+="\"latest_time\":\"$latest_time\""
content+="}"
done
content+="]}"
echo "$content" | runFileWrite "$output_dir/${app}.json"
done
} }

View File

@ -48,11 +48,20 @@ webuiGenerateBackupDashboard()
done < <(resticEnabledLocations) done < <(resticEnabledLocations)
locations_json+="]" locations_json+="]"
# Fetch the primary location's snapshots ONCE, then derive every app tile
# and the system-config status below by filtering it in jq — instead of a
# restic spawn per app plus one for system. jq reproduces restic's
# --tag/--host filtering (empty host = no host filter, as restic does).
local primary_idx primary_snaps="[]"
primary_idx=$(resticEnabledLocations | head -1)
if [[ -n "$primary_idx" ]]; then
primary_snaps=$(engineSnapshotsJson "$primary_idx" 2>/dev/null)
[[ -z "$primary_snaps" ]] && primary_snaps="[]"
fi
local apps_json="[" local apps_json="["
first=true first=true
if [[ -f "$docker_dir/$db_file" ]]; then if [[ -f "$docker_dir/$db_file" ]]; then
local primary_idx
primary_idx=$(resticEnabledLocations | head -1)
while IFS= read -r app; do while IFS= read -r app; do
[[ -z "$app" ]] && continue [[ -z "$app" ]] && continue
# The WebUI app is reproducible and skipped by backupAllApps — its # The WebUI app is reproducible and skipped by backupAllApps — its
@ -60,12 +69,11 @@ webuiGenerateBackupDashboard()
# perpetually-"No backup yet" tile for it. # perpetually-"No backup yet" tile for it.
[[ "$app" == "libreportal" ]] && continue [[ "$app" == "libreportal" ]] && continue
local latest_id="" latest_time="" local latest_id="" latest_time=""
if [[ -n "$primary_idx" ]]; then IFS=$'\t' read -r latest_id latest_time <<<"$(printf '%s' "$primary_snaps" | jq -r \
local snap_json --arg tag "app=$app" --arg host "$CFG_INSTALL_NAME" '
snap_json=$(engineSnapshotsJson "$primary_idx" "$app" "$CFG_INSTALL_NAME" 2>/dev/null) [ .[] | select($host=="" or .hostname==$host)
latest_id=$(echo "$snap_json" | grep -o '"short_id":"[^"]*"' | tail -1 | cut -d'"' -f4) | select((.tags // []) | index($tag)) ] | (.[-1] // {})
latest_time=$(echo "$snap_json" | grep -o '"time":"[^"]*"' | tail -1 | cut -d'"' -f4) | "\(.short_id // "")\t\(.time // "")"' 2>/dev/null)"
fi
$first || apps_json+="," $first || apps_json+=","
first=false first=false
apps_json+="{\"app\":\"$app\",\"latest_snapshot\":\"$latest_id\",\"latest_time\":\"$latest_time\"}" apps_json+="{\"app\":\"$app\",\"latest_snapshot\":\"$latest_id\",\"latest_time\":\"$latest_time\"}"
@ -75,14 +83,11 @@ webuiGenerateBackupDashboard()
# System-config backup status (tag system=config) — its own tracked item. # System-config backup status (tag system=config) — its own tracked item.
local system_id="" system_time="" local system_id="" system_time=""
local sys_idx IFS=$'\t' read -r system_id system_time <<<"$(printf '%s' "$primary_snaps" | jq -r \
sys_idx=$(resticEnabledLocations | head -1) --arg tag "system=config" --arg host "$CFG_INSTALL_NAME" '
if [[ -n "$sys_idx" ]]; then [ .[] | select($host=="" or .hostname==$host)
local sys_json | select((.tags // []) | index($tag)) ] | (.[-1] // {})
sys_json=$(engineSystemSnapshotsJson "$sys_idx" "$CFG_INSTALL_NAME" 2>/dev/null) | "\(.short_id // "")\t\(.time // "")"' 2>/dev/null)"
system_id=$(echo "$sys_json" | grep -o '"short_id":"[^"]*"' | tail -1 | cut -d'"' -f4)
system_time=$(echo "$sys_json" | grep -o '"time":"[^"]*"' | tail -1 | cut -d'"' -f4)
fi
local content="{" local content="{"
content+="\"generated_at\":\"$generated_at\"," content+="\"generated_at\":\"$generated_at\","

View File

@ -89,15 +89,18 @@ webuiGenerateBackupMigrate()
local app local app
while IFS= read -r app; do while IFS= read -r app; do
[[ -z "$app" ]] && continue [[ -z "$app" ]] && continue
local app_snaps_json # Filter the location's already-fetched snapshot list in-process
app_snaps_json=$(engineSnapshotsJson "$idx" "$app" "$host" 2>/dev/null) # (jq == restic --tag/--host) rather than a restic spawn per
local count # (host, app). Also gives an accurate count — the old grep -oc
count=$(printf '%s' "$app_snaps_json" | grep -oc '"short_id":"' || echo 0) # over single-line JSON always reported 1.
(( count == 0 )) && continue local count latest_id latest_date
IFS=$'\t' read -r count latest_id latest_date <<<"$(printf '%s' "$all_json" | jq -r \
local latest_id latest_date --arg tag "app=$app" --arg host "$host" '
latest_id=$(printf '%s' "$app_snaps_json" | grep -o '"short_id":"[^"]*"' | tail -1 | cut -d'"' -f4) [ .[] | select($host=="" or .hostname==$host)
latest_date=$(printf '%s' "$app_snaps_json" | grep -o '"time":"[^"]*"' | tail -1 | cut -d'"' -f4) | select((.tags // []) | index($tag)) ] as $f
| ($f[-1] // {}) as $l
| "\($f|length)\t\($l.short_id // "")\t\($l.time // "")"' 2>/dev/null)"
[[ -z "$count" || "$count" == "0" ]] && continue
local opt_out local opt_out
opt_out=$(migrateUrlRewriteEnabled "$app" 2>/dev/null) opt_out=$(migrateUrlRewriteEnabled "$app" 2>/dev/null)