artifacts-dashboard/frontend/src/components/workflow/transition-editor.tsx
Paweł Orzech 75313b83c0
Add multi-user workflows/pipelines and error tracking
Add multi-user automation features and per-user error tracking.

- Database migrations: add workflow_configs/workflow_runs (004), app_errors (005), pipeline_configs/pipeline_runs (006), and add user_token_hash to app_errors (007).
- Backend: introduce per-request token handling (X-API-Token) via app.api.deps and update many API routes (auth, automations, bank, characters, dashboard, events, exchange, logs) to use user-scoped Artifacts client and character scoping. Auth endpoints no longer store tokens server-side (validate-only); clear is a no-op on server.
- New Errors API and services: endpoint to list, filter, resolve, and report errors scoped to the requesting user; add error models, schemas, middleware/error handler and error_service for recording/hashing tokens.
- Pipelines & Workflows: add API routers, models, schemas and engine modules (pipeline/worker/coordinator, workflow runner/conditions) and action_executor updates to support workflow/pipeline execution.
- Logs: logs endpoint now prefers fetching recent action logs from the game API (with fallback to local DB), supports paging and filtering, and scopes results to the user.
- Frontend: add pipeline/workflow builders, lists, progress components and hooks (use-errors, use-pipelines, use-workflows), sentry client config, and updates to API client/constants/types.
- Misc: add middleware error handler, various engine strategy tweaks, tests adjusted.

Overall this change enables per-user API tokens, scopes DB queries to each user, introduces pipelines/workflows runtime support, and centralizes application error tracking.
2026-03-01 23:02:34 +01:00

188 lines
5.4 KiB
TypeScript

"use client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { TransitionCondition } from "@/lib/types";
const TRANSITION_TYPES = [
{ value: "strategy_complete", label: "Strategy Completes" },
{ value: "actions_count", label: "Actions Count" },
{ value: "timer", label: "Timer (seconds)" },
{ value: "inventory_full", label: "Inventory Full" },
{ value: "inventory_item_count", label: "Inventory Item Count" },
{ value: "bank_item_count", label: "Bank Item Count" },
{ value: "skill_level", label: "Skill Level" },
{ value: "gold_amount", label: "Gold Amount" },
] as const;
const OPERATORS = [
{ value: ">=", label: ">=" },
{ value: "<=", label: "<=" },
{ value: "==", label: "==" },
{ value: ">", label: ">" },
{ value: "<", label: "<" },
] as const;
const SKILLS = [
"mining",
"woodcutting",
"fishing",
"weaponcrafting",
"gearcrafting",
"jewelrycrafting",
"cooking",
"alchemy",
] as const;
interface TransitionEditorProps {
value: TransitionCondition | null;
onChange: (condition: TransitionCondition | null) => void;
}
export function TransitionEditor({ value, onChange }: TransitionEditorProps) {
const condType = value?.type ?? "";
const needsValue =
condType === "actions_count" ||
condType === "inventory_item_count" ||
condType === "bank_item_count" ||
condType === "skill_level" ||
condType === "gold_amount";
const needsItemCode =
condType === "inventory_item_count" || condType === "bank_item_count";
const needsSkill = condType === "skill_level";
const needsSeconds = condType === "timer";
function update(partial: Partial<TransitionCondition>) {
const base: TransitionCondition = value ?? {
type: "strategy_complete",
operator: ">=",
value: 0,
item_code: "",
skill: "",
seconds: 0,
};
onChange({ ...base, ...partial });
}
return (
<div className="space-y-3">
<div>
<Label className="text-xs text-muted-foreground">
Transition Condition
</Label>
<Select
value={condType || "none"}
onValueChange={(v) => {
if (v === "none") {
onChange(null);
} else {
update({
type: v as TransitionCondition["type"],
});
}
}}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="No transition (run until complete)" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
No transition (run until strategy completes)
</SelectItem>
{TRANSITION_TYPES.map((t) => (
<SelectItem key={t.value} value={t.value}>
{t.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{needsValue && (
<div className="flex gap-2">
<div className="w-20">
<Label className="text-xs text-muted-foreground">Operator</Label>
<Select
value={value?.operator ?? ">="}
onValueChange={(v) =>
update({ operator: v as TransitionCondition["operator"] })
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
{OPERATORS.map((op) => (
<SelectItem key={op.value} value={op.value}>
{op.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex-1">
<Label className="text-xs text-muted-foreground">Value</Label>
<Input
type="number"
className="h-8 text-xs"
value={value?.value ?? 0}
onChange={(e) => update({ value: Number(e.target.value) })}
/>
</div>
</div>
)}
{needsItemCode && (
<div>
<Label className="text-xs text-muted-foreground">Item Code</Label>
<Input
className="h-8 text-xs"
placeholder="e.g. copper_ore"
value={value?.item_code ?? ""}
onChange={(e) => update({ item_code: e.target.value })}
/>
</div>
)}
{needsSkill && (
<div>
<Label className="text-xs text-muted-foreground">Skill</Label>
<Select
value={value?.skill ?? "mining"}
onValueChange={(v) => update({ skill: v })}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
{SKILLS.map((s) => (
<SelectItem key={s} value={s}>
{s}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{needsSeconds && (
<div>
<Label className="text-xs text-muted-foreground">Seconds</Label>
<Input
type="number"
className="h-8 text-xs"
value={value?.seconds ?? 0}
onChange={(e) => update({ seconds: Number(e.target.value) })}
/>
</div>
)}
</div>
);
}