854 lines
21 KiB
TypeScript
854 lines
21 KiB
TypeScript
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}`);
|
||
}
|