working on the backup management parts

This commit is contained in:
Warp Agent
2025-12-29 02:44:52 +07:00
parent f1448d512c
commit fc64391cfb
20 changed files with 4897 additions and 249 deletions

View File

@@ -4,7 +4,7 @@ import { scstAPI, type SCSTHandler } from '@/api/scst'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { ArrowLeft, Plus, RefreshCw, HardDrive, Users } from 'lucide-react'
import { useState } from 'react'
import { useState, useEffect } from 'react'
export default function ISCSITargetDetail() {
const { id } = useParams<{ id: string }>()
@@ -13,6 +13,10 @@ export default function ISCSITargetDetail() {
const [showAddLUN, setShowAddLUN] = useState(false)
const [showAddInitiator, setShowAddInitiator] = useState(false)
useEffect(() => {
console.log('showAddLUN state:', showAddLUN)
}, [showAddLUN])
const { data, isLoading } = useQuery({
queryKey: ['scst-target', id],
queryFn: () => scstAPI.getTarget(id!),
@@ -22,6 +26,8 @@ export default function ISCSITargetDetail() {
const { data: handlers } = useQuery<SCSTHandler[]>({
queryKey: ['scst-handlers'],
queryFn: scstAPI.listHandlers,
staleTime: 0, // Always fetch fresh data
refetchOnMount: true,
})
if (isLoading) {
@@ -33,6 +39,8 @@ export default function ISCSITargetDetail() {
}
const { target, luns } = data
// Ensure luns is always an array, not null
const lunsArray = luns || []
return (
<div className="space-y-6 min-h-screen bg-background-dark p-6">
@@ -91,12 +99,12 @@ export default function ISCSITargetDetail() {
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-text-secondary">Total LUNs:</span>
<span className="font-medium text-white">{luns.length}</span>
<span className="font-medium text-white">{lunsArray.length}</span>
</div>
<div className="flex justify-between">
<span className="text-text-secondary">Active:</span>
<span className="font-medium text-white">
{luns.filter((l) => l.is_active).length}
{lunsArray.filter((l) => l.is_active).length}
</span>
</div>
</div>
@@ -140,14 +148,22 @@ export default function ISCSITargetDetail() {
<CardTitle>LUNs (Logical Unit Numbers)</CardTitle>
<CardDescription>Storage devices exported by this target</CardDescription>
</div>
<Button variant="outline" size="sm" onClick={() => setShowAddLUN(true)}>
<Button
variant="outline"
size="sm"
onClick={(e) => {
e.stopPropagation()
console.log('Add LUN button clicked, setting showAddLUN to true')
setShowAddLUN(true)
}}
>
<Plus className="h-4 w-4 mr-2" />
Add LUN
</Button>
</div>
</CardHeader>
<CardContent>
{luns.length > 0 ? (
{lunsArray.length > 0 ? (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-[#1a2632]">
@@ -170,7 +186,7 @@ export default function ISCSITargetDetail() {
</tr>
</thead>
<tbody className="bg-card-dark divide-y divide-border-dark">
{luns.map((lun) => (
{lunsArray.map((lun) => (
<tr key={lun.id} className="hover:bg-[#233648]">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">
{lun.lun_number}
@@ -204,7 +220,14 @@ export default function ISCSITargetDetail() {
<div className="text-center py-8">
<HardDrive className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-sm text-text-secondary mb-4">No LUNs configured</p>
<Button variant="outline" onClick={() => setShowAddLUN(true)}>
<Button
variant="outline"
onClick={(e) => {
e.stopPropagation()
console.log('Add First LUN button clicked, setting showAddLUN to true')
setShowAddLUN(true)
}}
>
<Plus className="h-4 w-4 mr-2" />
Add First LUN
</Button>
@@ -254,12 +277,21 @@ function AddLUNForm({ targetId, handlers, onClose, onSuccess }: AddLUNFormProps)
const [deviceName, setDeviceName] = useState('')
const [lunNumber, setLunNumber] = useState(0)
useEffect(() => {
console.log('AddLUNForm mounted, targetId:', targetId, 'handlers:', handlers)
}, [targetId, handlers])
const addLUNMutation = useMutation({
mutationFn: (data: { device_name: string; device_path: string; lun_number: number; handler_type: string }) =>
scstAPI.addLUN(targetId, data),
onSuccess: () => {
onSuccess()
},
onError: (error: any) => {
console.error('Failed to add LUN:', error)
const errorMessage = error.response?.data?.error || error.message || 'Failed to add LUN'
alert(errorMessage)
},
})
const handleSubmit = (e: React.FormEvent) => {
@@ -278,35 +310,62 @@ function AddLUNForm({ targetId, handlers, onClose, onSuccess }: AddLUNFormProps)
}
return (
<Card>
<CardHeader>
<CardTitle>Add LUN</CardTitle>
<CardDescription>Add a storage device to this target</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
<div className="bg-card-dark border border-border-dark rounded-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6 border-b border-border-dark">
<h2 className="text-xl font-bold text-white">Add LUN</h2>
<p className="text-sm text-text-secondary mt-1">Bind a ZFS volume or storage device to this target</p>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-4">
<div>
<label htmlFor="handlerType" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="handlerType" className="block text-sm font-medium text-white mb-1">
Handler Type *
</label>
<select
id="handlerType"
value={handlerType}
onChange={(e) => setHandlerType(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
className="w-full px-3 py-2 bg-[#0f161d] border border-border-dark rounded-lg text-white text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
required
>
<option value="">Select a handler</option>
{handlers.map((h) => (
<option key={h.name} value={h.name}>
{h.name} {h.description && `- ${h.description}`}
{h.label || h.name}
</option>
))}
</select>
</div>
<div>
<label htmlFor="deviceName" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="devicePath" className="block text-sm font-medium text-white mb-1">
ZFS Volume Path *
</label>
<input
id="devicePath"
type="text"
value={devicePath}
onChange={(e) => {
const path = e.target.value.trim()
setDevicePath(path)
// Auto-generate device name from path (e.g., /dev/zvol/pool/volume -> volume)
if (path && !deviceName) {
const parts = path.split('/')
const name = parts[parts.length - 1] || parts[parts.length - 2] || 'device'
setDeviceName(name)
}
}}
placeholder="/dev/zvol/pool/volume or /dev/sda"
className="w-full px-3 py-2 bg-[#0f161d] border border-border-dark rounded-lg text-white text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary font-mono"
required
/>
<p className="mt-1 text-xs text-text-secondary">
Enter ZFS volume path (e.g., /dev/zvol/pool/volume) or block device path
</p>
</div>
<div>
<label htmlFor="deviceName" className="block text-sm font-medium text-white mb-1">
Device Name *
</label>
<input
@@ -315,28 +374,16 @@ function AddLUNForm({ targetId, handlers, onClose, onSuccess }: AddLUNFormProps)
value={deviceName}
onChange={(e) => setDeviceName(e.target.value)}
placeholder="device1"
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
className="w-full px-3 py-2 bg-[#0f161d] border border-border-dark rounded-lg text-white text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
required
/>
<p className="mt-1 text-xs text-text-secondary">
Logical name for this device in SCST (auto-filled from volume path)
</p>
</div>
<div>
<label htmlFor="devicePath" className="block text-sm font-medium text-gray-700 mb-1">
Device Path *
</label>
<input
id="devicePath"
type="text"
value={devicePath}
onChange={(e) => setDevicePath(e.target.value)}
placeholder="/dev/sda or /dev/calypso/vg1/lv1"
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
required
/>
</div>
<div>
<label htmlFor="lunNumber" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="lunNumber" className="block text-sm font-medium text-white mb-1">
LUN Number *
</label>
<input
@@ -345,12 +392,15 @@ function AddLUNForm({ targetId, handlers, onClose, onSuccess }: AddLUNFormProps)
value={lunNumber}
onChange={(e) => setLunNumber(parseInt(e.target.value) || 0)}
min="0"
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
className="w-full px-3 py-2 bg-[#0f161d] border border-border-dark rounded-lg text-white text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
required
/>
<p className="mt-1 text-xs text-text-secondary">
Logical Unit Number (0-255, typically start from 0)
</p>
</div>
<div className="flex justify-end gap-2">
<div className="flex justify-end gap-2 pt-4 border-t border-border-dark">
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
@@ -359,8 +409,8 @@ function AddLUNForm({ targetId, handlers, onClose, onSuccess }: AddLUNFormProps)
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
</div>
)
}