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,
|
Plug,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
ChevronDown,
|
||||||
Sun,
|
Sun,
|
||||||
Moon,
|
Moon,
|
||||||
Terminal,
|
Terminal,
|
||||||
FileCode,
|
FileCode,
|
||||||
|
RefreshCw,
|
||||||
Server,
|
Server,
|
||||||
Server as ServerIcon,
|
Server as ServerIcon,
|
||||||
Settings,
|
Settings,
|
||||||
@ -53,6 +55,7 @@ import { ProxmoxHAPage } from "@/pages/Proxmox/HAPage";
|
|||||||
import { ProxmoxTasksPage } from "@/pages/Proxmox/TasksPage";
|
import { ProxmoxTasksPage } from "@/pages/Proxmox/TasksPage";
|
||||||
import { ProxmoxCertificatesPage } from "@/pages/Proxmox/CertificatesPage";
|
import { ProxmoxCertificatesPage } from "@/pages/Proxmox/CertificatesPage";
|
||||||
import { ProxmoxSettings } from "@/pages/Settings/Proxmox";
|
import { ProxmoxSettings } from "@/pages/Settings/Proxmox";
|
||||||
|
import { Updater } from "@/pages/Settings/Updater";
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ to: "/", icon: Home, label: "Dashboard" },
|
{ to: "/", icon: Home, label: "Dashboard" },
|
||||||
@ -88,15 +91,17 @@ const settingsItems = [
|
|||||||
{ to: "/settings/integrations", icon: Link, label: "Integrations" },
|
{ to: "/settings/integrations", icon: Link, label: "Integrations" },
|
||||||
{ to: "/settings/mcp", icon: Plug, label: "MCP Servers" },
|
{ to: "/settings/mcp", icon: Plug, label: "MCP Servers" },
|
||||||
{ to: "/settings/security", icon: Shield, label: "Security" },
|
{ to: "/settings/security", icon: Shield, label: "Security" },
|
||||||
|
{ to: "/settings/updater", icon: RefreshCw, label: "Updater" },
|
||||||
{ to: "/settings/proxmox", icon: Settings, label: "Proxmox" },
|
{ to: "/settings/proxmox", icon: Settings, label: "Proxmox" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const [expandedSections, setExpandedSections] = useState<string[]>([]);
|
||||||
const [appVersion, setAppVersion] = useState("");
|
const [appVersion, setAppVersion] = useState("");
|
||||||
const { theme, setTheme, setProviders, getActiveProvider } = useSettingsStore();
|
const { theme, setTheme, setProviders, getActiveProvider } = useSettingsStore();
|
||||||
const cleanupDone = useRef(false);
|
const cleanupDone = useRef(false);
|
||||||
void useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getAppVersionCmd().then(setAppVersion).catch(() => {});
|
getAppVersionCmd().then(setAppVersion).catch(() => {});
|
||||||
@ -171,30 +176,41 @@ export default function App() {
|
|||||||
<nav className="flex-1 px-2 py-3 space-y-1">
|
<nav className="flex-1 px-2 py-3 space-y-1">
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
|
const isExpanded = expandedSections.includes(item.to);
|
||||||
|
const isActive = location.pathname.startsWith(item.to);
|
||||||
return (
|
return (
|
||||||
<div key={item.to}>
|
<div key={item.to}>
|
||||||
<NavLink
|
<button
|
||||||
to={item.to}
|
onClick={() =>
|
||||||
className={({ isActive }) =>
|
setExpandedSections((prev) =>
|
||||||
`flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
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
|
isActive
|
||||||
? "bg-primary text-primary-foreground"
|
? "bg-primary text-primary-foreground"
|
||||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||||
}`
|
}`}
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<item.icon className="w-4 h-4 shrink-0" />
|
<item.icon className="w-4 h-4 shrink-0" />
|
||||||
{!collapsed && <span>{item.label}</span>}
|
{!collapsed && <span>{item.label}</span>}
|
||||||
</NavLink>
|
|
||||||
{!collapsed && (
|
{!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">
|
<div className="ml-4 space-y-1 pl-4 border-l border-muted">
|
||||||
{item.children.map((child) => (
|
{item.children.map((child) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={child.to}
|
key={child.to}
|
||||||
to={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 ${
|
`flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors ${
|
||||||
isActive
|
childActive
|
||||||
? "bg-primary text-primary-foreground"
|
? "bg-primary text-primary-foreground"
|
||||||
: "text-muted-foreground hover:bg-accent hover:text-accent-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/ha" element={<ProxmoxHAPage />} />
|
||||||
<Route path="/proxmox/tasks" element={<ProxmoxTasksPage />} />
|
<Route path="/proxmox/tasks" element={<ProxmoxTasksPage />} />
|
||||||
<Route path="/proxmox/certificates" element={<ProxmoxCertificatesPage />} />
|
<Route path="/proxmox/certificates" element={<ProxmoxCertificatesPage />} />
|
||||||
|
<Route path="/settings/updater" element={<Updater />} />
|
||||||
<Route path="/settings/proxmox" element={<ProxmoxSettings />} />
|
<Route path="/settings/proxmox" element={<ProxmoxSettings />} />
|
||||||
<Route path="/settings/integrations" element={<Integrations />} />
|
<Route path="/settings/integrations" element={<Integrations />} />
|
||||||
<Route path="/settings/mcp" element={<MCPServers />} />
|
<Route path="/settings/mcp" element={<MCPServers />} />
|
||||||
|
|||||||
@ -4,201 +4,22 @@ import { Label } from '@/components/ui/index';
|
|||||||
import { Switch } from '@/components/ui/index';
|
import { Switch } from '@/components/ui/index';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/index';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/index';
|
||||||
import { Button } 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() {
|
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 [defaultPort, setDefaultPort] = React.useState<string>('8006');
|
||||||
const [connectionTimeout, setConnectionTimeout] = React.useState<string>('30');
|
const [connectionTimeout, setConnectionTimeout] = React.useState<string>('30');
|
||||||
const [retryAttempts, setRetryAttempts] = React.useState<string>('3');
|
const [retryAttempts, setRetryAttempts] = React.useState<string>('3');
|
||||||
const [verifyCertificates, setVerifyCertificates] = React.useState(true);
|
const [verifyCertificates, setVerifyCertificates] = React.useState(true);
|
||||||
const [enableCaching, setEnableCaching] = React.useState(true);
|
const [enableCaching, setEnableCaching] = React.useState(true);
|
||||||
const [enableDebug, setEnableDebug] = React.useState(false);
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">Proxmox Settings</h1>
|
<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>
|
</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>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Cluster Configuration</CardTitle>
|
<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