v1.1: Add CIDR subnet filtering and Agent filter in Devices page
This commit is contained in:
commit
966058e2fe
422 files changed
+54656
No files matched your search
@@ -0,0 +1,265 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Plus, Loader2, MapPin } from "lucide-react";
|
||||
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||
import { useAgentsData } from "./useAgentsData";
|
||||
import { getAgentColumns } from "./columns";
|
||||
import { getExternalAccountColumns } from "./externalColumns";
|
||||
import UptimeStatsCards from "./UptimeStatsCards";
|
||||
import AgentsTableSection from "./AgentsTableSection";
|
||||
import ExternalAccountsSection from "./ExternalAccountsSection";
|
||||
import AgentsPageModals from "./AgentsPageModals";
|
||||
import AgentLocationMap from "@/components/admin/AgentLocationMap";
|
||||
import { SubnetConfigModal } from "@/components/admin/SubnetConfigModal";
|
||||
import { resolveAgentLabel } from "@/lib/admin-api";
|
||||
|
||||
export default function AgentsPage() {
|
||||
useEffect(() => {
|
||||
document.title = "Agents | BackOne - Deep Package Inspection";
|
||||
}, []);
|
||||
const {
|
||||
role,
|
||||
agents,
|
||||
uptimeMap,
|
||||
storageMap,
|
||||
isLoading,
|
||||
error,
|
||||
isCreateOpen,
|
||||
setIsCreateOpen,
|
||||
isDeleteOpen,
|
||||
setIsDeleteOpen,
|
||||
detailAgent,
|
||||
setDetailAgent,
|
||||
managedUsers,
|
||||
accountModalOpen,
|
||||
setAccountModalOpen,
|
||||
accountModalAgent,
|
||||
setAccountModalAgent,
|
||||
externalModalOpen,
|
||||
setExternalModalOpen,
|
||||
externalModalUser,
|
||||
setExternalModalUser,
|
||||
targetSiteUuid,
|
||||
setTargetSiteUuid,
|
||||
targetCreatedBy,
|
||||
setTargetCreatedBy,
|
||||
locations,
|
||||
isLocModalOpen,
|
||||
setIsLocModalOpen,
|
||||
locModalAgentUuid,
|
||||
setLocModalAgentUuid,
|
||||
locModalAgentLabel,
|
||||
setLocModalAgentLabel,
|
||||
locModalCurrentLoc,
|
||||
setLocModalCurrentLoc,
|
||||
viewAsLoading,
|
||||
selectedAgent,
|
||||
loadAgents,
|
||||
loadLocations,
|
||||
handleViewAs,
|
||||
openDelete,
|
||||
handleEditLocation,
|
||||
} = useAgentsData();
|
||||
|
||||
const [subnetModalOpen, setSubnetModalOpen] = useState(false);
|
||||
const [subnetModalAgent, setSubnetModalAgent] = useState<{ uuid: string; label: string } | null>(null);
|
||||
|
||||
const handleEditSubnet = (agentUuid: string, agentLabel: string) => {
|
||||
setSubnetModalAgent({ uuid: agentUuid, label: agentLabel });
|
||||
setSubnetModalOpen(true);
|
||||
};
|
||||
|
||||
const columns = getAgentColumns({
|
||||
role,
|
||||
uptimeMap,
|
||||
storageMap,
|
||||
managedUsers,
|
||||
viewAsLoading,
|
||||
setDetailAgent,
|
||||
setAccountModalOpen,
|
||||
setAccountModalAgent,
|
||||
handleViewAs,
|
||||
openDelete,
|
||||
handleEditLocation,
|
||||
handleEditSubnet,
|
||||
locationUuids: new Set(locations.map((l: any) => l.agent_uuid)),
|
||||
});
|
||||
|
||||
const externalColumns = getExternalAccountColumns((user) => {
|
||||
setExternalModalUser(user);
|
||||
setExternalModalOpen(true);
|
||||
});
|
||||
|
||||
const totalAgents = agents.length;
|
||||
const onlineAgents = agents.filter(a => {
|
||||
const hum = a.last_seen_at?.human;
|
||||
return hum === 'Online' || hum === 'Flows Detected' || (hum && hum.startsWith('Last seen:'));
|
||||
}).length;
|
||||
const offlineAgents = totalAgents - onlineAgents;
|
||||
const uptimeValues = agents.map(a => uptimeMap[a.uuid || a.serial] ?? 100);
|
||||
const avgUptime = uptimeValues.length > 0 ? uptimeValues.reduce((s, v) => s + v, 0) / uptimeValues.length : 100;
|
||||
|
||||
// External Accounts di halaman Agents = akun eksternal teknikal/operasional saja
|
||||
// AGENT_VIEWER = akun network agent (sudah ada di tabel agents) → BUKAN akun eksternal
|
||||
// COMPANY_* = akun customer/client → sudah ada di halaman User Accounts
|
||||
const EXCLUDED_ROLES = ['COMPANY_ADMIN', 'COMPANY_OPERATOR', 'COMPANY_VIEWER', 'AGENT_VIEWER'];
|
||||
const externalUsers = managedUsers.filter(u => !EXCLUDED_ROLES.includes(u.role || ''));
|
||||
// Grouping by site — semua sudah dipastikan hanya SUPER_ADMIN, EXECUTIVE, TENANT_ADMIN, SOC_ANALYST, ENGINEER
|
||||
const superadminUsers = externalUsers.filter(u => u.role === 'SUPER_ADMIN' || u.role === 'EXECUTIVE' || !u.site_uuid || u.site_uuid === 'default');
|
||||
const siabUsers = externalUsers.filter(u => u.site_uuid === '6681452d_9cae_4ff4_8ae8_0d504774265e' && u.role !== 'SUPER_ADMIN' && u.role !== 'EXECUTIVE');
|
||||
const nexusUsers = externalUsers.filter(u => u.site_uuid === '1959bb55_045b_47c7_bbdd_f33b7db197b9' && u.role !== 'SUPER_ADMIN' && u.role !== 'EXECUTIVE');
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-3 sm:justify-start sm:gap-4 w-full">
|
||||
<h2 className="text-xl sm:text-2xl font-bold tracking-tight text-white flex items-center gap-2 sm:gap-3 truncate">
|
||||
<span>Agents Inventory</span>
|
||||
{isLoading && <Loader2 className="w-4 h-4 sm:w-5 sm:h-5 animate-spin text-primary flex-shrink-0" />}
|
||||
</h2>
|
||||
{/* Mobile Actions: Inline next to Title */}
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0 sm:hidden">
|
||||
<HelpTrigger pageId="agents" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground mt-1 leading-normal sm:leading-relaxed">
|
||||
Managed DPI probes, remote collectors, and probe infrastructure.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Laptop/Tablet Actions: Floats on the right */}
|
||||
<div className="hidden sm:flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<HelpTrigger pageId="agents" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-500/15 border border-red-500/30 text-red-400 px-4 py-3 rounded-xl text-sm font-medium">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<UptimeStatsCards
|
||||
totalAgents={totalAgents}
|
||||
onlineAgents={onlineAgents}
|
||||
offlineAgents={offlineAgents}
|
||||
avgUptime={avgUptime}
|
||||
/>
|
||||
|
||||
{/* Agent Location Map — tampil untuk SUPER_ADMIN, TENANT_ADMIN, dan COMPANY_ADMIN */}
|
||||
{(role === 'SUPER_ADMIN' || role === 'TENANT_ADMIN' || role === 'COMPANY_ADMIN') && (
|
||||
<div className="rounded-2xl border border-white/8 bg-slate-900/40 overflow-hidden relative z-0">
|
||||
<div className="flex items-center justify-between px-5 py-3.5 border-b border-white/8">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<MapPin className="w-4 h-4 text-indigo-400" />
|
||||
<span className="text-sm font-semibold text-white">Agent Network Map</span>
|
||||
<span className="text-xs text-slate-500">
|
||||
{locations.length > 0
|
||||
? `${locations.length} of ${agents.length} agent${agents.length !== 1 ? 's' : ''} positioned`
|
||||
: 'No positions configured yet'}
|
||||
</span>
|
||||
</div>
|
||||
{agents.length > locations.length && (
|
||||
<span className="text-[11px] text-amber-400 bg-amber-400/10 border border-amber-400/20 px-2.5 py-1 rounded-full">
|
||||
{agents.length - locations.length} agent{agents.length - locations.length !== 1 ? 's' : ''} without location — click 📍 in the table
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<AgentLocationMap
|
||||
pins={locations
|
||||
.filter(loc => agents.some(a => (a.uuid || a.serial) === loc.agent_uuid))
|
||||
.map(loc => {
|
||||
const agent = agents.find(a => (a.uuid || a.serial) === loc.agent_uuid);
|
||||
const agentLabel = agent
|
||||
? (resolveAgentLabel(loc.agent_uuid, managedUsers) !== loc.agent_uuid
|
||||
? resolveAgentLabel(loc.agent_uuid, managedUsers)
|
||||
: (agent.label || loc.label || loc.agent_uuid))
|
||||
: (loc.label || loc.agent_uuid);
|
||||
const isOnline = !!(agent?.last_seen_at?.human === 'Online' ||
|
||||
agent?.last_seen_at?.human === 'Flows Detected' ||
|
||||
(agent?.last_seen_at?.human && agent.last_seen_at.human.startsWith('Last seen:')));
|
||||
const uptime = uptimeMap[loc.agent_uuid];
|
||||
return {
|
||||
agent_uuid: loc.agent_uuid,
|
||||
label: agentLabel,
|
||||
latitude: loc.latitude,
|
||||
longitude: loc.longitude,
|
||||
isOnline,
|
||||
uptimePercent: uptime,
|
||||
};
|
||||
})}
|
||||
onEditLocation={handleEditLocation}
|
||||
canEdit={role === 'SUPER_ADMIN' || role === 'TENANT_ADMIN'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AgentsTableSection
|
||||
agents={agents}
|
||||
isLoading={isLoading}
|
||||
columns={columns}
|
||||
role={role}
|
||||
onProvisionClick={() => setIsCreateOpen(true)}
|
||||
/>
|
||||
|
||||
{(role === 'SUPER_ADMIN' || role === 'TENANT_ADMIN') && (
|
||||
<ExternalAccountsSection
|
||||
role={role}
|
||||
isLoading={isLoading}
|
||||
externalUsers={externalUsers}
|
||||
superadminUsers={superadminUsers}
|
||||
siabUsers={siabUsers}
|
||||
nexusUsers={nexusUsers}
|
||||
externalColumns={externalColumns}
|
||||
setExternalModalUser={setExternalModalUser}
|
||||
setTargetSiteUuid={setTargetSiteUuid}
|
||||
setTargetCreatedBy={setTargetCreatedBy}
|
||||
setExternalModalOpen={setExternalModalOpen}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AgentsPageModals
|
||||
isCreateOpen={isCreateOpen}
|
||||
setIsCreateOpen={setIsCreateOpen}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
selectedAgent={selectedAgent}
|
||||
loadAgents={loadAgents}
|
||||
detailAgent={detailAgent}
|
||||
setDetailAgent={setDetailAgent}
|
||||
accountModalOpen={accountModalOpen}
|
||||
setAccountModalOpen={setAccountModalOpen}
|
||||
accountModalAgent={accountModalAgent}
|
||||
setAccountModalAgent={setAccountModalAgent}
|
||||
managedUsers={managedUsers}
|
||||
externalModalOpen={externalModalOpen}
|
||||
setExternalModalOpen={setExternalModalOpen}
|
||||
externalModalUser={externalModalUser}
|
||||
setExternalModalUser={setExternalModalUser}
|
||||
targetSiteUuid={targetSiteUuid}
|
||||
setTargetSiteUuid={setTargetSiteUuid}
|
||||
targetCreatedBy={targetCreatedBy}
|
||||
setTargetCreatedBy={setTargetCreatedBy}
|
||||
isLocModalOpen={isLocModalOpen}
|
||||
setIsLocModalOpen={setIsLocModalOpen}
|
||||
locModalAgentUuid={locModalAgentUuid}
|
||||
setLocModalAgentUuid={setLocModalAgentUuid}
|
||||
locModalAgentLabel={locModalAgentLabel}
|
||||
setLocModalAgentLabel={setLocModalAgentLabel}
|
||||
locModalCurrentLoc={locModalCurrentLoc}
|
||||
setLocModalCurrentLoc={setLocModalCurrentLoc}
|
||||
loadLocations={loadLocations}
|
||||
/>
|
||||
<SubnetConfigModal
|
||||
agentUuid={subnetModalAgent?.uuid || ""}
|
||||
agentLabel={subnetModalAgent?.label || ""}
|
||||
open={subnetModalOpen}
|
||||
onClose={() => setSubnetModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user