Remove verify feature and align main menu layout

Remove verify screen, CLI subcommand, and all related functions.
Fix logo/menu height alignment on main menu screen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shuki
2026-03-06 01:57:55 +02:00
parent 8149976170
commit 3d5a9cdfdd
10 changed files with 10 additions and 394 deletions

View File

@@ -100,7 +100,6 @@ Commands:
targets list|add|delete|show [--name=NAME] [--folders=PATHS]
remotes list|add|delete|show|test [--name=NAME]
snapshots list [--target=NAME] [--remote=NAME]
verify [--target=NAME] [--remote=NAME] [--all]
retention [--target=NAME] [--remote=NAME] [--all]
schedule install|show|remove
logs [--last] [--tail=N]

View File

@@ -20,7 +20,6 @@ source "$GNIZA_DIR/lib/remotes.sh"
source "$GNIZA_DIR/lib/backup.sh"
source "$GNIZA_DIR/lib/restore.sh"
source "$GNIZA_DIR/lib/retention.sh"
source "$GNIZA_DIR/lib/verify.sh"
source "$GNIZA_DIR/lib/schedule.sh"
source "$GNIZA_DIR/lib/notify.sh"
source "$GNIZA_DIR/lib/ssh.sh"
@@ -34,7 +33,6 @@ source "$GNIZA_DIR/lib/ui_remotes.sh"
source "$GNIZA_DIR/lib/ui_backup.sh"
source "$GNIZA_DIR/lib/ui_restore.sh"
source "$GNIZA_DIR/lib/ui_snapshots.sh"
source "$GNIZA_DIR/lib/ui_verify.sh"
source "$GNIZA_DIR/lib/ui_retention.sh"
source "$GNIZA_DIR/lib/ui_logs.sh"
source "$GNIZA_DIR/lib/ui_schedule.sh"
@@ -88,7 +86,6 @@ Commands:
targets list|add|delete|show [--name=NAME] [--folders=PATHS]
remotes list|add|delete|show|test [--name=NAME]
snapshots list [--target=NAME] [--remote=NAME]
verify [--target=NAME] [--remote=NAME] [--all]
retention [--target=NAME] [--remote=NAME] [--all]
schedule install|show|remove
logs [--last] [--tail=N]
@@ -338,27 +335,6 @@ run_cli() {
esac
;;
verify)
local target="" remote="" all=false
target=$(_parse_flag "--target" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
remote=$(_parse_flag "--remote" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
_has_flag "--all" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}" && all=true
if [[ -z "$remote" ]]; then
remote=$(list_remotes | head -1)
[[ -z "$remote" ]] && die "No remotes configured"
fi
_save_remote_globals
load_remote "$remote" || die "Failed to load remote: $remote"
if [[ "$all" == "true" || -z "$target" ]]; then
verify_all_targets
else
verify_target_backup "$target"
fi
_restore_remote_globals
;;
retention)
local target="" remote="" all=false
target=$(_parse_flag "--target" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true

View File

@@ -13,11 +13,10 @@ ui_main_menu() {
"3" "Targets" \
"4" "Remotes" \
"5" "Snapshots" \
"6" "Verify" \
"7" "Retention" \
"8" "Schedules" \
"9" "Logs" \
"10" "Settings" \
"6" "Retention" \
"7" "Schedules" \
"8" "Logs" \
"9" "Settings" \
"Q" "Quit") || break
case "$choice" in
@@ -26,11 +25,10 @@ ui_main_menu() {
3) ui_targets_menu ;;
4) ui_remotes_menu ;;
5) ui_snapshots_menu ;;
6) ui_verify_menu ;;
7) ui_retention_menu ;;
8) ui_schedule_menu ;;
9) ui_logs_menu ;;
10) ui_settings_menu ;;
6) ui_retention_menu ;;
7) ui_schedule_menu ;;
8) ui_logs_menu ;;
9) ui_settings_menu ;;
Q) break ;;
esac
done

View File

@@ -1,85 +0,0 @@
#!/usr/bin/env bash
# gniza4linux/lib/ui_verify.sh — Backup verification TUI
[[ -n "${_GNIZA4LINUX_UI_VERIFY_LOADED:-}" ]] && return 0
_GNIZA4LINUX_UI_VERIFY_LOADED=1
ui_verify_menu() {
while true; do
local choice
choice=$(ui_menu "Verify" \
"SINGLE" "Verify single target" \
"ALL" "Verify all targets" \
"BACK" "Return to main menu") || return 0
case "$choice" in
SINGLE) _ui_verify_single ;;
ALL) _ui_verify_all ;;
BACK) return 0 ;;
esac
done
}
_ui_verify_single() {
if ! has_targets; then
ui_msgbox "No targets configured."
return 0
fi
local -a items=()
local targets
targets=$(list_targets)
while IFS= read -r t; do
items+=("$t" "Target: $t")
done <<< "$targets"
local target
target=$(ui_menu "Select Target to Verify" "${items[@]}") || return 0
local tmpfile
tmpfile=$(mktemp /tmp/gniza-verify-XXXXXX.log)
(
echo "10"
if gniza --cli verify --target="$target" > "$tmpfile" 2>&1; then
echo "100"
else
echo "100"
fi
) | ui_gauge "Verifying target: $target"
if [[ -s "$tmpfile" ]]; then
ui_textbox "$tmpfile"
else
ui_msgbox "Verification of '$target' completed successfully."
fi
rm -f "$tmpfile"
}
_ui_verify_all() {
if ! has_targets; then
ui_msgbox "No targets configured."
return 0
fi
local targets
targets=$(list_targets)
local count=0 total=0
total=$(echo "$targets" | wc -l)
local output=""
while IFS= read -r t; do
((count++))
local pct=$(( count * 100 / total ))
echo "$pct"
local result
if result=$(gniza --cli verify --target="$t" 2>&1); then
output+="$t: OK\n"
else
output+="$t: FAILED\n$result\n"
fi
done <<< "$targets" | ui_gauge "Verifying all targets..."
ui_msgbox "Verification Results:\n\n$output"
}

View File

@@ -1,196 +0,0 @@
#!/usr/bin/env bash
# gniza4linux/lib/verify.sh — Remote backup integrity checks
[[ -n "${_GNIZA4LINUX_VERIFY_LOADED:-}" ]] && return 0
_GNIZA4LINUX_VERIFY_LOADED=1
verify_target_backup() {
local target_name="$1"
local snapshot_ts="${2:-}"
local errors=0
# Resolve timestamp
local ts; ts=$(resolve_snapshot_timestamp "$target_name" "$snapshot_ts") || return 1
log_info "Verifying backup for $target_name (snapshot: $ts)..."
if _is_rclone_mode; then
local snap_subpath="targets/${target_name}/snapshots/${ts}"
# Check .complete marker
if ! rclone_exists "${snap_subpath}/.complete"; then
log_error "Snapshot missing .complete marker: $snap_subpath"
return 1
fi
# Check meta.json
local meta; meta=$(rclone_cat "${snap_subpath}/meta.json" 2>/dev/null) || true
if [[ -z "$meta" ]]; then
log_warn "meta.json not found in snapshot"
((errors++)) || true
else
log_info " meta.json: present"
fi
# Check manifest.txt
if rclone_exists "${snap_subpath}/manifest.txt"; then
log_info " manifest.txt: present"
else
log_warn " manifest.txt: missing"
((errors++)) || true
fi
# Count files
local file_list; file_list=$(rclone_list_files "$snap_subpath" 2>/dev/null) || true
local file_count=0
[[ -n "$file_list" ]] && file_count=$(echo "$file_list" | wc -l)
if (( file_count == 0 )); then
log_warn "No files found in snapshot"
((errors++)) || true
else
log_info " files: $file_count file(s)"
fi
# Report size
local size_json; size_json=$(rclone_size "$snap_subpath" 2>/dev/null) || true
local bytes=0
if [[ -n "$size_json" ]]; then
bytes=$(echo "$size_json" | grep -oP '"bytes":\s*\K[0-9]+' || echo 0)
fi
log_info " size: $(human_size "$bytes")"
# Check latest.txt
local latest; latest=$(rclone_cat "targets/${target_name}/snapshots/latest.txt" 2>/dev/null) || true
if [[ -n "$latest" ]]; then
log_info " latest -> $latest"
else
log_warn " latest.txt not set"
fi
else
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
local snap_path="$snap_dir/$ts"
if [[ "${REMOTE_TYPE:-ssh}" == "local" ]]; then
# Check snapshot directory exists
if [[ ! -d "$snap_path" ]]; then
log_error "Snapshot directory not found: $snap_path"
return 1
fi
# Check meta.json
if [[ -f "$snap_path/meta.json" ]]; then
log_info " meta.json: present"
else
log_warn " meta.json: missing"
((errors++)) || true
fi
# Check manifest.txt
if [[ -f "$snap_path/manifest.txt" ]]; then
log_info " manifest.txt: present"
else
log_warn " manifest.txt: missing"
((errors++)) || true
fi
# Count files
local file_count; file_count=$(find "$snap_path" -type f 2>/dev/null | wc -l)
if (( file_count == 0 )); then
log_warn "No files found in snapshot"
((errors++)) || true
else
log_info " files: $file_count file(s)"
fi
# Report size
local total_size; total_size=$(du -sb "$snap_path" 2>/dev/null | cut -f1) || total_size=0
log_info " size: $(human_size "${total_size:-0}")"
# Check latest symlink
local base; base=$(get_remote_target_base "$target_name")
if [[ -L "$base/latest" ]]; then
local latest_target; latest_target=$(readlink "$base/latest" 2>/dev/null)
log_info " latest -> $(basename "$latest_target")"
else
log_warn " latest symlink not set"
fi
else
# SSH remote
if ! remote_exec "test -d '$snap_path'" 2>/dev/null; then
log_error "Snapshot directory not found: $snap_path"
return 1
fi
# Check meta.json
if remote_exec "test -f '$snap_path/meta.json'" 2>/dev/null; then
log_info " meta.json: present"
else
log_warn " meta.json: missing"
((errors++)) || true
fi
# Check manifest.txt
if remote_exec "test -f '$snap_path/manifest.txt'" 2>/dev/null; then
log_info " manifest.txt: present"
else
log_warn " manifest.txt: missing"
((errors++)) || true
fi
# Count files
local file_count; file_count=$(remote_exec "find '$snap_path' -type f | wc -l" 2>/dev/null)
if [[ "${file_count:-0}" -eq 0 ]]; then
log_warn "No files found in snapshot"
((errors++)) || true
else
log_info " files: $file_count file(s)"
fi
# Report size
local total_size; total_size=$(remote_exec "du -sb '$snap_path' | cut -f1" 2>/dev/null)
log_info " size: $(human_size "${total_size:-0}")"
# Check latest symlink
local base; base=$(get_remote_target_base "$target_name")
local latest_target; latest_target=$(remote_exec "readlink '$base/latest' 2>/dev/null" 2>/dev/null)
if [[ -n "$latest_target" ]]; then
log_info " latest -> $(basename "$latest_target")"
else
log_warn " latest symlink not set"
fi
fi
fi
if (( errors > 0 )); then
log_error "Verification found $errors issue(s) for $target_name"
return 1
fi
log_info "Verification passed for $target_name"
return 0
}
verify_all_targets() {
local targets; targets=$(list_remote_targets)
local total=0 passed=0 failed=0
if [[ -z "$targets" ]]; then
log_warn "No remote targets found to verify"
return 0
fi
while IFS= read -r target_name; do
[[ -z "$target_name" ]] && continue
((total++)) || true
if verify_target_backup "$target_name"; then
((passed++)) || true
else
((failed++)) || true
fi
done <<< "$targets"
echo ""
log_info "Verification complete: $passed/$total passed, $failed failed"
(( failed > 0 )) && return 1
return 0
}

View File

@@ -9,7 +9,6 @@ from tui.screens.target_edit import TargetEditScreen
from tui.screens.remotes import RemotesScreen
from tui.screens.remote_edit import RemoteEditScreen
from tui.screens.snapshots import SnapshotsScreen
from tui.screens.verify import VerifyScreen
from tui.screens.retention import RetentionScreen
from tui.screens.schedule import ScheduleScreen
from tui.screens.logs import LogsScreen
@@ -31,7 +30,6 @@ class GnizaApp(App):
"remotes": RemotesScreen,
"remote_edit": RemoteEditScreen,
"snapshots": SnapshotsScreen,
"verify": VerifyScreen,
"retention": RetentionScreen,
"schedule": ScheduleScreen,
"logs": LogsScreen,

View File

@@ -29,8 +29,8 @@ Screen {
#menu-list {
width: 1fr;
max-width: 35;
height: auto;
margin: 1 2;
height: 21;
margin: 0 2;
}
/* Data tables */
@@ -46,7 +46,6 @@ DataTable {
#schedule-screen,
#backup-screen,
#restore-screen,
#verify-screen,
#retention-screen,
#snapshots-screen,
#targets-screen,
@@ -83,7 +82,6 @@ Select {
/* Button rows */
#backup-buttons,
#restore-buttons,
#verify-buttons,
#ret-buttons,
#targets-buttons,
#remotes-buttons,
@@ -98,7 +96,6 @@ Select {
#backup-buttons Button,
#restore-buttons Button,
#verify-buttons Button,
#ret-buttons Button,
#targets-buttons Button,
#remotes-buttons Button,

View File

@@ -6,7 +6,6 @@ from tui.screens.target_edit import TargetEditScreen
from tui.screens.remotes import RemotesScreen
from tui.screens.remote_edit import RemoteEditScreen
from tui.screens.snapshots import SnapshotsScreen
from tui.screens.verify import VerifyScreen
from tui.screens.retention import RetentionScreen
from tui.screens.schedule import ScheduleScreen
from tui.screens.logs import LogsScreen

View File

@@ -33,7 +33,6 @@ MENU_ITEMS = [
("targets", "Targets"),
("remotes", "Remotes"),
("snapshots", "Snapshots"),
("verify", "Verify"),
("retention", "Retention"),
("schedule", "Schedules"),
("logs", "Logs"),

View File

@@ -1,69 +0,0 @@
from textual.app import ComposeResult
from textual.screen import Screen
from textual.widgets import Header, Footer, Static, Button, Select
from textual.containers import Vertical, Horizontal
from textual import work
from tui.config import list_conf_dir
from tui.backend import stream_cli
from tui.widgets import ConfirmDialog, OperationLog
class VerifyScreen(Screen):
BINDINGS = [("escape", "go_back", "Back")]
def compose(self) -> ComposeResult:
yield Header()
targets = list_conf_dir("targets.d")
with Vertical(id="verify-screen"):
yield Static("Verify Backups", id="screen-title")
if not targets:
yield Static("No targets configured.")
else:
yield Static("Target:")
yield Select(
[(t, t) for t in targets],
id="verify-target",
prompt="Select target",
)
with Horizontal(id="verify-buttons"):
yield Button("Verify Selected", variant="primary", id="btn-verify")
yield Button("Verify All", variant="warning", id="btn-verify-all")
yield Button("Back", id="btn-back")
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "btn-back":
self.app.pop_screen()
elif event.button.id == "btn-verify":
target_sel = self.query_one("#verify-target", Select)
if target_sel.value is Select.BLANK:
self.notify("Select a target first", severity="error")
return
self._do_verify(str(target_sel.value))
elif event.button.id == "btn-verify-all":
self._do_verify_all()
@work
async def _do_verify(self, target: str) -> None:
log_screen = OperationLog(f"Verify: {target}")
self.app.push_screen(log_screen)
rc = await stream_cli(log_screen.write, "verify", f"--target={target}")
if rc == 0:
log_screen.write("\n[green]Verification completed successfully.[/green]")
else:
log_screen.write(f"\n[red]Verification failed (exit code {rc}).[/red]")
@work
async def _do_verify_all(self) -> None:
log_screen = OperationLog("Verify All Targets")
self.app.push_screen(log_screen)
rc = await stream_cli(log_screen.write, "verify", "--all")
if rc == 0:
log_screen.write("\n[green]All verifications completed.[/green]")
else:
log_screen.write(f"\n[red]Verification failed (exit code {rc}).[/red]")
def action_go_back(self) -> None:
self.app.pop_screen()