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

325 lines
16 KiB
TypeScript

import { Form, Head, Link, router, usePage } from '@inertiajs/react';
import { Trash2 } from 'lucide-react';
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 Credential = {
id: number;
name: string;
username: string;
connection_type: string;
privilege_type: string;
is_master: boolean;
is_active: boolean;
};
type Props = {
device: {
id: number;
name: string;
hostname?: string;
management_address: string;
management_port: number;
connection_type: string;
connection_ports?: Record<string, number>;
status: string;
location?: string;
serial_number?: string;
firmware_version?: string;
software_version?: string;
device_facts?: Record<string, string>;
notes?: string;
vendor: { name: string; slug: string };
activation_status: string;
activation_message?: string;
type: { name: string };
model?: { name: string };
creator?: { name: string };
};
credentials: Credential[];
};
export default function DeviceShow({ device, credentials }: Props) {
const { auth, errors } = usePage().props;
const rows = [
['Vendor', device.vendor.name],
['Tipe', device.type.name],
['Model', device.model?.name ?? '-'],
['Alamat Management', device.management_address],
[
'Koneksi',
Object.entries(
device.connection_ports ?? {
[device.connection_type]: device.management_port,
},
)
.map(([protocol, port]) => `${protocol}: ${port}`)
.join(' · '),
],
['Hostname', device.hostname ?? '-'],
['Serial Number', device.serial_number ?? '-'],
['Versi RouterOS', device.software_version ?? '-'],
['Firmware', device.firmware_version ?? '-'],
[
'Board / Model Terdeteksi',
device.device_facts?.['board-name'] ??
device.device_facts?.model ??
'-',
],
['Arsitektur', device.device_facts?.['architecture-name'] ?? '-'],
['CPU', device.device_facts?.cpu ?? '-'],
['Jumlah CPU', device.device_facts?.['cpu-count'] ?? '-'],
['Total Memori', device.device_facts?.['total-memory'] ?? '-'],
['Uptime', device.device_facts?.uptime ?? '-'],
['Lokasi', device.location ?? '-'],
['Status', device.status],
['Dibuat Oleh', device.creator?.name ?? '-'],
];
return (
<>
<Head title={device.name} />
<div className="mx-auto flex w-full max-w-5xl flex-1 flex-col gap-6 p-4 md:p-6">
<div className="flex justify-between">
<div>
<h1 className="text-2xl font-semibold">
{device.name}
</h1>
<p className="text-sm text-muted-foreground">
Detail inventaris perangkat
</p>
</div>
{auth.permissions.includes('device.update') && (
<Button asChild>
<Link href={`/devices/${device.id}/edit`}>
Edit Perangkat
</Link>
</Button>
)}
</div>
{['mikrotik', 'zte', 'hsgq', 'hisfocus'].includes(
device.vendor.slug,
) && (
<Card>
<CardHeader>
<CardTitle>Aktivasi {device.vendor.name}</CardTitle>
</CardHeader>
<CardContent className="flex flex-col justify-between gap-4 md:flex-row md:items-center">
<div className="text-sm">
<p>
Status:{' '}
<strong className="uppercase">
{device.activation_status}
</strong>
</p>
<p className="text-muted-foreground">
{device.activation_message ??
'Aktivasi akan menguji koneksi dan membuat Base User management dengan aman.'}
</p>
{errors.activation && (
<p className="mt-2 text-destructive">
{errors.activation}
</p>
)}
</div>
{auth.permissions.includes('device.connect') && (
<Form
action={`/devices/${device.id}/activate`}
method="post"
>
{({ processing }) => (
<Button disabled={processing}>
{processing
? 'Mengaktifkan...'
: 'Cek & Aktifkan Perangkat'}
</Button>
)}
</Form>
)}
</CardContent>
</Card>
)}
<Card>
<CardHeader>
<CardTitle>Informasi</CardTitle>
</CardHeader>
<CardContent className="grid gap-4 md:grid-cols-2">
{rows.map(([label, value]) => (
<div key={label} className="border-b pb-3">
<div className="text-xs text-muted-foreground">
{label}
</div>
<div className="font-medium">{value}</div>
</div>
))}
{device.notes && (
<div className="md:col-span-2">
<div className="text-xs text-muted-foreground">
Catatan
</div>
<p>{device.notes}</p>
</div>
)}
</CardContent>
</Card>
{auth.permissions.includes('device.credential.view') && (
<Card>
<CardHeader>
<CardTitle>Credential Perangkat</CardTitle>
</CardHeader>
<CardContent className="space-y-5">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b text-left">
<th className="py-2">Nama</th>
<th>Username</th>
<th>Password</th>
<th>Protokol</th>
<th>Privilege</th>
<th></th>
</tr>
</thead>
<tbody>
{credentials.map((item) => (
<tr
key={item.id}
className="border-b"
>
<td className="py-3">
{item.name}
{item.is_master && (
<span className="ml-2 rounded bg-amber-100 px-2 py-1 text-xs text-amber-800">
MASTER
</span>
)}
</td>
<td>{item.username}</td>
<td>••••••••••••</td>
<td>{item.connection_type}</td>
<td>{item.privilege_type}</td>
<td className="text-right">
{auth.permissions.includes(
'device.credential.delete',
) && (
<Button
size="icon"
variant="ghost"
onClick={() =>
confirm(
'Hapus credential ini?',
) &&
router.delete(
`/devices/${device.id}/credentials/${item.id}`,
)
}
>
<Trash2 className="size-4" />
</Button>
)}
</td>
</tr>
))}
</tbody>
</table>
{credentials.length === 0 && (
<p className="py-5 text-center text-muted-foreground">
Belum ada credential.
</p>
)}
</div>
{auth.permissions.includes(
'device.credential.create',
) && (
<Form
action={`/devices/${device.id}/credentials`}
method="post"
resetOnSuccess
className="grid gap-4 border-t pt-5 md:grid-cols-3"
>
<div>
<Label>Nama Credential</Label>
<Input name="name" required />
</div>
<div>
<Label>Username</Label>
<Input name="username" required />
</div>
<div>
<Label>Password</Label>
<Input
name="password"
type="password"
required
autoComplete="new-password"
/>
</div>
<div>
<Label>Enable Password (ZTE/CLI)</Label>
<Input
name="enable_password"
type="password"
autoComplete="new-password"
/>
</div>
<div>
<Label>Protokol</Label>
<select
name="connection_type"
defaultValue={
device.connection_type
}
className="h-10 w-full rounded-md border bg-background px-3"
>
<option value="routeros_api">
RouterOS API
</option>
<option value="ssh">SSH</option>
<option value="telnet">
Telnet
</option>
<option value="snmp">SNMP</option>
<option value="vendor_api">
Vendor API
</option>
</select>
</div>
<div>
<Label>Privilege</Label>
<select
name="privilege_type"
defaultValue="custom"
className="h-10 w-full rounded-md border bg-background px-3"
>
<option value="master">
Master
</option>
<option value="noc">NOC</option>
<option value="technician">
Technician
</option>
<option value="monitoring">
Monitoring
</option>
<option value="custom">
Custom
</option>
</select>
</div>
<div className="flex items-end">
<Button>Simpan Credential</Button>
</div>
</Form>
)}
</CardContent>
</Card>
)}
<Button variant="outline" asChild>
<Link href="/devices">Kembali ke Daftar</Link>
</Button>
</div>
</>
);
}