feature/proxmox-v1.2.0 #90
File diff suppressed because one or more lines are too long
78
src/components/Proxmox/CLICommandsList.tsx
Normal file
78
src/components/Proxmox/CLICommandsList.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
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 CLICommand {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
description: string;
|
||||
example: string;
|
||||
}
|
||||
|
||||
interface CLICommandsListProps {
|
||||
commands: CLICommand[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onRun?: (command: CLICommand) => void;
|
||||
}
|
||||
|
||||
export function CLICommandsList({
|
||||
commands,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onRun,
|
||||
}: CLICommandsListProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>CLI Commands</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>Category</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Example</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{commands.map((cmd) => (
|
||||
<TableRow key={cmd.id}>
|
||||
<TableCell className="font-medium">{cmd.name}</TableCell>
|
||||
<TableCell>{cmd.category}</TableCell>
|
||||
<TableCell>{cmd.description}</TableCell>
|
||||
<TableCell className="font-mono text-xs">{cmd.example}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-accent"
|
||||
onClick={() => onRun?.(cmd)}
|
||||
title="Run"
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
127
src/components/Proxmox/ClusterOperationsList.tsx
Normal file
127
src/components/Proxmox/ClusterOperationsList.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
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 ClusterOperationInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
status: string;
|
||||
node?: string;
|
||||
started?: string;
|
||||
ended?: string;
|
||||
progress?: number;
|
||||
}
|
||||
|
||||
interface ClusterOperationsListProps {
|
||||
operations: ClusterOperationInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onCancel?: (op: ClusterOperationInfo) => void;
|
||||
}
|
||||
|
||||
export function ClusterOperationsList({
|
||||
operations,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onCancel,
|
||||
}: ClusterOperationsListProps) {
|
||||
const runningCount = operations.filter((o) => o.status === 'running').length;
|
||||
const completedCount = operations.filter((o) => o.status === 'completed').length;
|
||||
const failedCount = operations.filter((o) => o.status === 'failed').length;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Cluster Operations</CardTitle>
|
||||
<div className="flex space-x-2">
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-yellow-500">●</span>
|
||||
<span>{runningCount} Running</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-green-500">●</span>
|
||||
<span>{completedCount} Completed</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<span className="text-red-500">●</span>
|
||||
<span>{failedCount} Failed</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>Type</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Node</TableHead>
|
||||
<TableHead>Started</TableHead>
|
||||
<TableHead>Ended</TableHead>
|
||||
<TableHead>Progress</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{operations.map((op) => (
|
||||
<TableRow key={op.id}>
|
||||
<TableCell className="font-medium">{op.name}</TableCell>
|
||||
<TableCell>{op.type}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
|
||||
op.status === 'running' ? 'bg-yellow-100 text-yellow-800' :
|
||||
op.status === 'completed' ? 'bg-green-100 text-green-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{op.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{op.node || '-'}</TableCell>
|
||||
<TableCell>{op.started || '-'}</TableCell>
|
||||
<TableCell>{op.ended || '-'}</TableCell>
|
||||
<TableCell>
|
||||
{op.progress !== undefined && (
|
||||
<div className="w-full max-w-[100px]">
|
||||
<div className="h-2 w-full rounded-full bg-gray-200">
|
||||
<div
|
||||
className="h-2 rounded-full bg-primary"
|
||||
style={{ width: `${op.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-center mt-1">{op.progress}%</div>
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{op.status === 'running' && (
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||
onClick={() => onCancel?.(op)}
|
||||
title="Cancel"
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
122
src/components/Proxmox/ConnectionList.tsx
Normal file
122
src/components/Proxmox/ConnectionList.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
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 ConnectionInfo {
|
||||
id: string;
|
||||
remote: string;
|
||||
node: string;
|
||||
endpoint: string;
|
||||
status: 'connected' | 'connecting' | 'disconnected' | 'error';
|
||||
lastConnected?: string;
|
||||
latency?: number;
|
||||
}
|
||||
|
||||
interface ConnectionListProps {
|
||||
connections: ConnectionInfo[];
|
||||
onRefresh?: () => void;
|
||||
isLoading?: boolean;
|
||||
onReconnect?: (conn: ConnectionInfo) => void;
|
||||
onDisconnect?: (conn: ConnectionInfo) => void;
|
||||
}
|
||||
|
||||
export function ConnectionList({
|
||||
connections,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onReconnect,
|
||||
onDisconnect,
|
||||
}: ConnectionListProps) {
|
||||
const connectedCount = connections.filter((c) => c.status === 'connected').length;
|
||||
const disconnectedCount = connections.filter((c) => c.status === 'disconnected').length;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Connection Cache</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>
|
||||
<Button variant="outline" size="sm" onClick={() => onReconnect?.({ id: 'all', remote: '', node: '', endpoint: '', status: 'disconnected' })}>
|
||||
Reconnect All
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Remote</TableHead>
|
||||
<TableHead>Node</TableHead>
|
||||
<TableHead>Endpoint</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Last Connected</TableHead>
|
||||
<TableHead>Latency</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{connections.map((conn) => (
|
||||
<TableRow key={conn.id}>
|
||||
<TableCell className="font-medium">{conn.remote}</TableCell>
|
||||
<TableCell>{conn.node}</TableCell>
|
||||
<TableCell>{conn.endpoint}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
|
||||
conn.status === 'connected' ? 'bg-green-100 text-green-800' :
|
||||
conn.status === 'connecting' ? 'bg-yellow-100 text-yellow-800' :
|
||||
conn.status === 'error' ? 'bg-red-100 text-red-800' :
|
||||
'bg-gray-100 text-gray-800'
|
||||
}`}>
|
||||
{conn.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{conn.lastConnected || '-'}</TableCell>
|
||||
<TableCell>{conn.latency ? `${conn.latency}ms` : '-'}</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={() => onReconnect?.(conn)}
|
||||
title="Reconnect"
|
||||
>
|
||||
<span className="h-4 w-4 text-xs">🔄</span>
|
||||
</button>
|
||||
{conn.status === 'connected' && (
|
||||
<button
|
||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||
onClick={() => onDisconnect?.(conn)}
|
||||
title="Disconnect"
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
48
src/components/Proxmox/SearchBar.tsx
Normal file
48
src/components/Proxmox/SearchBar.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Input } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
interface SearchBarProps {
|
||||
value: string;
|
||||
onChange?: (value: string) => void;
|
||||
onSearch?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function SearchBar({
|
||||
value,
|
||||
onChange,
|
||||
onSearch,
|
||||
placeholder = 'Search resources...',
|
||||
isLoading,
|
||||
}: SearchBarProps) {
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
onSearch?.(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
className="pl-9"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
{onSearch && (
|
||||
<Button size="sm" onClick={() => onSearch(value)} disabled={isLoading}>
|
||||
Search
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -19,3 +19,7 @@ export { SubscriptionList } from './SubscriptionList';
|
||||
export { NoteList } from './NoteList';
|
||||
export { SearchResults } from './SearchResults';
|
||||
export { ClusterSelector } from './ClusterSelectorAdvanced';
|
||||
export { SearchBar } from './SearchBar';
|
||||
export { ClusterOperationsList } from './ClusterOperationsList';
|
||||
export { ConnectionList } from './ConnectionList';
|
||||
export { CLICommandsList } from './CLICommandsList';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user