import React, { useState } from "react"; import { Alert, StyleSheet, Text, View } from "react-native"; import { fonts, radius, text } from "../../theme/tokens"; import { useTheme } from "../../theme/useTheme"; import { Badge } from "../../components/Badge"; import { Button } from "../../components/Button"; import { ErrorNotice, Field, ManageShell } from "../../components/ManageShell"; import { Select } from "../../components/Select"; import { Switch } from "../../components/Switch"; import { TextField } from "../../components/TextField"; import { Card, Divider, Mono, SectionLabel } from "../../components/primitives"; import { useAppState } from "../../state/AppState"; import { useResource } from "../../state/useResource"; import type { Host } from "../../api/client"; /** * Git servers — one entry per forge host, carrying the server's own * credentials: a forge token (encrypted at rest, write-only) and an ed25519 * deploy keypair whose public half is shown here for the operator to paste * into the forge. Projects on a configured server need no per-repo * credentials. Writes are admin-only; 403s surface inline. */ const FORGE_OPTIONS = ["github", "gitlab", "gitea", "forgejo", "bitbucket"]; export function GitServersScreen() { const { colors } = useTheme(); const { client } = useAppState(); const { data, error: loadError, loading, reload } = useResource("/hosts"); const hosts = data ?? []; const [showForm, setShowForm] = useState(false); const [hostname, setHostname] = useState(""); const [forgeType, setForgeType] = useState("github"); const [baseUrl, setBaseUrl] = useState(""); const [token, setToken] = useState(""); const [generateKey, setGenerateKey] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); async function add() { if (!client) return; if (!hostname.trim()) { setError("Hostname is required."); return; } setError(null); setBusy(true); try { await client.api("/hosts", { body: { hostname: hostname.trim(), forge_type: forgeType, base_url: baseUrl.trim() || undefined, token: token || undefined, generate_ssh_key: generateKey, }, }); setHostname(""); setBaseUrl(""); setToken(""); setGenerateKey(true); setShowForm(false); reload(); } catch (e) { setError(e instanceof Error ? e.message : "Couldn’t add the git server."); } finally { setBusy(false); } } function confirmDelete(h: Host) { Alert.alert( "Delete git server?", `Remove ${h.hostname} and its stored credentials. Projects on it lose their token + deploy key.`, [ { text: "Cancel", style: "cancel" }, { text: "Delete", style: "destructive", onPress: () => { client ?.api(`/hosts/${encodeURIComponent(h.hostname)}`, { method: "DELETE" }) .then(() => reload()) .catch((e) => setError(e instanceof Error ? e.message : "Delete failed."), ); }, }, ], ); } return ( {`${hosts.length} server${hosts.length === 1 ? "" : "s"}`} {showForm ? (