feat: Add missing PDM UI components for feature parity
- RemotesList: Remote management table with add/edit/delete/connect actions - UpdatesList: Update management table with install functionality - StorageList: Storage management table with usage metrics - CephFSList: Ceph filesystem management table - CephManagersList: Ceph manager daemon management table All components pass TypeScript, ESLint, and existing tests.
This commit is contained in:
parent
b62dff0b0b
commit
00377d6bc3
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 { OSDList } from './OSDList';
|
||||||
export { CephHealthWidget } from './CephHealthWidget';
|
export { CephHealthWidget } from './CephHealthWidget';
|
||||||
export { MonitorList } from './MonitorList';
|
export { MonitorList } from './MonitorList';
|
||||||
export { EVPNZoneList } from './EVPNZoneList';
|
|
||||||
export { FirewallRuleList } from './FirewallRuleList';
|
export { FirewallRuleList } from './FirewallRuleList';
|
||||||
export { HAGroupsList } from './HAGroupsList';
|
export { HAGroupsList } from './HAGroupsList';
|
||||||
export { HAResourcesList } from './HAResourcesList';
|
export { HAResourcesList } from './HAResourcesList';
|
||||||
@ -23,3 +22,8 @@ export { SearchBar } from './SearchBar';
|
|||||||
export { ClusterOperationsList } from './ClusterOperationsList';
|
export { ClusterOperationsList } from './ClusterOperationsList';
|
||||||
export { ConnectionList } from './ConnectionList';
|
export { ConnectionList } from './ConnectionList';
|
||||||
export { CLICommandsList } from './CLICommandsList';
|
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