"use client"; import React, { useEffect, useState } from "react"; import { Plus, Pencil, Trash2, Power, RefreshCw, Radio } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from "@/components/ui/dialog"; import { useToast } from "@/hooks/use-toast"; import { CentralForwarderAPI, CentralForwarderConfig, ForwarderRuntimeStatus, } from "@/lib/api/centralForwarder"; const emptyForm: CentralForwarderConfig = { config_name: "", company_code: "*", company_id: "", edge_id: "", broker_host: "211.115.91.170", broker_port: 31883, username: "ingestion", password: "", use_tls: "N", client_id_prefix: "pipeline-forwarder", topic_pattern: "dt/v1/data/{company_id}/{edge_id}", status_topic_pattern: "dt/v1/status/{company_id}/{edge_id}", batch_size: 50, batch_timeout_ms: 3000, heartbeat_interval_sec: 60, qos: 1, is_enabled: "N", description: "", }; export default function CentralForwarderPage() { const { toast } = useToast(); const [configs, setConfigs] = useState([]); const [runtime, setRuntime] = useState([]); const [loading, setLoading] = useState(true); const [modalOpen, setModalOpen] = useState(false); const [form, setForm] = useState({ ...emptyForm }); const [editingId, setEditingId] = useState(null); const [saving, setSaving] = useState(false); const load = async () => { setLoading(true); try { const [list, rt] = await Promise.all([ CentralForwarderAPI.list(), CentralForwarderAPI.runtimeStatus(), ]); setConfigs(list); setRuntime(rt); } catch (err) { toast({ title: "조회 실패", description: (err as Error).message, variant: "destructive", }); } finally { setLoading(false); } }; useEffect(() => { load(); const t = setInterval(load, 10000); return () => clearInterval(t); }, []); const rtMap = new Map(runtime.map(r => [r.config_id, r])); const openCreate = () => { setEditingId(null); setForm({ ...emptyForm }); setModalOpen(true); }; const openEdit = async (id: number) => { try { const cfg = await CentralForwarderAPI.get(id); setEditingId(id); setForm({ ...cfg, password: "" }); // 비밀번호는 비움 (필요 시 새로 입력) setModalOpen(true); } catch (err) { toast({ title: "조회 실패", description: (err as Error).message, variant: "destructive", }); } }; const save = async () => { setSaving(true); try { if (editingId) { const payload = { ...form }; if (!payload.password) delete (payload as { password?: string }).password; await CentralForwarderAPI.update(editingId, payload); } else { await CentralForwarderAPI.create(form); } toast({ title: "저장 완료" }); setModalOpen(false); await load(); } catch (err) { toast({ title: "저장 실패", description: (err as Error).message, variant: "destructive", }); } finally { setSaving(false); } }; const toggle = async (id: number, enabled: boolean) => { try { await CentralForwarderAPI.toggle(id, enabled); toast({ title: enabled ? "포워더 시작" : "포워더 중지" }); await load(); } catch (err) { toast({ title: "상태 변경 실패", description: (err as Error).message, variant: "destructive", }); } }; const remove = async (id: number) => { if (!confirm("이 포워더 설정을 삭제하시겠습니까?")) return; try { await CentralForwarderAPI.delete(id); toast({ title: "삭제 완료" }); await load(); } catch (err) { toast({ title: "삭제 실패", description: (err as Error).message, variant: "destructive", }); } }; return (

중앙 MQTT 포워더

수집한 장비 데이터를 IDC 중앙 EMQX로 전송 (Pipeline = Edge 역할)

{loading ? (
{Array.from({ length: 4 }).map((_, i) => (
))}
) : configs.length === 0 ? (

등록된 포워더 설정이 없습니다

) : (
{configs.map(cfg => { const rt = rtMap.get(cfg.id!); const enabled = cfg.is_enabled === "Y"; return (
{enabled ? "활성" : "비활성"} {rt && ( {rt.connected ? "연결됨" : "연결끊김"} )}

{cfg.config_name}

{cfg.company_code === "*" ? "공통" : cfg.company_code} · {cfg.edge_id}

{cfg.broker_host}:{cfg.broker_port}

{cfg.topic_pattern}

{rt && (

전송 {rt.messagesForwarded} · 실패 {rt.messagesFailed} · 버퍼 {rt.buffered}

)}
); })}
)}
{editingId ? "포워더 설정 수정" : "새 포워더 설정"} IDC 중앙 MQTT(EMQX)로 수집 데이터를 전송하는 설정입니다.
setForm({ ...form, config_name: v })} /> setForm({ ...form, company_code: v })} /> setForm({ ...form, company_id: v })} placeholder="예: 7f5c058c-ef65-45e3-..." /> setForm({ ...form, edge_id: v })} placeholder="예: edge-0f4d04ed" /> setForm({ ...form, broker_host: v })} /> setForm({ ...form, broker_port: Number(v) })} /> setForm({ ...form, username: v })} /> setForm({ ...form, password: v })} /> setForm({ ...form, topic_pattern: v })} className="sm:col-span-2" /> setForm({ ...form, batch_size: Number(v) })} /> setForm({ ...form, batch_timeout_ms: Number(v) })} /> setForm({ ...form, heartbeat_interval_sec: Number(v) })} /> setForm({ ...form, qos: Number(v) })} />
setForm({ ...form, is_enabled: c ? "Y" : "N" })} />
setForm({ ...form, description: e.target.value })} />
); } function Field({ label, value, onChange, type = "text", placeholder, className, }: { label: string; value: string; onChange: (v: string) => void; type?: string; placeholder?: string; className?: string; }) { return (
onChange(e.target.value)} placeholder={placeholder} />
); }