tftsr-devops_investigation/src/pages/Settings/Updater.tsx
Shaun Arman 87ccbb6464
All checks were successful
Test / frontend-tests (pull_request) Successful in 1m44s
Test / frontend-typecheck (pull_request) Successful in 1m57s
PR Review Automation / review (pull_request) Successful in 4m19s
Test / rust-fmt-check (pull_request) Successful in 12m57s
Test / rust-clippy (pull_request) Successful in 14m41s
Test / rust-tests (pull_request) Successful in 16m43s
fix(proxmox): remove dummy data, fix add-remote, fix updater
- Replace hardcoded dummy data in VMs, Containers, Storage, Backup, and
  Firewall pages with live API calls; show empty-state UI when no
  clusters are configured
- Add list_proxmox_containers backend command (LXC via cluster/resources)
  and register it in the Tauri handler and frontend proxmoxClient.ts
- Fix add_proxmox_cluster to store credentials without requiring a live
  Proxmox connection; persist username in DB (migration 034); update
  list/get queries to read username column from new schema
- Replace alert() in RemotesPage with toast.error() + rethrow so errors
  surface correctly in Tauri WebView
- Replace tauri-plugin-updater with direct Gitea HTTP API call for
  update checks; use tauri-plugin-opener for browser launch; Updater UI
  now shows current/latest version and release notes
- Add gogs.tftsr.com to CSP connect-src
- Fix all 74 pre-existing ESLint no-explicit-any warnings in
  proxmoxClient.ts; remove stale eslint-disable directive in ACLPage.tsx
- All checks pass: cargo fmt, clippy -D warnings, 411 Rust tests,
  tsc --noEmit, eslint --max-warnings 0, 386 frontend tests
2026-06-13 17:33:23 -05:00

185 lines
6.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
import { Button } from '@/components/ui/index';
import { RefreshCw, Check, AlertCircle, Loader, ExternalLink } from 'lucide-react';
import {
checkAppUpdatesCmd,
installAppUpdatesCmd,
getUpdateChannelCmd,
setUpdateChannelCmd,
type UpdateCheckResult,
} from '@/lib/tauriCommands';
export function Updater() {
const [channel, setChannel] = useState('stable');
const [checking, setChecking] = useState(false);
const [result, setResult] = useState<UpdateCheckResult | null>(null);
const [error, setError] = useState<string | null>(null);
const loadChannel = async () => {
try {
const ch = await getUpdateChannelCmd();
setChannel(ch);
} catch {
console.error('Failed to load channel');
}
};
const checkForUpdates = async () => {
setChecking(true);
setError(null);
try {
const data = await checkAppUpdatesCmd();
setResult(data);
} catch (err) {
setError(String(err));
} finally {
setChecking(false);
}
};
const handleDownloadUpdate = async () => {
try {
await installAppUpdatesCmd();
} catch (err) {
setError('Failed to open releases page: ' + String(err));
}
};
const handleChannelChange = async (newChannel: string) => {
setChannel(newChannel);
try {
await setUpdateChannelCmd(newChannel);
} catch {
setError('Failed to update channel');
}
};
useEffect(() => {
void loadChannel();
void checkForUpdates();
}, []);
return (
<div className="space-y-4">
<div>
<h1 className="text-2xl font-bold">Updater</h1>
<p className="text-muted-foreground">Configure application updates</p>
</div>
<Card>
<CardHeader>
<CardTitle>Update Channel</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex space-x-4">
<button
onClick={() => handleChannelChange('stable')}
className={`flex-1 p-4 rounded-lg border-2 transition-all ${
channel === 'stable'
? 'border-primary bg-primary/5'
: 'border-border hover:border-muted-foreground'
}`}
>
<div className="font-semibold">Stable</div>
<div className="text-sm text-muted-foreground">Production-ready releases</div>
</button>
<button
onClick={() => handleChannelChange('pre-release')}
className={`flex-1 p-4 rounded-lg border-2 transition-all ${
channel === 'pre-release'
? 'border-primary bg-primary/5'
: 'border-border hover:border-muted-foreground'
}`}
>
<div className="font-semibold">Pre-Release</div>
<div className="text-sm text-muted-foreground">Latest development builds</div>
</button>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle>Check for Updates</CardTitle>
<Button
variant="outline"
size="sm"
onClick={checkForUpdates}
disabled={checking}
>
{checking ? (
<>
<Loader className="mr-2 h-4 w-4 animate-spin" />
Checking...
</>
) : (
<>
<RefreshCw className="mr-2 h-4 w-4" />
Check Now
</>
)}
</Button>
</CardHeader>
<CardContent className="space-y-4">
{error && (
<div className="flex items-center space-x-2 rounded-lg bg-destructive/15 p-3 text-destructive">
<AlertCircle className="h-4 w-4 flex-shrink-0" />
<span className="text-sm">{error}</span>
</div>
)}
{result && (
<div className="text-sm text-muted-foreground space-y-1">
<div>Current version: <span className="font-mono font-medium text-foreground">{result.currentVersion}</span></div>
<div>Latest version: <span className="font-mono font-medium text-foreground">{result.latestVersion || '—'}</span></div>
</div>
)}
{result?.updateAvailable ? (
<div className="space-y-3">
<div className="flex items-center justify-between rounded-lg bg-green-50 p-4 dark:bg-green-900/20">
<div className="flex items-center space-x-3">
<div className="rounded-full bg-green-600 p-1 text-white">
<Check className="h-4 w-4" />
</div>
<div>
<div className="font-semibold text-green-900 dark:text-green-100">
Update Available v{result.latestVersion}
</div>
<div className="text-sm text-green-700 dark:text-green-300">
Click below to open the releases page and download
</div>
</div>
</div>
<Button onClick={handleDownloadUpdate}>
<ExternalLink className="mr-2 h-4 w-4" />
Download Update
</Button>
</div>
{result.releaseNotes && (
<div className="rounded-lg border p-3 text-sm">
<div className="font-medium mb-1">Release Notes</div>
<pre className="whitespace-pre-wrap text-muted-foreground font-sans">{result.releaseNotes}</pre>
</div>
)}
</div>
) : result ? (
<div className="flex items-center space-x-3 rounded-lg bg-muted p-4">
<div className="rounded-full bg-muted-foreground p-1 text-background">
<Check className="h-4 w-4" />
</div>
<div>
<div className="font-semibold">Up to Date</div>
<div className="text-sm text-muted-foreground">
You are running the latest version
</div>
</div>
</div>
) : null}
</CardContent>
</Card>
</div>
);
}