Files
device-management/resources/js/pages/devices/form.tsx
T
Wian Drs 60946ddf37
tests / ci (push) Has been cancelled
update lisensi prosess
2026-08-25 13:43:55 +07:00

317 lines
17 KiB
TypeScript

import { Form, Head, Link } from '@inertiajs/react';
import { useState } from 'react';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
type Item = { id: number; name: string; slug?: string };
type Model = Item & { device_vendor_id: number; device_type_id: number };
type Props = {
device:
| (Record<string, unknown> & {
connection_ports?: Record<string, number>;
})
| null;
vendors: Item[];
types: Item[];
models: Model[];
};
export default function DeviceForm({ device, vendors, types, models }: Props) {
const editing = !!device;
const initialVendor = vendors.find(
(vendor) => vendor.id === Number(device?.device_vendor_id),
);
const [vendorSlug, setVendorSlug] = useState(initialVendor?.slug ?? '');
const fields = [
['name', 'Nama Perangkat'],
['management_address', 'IP / Domain Management'],
['location', 'Lokasi'],
];
const protocolProfiles: Record<string, Array<[string, string, number]>> = {
mikrotik: [['routeros_api', 'Port RouterOS API', 8728]],
zte: [['ssh', 'Port SSH', 22]],
hsgq: [
['ssh', 'Port SSH', 22],
['telnet', 'Port Telnet', 23],
],
hisfocus: [
['telnet', 'Port Telnet', 23],
['web_http', 'Port WebGUI HTTP', 80],
],
};
const flexibleProtocols: Array<[string, string, number]> = [
['ssh', 'Port SSH', 22],
['telnet', 'Port Telnet', 23],
['web_http', 'Port WebGUI HTTP', 80],
['snmp', 'Port SNMP', 161],
['vendor_api', 'Port Vendor API', 0],
];
const protocols = protocolProfiles[vendorSlug] ?? flexibleProtocols;
return (
<>
<Head title={editing ? 'Edit Perangkat' : 'Tambah Perangkat'} />
<div className="mx-auto flex w-full max-w-5xl flex-1 flex-col gap-6 p-4 md:p-6">
<div>
<h1 className="text-2xl font-semibold">
{editing ? 'Edit' : 'Tambah'} Perangkat
</h1>
<p className="text-sm text-muted-foreground">
Data teknis lainnya akan diambil otomatis saat perangkat
pertama kali terhubung.
</p>
</div>
<Form
action={editing ? `/devices/${device.id}` : '/devices'}
method={editing ? 'put' : 'post'}
>
{({ processing, errors }) => (
<Card>
<CardHeader>
<CardTitle>Informasi Perangkat</CardTitle>
</CardHeader>
<CardContent className="grid gap-5 md:grid-cols-2">
{fields.map(([name, label]) => (
<div key={name} className="grid gap-2">
<Label>{label}</Label>
<Input
name={name}
type="text"
defaultValue={String(
device?.[name] ?? '',
)}
required={[
'name',
'management_address',
].includes(name)}
/>
<InputError message={errors[name]} />
</div>
))}
<div className="grid gap-2">
<Label>Vendor</Label>
<select
name="device_vendor_id"
defaultValue={String(
device?.device_vendor_id ?? '',
)}
required
onChange={(event) => {
const selected = vendors.find(
(vendor) =>
vendor.id ===
Number(event.target.value),
);
setVendorSlug(selected?.slug ?? '');
}}
className="h-10 rounded-md border bg-background px-3"
>
<option value="">Pilih vendor</option>
{vendors.map((x) => (
<option key={x.id} value={x.id}>
{x.name}
</option>
))}
</select>
<InputError
message={errors.device_vendor_id}
/>
</div>
<div className="grid gap-2">
<Label>Tipe</Label>
<select
name="device_type_id"
defaultValue={String(
device?.device_type_id ?? '',
)}
required
className="h-10 rounded-md border bg-background px-3"
>
<option value="">Pilih tipe</option>
{types.map((x) => (
<option key={x.id} value={x.id}>
{x.name}
</option>
))}
</select>
<InputError
message={errors.device_type_id}
/>
</div>
<div className="grid gap-2">
<Label>Model</Label>
<select
name="device_model_id"
defaultValue={String(
device?.device_model_id ?? '',
)}
className="h-10 rounded-md border bg-background px-3"
>
<option value="">Tanpa model</option>
{models.map((x) => (
<option key={x.id} value={x.id}>
{x.name}
</option>
))}
</select>
</div>
<div className="rounded-lg border p-4 md:col-span-2">
<h3 className="font-medium">
Koneksi Management
</h3>
<p className="mb-4 text-sm text-muted-foreground">
Kosongkan port jika protokol tersebut
tidak tersedia pada perangkat.
</p>
<InputError
message={errors.connection_ports}
/>
<div className="grid gap-4 md:grid-cols-2">
{protocols.map(
([
protocol,
label,
defaultPort,
]) => (
<div
key={`${vendorSlug}-${protocol}`}
className="grid gap-2"
>
<Label>{label}</Label>
<Input
name={`connection_ports[${protocol}]`}
type="number"
min={1}
max={65535}
placeholder={
defaultPort
? String(
defaultPort,
)
: 'Port custom'
}
defaultValue={
device
?.connection_ports?.[
protocol
] ?? ''
}
/>
<InputError
message={
errors[
`connection_ports.${protocol}`
]
}
/>
</div>
),
)}
</div>
</div>
<div className="grid gap-2 md:col-span-2">
<Label>Catatan</Label>
<textarea
name="notes"
defaultValue={String(
device?.notes ?? '',
)}
className="min-h-24 rounded-md border bg-background p-3"
/>
</div>
{!editing && (
<>
<div className="grid gap-2">
<Label>Username Awal</Label>
<Input
name="initial_username"
required
autoComplete="off"
/>
<InputError
message={
errors.initial_username
}
/>
</div>
<div className="grid gap-2">
<Label>Password Awal</Label>
<Input
name="initial_password"
type="password"
autoComplete="new-password"
/>
<p className="text-xs text-muted-foreground">
Boleh kosong untuk perangkat
MikroTik yang masih memakai
password kosong.
</p>
<InputError
message={
errors.initial_password
}
/>
</div>
<div className="grid gap-2 md:col-span-2">
<Label>
Enable Password (opsional)
</Label>
<Input
name="initial_enable_password"
type="password"
autoComplete="new-password"
/>
<p className="text-xs text-muted-foreground">
Khusus perangkat CLI seperti ZTE
C300/C320. Default pabrik
tertentu adalah zxr10; isi
sesuai kondisi perangkat.
</p>
<InputError
message={
errors.initial_enable_password
}
/>
</div>
<label className="flex items-start gap-3 rounded-lg border p-4 md:col-span-2">
<input
type="checkbox"
name="remove_legacy_users_on_activation"
value="1"
className="mt-1"
/>
<span>
<span className="block font-medium">
Hapus user lama setelah
koneksi pertama
</span>
<span className="block text-sm text-muted-foreground">
Dijalankan hanya setelah
Base User RADIQ berhasil
dibuat dan diuji login. User
grup full tetap
dipertahankan untuk mencegah
perangkat terkunci.
</span>
</span>
</label>
</>
)}
<div className="flex justify-end gap-3 md:col-span-2">
<Button variant="outline" asChild>
<Link href="/devices">Batal</Link>
</Button>
<Button disabled={processing}>
Simpan Perangkat
</Button>
</div>
</CardContent>
</Card>
)}
</Form>
</div>
</>
);
}