重构模型平台前后端并移除Redis依赖
This commit is contained in:
@@ -0,0 +1,853 @@
|
||||
export type DemoUser = {
|
||||
userId: string;
|
||||
userName: string;
|
||||
username: string;
|
||||
roleCode: "admin" | "developer";
|
||||
roleName: string;
|
||||
};
|
||||
|
||||
export type DemoWorkspace = {
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
};
|
||||
|
||||
export const demoUsers: DemoUser[] = [
|
||||
{ userId: "0000000000RF6FG1SDBXG59S13", userName: "张三", username: "admin-zhang", roleCode: "admin", roleName: "管理员" },
|
||||
{ userId: "0000000000H2QYCGPCWQM1JSGS", userName: "李四", username: "admin-li", roleCode: "admin", roleName: "管理员" },
|
||||
{ userId: "0000000000RWG40ESZPGJT629J", userName: "王五", username: "dev-wang", roleCode: "developer", roleName: "开发人员" },
|
||||
{ userId: "00000000004CQV7WASJA6N6FW4", userName: "赵六", username: "dev-zhao", roleCode: "developer", roleName: "开发人员" },
|
||||
];
|
||||
|
||||
export const demoWorkspaces: DemoWorkspace[] = [
|
||||
{ workspaceId: "00000000000BM630VT9ARVFZPC", workspaceName: "模型开发 Workspace" },
|
||||
{ workspaceId: "0000000000AE0NC0V5T424KK86", workspaceName: "风险验证 Workspace" },
|
||||
];
|
||||
|
||||
function readStoredContext(): Partial<{
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
}> {
|
||||
if (typeof window === "undefined") return {};
|
||||
try {
|
||||
return JSON.parse(
|
||||
window.localStorage.getItem("model-platform-demo-context") ?? "{}",
|
||||
) as Partial<{ userId: string; workspaceId: string }>;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const storedContext = readStoredContext();
|
||||
const initialUser = demoUsers.find((item) => item.userId === storedContext.userId)
|
||||
?? demoUsers[0];
|
||||
const initialWorkspace = demoWorkspaces.find(
|
||||
(item) => item.workspaceId === storedContext.workspaceId,
|
||||
) ?? demoWorkspaces[0];
|
||||
|
||||
export const demoContext = {
|
||||
...initialUser,
|
||||
...initialWorkspace,
|
||||
};
|
||||
|
||||
export function setDemoContext(input: {
|
||||
user?: DemoUser;
|
||||
workspace?: DemoWorkspace;
|
||||
}): void {
|
||||
if (input.user) Object.assign(demoContext, input.user);
|
||||
if (input.workspace) Object.assign(demoContext, input.workspace);
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem("model-platform-demo-context", JSON.stringify({
|
||||
userId: demoContext.userId,
|
||||
workspaceId: demoContext.workspaceId,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export type ScriptType = "python" | "notebook";
|
||||
export type Visibility = "private" | "workspace" | "public";
|
||||
|
||||
export type Employee = {
|
||||
user_id: string;
|
||||
username: string;
|
||||
display_name: string;
|
||||
email: string | null;
|
||||
status: "active" | "disabled" | "locked";
|
||||
role_code: "admin" | "developer";
|
||||
role_name: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type ScriptItem = {
|
||||
script_id: string;
|
||||
workspace_id: string;
|
||||
current_object_id: string;
|
||||
owner_user_id: string;
|
||||
script_name: string;
|
||||
script_type: ScriptType;
|
||||
visibility: Visibility;
|
||||
status: string;
|
||||
relative_path: string;
|
||||
content_hash: string;
|
||||
size_bytes: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type WorkspaceDirectory = {
|
||||
path: string;
|
||||
name: string;
|
||||
parent_path: string;
|
||||
};
|
||||
|
||||
type ApiEnvelope<T> = {
|
||||
request_id: string;
|
||||
data: T;
|
||||
meta: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type ApiErrorEnvelope = {
|
||||
detail?: string | {
|
||||
code?: string;
|
||||
message?: string;
|
||||
};
|
||||
error?: {
|
||||
code?: string;
|
||||
message?: string;
|
||||
details?: {
|
||||
editor_name?: string;
|
||||
lease_expires_at?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export class ApiRequestError extends Error {
|
||||
readonly status: number;
|
||||
readonly code?: string;
|
||||
|
||||
constructor(message: string, status: number, code?: string) {
|
||||
super(message);
|
||||
this.name = "ApiRequestError";
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
async function apiRequest<T>(
|
||||
path: string,
|
||||
init: RequestInit = {},
|
||||
): Promise<T> {
|
||||
const response = await fetch(path, {
|
||||
...init,
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"X-User-ID": demoContext.userId,
|
||||
"X-Workspace-ID": demoContext.workspaceId,
|
||||
"X-Request-ID": crypto.randomUUID().replaceAll("-", ""),
|
||||
...(init.body ? { "Content-Type": "application/json" } : {}),
|
||||
...init.headers,
|
||||
},
|
||||
});
|
||||
|
||||
const payload = (await response.json().catch(() => ({}))) as
|
||||
| ApiEnvelope<T>
|
||||
| ApiErrorEnvelope;
|
||||
if (!response.ok) {
|
||||
const error = payload as ApiErrorEnvelope;
|
||||
const detailMessage = typeof error.detail === "string"
|
||||
? error.detail
|
||||
: error.detail?.message;
|
||||
const editor = error.error?.details?.editor_name;
|
||||
throw new ApiRequestError(
|
||||
(editor ? `${error.error?.message ?? "文件正在编辑"}(${editor})` : undefined)
|
||||
?? error.error?.message
|
||||
?? detailMessage
|
||||
?? `请求失败(HTTP ${response.status})`,
|
||||
response.status,
|
||||
typeof error.detail === "object"
|
||||
? error.detail?.code
|
||||
: error.error?.code,
|
||||
);
|
||||
}
|
||||
return (payload as ApiEnvelope<T>).data;
|
||||
}
|
||||
|
||||
export async function listScripts(): Promise<ScriptItem[]> {
|
||||
return apiRequest<ScriptItem[]>("/api/v1/scripts");
|
||||
}
|
||||
|
||||
function initialContent(scriptType: ScriptType): string {
|
||||
if (scriptType === "python") {
|
||||
return [
|
||||
'"""模型实验开发平台构建脚本。"""',
|
||||
"",
|
||||
"",
|
||||
"def main() -> None:",
|
||||
' print("Hello, Model Platform!")',
|
||||
"",
|
||||
"",
|
||||
'if __name__ == "__main__":',
|
||||
" main()",
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
return JSON.stringify(
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
cell_type: "markdown",
|
||||
metadata: {},
|
||||
source: ["# 新建模型实验\\n", "在这里开始数据探索与模型构建。"],
|
||||
},
|
||||
{
|
||||
cell_type: "code",
|
||||
execution_count: null,
|
||||
metadata: {},
|
||||
outputs: [],
|
||||
source: ["print('Hello, Model Platform!')\\n"],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
kernelspec: {
|
||||
display_name: "Python 3",
|
||||
language: "python",
|
||||
name: "python3",
|
||||
},
|
||||
language_info: {
|
||||
name: "python",
|
||||
version: "3.12",
|
||||
},
|
||||
},
|
||||
nbformat: 4,
|
||||
nbformat_minor: 5,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
export async function createScript(input: {
|
||||
name: string;
|
||||
scriptType: ScriptType;
|
||||
visibility: Visibility;
|
||||
parentPath?: string | null;
|
||||
}): Promise<ScriptItem> {
|
||||
return apiRequest<ScriptItem>("/api/v1/scripts", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
script_name: input.name.trim(),
|
||||
script_type: input.scriptType,
|
||||
visibility: input.visibility,
|
||||
content: initialContent(input.scriptType),
|
||||
parent_path: input.parentPath,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadScript(
|
||||
file: File,
|
||||
parentPath = "",
|
||||
visibility: Visibility = "workspace",
|
||||
): Promise<ScriptItem> {
|
||||
const parameters = new URLSearchParams({
|
||||
file_name: file.name,
|
||||
parent_path: parentPath,
|
||||
visibility,
|
||||
});
|
||||
return apiRequest<ScriptItem>(
|
||||
`/api/v1/scripts/upload?${parameters.toString()}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/octet-stream" },
|
||||
body: file,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteScript(
|
||||
scriptId: string,
|
||||
): Promise<{ script_id: string; status: string; versions_preserved: boolean }> {
|
||||
return apiRequest(`/api/v1/scripts/${scriptId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function listWorkspaceDirectories(): Promise<
|
||||
WorkspaceDirectory[]
|
||||
> {
|
||||
const data = await apiRequest<{ directories: WorkspaceDirectory[] }>(
|
||||
"/api/v1/workspace-tree",
|
||||
);
|
||||
return data.directories;
|
||||
}
|
||||
|
||||
export async function createWorkspaceDirectory(
|
||||
directoryName: string,
|
||||
parentPath = "",
|
||||
): Promise<WorkspaceDirectory> {
|
||||
return apiRequest<WorkspaceDirectory>("/api/v1/workspace-directories", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
directory_name: directoryName,
|
||||
parent_path: parentPath,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteWorkspaceDirectory(
|
||||
path: string,
|
||||
): Promise<{
|
||||
path: string;
|
||||
status: string;
|
||||
deleted_scripts: number;
|
||||
versions_preserved: boolean;
|
||||
}> {
|
||||
const parameters = new URLSearchParams({ path });
|
||||
return apiRequest(`/api/v1/workspace-directories?${parameters.toString()}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
export type FileLockSession = {
|
||||
edit_session_id: string;
|
||||
workspace_id: string;
|
||||
storage_object_id: string;
|
||||
user_id: string;
|
||||
session_status: "active" | "closed" | "expired";
|
||||
lease_seconds: number;
|
||||
heartbeat_interval_seconds: number;
|
||||
expires_at: string;
|
||||
runtime_id: string;
|
||||
jupyter_session_id: string;
|
||||
jupyter_url?: string;
|
||||
relative_path?: string;
|
||||
lock_token?: string;
|
||||
};
|
||||
|
||||
export type ActiveEditSession = FileLockSession & {
|
||||
script_id: string;
|
||||
script_name: string;
|
||||
lock_token: string;
|
||||
ticket_expires_at?: string;
|
||||
};
|
||||
|
||||
export type JupyterAccessTicket = {
|
||||
edit_session_id: string;
|
||||
jupyter_url: string;
|
||||
expires_at: string;
|
||||
};
|
||||
|
||||
export type StableVersion = {
|
||||
versions_id: string;
|
||||
workspace_id: string;
|
||||
script_id: string;
|
||||
source_object_id: string;
|
||||
artifact_object_id: string;
|
||||
version_no: number;
|
||||
version_label: string;
|
||||
source_path: string;
|
||||
artifact_path: string;
|
||||
content_hash: string;
|
||||
file_size_bytes: number;
|
||||
visibility: Visibility;
|
||||
release_note: string | null;
|
||||
created_by: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export async function acquireFileLock(
|
||||
script: ScriptItem,
|
||||
): Promise<ActiveEditSession> {
|
||||
const session = await apiRequest<FileLockSession>(
|
||||
`/api/v1/files/${script.current_object_id}/lock`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
if (!session.lock_token) {
|
||||
throw new Error("加锁成功响应缺少 lock_token");
|
||||
}
|
||||
return {
|
||||
...session,
|
||||
script_id: script.script_id,
|
||||
script_name: script.script_name,
|
||||
lock_token: session.lock_token,
|
||||
};
|
||||
}
|
||||
|
||||
export async function heartbeatFileLock(
|
||||
session: ActiveEditSession,
|
||||
): Promise<FileLockSession> {
|
||||
return apiRequest<FileLockSession>(
|
||||
`/api/v1/file-locks/${session.edit_session_id}/heartbeat`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ lock_token: session.lock_token }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function releaseFileLock(
|
||||
session: ActiveEditSession,
|
||||
): Promise<FileLockSession> {
|
||||
return apiRequest<FileLockSession>(
|
||||
`/api/v1/file-locks/${session.edit_session_id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ lock_token: session.lock_token }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function releaseFileLockOnUnload(session: ActiveEditSession): void {
|
||||
void fetch(`/api/v1/file-locks/${session.edit_session_id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
keepalive: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-User-ID": demoContext.userId,
|
||||
"X-Workspace-ID": demoContext.workspaceId,
|
||||
"X-Request-ID": crypto.randomUUID().replaceAll("-", ""),
|
||||
},
|
||||
body: JSON.stringify({ lock_token: session.lock_token }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function createJupyterAccessTicket(
|
||||
session: ActiveEditSession,
|
||||
): Promise<JupyterAccessTicket> {
|
||||
return apiRequest<JupyterAccessTicket>("/api/v1/jupyter/access-tickets", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
edit_session_id: session.edit_session_id,
|
||||
lock_token: session.lock_token,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function listScriptVersions(
|
||||
scriptId: string,
|
||||
): Promise<StableVersion[]> {
|
||||
return apiRequest<StableVersion[]>(`/api/v1/scripts/${scriptId}/versions`);
|
||||
}
|
||||
|
||||
export async function publishScriptVersion(input: {
|
||||
script: ScriptItem;
|
||||
releaseNote: string;
|
||||
visibility: Visibility;
|
||||
}): Promise<StableVersion> {
|
||||
return apiRequest<StableVersion>(
|
||||
`/api/v1/scripts/${input.script.script_id}/versions`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
source_object_id: input.script.current_object_id,
|
||||
release_note: input.releaseNote.trim() || null,
|
||||
visibility: input.visibility,
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export type ScheduleArtifact = {
|
||||
versions_id: string;
|
||||
version_label: string;
|
||||
script_id: string;
|
||||
script_name: string;
|
||||
script_type: ScriptType;
|
||||
content_hash: string;
|
||||
file_size_bytes: number;
|
||||
visibility: Visibility;
|
||||
created_by: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type ScheduleNode = {
|
||||
node_id: string;
|
||||
schedule_id: string;
|
||||
node_key: string;
|
||||
node_name: string;
|
||||
versions_id: string;
|
||||
timeout_seconds: number;
|
||||
retry_count: number;
|
||||
retry_interval_sec: number;
|
||||
position_x: number;
|
||||
position_y: number;
|
||||
arguments_json: Record<string, unknown>;
|
||||
env_refs_json: Record<string, string>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
version: {
|
||||
versions_id: string;
|
||||
version_label: string;
|
||||
script_id: string;
|
||||
script_name: string;
|
||||
script_type: ScriptType;
|
||||
content_hash: string;
|
||||
created_at: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ScheduleEdge = {
|
||||
edge_id: string;
|
||||
schedule_id: string;
|
||||
source_node_id: string;
|
||||
target_node_id: string;
|
||||
condition_expr: string | null;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type DagValidation = {
|
||||
valid: boolean;
|
||||
node_count: number;
|
||||
edge_count: number;
|
||||
root_node_ids: string[];
|
||||
leaf_node_ids: string[];
|
||||
topological_order: string[];
|
||||
errors: Array<{
|
||||
code: string;
|
||||
message: string;
|
||||
edge_id?: string;
|
||||
node_ids?: string[];
|
||||
}>;
|
||||
};
|
||||
|
||||
export type Schedule = {
|
||||
schedule_id: string;
|
||||
workspace_id: string;
|
||||
schedule_name: string;
|
||||
description: string | null;
|
||||
trigger_type: "manual" | "cron" | "api";
|
||||
cron_expression: string | null;
|
||||
timezone: string;
|
||||
enabled: boolean;
|
||||
workflow_version: number;
|
||||
max_concurrency: number;
|
||||
failure_policy: "stop" | "continue";
|
||||
last_run_at: string | null;
|
||||
next_run_at: string | null;
|
||||
created_by: string;
|
||||
updated_by: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
node_count: number;
|
||||
edge_count: number;
|
||||
nodes: ScheduleNode[];
|
||||
edges: ScheduleEdge[];
|
||||
dag_validation: DagValidation;
|
||||
};
|
||||
|
||||
export type CronPreview = {
|
||||
cron_expression: string;
|
||||
timezone: string;
|
||||
base_time: string;
|
||||
occurrences: Array<{
|
||||
local_time: string;
|
||||
utc_time: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ScheduleRunStatus =
|
||||
| "queued"
|
||||
| "running"
|
||||
| "succeeded"
|
||||
| "failed"
|
||||
| "cancelled"
|
||||
| "timed_out";
|
||||
|
||||
export type ScheduleNodeRunStatus =
|
||||
| ScheduleRunStatus
|
||||
| "skipped";
|
||||
|
||||
export type ScheduleRunSummary = {
|
||||
run_id: string;
|
||||
schedule_id: string;
|
||||
workspace_id: string;
|
||||
workflow_version: number;
|
||||
trigger_type: "manual" | "cron" | "api" | "retry";
|
||||
run_status: ScheduleRunStatus;
|
||||
state_version: number;
|
||||
queued_at: string;
|
||||
started_at: string | null;
|
||||
finished_at: string | null;
|
||||
duration_ms: number | null;
|
||||
error_code: string | null;
|
||||
error_message: string | null;
|
||||
logs_object_id: string | null;
|
||||
result_object_id: string | null;
|
||||
};
|
||||
|
||||
export type ScheduleNodeRun = {
|
||||
node_run_id: string;
|
||||
run_id: string;
|
||||
node_id: string;
|
||||
versions_id: string;
|
||||
attempt_no: number;
|
||||
node_status: ScheduleNodeRunStatus;
|
||||
state_version: number;
|
||||
started_at: string | null;
|
||||
finished_at: string | null;
|
||||
duration_ms: number | null;
|
||||
exit_code: number | null;
|
||||
message: string | null;
|
||||
logs_object_id: string | null;
|
||||
result_object_id: string | null;
|
||||
};
|
||||
|
||||
export type ScheduleRunDetail = ScheduleRunSummary & {
|
||||
node_runs: ScheduleNodeRun[];
|
||||
};
|
||||
|
||||
export async function listSchedules(): Promise<Schedule[]> {
|
||||
return apiRequest<Schedule[]>("/api/v1/schedules");
|
||||
}
|
||||
|
||||
export async function getSchedule(scheduleId: string): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}`);
|
||||
}
|
||||
|
||||
export async function createSchedule(input: {
|
||||
schedule_name: string;
|
||||
description?: string | null;
|
||||
trigger_type?: "manual" | "cron" | "api";
|
||||
cron_expression?: string | null;
|
||||
timezone?: string;
|
||||
enabled?: boolean;
|
||||
max_concurrency?: number;
|
||||
failure_policy?: "stop" | "continue";
|
||||
}): Promise<Schedule> {
|
||||
return apiRequest<Schedule>("/api/v1/schedules", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateSchedule(
|
||||
scheduleId: string,
|
||||
input: {
|
||||
workflow_version: number;
|
||||
schedule_name?: string;
|
||||
description?: string | null;
|
||||
trigger_type?: "manual" | "cron" | "api";
|
||||
cron_expression?: string | null;
|
||||
timezone?: string;
|
||||
enabled?: boolean;
|
||||
max_concurrency?: number;
|
||||
failure_policy?: "stop" | "continue";
|
||||
},
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteSchedule(
|
||||
scheduleId: string,
|
||||
workflowVersion: number,
|
||||
): Promise<{ schedule_id: string; deleted: boolean; workflow_version: number }> {
|
||||
return apiRequest(`/api/v1/schedules/${scheduleId}`, {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ workflow_version: workflowVersion }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function listScheduleArtifacts(): Promise<ScheduleArtifact[]> {
|
||||
return apiRequest<ScheduleArtifact[]>("/api/v1/schedule-artifacts");
|
||||
}
|
||||
|
||||
export async function hideScheduleArtifact(
|
||||
versionsId: string,
|
||||
): Promise<{
|
||||
versions_id: string;
|
||||
deleted: boolean;
|
||||
artifact_preserved: boolean;
|
||||
}> {
|
||||
return apiRequest(`/api/v1/versions/${versionsId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
export async function listEmployees(): Promise<Employee[]> {
|
||||
return apiRequest<Employee[]>("/api/v1/admin/employees");
|
||||
}
|
||||
|
||||
export async function createEmployee(input: {
|
||||
username: string;
|
||||
display_name: string;
|
||||
email?: string | null;
|
||||
role_code: "admin" | "developer";
|
||||
}): Promise<Employee> {
|
||||
return apiRequest<Employee>("/api/v1/admin/employees", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateEmployee(
|
||||
userId: string,
|
||||
input: {
|
||||
display_name?: string;
|
||||
email?: string | null;
|
||||
role_code?: "admin" | "developer";
|
||||
status?: "active" | "disabled" | "locked";
|
||||
},
|
||||
): Promise<Employee> {
|
||||
return apiRequest<Employee>(`/api/v1/admin/employees/${userId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteEmployee(
|
||||
userId: string,
|
||||
): Promise<{ user_id: string; deleted: boolean }> {
|
||||
return apiRequest(`/api/v1/admin/employees/${userId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
export async function createScheduleNode(
|
||||
scheduleId: string,
|
||||
input: {
|
||||
workflow_version: number;
|
||||
node_key: string;
|
||||
node_name: string;
|
||||
versions_id: string;
|
||||
timeout_seconds?: number;
|
||||
retry_count?: number;
|
||||
retry_interval_sec?: number;
|
||||
position_x?: number;
|
||||
position_y?: number;
|
||||
arguments_json?: Record<string, unknown>;
|
||||
env_refs_json?: Record<string, string>;
|
||||
},
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}/nodes`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateScheduleNode(
|
||||
scheduleId: string,
|
||||
nodeId: string,
|
||||
input: {
|
||||
workflow_version: number;
|
||||
node_name?: string;
|
||||
versions_id?: string;
|
||||
timeout_seconds?: number;
|
||||
retry_count?: number;
|
||||
retry_interval_sec?: number;
|
||||
position_x?: number;
|
||||
position_y?: number;
|
||||
arguments_json?: Record<string, unknown>;
|
||||
env_refs_json?: Record<string, string>;
|
||||
},
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(
|
||||
`/api/v1/schedules/${scheduleId}/nodes/${nodeId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: JSON.stringify(input),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteScheduleNode(
|
||||
scheduleId: string,
|
||||
nodeId: string,
|
||||
workflowVersion: number,
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(
|
||||
`/api/v1/schedules/${scheduleId}/nodes/${nodeId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ workflow_version: workflowVersion }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function createScheduleEdge(
|
||||
scheduleId: string,
|
||||
input: {
|
||||
workflow_version: number;
|
||||
source_node_id: string;
|
||||
target_node_id: string;
|
||||
condition_expr?: string | null;
|
||||
},
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}/edges`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteScheduleEdge(
|
||||
scheduleId: string,
|
||||
edgeId: string,
|
||||
workflowVersion: number,
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(
|
||||
`/api/v1/schedules/${scheduleId}/edges/${edgeId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ workflow_version: workflowVersion }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function validateSchedule(
|
||||
scheduleId: string,
|
||||
): Promise<DagValidation & {
|
||||
schedule_id: string;
|
||||
workflow_version: number;
|
||||
}> {
|
||||
return apiRequest(`/api/v1/schedules/${scheduleId}/validate`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export async function previewCron(input: {
|
||||
cron_expression: string;
|
||||
timezone: string;
|
||||
count?: number;
|
||||
base_time?: string;
|
||||
}): Promise<CronPreview> {
|
||||
return apiRequest<CronPreview>("/api/v1/cron/preview", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function runScheduleNow(
|
||||
scheduleId: string,
|
||||
): Promise<ScheduleRunDetail> {
|
||||
return apiRequest<ScheduleRunDetail>(
|
||||
`/api/v1/schedules/${scheduleId}/run`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Idempotency-Key": crypto.randomUUID(),
|
||||
},
|
||||
body: JSON.stringify({ reason: "manual_run" }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function listScheduleRuns(input: {
|
||||
scheduleId?: string;
|
||||
status?: ScheduleRunStatus;
|
||||
limit?: number;
|
||||
} = {}): Promise<ScheduleRunSummary[]> {
|
||||
const query = new URLSearchParams();
|
||||
if (input.scheduleId) query.set("schedule_id", input.scheduleId);
|
||||
if (input.status) query.set("status", input.status);
|
||||
query.set("limit", String(input.limit ?? 20));
|
||||
return apiRequest<ScheduleRunSummary[]>(
|
||||
`/api/v1/schedule-runs?${query.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getScheduleRun(
|
||||
runId: string,
|
||||
): Promise<ScheduleRunDetail> {
|
||||
return apiRequest<ScheduleRunDetail>(`/api/v1/schedule-runs/${runId}`);
|
||||
}
|
||||
Reference in New Issue
Block a user