feat(portal): M10.2 — workflows node-graph editor
Builds the §3 workflows editor as a client component at
`/[slug]/workflows`. IT_ADMIN only. Full-bleed layout (own
`layout.tsx`) — palette (234px) + canvas (flex) + inspector (286px).
* `src/lib/flow-modules.ts` — TS port of the handoff `FLOW_MODULES`
catalog: 18 modules across Triggers / Scanner / CERTifAI / Logic /
Actions, each with kind-colored monogram, input/output ports, and a
typed settings schema (select / text / num / area / toggle). Helpers
for `nodeH`, `portX/Y`, `defConfig`, `wirePath` (bezier), `seedFlow`
(7-node sample workflow), `modsByCat`. KIND_COLOR token map.
* `src/components/portal/workflows/WorkflowEditor.tsx` — client
component with:
- Palette: collapsible category tree, draggable items, kind-colored
dots and monos.
- Canvas: dotted grid that pans (drag background) and zooms (+/− with
`Maximize2` reset, 0.5–1.6). Floating toolbar = workflow name input
(running pulse on the dot during a test run) + node/link count +
Validate / Save / **Test run** buttons. Save respects the frozen
write-guard; Test run highlights nodes in BFS order from triggers
with animated wires (`.wire.run` keyframes already in globals.css).
- Nodes: 202px cards with kind-bordered monogram + title, first-config
value or `desc` in the body, input ports on left, output ports on
right (multi-output gates labeled PASS/FAIL, etc.). Drag to move,
click to select. Delete/Backspace removes selection.
- Wires: bezier paths via `wirePath`. Drag output port → input port
creates an edge (replaces existing edges into that input). Click to
select. Pending wire shows dashed.
- Inspector: live form against `selNode.config` driven by the module's
settings schema. Per-type fields (select / text / num / area /
toggle). Empty state shows the kind legend; edge selection shows a
delete-link affordance.
- Toasts: inline bottom-right queue with mono status-code footer for
the workflow actions (`workflow.valid`, `workflow.saved`,
`workflow.tested`, `402 → reactivation.requested` when frozen).
* `src/app/[slug]/workflows/layout.tsx` — strips `.content-inner` and
fills `position: absolute; inset: 0` so the editor's 3-column flex
fills the entire content area.
The page returns 200 against `BP_DEV_FIXTURE=admin-acme` with every
flow-* class marker present.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getPortalSession } from "@/lib/get-session";
|
||||
import { hasOrgRole } from "@/lib/session";
|
||||
import { loadTenantForShell } from "@/lib/portal-data";
|
||||
import { NotAllowed } from "@/components/portal/NotAllowed";
|
||||
import { WorkflowEditor } from "@/components/portal/workflows/WorkflowEditor";
|
||||
|
||||
export default async function WorkflowsPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const session = await getPortalSession();
|
||||
|
||||
if (!session) redirect(`/${slug}/dashboard`);
|
||||
if (!hasOrgRole(session, "IT_ADMIN")) {
|
||||
return <NotAllowed need="IT_ADMIN" />;
|
||||
}
|
||||
|
||||
const t = await loadTenantForShell(slug);
|
||||
if (!t) return null;
|
||||
|
||||
return <WorkflowEditor frozen={t.status === "frozen"} />;
|
||||
}
|
||||
Reference in New Issue
Block a user