/** * @file GitCredentialsConfig.tsx * @description The **Git credentials** tab of Settings (issue #45) — the * logins AgentHub itself gives git for a dashboard fetch/pull/push, one per * host, shared by everyone using this install. Lists them, replaces a token, * adds one, deletes one. A token is write-only here: the server never returns * one, so the form starts blank and the list shows only that one is saved. * The git bar's login form saves into the same store. * @author Nguyễn Ngọc Trí Vĩ */ import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { KeyRound, Pencil, Plus, Trash2 } from "lucide-react"; import { api, type GitCredential } from "../lib/api"; const INPUT = "w-full rounded border border-border-light bg-surface-0 px-2 py-1 text-xs text-fg-primary placeholder:text-fg-muted focus:border-accent focus:outline-none"; export function GitCredentialsConfig() { const { t } = useTranslation("settings"); const [items, setItems] = useState([]); const [error, setError] = useState(null); const [form, setForm] = useState<{ url: string; username: string; token: string } | null>(null); const load = useCallback(async () => { try { setItems((await api.gitCredentials.list()).items); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }, []); useEffect(() => { void load(); }, [load]); const save = async () => { if (!form) return; setError(null); let url: URL; try { url = new URL(form.url.trim()); } catch { setError(t("gitCreds.badUrl")); return; } try { const r = await api.gitCredentials.save({ protocol: url.protocol.replace(/:$/, ""), host: url.host, username: form.username.trim(), password: form.token, }); setItems(r.items); setForm(null); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }; const remove = async (c: GitCredential) => { if (!window.confirm(t("gitCreds.removeConfirm"))) return; try { setItems((await api.gitCredentials.remove(c.protocol, c.host)).items); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }; return (

{t("gitCreds.title")}

{t("gitCreds.description")}

{error && (

{error}

)} {form && (
{ e.preventDefault(); void save(); }} > setForm({ ...form, url: e.target.value })} className={`${INPUT} font-mono`} /> setForm({ ...form, username: e.target.value })} className={INPUT} /> setForm({ ...form, token: e.target.value })} className={INPUT} />
)}
{items.map((c) => ( ))} {items.length === 0 && ( )}
{t("gitCreds.colHost")} {t("gitCreds.colUser")} {t("gitCreds.colSaved")}
{c.host} {c.username} {new Date(c.savedAt).toLocaleString()}
{t("gitCreds.empty")}
); }