feat: move auto-updater to Settings > Updater; collapse Proxmox nav by default
- Restore Settings/Updater.tsx with channel selection and update check UI - Strip updater state/functions/Card from Settings/Proxmox.tsx; update description - Add Updater to settingsItems and /settings/updater route in App.tsx - Replace always-open Proxmox NavLink with accordion toggle (expandedSections state) - Add ChevronDown/RefreshCw to lucide imports; promote useLocation from void call
This commit is contained in:
parent
655f8936c9
commit
d24f9e2adf
37
src/App.tsx
37
src/App.tsx
@ -11,10 +11,12 @@ import {
|
||||
Plug,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
Sun,
|
||||
Moon,
|
||||
Terminal,
|
||||
FileCode,
|
||||
RefreshCw,
|
||||
Server,
|
||||
Server as ServerIcon,
|
||||
Settings,
|
||||
@ -53,6 +55,7 @@ import { ProxmoxHAPage } from "@/pages/Proxmox/HAPage";
|
||||
import { ProxmoxTasksPage } from "@/pages/Proxmox/TasksPage";
|
||||
import { ProxmoxCertificatesPage } from "@/pages/Proxmox/CertificatesPage";
|
||||
import { ProxmoxSettings } from "@/pages/Settings/Proxmox";
|
||||
import { Updater } from "@/pages/Settings/Updater";
|
||||
|
||||
const navItems = [
|
||||
{ to: "/", icon: Home, label: "Dashboard" },
|
||||
@ -88,15 +91,17 @@ const settingsItems = [
|
||||
{ to: "/settings/integrations", icon: Link, label: "Integrations" },
|
||||
{ to: "/settings/mcp", icon: Plug, label: "MCP Servers" },
|
||||
{ to: "/settings/security", icon: Shield, label: "Security" },
|
||||
{ to: "/settings/updater", icon: RefreshCw, label: "Updater" },
|
||||
{ to: "/settings/proxmox", icon: Settings, label: "Proxmox" },
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [expandedSections, setExpandedSections] = useState<string[]>([]);
|
||||
const [appVersion, setAppVersion] = useState("");
|
||||
const { theme, setTheme, setProviders, getActiveProvider } = useSettingsStore();
|
||||
const cleanupDone = useRef(false);
|
||||
void useLocation();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
getAppVersionCmd().then(setAppVersion).catch(() => {});
|
||||
@ -171,30 +176,41 @@ export default function App() {
|
||||
<nav className="flex-1 px-2 py-3 space-y-1">
|
||||
{navItems.map((item) => {
|
||||
if (item.children) {
|
||||
const isExpanded = expandedSections.includes(item.to);
|
||||
const isActive = location.pathname.startsWith(item.to);
|
||||
return (
|
||||
<div key={item.to}>
|
||||
<NavLink
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
<button
|
||||
onClick={() =>
|
||||
setExpandedSections((prev) =>
|
||||
prev.includes(item.to)
|
||||
? prev.filter((t) => t !== item.to)
|
||||
: [...prev, item.to]
|
||||
)
|
||||
}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
}`
|
||||
}
|
||||
}`}
|
||||
>
|
||||
<item.icon className="w-4 h-4 shrink-0" />
|
||||
{!collapsed && <span>{item.label}</span>}
|
||||
</NavLink>
|
||||
{!collapsed && (
|
||||
isExpanded
|
||||
? <ChevronDown className="w-3 h-3 ml-auto" />
|
||||
: <ChevronRight className="w-3 h-3 ml-auto" />
|
||||
)}
|
||||
</button>
|
||||
{!collapsed && isExpanded && (
|
||||
<div className="ml-4 space-y-1 pl-4 border-l border-muted">
|
||||
{item.children.map((child) => (
|
||||
<NavLink
|
||||
key={child.to}
|
||||
to={child.to}
|
||||
className={({ isActive }) =>
|
||||
className={({ isActive: childActive }) =>
|
||||
`flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors ${
|
||||
isActive
|
||||
childActive
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
}`
|
||||
@ -298,6 +314,7 @@ export default function App() {
|
||||
<Route path="/proxmox/ha" element={<ProxmoxHAPage />} />
|
||||
<Route path="/proxmox/tasks" element={<ProxmoxTasksPage />} />
|
||||
<Route path="/proxmox/certificates" element={<ProxmoxCertificatesPage />} />
|
||||
<Route path="/settings/updater" element={<Updater />} />
|
||||
<Route path="/settings/proxmox" element={<ProxmoxSettings />} />
|
||||
<Route path="/settings/integrations" element={<Integrations />} />
|
||||
<Route path="/settings/mcp" element={<MCPServers />} />
|
||||
|
||||
@ -4,201 +4,22 @@ import { Label } from '@/components/ui/index';
|
||||
import { Switch } from '@/components/ui/index';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { RefreshCw, Check, AlertCircle, Loader } from 'lucide-react';
|
||||
import {
|
||||
checkAppUpdatesCmd,
|
||||
installAppUpdatesCmd,
|
||||
getUpdateChannelCmd,
|
||||
setUpdateChannelCmd,
|
||||
} from '@/lib/tauriCommands';
|
||||
|
||||
export function ProxmoxSettings() {
|
||||
const [autoUpdate, setAutoUpdate] = React.useState(true);
|
||||
const [updateChannel, setUpdateChannel] = React.useState<'stable' | 'pre-release'>('stable');
|
||||
const [autoCheck, setAutoCheck] = React.useState(true);
|
||||
const [lastCheck, setLastCheck] = React.useState<string>('Never');
|
||||
const [defaultPort, setDefaultPort] = React.useState<string>('8006');
|
||||
const [connectionTimeout, setConnectionTimeout] = React.useState<string>('30');
|
||||
const [retryAttempts, setRetryAttempts] = React.useState<string>('3');
|
||||
const [verifyCertificates, setVerifyCertificates] = React.useState(true);
|
||||
const [enableCaching, setEnableCaching] = React.useState(true);
|
||||
const [enableDebug, setEnableDebug] = React.useState(false);
|
||||
const [checking, setChecking] = React.useState(false);
|
||||
const [updateAvailable, setUpdateAvailable] = React.useState(false);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const loadChannel = async () => {
|
||||
try {
|
||||
const ch = await getUpdateChannelCmd();
|
||||
setUpdateChannel(ch as 'stable' | 'pre-release');
|
||||
} catch {
|
||||
console.error('Failed to load channel');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckUpdates = async () => {
|
||||
setChecking(true);
|
||||
setError(null);
|
||||
try {
|
||||
const available = await checkAppUpdatesCmd();
|
||||
setUpdateAvailable(available);
|
||||
setLastCheck(new Date().toLocaleString());
|
||||
} catch {
|
||||
setError('Failed to check for updates');
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstallUpdate = async () => {
|
||||
try {
|
||||
await installAppUpdatesCmd();
|
||||
setUpdateAvailable(false);
|
||||
} catch {
|
||||
setError('Failed to install update');
|
||||
}
|
||||
};
|
||||
|
||||
const handleChannelChange = async (value: string) => {
|
||||
setUpdateChannel(value as 'stable' | 'pre-release');
|
||||
try {
|
||||
await setUpdateChannelCmd(value);
|
||||
} catch {
|
||||
setError('Failed to update channel');
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
void loadChannel();
|
||||
void handleCheckUpdates();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Proxmox Settings</h1>
|
||||
<p className="text-muted-foreground">Configure Proxmox Datacenter Manager integration</p>
|
||||
<p className="text-muted-foreground">Default settings for Proxmox cluster connections</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Update Management</CardTitle>
|
||||
<CardDescription>Configure how Proxmox updates are managed</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="autoUpdate">Auto-check for updates</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automatically check for new Proxmox updates
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={autoUpdate}
|
||||
onCheckedChange={setAutoUpdate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="updateChannel">Update Channel</Label>
|
||||
<Select
|
||||
value={updateChannel}
|
||||
onValueChange={handleChannelChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="stable">Stable</SelectItem>
|
||||
<SelectItem value="pre-release">Pre-Release</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{updateChannel === 'stable'
|
||||
? 'Receive only stable, production-ready updates'
|
||||
: 'Receive pre-release updates with new features (may be less stable)'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="autoCheck">Auto-download updates</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automatically download updates when available
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={autoCheck}
|
||||
onCheckedChange={setAutoCheck}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">Last check</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{lastCheck}
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleCheckUpdates} variant="outline">
|
||||
{checking ? (
|
||||
<>
|
||||
<Loader className="mr-2 h-4 w-4 animate-spin" />
|
||||
Checking...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Check Now
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="flex items-center space-x-2 rounded-lg bg-destructive/15 p-3 text-destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<span className="text-sm">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updateAvailable ? (
|
||||
<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
|
||||
</div>
|
||||
<div className="text-sm text-green-700 dark:text-green-300">
|
||||
A new version is ready to install
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={handleInstallUpdate}>
|
||||
Install Update
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between rounded-lg bg-muted p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Cluster Configuration</CardTitle>
|
||||
|
||||
170
src/pages/Settings/Updater.tsx
Normal file
170
src/pages/Settings/Updater.tsx
Normal file
@ -0,0 +1,170 @@
|
||||
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 } from 'lucide-react';
|
||||
import {
|
||||
checkAppUpdatesCmd,
|
||||
installAppUpdatesCmd,
|
||||
getUpdateChannelCmd,
|
||||
setUpdateChannelCmd,
|
||||
} from '@/lib/tauriCommands';
|
||||
|
||||
export function Updater() {
|
||||
const [channel, setChannel] = useState('stable');
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||
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 available = await checkAppUpdatesCmd();
|
||||
setUpdateAvailable(available);
|
||||
} catch {
|
||||
setError('Failed to check for updates');
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstallUpdate = async () => {
|
||||
try {
|
||||
await installAppUpdatesCmd();
|
||||
setUpdateAvailable(false);
|
||||
} catch {
|
||||
setError('Failed to install update');
|
||||
}
|
||||
};
|
||||
|
||||
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 auto-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>
|
||||
{error && (
|
||||
<div className="mb-4 flex items-center space-x-2 rounded-lg bg-destructive/15 p-3 text-destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<span className="text-sm">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updateAvailable ? (
|
||||
<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
|
||||
</div>
|
||||
<div className="text-sm text-green-700 dark:text-green-300">
|
||||
A new version is ready to install
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={handleInstallUpdate}>
|
||||
Install Update
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between rounded-lg bg-muted p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user