Files
I-Fun c649f055d8 feat: initialize Infra Manager with React, Tailwind CSS, and Inertia.js
- Added main application entry point (app.jsx) and bootstrap file for React setup.
- Created Wiki index page (Index.jsx) for documentation and file management.
- Implemented utility functions for formatting and status handling in utils.js.
- Set up Blade template (app.blade.php) for Inertia.js integration.
- Defined API routes for metrics and terminal access in api.php.
- Established console commands for scheduled tasks in console.php.
- Created web routes for various application features including authentication, hypervisors, containers, cloud accounts, security, backups, and wiki management in web.php.
- Developed setup scripts for Windows (setup.ps1) and Linux (setup.sh) environments.
- Configured Tailwind CSS for styling with custom themes and animations in tailwind.config.js.
- Set up Vite for asset bundling and development server in vite.config.js.
2026-04-07 20:29:32 +07:00

164 lines
8.1 KiB
JavaScript

import AppLayout from '@/Layouts/AppLayout';
import { Head, useForm, router } from '@inertiajs/react';
import { useState } from 'react';
import { motion } from 'framer-motion';
import { Server, CheckCircle, XCircle, Loader2 } from 'lucide-react';
import { GlassCard } from '@/Components/ui/GlassCard';
import { Button } from '@/Components/ui/Button';
import { cn } from '@/lib/utils';
const TYPES = [
{ value: 'vcenter', label: 'VMware vCenter', port: 443 },
{ value: 'esxi', label: 'VMware ESXi', port: 443 },
{ value: 'proxmox', label: 'Proxmox VE', port: 8006 },
{ value: 'hyper-v', label: 'Hyper-V', port: 5985 },
{ value: 'xen', label: 'XenServer', port: 443 },
{ value: 'kvm', label: 'KVM/QEMU', port: 22 },
];
function Field({ label, error, children }) {
return (
<div className="space-y-1.5">
<label className="block text-xs font-medium text-white/60">{label}</label>
{children}
{error && <p className="text-xs text-red-400">{error}</p>}
</div>
);
}
function Input({ className, ...props }) {
return (
<input className={cn(
'w-full px-3 py-2.5 bg-white/5 border border-white/10 rounded-xl text-sm text-white placeholder:text-white/20',
'focus:outline-none focus:border-cyan-500/60 focus:bg-white/[0.07] transition-all',
className
)} {...props} />
);
}
export default function HypervisorCreate() {
const { data, setData, post, processing, errors } = useForm({
name: '', type: 'vcenter', host: '', port: 443,
username: '', password: '', ssl_verify: true, datacenter: '',
});
const [testResult, setTestResult] = useState(null);
const [testing, setTesting] = useState(false);
const handleTypeChange = (type) => {
const t = TYPES.find(t => t.value === type);
setData(d => ({ ...d, type, port: t?.port ?? 443 }));
};
const testConn = async () => {
setTesting(true);
setTestResult(null);
try {
const res = await fetch('/hypervisors/test-temp', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content },
body: JSON.stringify(data),
});
const json = await res.json();
setTestResult(json);
} catch (e) {
setTestResult({ success: false, message: 'Connection failed: ' + e.message });
} finally {
setTesting(false);
}
};
return (
<AppLayout title="Add Hypervisor">
<Head title="Add Hypervisor" />
<div className="max-w-2xl space-y-5">
<div>
<h2 className="text-xl font-bold text-white">Add Hypervisor</h2>
<p className="text-sm text-white/40">Connect a new virtualization platform</p>
</div>
<GlassCard className="p-6">
<form onSubmit={e => { e.preventDefault(); post('/hypervisors'); }} className="space-y-5">
{/* Type selector */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-white/60">Hypervisor Type</label>
<div className="grid grid-cols-3 gap-2">
{TYPES.map(t => (
<button key={t.value} type="button" onClick={() => handleTypeChange(t.value)}
className={cn(
'px-3 py-2 rounded-xl border text-xs font-medium transition-all',
data.type === t.value
? 'border-cyan-500/50 bg-cyan-500/10 text-cyan-400'
: 'border-white/10 bg-white/5 text-white/50 hover:border-white/20 hover:text-white'
)}>
{t.label}
</button>
))}
</div>
</div>
<Field label="Display Name" error={errors.name}>
<Input value={data.name} onChange={e => setData('name', e.target.value)} placeholder="Production vCenter" required />
</Field>
<div className="grid grid-cols-3 gap-3">
<div className="col-span-2">
<Field label="Hostname / IP" error={errors.host}>
<Input value={data.host} onChange={e => setData('host', e.target.value)} placeholder="vcenter.example.com" required />
</Field>
</div>
<Field label="Port" error={errors.port}>
<Input type="number" value={data.port} onChange={e => setData('port', parseInt(e.target.value))} />
</Field>
</div>
<div className="grid grid-cols-2 gap-3">
<Field label="Username" error={errors.username}>
<Input value={data.username} onChange={e => setData('username', e.target.value)} placeholder="administrator@vsphere.local" required />
</Field>
<Field label="Password" error={errors.password}>
<Input type="password" value={data.password} onChange={e => setData('password', e.target.value)} required />
</Field>
</div>
<Field label="Datacenter (optional)" error={errors.datacenter}>
<Input value={data.datacenter} onChange={e => setData('datacenter', e.target.value)} placeholder="Datacenter name" />
</Field>
<label className="flex items-center gap-3 cursor-pointer">
<input type="checkbox" checked={data.ssl_verify} onChange={e => setData('ssl_verify', e.target.checked)}
className="w-4 h-4 rounded border-white/20 bg-white/5 text-cyan-500 focus:ring-0" />
<span className="text-sm text-white/70">Verify SSL certificate</span>
</label>
{/* Test Result */}
{testResult && (
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}
className={cn(
'flex items-center gap-3 p-3 rounded-xl border text-sm',
testResult.success
? 'bg-green-500/10 border-green-500/30 text-green-300'
: 'bg-red-500/10 border-red-500/30 text-red-300'
)}>
{testResult.success
? <CheckCircle className="w-4 h-4 shrink-0" />
: <XCircle className="w-4 h-4 shrink-0" />}
{testResult.message}
</motion.div>
)}
<div className="flex items-center justify-between pt-2">
<Button type="button" variant="secondary" onClick={testConn} loading={testing}>
{!testing && 'Test Connection'}
</Button>
<div className="flex items-center gap-3">
<a href="/hypervisors"><Button type="button" variant="ghost">Cancel</Button></a>
<Button type="submit" loading={processing}>Add Hypervisor</Button>
</div>
</div>
</form>
</GlassCard>
</div>
</AppLayout>
);
}