feat: Mantine design system with dark mode
This commit is contained in:
parent
6e12720ad6
commit
38d4899b07
18 changed files with 1170 additions and 322 deletions
|
|
@ -1,4 +1,22 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
Alert,
|
||||
Anchor,
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Table,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconAlertCircle,
|
||||
IconCheck,
|
||||
IconExternalLink,
|
||||
IconRefresh,
|
||||
IconUpload,
|
||||
} from "@tabler/icons-react";
|
||||
import {
|
||||
ApiError,
|
||||
LANGFLOW_URL,
|
||||
|
|
@ -6,6 +24,7 @@ import {
|
|||
type FlowDefinition,
|
||||
type FlowVersion,
|
||||
} from "../api";
|
||||
import StatusBadge from "./StatusBadge";
|
||||
|
||||
export default function AdminTab() {
|
||||
const [flows, setFlows] = useState<FlowDefinition[]>([]);
|
||||
|
|
@ -86,61 +105,110 @@ export default function AdminTab() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h2>Administration</h2>
|
||||
{error && <p style={{ color: "crimson" }}>{error}</p>}
|
||||
{info && <p style={{ color: "green" }}>{info}</p>}
|
||||
<p>
|
||||
Flows werden visuell in Langflow entworfen:{" "}
|
||||
<a href={LANGFLOW_URL} target="_blank" rel="noreferrer">
|
||||
Langflow öffnen
|
||||
</a>
|
||||
</p>
|
||||
<h3>Import aus Langflow</h3>
|
||||
<div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
|
||||
<input
|
||||
value={langflowId}
|
||||
onChange={(e) => setLangflowId(e.target.value)}
|
||||
placeholder="Langflow Flow-ID (UUID)"
|
||||
style={{ flex: 1, fontFamily: "monospace" }}
|
||||
/>
|
||||
<input
|
||||
value={importName}
|
||||
onChange={(e) => setImportName(e.target.value)}
|
||||
placeholder="Name (optional)"
|
||||
/>
|
||||
<button onClick={() => void onImport()} disabled={!langflowId.trim()}>
|
||||
Importieren + Deployen
|
||||
</button>
|
||||
</div>
|
||||
<h3>Flows + Versionen</h3>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<button onClick={() => void onSync()}>
|
||||
Aus backend/flows synchronisieren
|
||||
</button>
|
||||
</div>
|
||||
{flows.length === 0 && <p>Keine Flows vorhanden.</p>}
|
||||
<ul>
|
||||
{flows.map((f) => (
|
||||
<li key={f.id} style={{ marginBottom: 8 }}>
|
||||
<strong>{f.name}</strong>{" "}
|
||||
<span style={{ fontFamily: "monospace", fontSize: "0.85em" }}>
|
||||
{f.source_path ? `(${f.source_path})` : "(API)"}
|
||||
</span>
|
||||
<ul>
|
||||
{(versions[f.id] ?? []).map((v) => (
|
||||
<li key={v.id}>
|
||||
v{v.version_no} ·{" "}
|
||||
{v.deployed_at ? `deployed (${v.lfx_flow_id})` : "nicht deployed"} ·{" "}
|
||||
{v.author || "–"}{" "}
|
||||
<button onClick={() => void onDeploy(f.id, v.version_no)}>
|
||||
Deployen
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Title order={3} mb="xs">
|
||||
Flows & Versionen
|
||||
</Title>
|
||||
{error && (
|
||||
<Alert color="red" icon={<IconAlertCircle size={18} />} mb="md">
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
{info && (
|
||||
<Alert color="green" icon={<IconCheck size={18} />} mb="md">
|
||||
{info}
|
||||
</Alert>
|
||||
)}
|
||||
<Card withBorder padding="md" mb="md">
|
||||
<Text mb="xs">
|
||||
Flows werden visuell in Langflow entworfen:{" "}
|
||||
<Anchor href={LANGFLOW_URL} target="_blank" rel="noreferrer">
|
||||
Langflow öffnen <IconExternalLink size={14} />
|
||||
</Anchor>
|
||||
</Text>
|
||||
<Group align="flex-end">
|
||||
<TextInput
|
||||
label="Langflow Flow-ID (UUID)"
|
||||
value={langflowId}
|
||||
onChange={(e) => setLangflowId(e.target.value)}
|
||||
style={{ flex: 1 }}
|
||||
styles={{ input: { fontFamily: "monospace" } }}
|
||||
/>
|
||||
<TextInput
|
||||
label="Name (optional)"
|
||||
value={importName}
|
||||
onChange={(e) => setImportName(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
leftSection={<IconUpload size={16} />}
|
||||
onClick={() => void onImport()}
|
||||
disabled={!langflowId.trim()}
|
||||
>
|
||||
Importieren + Deployen
|
||||
</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
<Card withBorder padding="md" mb="md">
|
||||
<Group>
|
||||
<Button
|
||||
variant="light"
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
onClick={() => void onSync()}
|
||||
>
|
||||
Aus backend/flows synchronisieren
|
||||
</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
{flows.length === 0 ? (
|
||||
<Text c="dimmed">Keine Flows vorhanden.</Text>
|
||||
) : (
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Flow</Table.Th>
|
||||
<Table.Th>Version</Table.Th>
|
||||
<Table.Th>Status</Table.Th>
|
||||
<Table.Th>Autor</Table.Th>
|
||||
<Table.Th></Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{flows.map((f) =>
|
||||
(versions[f.id] ?? []).map((v, i) => (
|
||||
<Table.Tr key={v.id}>
|
||||
<Table.Td>
|
||||
{i === 0 && (
|
||||
<>
|
||||
<strong>{f.name}</strong>{" "}
|
||||
<Text size="xs" c="dimmed" component="span">
|
||||
{f.source_path ? `(${f.source_path})` : "(API)"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Table.Td>
|
||||
<Table.Td>v{v.version_no}</Table.Td>
|
||||
<Table.Td>
|
||||
{v.deployed_at ? (
|
||||
<StatusBadge status="deployed" />
|
||||
) : (
|
||||
<StatusBadge status="pending" />
|
||||
)}
|
||||
</Table.Td>
|
||||
<Table.Td>{v.author || "–"}</Table.Td>
|
||||
<Table.Td>
|
||||
<Button
|
||||
size="compact-sm"
|
||||
variant="light"
|
||||
onClick={() => void onDeploy(f.id, v.version_no)}
|
||||
>
|
||||
Deployen
|
||||
</Button>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)),
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue