feature/proxmox-v1.2.0 #90
1606
.logs/subtask2.log
1606
.logs/subtask2.log
File diff suppressed because one or more lines are too long
100
src/components/Proxmox/CephFSList.tsx
Normal file
100
src/components/Proxmox/CephFSList.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
interface CephFSInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
pool: string;
|
||||
dataPool?: string;
|
||||
metadataPool?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface CephFSListProps {
|
||||
cephfs: CephFSInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onEdit?: (cephfs: CephFSInfo) => void;
|
||||
onDelete?: (cephfs: CephFSInfo) => void;
|
||||
}
|
||||
|
||||
export function CephFSList({
|
||||
cephfs,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: CephFSListProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Ceph Filesystems</CardTitle>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading}>
|
||||
Refresh
|
||||
</Button>
|
||||
<Button size="sm">
|
||||
<span className="mr-2 h-4 w-4">+</span>
|
||||
New Filesystem
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Pool</TableHead>
|
||||
<TableHead>Data Pool</TableHead>
|
||||
<TableHead>Metadata Pool</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{cephfs.map((fs) => (
|
||||
<TableRow key={fs.id}>
|
||||
<TableCell className="font-medium">{fs.name}</TableCell>
|
||||
<TableCell>{fs.pool}</TableCell>
|
||||
<TableCell>{fs.dataPool || '-'}</TableCell>
|
||||
<TableCell>{fs.metadataPool || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<span className="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium bg-green-100 text-green-800">
|
||||
{fs.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
onClick={() => onEdit?.(fs)}
|
||||
title="Edit"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">✏️</span>
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||
onClick={() => onDelete?.(fs)}
|
||||
title="Delete"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">🗑️</span>
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
title="More"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
73
src/components/Proxmox/CephManagersList.tsx
Normal file
73
src/components/Proxmox/CephManagersList.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
interface CephManagerInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
daemon: string;
|
||||
host: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface CephManagersListProps {
|
||||
managers: CephManagerInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function CephManagersList({
|
||||
managers,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
}: CephManagersListProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Ceph Managers</CardTitle>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading}>
|
||||
Refresh
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Daemon</TableHead>
|
||||
<TableHead>Host</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{managers.map((mgr) => (
|
||||
<TableRow key={mgr.id}>
|
||||
<TableCell className="font-medium">{mgr.name}</TableCell>
|
||||
<TableCell>{mgr.daemon}</TableCell>
|
||||
<TableCell>{mgr.host}</TableCell>
|
||||
<TableCell>
|
||||
<span className="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium bg-green-100 text-green-800">
|
||||
{mgr.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
title="More"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { MoreHorizontal, Trash2 } from 'lucide-react';
|
||||
|
||||
interface EVPNZoneInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
fabric: string;
|
||||
status: 'available' | 'error' | 'pending' | 'unknown';
|
||||
vni?: number;
|
||||
routeTarget?: string;
|
||||
}
|
||||
|
||||
interface EVPNZoneListProps {
|
||||
zones: EVPNZoneInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onEdit?: (zone: EVPNZoneInfo) => void;
|
||||
onDelete?: (zone: EVPNZoneInfo) => void;
|
||||
}
|
||||
|
||||
export function EVPNZoneList({
|
||||
zones,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: EVPNZoneListProps) {
|
||||
const availableCount = zones.filter((z) => z.status === 'available').length;
|
||||
const errorCount = zones.filter((z) => z.status === 'error').length;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>EVPN Zones</CardTitle>
|
||||
<div className="flex space-x-2">
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-green-500">●</span>
|
||||
<span>{availableCount} Available</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-red-500">●</span>
|
||||
<span>{errorCount} Errors</span>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading}>
|
||||
Refresh
|
||||
</Button>
|
||||
<Button size="sm">
|
||||
<span className="mr-2 h-4 w-4">+</span>
|
||||
New Zone
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Fabric</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>VNI</TableHead>
|
||||
<TableHead>Route Target</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{zones.map((zone) => (
|
||||
<TableRow key={zone.id}>
|
||||
<TableCell className="font-medium">{zone.name}</TableCell>
|
||||
<TableCell>{zone.type}</TableCell>
|
||||
<TableCell>{zone.fabric}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
|
||||
zone.status === 'available' ? 'bg-green-100 text-green-800' :
|
||||
zone.status === 'error' ? 'bg-red-100 text-red-800' :
|
||||
'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{zone.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{zone.vni || '-'}</TableCell>
|
||||
<TableCell>{zone.routeTarget || '-'}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
onClick={() => onEdit?.(zone)}
|
||||
title="Edit"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">✏️</span>
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||
onClick={() => onDelete?.(zone)}
|
||||
title="Delete"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
title="More"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
151
src/components/Proxmox/RemotesList.tsx
Normal file
151
src/components/Proxmox/RemotesList.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
interface RemoteInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'pve' | 'pbs';
|
||||
url: string;
|
||||
nodeCount?: number;
|
||||
status: 'connected' | 'disconnected' | 'error';
|
||||
lastConnected?: string;
|
||||
}
|
||||
|
||||
interface RemotesListProps {
|
||||
remotes: RemoteInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onAdd?: () => void;
|
||||
onEdit?: (remote: RemoteInfo) => void;
|
||||
onDelete?: (remote: RemoteInfo) => void;
|
||||
onConnect?: (remote: RemoteInfo) => void;
|
||||
onDisconnect?: (remote: RemoteInfo) => void;
|
||||
}
|
||||
|
||||
export function RemotesList({
|
||||
remotes,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onAdd,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
}: RemotesListProps) {
|
||||
const connectedCount = remotes.filter((r) => r.status === 'connected').length;
|
||||
const disconnectedCount = remotes.filter((r) => r.status === 'disconnected').length;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Remotes</CardTitle>
|
||||
<div className="flex space-x-2">
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-green-500">●</span>
|
||||
<span>{connectedCount} Connected</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-red-500">●</span>
|
||||
<span>{disconnectedCount} Disconnected</span>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading}>
|
||||
Refresh
|
||||
</Button>
|
||||
{onAdd && (
|
||||
<Button size="sm" onClick={onAdd}>
|
||||
<span className="mr-2 h-4 w-4">+</span>
|
||||
Add
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>URL</TableHead>
|
||||
<TableHead>Nodes</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Last Connected</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{remotes.map((remote) => (
|
||||
<TableRow key={remote.id}>
|
||||
<TableCell className="font-medium">{remote.name}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
|
||||
remote.type === 'pve' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800'
|
||||
}`}>
|
||||
{remote.type.toUpperCase()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{remote.url}</TableCell>
|
||||
<TableCell>{remote.nodeCount || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
|
||||
remote.status === 'connected' ? 'bg-green-100 text-green-800' :
|
||||
remote.status === 'error' ? 'bg-red-100 text-red-800' :
|
||||
'bg-gray-100 text-gray-800'
|
||||
}`}>
|
||||
{remote.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{remote.lastConnected || '-'}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
onClick={() => onEdit?.(remote)}
|
||||
title="Edit"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">✏️</span>
|
||||
</button>
|
||||
{remote.status === 'connected' ? (
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||
onClick={() => onDisconnect?.(remote)}
|
||||
title="Disconnect"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">🔌</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-green-100 hover:text-green-600"
|
||||
onClick={() => onConnect?.(remote)}
|
||||
title="Connect"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">🔌</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||
onClick={() => onDelete?.(remote)}
|
||||
title="Delete"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">🗑️</span>
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
title="More"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
105
src/components/Proxmox/StorageList.tsx
Normal file
105
src/components/Proxmox/StorageList.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
interface StorageInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
remote: string;
|
||||
node?: string;
|
||||
used: string;
|
||||
total: string;
|
||||
available: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface StorageListProps {
|
||||
storages: StorageInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onEdit?: (storage: StorageInfo) => void;
|
||||
onDelete?: (storage: StorageInfo) => void;
|
||||
}
|
||||
|
||||
export function StorageList({
|
||||
storages,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: StorageListProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Storages</CardTitle>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading}>
|
||||
Refresh
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Remote</TableHead>
|
||||
<TableHead>Node</TableHead>
|
||||
<TableHead>Used</TableHead>
|
||||
<TableHead>Total</TableHead>
|
||||
<TableHead>Available</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{storages.map((storage) => (
|
||||
<TableRow key={storage.id}>
|
||||
<TableCell className="font-medium">{storage.name}</TableCell>
|
||||
<TableCell>{storage.type}</TableCell>
|
||||
<TableCell>{storage.remote}</TableCell>
|
||||
<TableCell>{storage.node || '-'}</TableCell>
|
||||
<TableCell>{storage.used}</TableCell>
|
||||
<TableCell>{storage.total}</TableCell>
|
||||
<TableCell>{storage.available}</TableCell>
|
||||
<TableCell>
|
||||
<span className="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium bg-green-100 text-green-800">
|
||||
{storage.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
onClick={() => onEdit?.(storage)}
|
||||
title="Edit"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">✏️</span>
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||
onClick={() => onDelete?.(storage)}
|
||||
title="Delete"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">🗑️</span>
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
title="More"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
113
src/components/Proxmox/UpdatesList.tsx
Normal file
113
src/components/Proxmox/UpdatesList.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
interface UpdateInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
remote: string;
|
||||
node?: string;
|
||||
category: string;
|
||||
installed: string;
|
||||
available: string;
|
||||
status: 'up-to-date' | 'available' | 'error';
|
||||
}
|
||||
|
||||
interface UpdatesListProps {
|
||||
updates: UpdateInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onInstall?: (update: UpdateInfo) => void;
|
||||
}
|
||||
|
||||
export function UpdatesList({
|
||||
updates,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onInstall,
|
||||
}: UpdatesListProps) {
|
||||
const upToDateCount = updates.filter((u) => u.status === 'up-to-date').length;
|
||||
const availableCount = updates.filter((u) => u.status === 'available').length;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Updates</CardTitle>
|
||||
<div className="flex space-x-2">
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-green-500">●</span>
|
||||
<span>{upToDateCount} Up-to-date</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-yellow-500">●</span>
|
||||
<span>{availableCount} Available</span>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Version</TableHead>
|
||||
<TableHead>Remote</TableHead>
|
||||
<TableHead>Node</TableHead>
|
||||
<TableHead>Category</TableHead>
|
||||
<TableHead>Installed</TableHead>
|
||||
<TableHead>Available</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{updates.map((update) => (
|
||||
<TableRow key={update.id}>
|
||||
<TableCell className="font-medium">{update.name}</TableCell>
|
||||
<TableCell>{update.version}</TableCell>
|
||||
<TableCell>{update.remote}</TableCell>
|
||||
<TableCell>{update.node || '-'}</TableCell>
|
||||
<TableCell>{update.category}</TableCell>
|
||||
<TableCell>{update.installed}</TableCell>
|
||||
<TableCell>{update.available}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
|
||||
update.status === 'up-to-date' ? 'bg-green-100 text-green-800' :
|
||||
update.status === 'error' ? 'bg-red-100 text-red-800' :
|
||||
'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{update.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{update.status === 'available' && (
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
onClick={() => onInstall?.(update)}
|
||||
title="Install"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">⬇️</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
title="More"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -8,7 +8,6 @@ export { PoolList } from './PoolList';
|
||||
export { OSDList } from './OSDList';
|
||||
export { CephHealthWidget } from './CephHealthWidget';
|
||||
export { MonitorList } from './MonitorList';
|
||||
export { EVPNZoneList } from './EVPNZoneList';
|
||||
export { FirewallRuleList } from './FirewallRuleList';
|
||||
export { HAGroupsList } from './HAGroupsList';
|
||||
export { HAResourcesList } from './HAResourcesList';
|
||||
@ -23,3 +22,8 @@ export { SearchBar } from './SearchBar';
|
||||
export { ClusterOperationsList } from './ClusterOperationsList';
|
||||
export { ConnectionList } from './ConnectionList';
|
||||
export { CLICommandsList } from './CLICommandsList';
|
||||
export { RemotesList } from './RemotesList';
|
||||
export { UpdatesList } from './UpdatesList';
|
||||
export { StorageList } from './StorageList';
|
||||
export { CephFSList } from './CephFSList';
|
||||
export { CephManagersList } from './CephManagersList';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user