fix: authorize flow management, gate runs, token-gate callbacks, strict tenants
This commit is contained in:
parent
cfcf0e8bd6
commit
94e5e53092
21 changed files with 662 additions and 160 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Link, Route, Routes, useNavigate } from "react-router-dom";
|
||||
import { Link, Navigate, Route, Routes, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Anchor,
|
||||
AppShell,
|
||||
|
|
@ -10,6 +10,7 @@ import {
|
|||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import type { ReactNode } from "react";
|
||||
import { AuthProvider, useAuth } from "./auth";
|
||||
import SchemeToggle from "./components/SchemeToggle";
|
||||
import StartPage from "./pages/StartPage";
|
||||
|
|
@ -66,6 +67,21 @@ function Nav() {
|
|||
);
|
||||
}
|
||||
|
||||
function RequireAuth({ children }: { children: ReactNode }) {
|
||||
const { me, loading } = useAuth();
|
||||
if (loading) {
|
||||
return (
|
||||
<Center mt="xl">
|
||||
<Loader />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
// Runs, run history and approvals are login-gated server-side;
|
||||
// anonymous visitors go to the magic-link login.
|
||||
if (!me) return <Navigate to="/login" replace />;
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
function Shell() {
|
||||
const { loading } = useAuth();
|
||||
|
||||
|
|
@ -103,9 +119,30 @@ function Shell() {
|
|||
<Route path="/" element={<StartPage />} />
|
||||
<Route path="/access" element={<AccessPage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/app/workflows" element={<WorkflowsPage />} />
|
||||
<Route path="/app/tasks" element={<TasksPage />} />
|
||||
<Route path="/app/admin" element={<AdminPage />} />
|
||||
<Route
|
||||
path="/app/workflows"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<WorkflowsPage />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/tasks"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<TasksPage />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/admin"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<AdminPage />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="*" element={<StartPage />} />
|
||||
</Routes>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -153,15 +153,11 @@ export const api = {
|
|||
req<InputSchema>(`/api/flows/${flowId}/input-schema`),
|
||||
listRuns: (flowId: string) => req<Run[]>(`/api/flows/${flowId}/runs`),
|
||||
listApprovals: () => req<ApprovalTask[]>("/api/approvals"),
|
||||
decide: (
|
||||
taskId: string,
|
||||
decision: "approve" | "reject",
|
||||
approver: string,
|
||||
note: string,
|
||||
) =>
|
||||
decide: (taskId: string, decision: "approve" | "reject", note: string) =>
|
||||
req<Decision>(`/api/approvals/${taskId}/decision`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ decision, approver, note }),
|
||||
// Identity comes from the session; the server records the user.
|
||||
body: JSON.stringify({ decision, note }),
|
||||
}),
|
||||
importFlow: (langflowFlowId: string, name?: string) =>
|
||||
req<{ definition: FlowDefinition; version: FlowVersion }>(
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ import {
|
|||
} from "@mantine/core";
|
||||
import { IconAlertCircle, IconCheck, IconRefresh, IconX } from "@tabler/icons-react";
|
||||
import { ApiError, api, type ApprovalTask } from "../api";
|
||||
import { useAuth } from "../auth";
|
||||
import StatusBadge from "./StatusBadge";
|
||||
|
||||
export default function ApprovalsTab() {
|
||||
const { me } = useAuth();
|
||||
const [tasks, setTasks] = useState<ApprovalTask[]>([]);
|
||||
const [approver, setApprover] = useState("");
|
||||
const [note, setNote] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [busyId, setBusyId] = useState("");
|
||||
|
|
@ -37,7 +38,7 @@ export default function ApprovalsTab() {
|
|||
setBusyId(taskId);
|
||||
setError("");
|
||||
try {
|
||||
const result = await api.decide(taskId, decision, approver, note);
|
||||
const result = await api.decide(taskId, decision, note);
|
||||
setTasks((prev) =>
|
||||
prev.map((t) => (t.id === taskId ? result.task : t)),
|
||||
);
|
||||
|
|
@ -61,12 +62,11 @@ export default function ApprovalsTab() {
|
|||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
<Text size="sm" c="dimmed" mb="md">
|
||||
Angemeldet als {me?.email ?? "–"} — Entscheidungen werden diesem
|
||||
Konto zugeordnet.
|
||||
</Text>
|
||||
<Group mb="md" align="flex-end">
|
||||
<TextInput
|
||||
label="Name (Bearbeiter)"
|
||||
value={approver}
|
||||
onChange={(e) => setApprover(e.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Notiz"
|
||||
value={note}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,9 @@ import {
|
|||
} from "@mantine/core";
|
||||
import { IconAlertCircle, IconPlayerPlay } from "@tabler/icons-react";
|
||||
import { ApiError, api, type FlowDefinition, type InputSchema, type Run } from "../api";
|
||||
import { useAuth } from "../auth";
|
||||
import StatusBadge from "../components/StatusBadge";
|
||||
|
||||
export default function WorkflowsPage() {
|
||||
const { me } = useAuth();
|
||||
const [flows, setFlows] = useState<FlowDefinition[]>([]);
|
||||
const [flowId, setFlowId] = useState("");
|
||||
const [schema, setSchema] = useState<InputSchema | null>(null);
|
||||
|
|
@ -86,11 +84,6 @@ export default function WorkflowsPage() {
|
|||
<Title order={2} mb="md">
|
||||
Workflows
|
||||
</Title>
|
||||
{!me && (
|
||||
<Alert color="blue" mb="md">
|
||||
Sie sind nicht angemeldet — Läufe nutzen den Standard-Mandanten.
|
||||
</Alert>
|
||||
)}
|
||||
{error && (
|
||||
<Alert color="red" icon={<IconAlertCircle size={18} />} mb="md">
|
||||
{error}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue