import React, { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { apiFetch } from "../api"; import { useAuth } from "../state"; const emptyForm = { name: "", host: "", port: 5432, dbname: "", username: "", password: "", sslmode: "prefer", use_pg_stat_statements: true, tags: {}, }; const emptyEditForm = { id: null, name: "", host: "", port: 5432, dbname: "", username: "", password: "", sslmode: "prefer", use_pg_stat_statements: true, }; export function TargetsPage() { const { tokens, refresh, me } = useAuth(); const [targets, setTargets] = useState([]); const [form, setForm] = useState(emptyForm); const [editForm, setEditForm] = useState(emptyEditForm); const [editing, setEditing] = useState(false); const [error, setError] = useState(""); const [loading, setLoading] = useState(true); const [testState, setTestState] = useState({ loading: false, message: "", ok: null }); const [saveState, setSaveState] = useState({ loading: false, message: "" }); const canManage = me?.role === "admin" || me?.role === "operator"; const load = async () => { setLoading(true); try { setTargets(await apiFetch("/targets", {}, tokens, refresh)); setError(""); } catch (e) { setError(String(e.message || e)); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); const createTarget = async (e) => { e.preventDefault(); try { await apiFetch("/targets", { method: "POST", body: JSON.stringify(form) }, tokens, refresh); setForm(emptyForm); await load(); } catch (e) { setError(String(e.message || e)); } }; const testConnection = async () => { setTestState({ loading: true, message: "", ok: null }); try { const result = await apiFetch( "/targets/test-connection", { method: "POST", body: JSON.stringify({ host: form.host, port: form.port, dbname: form.dbname, username: form.username, password: form.password, sslmode: form.sslmode, }), }, tokens, refresh ); setTestState({ loading: false, message: `${result.message} (PostgreSQL ${result.server_version})`, ok: true }); } catch (e) { setTestState({ loading: false, message: String(e.message || e), ok: false }); } }; const deleteTarget = async (id) => { if (!confirm("Delete target?")) return; try { await apiFetch(`/targets/${id}`, { method: "DELETE" }, tokens, refresh); await load(); } catch (e) { setError(String(e.message || e)); } }; const startEdit = (target) => { setEditing(true); setSaveState({ loading: false, message: "" }); setEditForm({ id: target.id, name: target.name, host: target.host, port: target.port, dbname: target.dbname, username: target.username, password: "", sslmode: target.sslmode, use_pg_stat_statements: target.use_pg_stat_statements !== false, }); }; const cancelEdit = () => { setEditing(false); setEditForm(emptyEditForm); setSaveState({ loading: false, message: "" }); }; const saveEdit = async (e) => { e.preventDefault(); if (!editForm.id) return; setSaveState({ loading: true, message: "" }); try { const payload = { name: editForm.name, host: editForm.host, port: Number(editForm.port), dbname: editForm.dbname, username: editForm.username, sslmode: editForm.sslmode, use_pg_stat_statements: !!editForm.use_pg_stat_statements, }; if (editForm.password.trim()) payload.password = editForm.password; await apiFetch(`/targets/${editForm.id}`, { method: "PUT", body: JSON.stringify(payload) }, tokens, refresh); setSaveState({ loading: false, message: "Target updated." }); setEditing(false); setEditForm(emptyEditForm); await load(); } catch (e) { setSaveState({ loading: false, message: String(e.message || e) }); } }; return (
Quick checks for the most common connection issues.
Connection refused: host/port is wrong or database is unreachable.
rejected SSL upgrade: set SSL mode to disable.
localhost points to the backend container itself, not your host machine.
Loading targets...
) : (| Name | Host | DB | Query Insights | Actions |
|---|---|---|---|---|
| {t.name} | {t.host}:{t.port} | {t.dbname} | {t.use_pg_stat_statements ? "Enabled" : "Disabled"} | Details{" "} {canManage && } {canManage && } |