1457 lines
38 KiB
TypeScript
1457 lines
38 KiB
TypeScript
export type DemoUser = {
|
||
userId: string;
|
||
userName: string;
|
||
username: string;
|
||
roleCode: "admin" | "developer";
|
||
roleName: string;
|
||
};
|
||
|
||
export type DemoWorkspace = {
|
||
workspaceId: string;
|
||
workspaceName: string;
|
||
};
|
||
|
||
export function createUuid(): string {
|
||
const cryptoApi = globalThis.crypto;
|
||
if (typeof cryptoApi?.randomUUID === "function") {
|
||
return cryptoApi.randomUUID();
|
||
}
|
||
|
||
const bytes = new Uint8Array(16);
|
||
if (typeof cryptoApi?.getRandomValues === "function") {
|
||
cryptoApi.getRandomValues(bytes);
|
||
} else {
|
||
for (let index = 0; index < bytes.length; index += 1) {
|
||
bytes[index] = Math.floor(Math.random() * 256);
|
||
}
|
||
}
|
||
bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;
|
||
bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
|
||
|
||
const hex = Array.from(bytes, (value) =>
|
||
value.toString(16).padStart(2, "0"),
|
||
).join("");
|
||
return [
|
||
hex.slice(0, 8),
|
||
hex.slice(8, 12),
|
||
hex.slice(12, 16),
|
||
hex.slice(16, 20),
|
||
hex.slice(20),
|
||
].join("-");
|
||
}
|
||
|
||
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,
|
||
}));
|
||
}
|
||
}
|
||
|
||
// API client for the platform backend.
|
||
//
|
||
// All endpoints that take a workspace context require the caller to
|
||
// pass `workspaceId` explicitly. Components read the active workspace
|
||
// from `useAuth().currentWorkspace` and thread it through; the cookie
|
||
// set by `/api/v1/auth/login` is sent automatically thanks to
|
||
// `credentials: "same-origin"`, and the backend reads it via the
|
||
// shared `request_context` dependency.
|
||
//
|
||
// 401 from any endpoint means the session has expired or was never
|
||
// established; the global `apiRequest` helper bounces the user to
|
||
// `/login` so the platform never tries to render with a stale identity.
|
||
|
||
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;
|
||
};
|
||
|
||
// Workspace (Project) types - 对应 API.md 第七部分系统管理接口
|
||
export type Workspace = {
|
||
workspace_id: string;
|
||
workspace_code: string;
|
||
workspace_name: string;
|
||
active_root_uri: string;
|
||
quota_bytes: number;
|
||
status: "active" | "archived" | "disabled";
|
||
description: string | null;
|
||
created_by: string;
|
||
created_at: string;
|
||
updated_at: string | null;
|
||
};
|
||
|
||
export type WorkspaceMember = {
|
||
user_id: string;
|
||
username: string;
|
||
display_name: string;
|
||
email: string | null;
|
||
role_code: "admin" | "developer";
|
||
role_name: string;
|
||
member_status: "active" | "disabled" | "locked";
|
||
joined_at: string;
|
||
};
|
||
|
||
export type WorkspaceCreatePayload = {
|
||
workspace_code: string;
|
||
workspace_name: string;
|
||
quota_bytes?: number;
|
||
description?: string;
|
||
};
|
||
|
||
export type WorkspaceUpdatePayload = {
|
||
workspace_name?: string;
|
||
quota_bytes?: number;
|
||
description?: string;
|
||
status?: "active" | "archived";
|
||
};
|
||
|
||
export type WorkspaceMemberAddPayload = {
|
||
user_id: string;
|
||
role_code?: "admin" | "developer";
|
||
};
|
||
|
||
export type WorkspaceMemberUpdatePayload = {
|
||
role_code?: "admin" | "developer";
|
||
member_status?: "active" | "disabled" | "locked";
|
||
};
|
||
|
||
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;
|
||
is_locked: boolean;
|
||
relative_path: string;
|
||
jupyter_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;
|
||
}
|
||
}
|
||
|
||
function appendWorkspaceId(path: string, workspaceId: string): string {
|
||
// `path` may already contain a query string. Use URLSearchParams to
|
||
// merge cleanly either way.
|
||
const separator = path.includes("?") ? "&" : "?";
|
||
return `${path}${separator}workspace_id=${encodeURIComponent(workspaceId)}`;
|
||
}
|
||
|
||
async function apiRequest<T>(
|
||
path: string,
|
||
init: RequestInit = {},
|
||
workspaceId?: string,
|
||
): Promise<T> {
|
||
const finalPath = workspaceId ? appendWorkspaceId(path, workspaceId) : path;
|
||
const response = await fetch(finalPath, {
|
||
...init,
|
||
credentials: "same-origin",
|
||
headers: {
|
||
"X-Request-ID": createUuid().replaceAll("-", ""),
|
||
...(init.body ? { "Content-Type": "application/json" } : {}),
|
||
...init.headers,
|
||
},
|
||
});
|
||
|
||
// Session expired / never authenticated — bounce to login. The
|
||
// /login route itself is the only path that must remain reachable
|
||
// while anonymous, so the redirect there is safe.
|
||
if (response.status === 401 && typeof window !== "undefined") {
|
||
const here = window.location.pathname;
|
||
if (here !== "/login") {
|
||
window.location.assign("/login");
|
||
}
|
||
throw new ApiRequestError("未登录或登录已过期", 401);
|
||
}
|
||
|
||
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(workspaceId: string): Promise<ScriptItem[]> {
|
||
return apiRequest<ScriptItem[]>("/api/v1/scripts", {}, workspaceId);
|
||
}
|
||
|
||
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: [
|
||
{
|
||
id: "intro",
|
||
cell_type: "markdown",
|
||
metadata: {},
|
||
source: ["# 新建模型实验\n", "在这里开始数据探索与模型构建。"],
|
||
},
|
||
{
|
||
id: "main",
|
||
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(
|
||
workspaceId: string,
|
||
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,
|
||
}),
|
||
},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function uploadScript(
|
||
workspaceId: string,
|
||
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,
|
||
},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function setScriptLock(
|
||
workspaceId: string,
|
||
scriptId: string,
|
||
isLocked: boolean,
|
||
): Promise<ScriptItem> {
|
||
return apiRequest<ScriptItem>(
|
||
`/api/v1/scripts/${encodeURIComponent(scriptId)}/lock`,
|
||
{
|
||
method: "PATCH",
|
||
body: JSON.stringify({ is_locked: isLocked }),
|
||
},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function updateScript(
|
||
workspaceId: string,
|
||
scriptId: string,
|
||
input: { content: string },
|
||
): Promise<ScriptItem> {
|
||
return apiRequest<ScriptItem>(
|
||
`/api/v1/scripts/${scriptId}`,
|
||
{ method: "PUT", body: JSON.stringify(input) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function deleteScript(
|
||
workspaceId: string,
|
||
scriptId: string,
|
||
): Promise<{ script_id: string; status: string; versions_preserved: boolean }> {
|
||
return apiRequest(
|
||
`/api/v1/scripts/${scriptId}`,
|
||
{ method: "DELETE" },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function listWorkspaceDirectories(
|
||
workspaceId: string,
|
||
): Promise<WorkspaceDirectory[]> {
|
||
const data = await apiRequest<{ directories: WorkspaceDirectory[] }>(
|
||
"/api/v1/workspace-tree",
|
||
{},
|
||
workspaceId,
|
||
);
|
||
return data.directories;
|
||
}
|
||
|
||
export async function createWorkspaceDirectory(
|
||
workspaceId: string,
|
||
directoryName: string,
|
||
parentPath = "",
|
||
): Promise<WorkspaceDirectory> {
|
||
return apiRequest<WorkspaceDirectory>(
|
||
"/api/v1/workspace-directories",
|
||
{
|
||
method: "POST",
|
||
body: JSON.stringify({
|
||
directory_name: directoryName,
|
||
parent_path: parentPath,
|
||
}),
|
||
},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function deleteWorkspaceDirectory(
|
||
workspaceId: string,
|
||
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" },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
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;
|
||
jupyter_path: 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;
|
||
};
|
||
|
||
// The current backend authorizes Jupyter through the session cookie and
|
||
// deliberately has no persisted file-lock or access-ticket endpoints.
|
||
// Keep the editor's session-shaped UI contract locally while opening the
|
||
// existing authenticated Jupyter proxy directly.
|
||
|
||
export async function acquireFileLock(
|
||
workspaceId: string,
|
||
script: ScriptItem,
|
||
): Promise<ActiveEditSession> {
|
||
const now = Date.now();
|
||
return {
|
||
edit_session_id: createUuid().replaceAll("-", ""),
|
||
workspace_id: workspaceId,
|
||
storage_object_id: script.current_object_id,
|
||
user_id: script.owner_user_id,
|
||
session_status: "active",
|
||
lease_seconds: 3600,
|
||
heartbeat_interval_seconds: 300,
|
||
expires_at: new Date(now + 3600_000).toISOString(),
|
||
runtime_id: workspaceId,
|
||
jupyter_session_id: "unlocked-session",
|
||
relative_path: script.relative_path,
|
||
lock_token: "unlocked-session",
|
||
script_id: script.script_id,
|
||
script_name: script.script_name,
|
||
jupyter_path: script.jupyter_path,
|
||
};
|
||
}
|
||
|
||
export async function heartbeatFileLock(
|
||
_workspaceId: string,
|
||
session: ActiveEditSession,
|
||
): Promise<FileLockSession> {
|
||
return {
|
||
...session,
|
||
expires_at: new Date(Date.now() + 3600_000).toISOString(),
|
||
};
|
||
}
|
||
|
||
export async function releaseFileLock(
|
||
_workspaceId: string,
|
||
session: ActiveEditSession,
|
||
): Promise<FileLockSession> {
|
||
return { ...session, session_status: "closed" };
|
||
}
|
||
|
||
export function releaseFileLockOnUnload(
|
||
_workspaceId: string,
|
||
_session: ActiveEditSession,
|
||
): void {
|
||
// No backend lock is created in compatibility mode.
|
||
}
|
||
|
||
async function waitForJupyterReady(jupyterUrl: string): Promise<void> {
|
||
const retryableStatuses = new Set([502, 503, 504]);
|
||
let lastStatus = 0;
|
||
|
||
for (let attempt = 0; attempt < 10; attempt += 1) {
|
||
const response = await fetch(jupyterUrl, {
|
||
credentials: "same-origin",
|
||
cache: "no-store",
|
||
});
|
||
if (response.ok) return;
|
||
|
||
lastStatus = response.status;
|
||
if (!retryableStatuses.has(response.status)) {
|
||
throw new ApiRequestError(
|
||
`Jupyter 打开失败(HTTP ${response.status})`,
|
||
response.status,
|
||
);
|
||
}
|
||
await new Promise((resolve) => window.setTimeout(resolve, 400));
|
||
}
|
||
|
||
throw new ApiRequestError(
|
||
`Jupyter 服务启动超时${lastStatus ? `(HTTP ${lastStatus})` : ""}`,
|
||
lastStatus || 504,
|
||
);
|
||
}
|
||
|
||
export async function createJupyterAccessTicket(
|
||
workspaceId: string,
|
||
session: ActiveEditSession,
|
||
): Promise<JupyterAccessTicket> {
|
||
const editorRoute = session.script_name.toLowerCase().endsWith(".ipynb")
|
||
? "notebooks"
|
||
: "edit";
|
||
const encodedPath = session.jupyter_path
|
||
.split("/")
|
||
.filter(Boolean)
|
||
.map(encodeURIComponent)
|
||
.join("/");
|
||
if (!encodedPath) {
|
||
throw new Error("脚本缺少 Jupyter 存储路径");
|
||
}
|
||
|
||
const jupyterUrl = `/jupyter/${encodeURIComponent(workspaceId)}/${editorRoute}/${encodedPath}`;
|
||
await waitForJupyterReady(jupyterUrl);
|
||
return {
|
||
edit_session_id: session.edit_session_id,
|
||
jupyter_url: jupyterUrl,
|
||
expires_at: new Date(Date.now() + 3600_000).toISOString(),
|
||
};
|
||
}
|
||
|
||
export type LatestVersion = {
|
||
versions_id: string;
|
||
version_label: string;
|
||
};
|
||
|
||
export async function getLatestScriptVersion(
|
||
workspaceId: string,
|
||
scriptId: string,
|
||
): Promise<LatestVersion | null> {
|
||
return apiRequest<LatestVersion | null>(
|
||
`/api/v1/scripts/${scriptId}/latest-version`,
|
||
{},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function publishScriptVersion(
|
||
workspaceId: string,
|
||
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,
|
||
}),
|
||
},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
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 PythonVersion = "3.8" | "3.10" | "3.12";
|
||
|
||
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>;
|
||
python_version: PythonVersion;
|
||
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 type ScheduleRunArtifact = {
|
||
url: string;
|
||
file_name: string;
|
||
mime_type: string | null;
|
||
size_bytes: number;
|
||
};
|
||
|
||
export type ScheduleNodeRunArtifacts = {
|
||
run_id: string;
|
||
node_run_id: string;
|
||
log: ScheduleRunArtifact | null;
|
||
result: ScheduleRunArtifact | null;
|
||
};
|
||
|
||
export async function listSchedules(workspaceId: string): Promise<Schedule[]> {
|
||
return apiRequest<Schedule[]>("/api/v1/schedules", {}, workspaceId);
|
||
}
|
||
|
||
export async function getSchedule(
|
||
workspaceId: string,
|
||
scheduleId: string,
|
||
): Promise<Schedule> {
|
||
return apiRequest<Schedule>(
|
||
`/api/v1/schedules/${scheduleId}`,
|
||
{},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function createSchedule(
|
||
workspaceId: string,
|
||
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) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function updateSchedule(
|
||
workspaceId: string,
|
||
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) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function deleteSchedule(
|
||
workspaceId: string,
|
||
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 }) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function listScheduleArtifacts(
|
||
workspaceId: string,
|
||
): Promise<ScheduleArtifact[]> {
|
||
return apiRequest<ScheduleArtifact[]>(
|
||
"/api/v1/schedule-artifacts",
|
||
{},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function hideScheduleArtifact(
|
||
workspaceId: string,
|
||
versionsId: string,
|
||
): Promise<{
|
||
versions_id: string;
|
||
deleted: boolean;
|
||
artifact_preserved: boolean;
|
||
}> {
|
||
return apiRequest(
|
||
`/api/v1/versions/${versionsId}`,
|
||
{ method: "DELETE" },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
// 系统管理级别接口 - 不区分 workspace
|
||
export async function listPlatformEmployees(): Promise<Employee[]> {
|
||
return apiRequest<Employee[]>("/api/v1/platform/employees");
|
||
}
|
||
|
||
export async function createPlatformEmployee(
|
||
input: {
|
||
username: string;
|
||
display_name: string;
|
||
email?: string | undefined;
|
||
password: string;
|
||
role_code?: "admin" | "developer";
|
||
},
|
||
): Promise<Employee> {
|
||
return apiRequest<Employee>(
|
||
"/api/v1/platform/employees",
|
||
{ method: "POST", body: JSON.stringify({ ...input, role_code: input.role_code ?? "developer" }) },
|
||
);
|
||
}
|
||
|
||
export async function updatePlatformEmployee(
|
||
userId: string,
|
||
input: {
|
||
display_name?: string;
|
||
email?: string | null;
|
||
role_code?: "admin" | "developer";
|
||
status?: "active" | "disabled" | "locked";
|
||
},
|
||
): Promise<Employee> {
|
||
return apiRequest<Employee>(
|
||
`/api/v1/platform/employees/${userId}`,
|
||
{ method: "PATCH", body: JSON.stringify(input) },
|
||
);
|
||
}
|
||
|
||
export async function deletePlatformEmployee(
|
||
userId: string,
|
||
): Promise<{ user_id: string; deleted: boolean }> {
|
||
return apiRequest(
|
||
`/api/v1/platform/employees/${userId}`,
|
||
{ method: "DELETE" },
|
||
);
|
||
}
|
||
|
||
// 兼容旧的 workspace 级别接口(已废弃,建议使用 system-level 接口)
|
||
/** @deprecated 使用 listPlatformEmployees 代替 */
|
||
export async function listEmployees(workspaceId: string): Promise<Employee[]> {
|
||
return apiRequest<Employee[]>("/api/v1/admin/employees", {}, workspaceId);
|
||
}
|
||
|
||
/** @deprecated 使用 createPlatformEmployee 代替 */
|
||
export async function createEmployee(
|
||
workspaceId: string,
|
||
input: {
|
||
username: string;
|
||
display_name: string;
|
||
email?: string | null;
|
||
role_code: "admin" | "developer";
|
||
password: string;
|
||
},
|
||
): Promise<Employee> {
|
||
return apiRequest<Employee>(
|
||
"/api/v1/admin/employees",
|
||
{ method: "POST", body: JSON.stringify(input) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
/** @deprecated 使用 updatePlatformEmployee 代替 */
|
||
export async function updateEmployee(
|
||
workspaceId: string,
|
||
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) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
/** @deprecated 使用 deletePlatformEmployee 代替 */
|
||
export async function deleteEmployee(
|
||
workspaceId: string,
|
||
userId: string,
|
||
): Promise<{ user_id: string; deleted: boolean }> {
|
||
return apiRequest(
|
||
`/api/v1/admin/employees/${userId}`,
|
||
{ method: "DELETE" },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
// ----------------------------------------------------------------------------
|
||
// Workspace (Project) Management APIs - 对应 API.md 第七部分系统管理接口
|
||
// ----------------------------------------------------------------------------
|
||
|
||
export async function listWorkspaces(): Promise<Workspace[]> {
|
||
return apiRequest<Workspace[]>("/api/v1/platform/workspaces");
|
||
}
|
||
|
||
export async function createWorkspace(
|
||
input: WorkspaceCreatePayload,
|
||
): Promise<Workspace> {
|
||
return apiRequest<Workspace>(
|
||
"/api/v1/platform/workspaces",
|
||
{ method: "POST", body: JSON.stringify(input) },
|
||
);
|
||
}
|
||
|
||
export async function getWorkspace(workspaceId: string): Promise<Workspace> {
|
||
return apiRequest<Workspace>(`/api/v1/platform/workspaces/${workspaceId}`);
|
||
}
|
||
|
||
export async function updateWorkspace(
|
||
workspaceId: string,
|
||
input: WorkspaceUpdatePayload,
|
||
): Promise<Workspace> {
|
||
return apiRequest<Workspace>(
|
||
`/api/v1/platform/workspaces/${workspaceId}`,
|
||
{ method: "PATCH", body: JSON.stringify(input) },
|
||
);
|
||
}
|
||
|
||
export async function deleteWorkspace(workspaceId: string): Promise<{
|
||
workspace_id: string;
|
||
deleted: boolean;
|
||
}> {
|
||
return apiRequest(
|
||
`/api/v1/platform/workspaces/${workspaceId}`,
|
||
{ method: "DELETE" },
|
||
);
|
||
}
|
||
|
||
export async function listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMember[]> {
|
||
return apiRequest<WorkspaceMember[]>(
|
||
`/api/v1/platform/workspaces/${workspaceId}/members`,
|
||
);
|
||
}
|
||
|
||
export async function addWorkspaceMember(
|
||
workspaceId: string,
|
||
input: WorkspaceMemberAddPayload,
|
||
): Promise<WorkspaceMember> {
|
||
return apiRequest<WorkspaceMember>(
|
||
`/api/v1/platform/workspaces/${workspaceId}/members`,
|
||
{ method: "POST", body: JSON.stringify(input) },
|
||
);
|
||
}
|
||
|
||
export async function updateWorkspaceMember(
|
||
workspaceId: string,
|
||
userId: string,
|
||
input: WorkspaceMemberUpdatePayload,
|
||
): Promise<WorkspaceMember> {
|
||
return apiRequest<WorkspaceMember>(
|
||
`/api/v1/platform/workspaces/${workspaceId}/members/${userId}`,
|
||
{ method: "PATCH", body: JSON.stringify(input) },
|
||
);
|
||
}
|
||
|
||
export async function deleteWorkspaceMember(
|
||
workspaceId: string,
|
||
userId: string,
|
||
): Promise<{ user_id: string; deleted: boolean }> {
|
||
return apiRequest(
|
||
`/api/v1/platform/workspaces/${workspaceId}/members/${userId}`,
|
||
{ method: "DELETE" },
|
||
);
|
||
}
|
||
|
||
export async function createScheduleNode(
|
||
workspaceId: string,
|
||
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>;
|
||
python_version?: PythonVersion;
|
||
},
|
||
): Promise<Schedule> {
|
||
return apiRequest<Schedule>(
|
||
`/api/v1/schedules/${scheduleId}/nodes`,
|
||
{ method: "POST", body: JSON.stringify(input) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function updateScheduleNode(
|
||
workspaceId: string,
|
||
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>;
|
||
python_version?: PythonVersion;
|
||
},
|
||
): Promise<Schedule> {
|
||
return apiRequest<Schedule>(
|
||
`/api/v1/schedules/${scheduleId}/nodes/${nodeId}`,
|
||
{ method: "PUT", body: JSON.stringify(input) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function deleteScheduleNode(
|
||
workspaceId: string,
|
||
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 }) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function createScheduleEdge(
|
||
workspaceId: string,
|
||
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) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function deleteScheduleEdge(
|
||
workspaceId: string,
|
||
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 }) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function validateSchedule(
|
||
workspaceId: string,
|
||
scheduleId: string,
|
||
): Promise<DagValidation & {
|
||
schedule_id: string;
|
||
workflow_version: number;
|
||
}> {
|
||
return apiRequest(
|
||
`/api/v1/schedules/${scheduleId}/validate`,
|
||
{ method: "POST" },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function previewCron(
|
||
workspaceId: string,
|
||
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) },
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function runScheduleNow(
|
||
workspaceId: string,
|
||
scheduleId: string,
|
||
): Promise<ScheduleRunDetail> {
|
||
return apiRequest<ScheduleRunDetail>(
|
||
`/api/v1/schedules/${scheduleId}/run`,
|
||
{
|
||
method: "POST",
|
||
headers: {
|
||
"Idempotency-Key": createUuid(),
|
||
},
|
||
body: JSON.stringify({ reason: "manual_run" }),
|
||
},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function listScheduleRuns(
|
||
workspaceId: string,
|
||
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()}`,
|
||
{},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function getScheduleRun(
|
||
workspaceId: string,
|
||
runId: string,
|
||
): Promise<ScheduleRunDetail> {
|
||
return apiRequest<ScheduleRunDetail>(
|
||
`/api/v1/schedule-runs/${runId}`,
|
||
{},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
export async function getScheduleNodeRunArtifacts(
|
||
workspaceId: string,
|
||
runId: string,
|
||
nodeRunId: string,
|
||
): Promise<ScheduleNodeRunArtifacts> {
|
||
return apiRequest<ScheduleNodeRunArtifacts>(
|
||
`/api/v1/schedule-runs/${runId}/node-runs/${nodeRunId}/artifacts`,
|
||
{},
|
||
workspaceId,
|
||
);
|
||
}
|
||
|
||
// ----------------------------------------------------------------------------
|
||
// Workspace-bound API surface.
|
||
//
|
||
// `useApi()` in ~/context/AuthContext returns an object where every
|
||
// function has had its first `workspaceId` argument pre-filled. The
|
||
// type below lets consumers import the bound type without depending
|
||
// on the raw functions. Keep this last in the file so the type
|
||
// references all the exports above.
|
||
// ----------------------------------------------------------------------------
|
||
export type WorkspaceBoundApi = {
|
||
listScripts: () => Promise<ScriptItem[]>;
|
||
createScript: (
|
||
input: Parameters<typeof createScript>[1],
|
||
) => Promise<ScriptItem>;
|
||
uploadScript: (
|
||
file: File,
|
||
parentPath?: string,
|
||
visibility?: Visibility,
|
||
) => Promise<ScriptItem>;
|
||
updateScript: (
|
||
scriptId: string,
|
||
input: Parameters<typeof updateScript>[2],
|
||
) => Promise<ScriptItem>;
|
||
deleteScript: (
|
||
scriptId: string,
|
||
) => Promise<{ script_id: string; status: string; versions_preserved: boolean }>;
|
||
setScriptLock: (
|
||
scriptId: string,
|
||
isLocked: boolean,
|
||
) => Promise<ScriptItem>;
|
||
listWorkspaceDirectories: () => Promise<WorkspaceDirectory[]>;
|
||
createWorkspaceDirectory: (
|
||
directoryName: string,
|
||
parentPath?: string,
|
||
) => Promise<WorkspaceDirectory>;
|
||
deleteWorkspaceDirectory: (
|
||
path: string,
|
||
) => Promise<{
|
||
path: string;
|
||
status: string;
|
||
deleted_scripts: number;
|
||
versions_preserved: boolean;
|
||
}>;
|
||
acquireFileLock: (
|
||
script: ScriptItem,
|
||
) => Promise<ActiveEditSession>;
|
||
heartbeatFileLock: (
|
||
session: ActiveEditSession,
|
||
) => Promise<FileLockSession>;
|
||
releaseFileLock: (
|
||
session: ActiveEditSession,
|
||
) => Promise<FileLockSession>;
|
||
releaseFileLockOnUnload: (session: ActiveEditSession) => void;
|
||
createJupyterAccessTicket: (
|
||
session: ActiveEditSession,
|
||
) => Promise<JupyterAccessTicket>;
|
||
getLatestScriptVersion: (scriptId: string) => Promise<LatestVersion | null>;
|
||
publishScriptVersion: (
|
||
input: Parameters<typeof publishScriptVersion>[1],
|
||
) => Promise<StableVersion>;
|
||
listSchedules: () => Promise<Schedule[]>;
|
||
getSchedule: (scheduleId: string) => Promise<Schedule>;
|
||
createSchedule: (
|
||
input: Parameters<typeof createSchedule>[1],
|
||
) => Promise<Schedule>;
|
||
updateSchedule: (
|
||
scheduleId: string,
|
||
input: Parameters<typeof updateSchedule>[2],
|
||
) => Promise<Schedule>;
|
||
deleteSchedule: (
|
||
scheduleId: string,
|
||
workflowVersion: number,
|
||
) => Promise<{ schedule_id: string; deleted: boolean; workflow_version: number }>;
|
||
listScheduleArtifacts: () => Promise<ScheduleArtifact[]>;
|
||
hideScheduleArtifact: (
|
||
versionsId: string,
|
||
) => Promise<{
|
||
versions_id: string;
|
||
deleted: boolean;
|
||
artifact_preserved: boolean;
|
||
}>;
|
||
listEmployees: () => Promise<Employee[]>;
|
||
listPlatformEmployees: () => Promise<Employee[]>;
|
||
createEmployee: (
|
||
input: Parameters<typeof createEmployee>[1],
|
||
) => Promise<Employee>;
|
||
createPlatformEmployee: (
|
||
input: {
|
||
username: string;
|
||
display_name: string;
|
||
email?: string | undefined;
|
||
password: string;
|
||
role_code?: "admin" | "developer";
|
||
},
|
||
) => Promise<Employee>;
|
||
updateEmployee: (
|
||
userId: string,
|
||
input: Parameters<typeof updateEmployee>[2],
|
||
) => Promise<Employee>;
|
||
updatePlatformEmployee: (
|
||
userId: string,
|
||
input: {
|
||
display_name?: string;
|
||
email?: string | null;
|
||
role_code?: "admin" | "developer";
|
||
status?: "active" | "disabled" | "locked";
|
||
},
|
||
) => Promise<Employee>;
|
||
deleteEmployee: (
|
||
userId: string,
|
||
) => Promise<{ user_id: string; deleted: boolean }>;
|
||
deletePlatformEmployee: (
|
||
userId: string,
|
||
) => Promise<{ user_id: string; deleted: boolean }>;
|
||
createScheduleNode: (
|
||
scheduleId: string,
|
||
input: Parameters<typeof createScheduleNode>[2],
|
||
) => Promise<Schedule>;
|
||
updateScheduleNode: (
|
||
scheduleId: string,
|
||
nodeId: string,
|
||
input: Parameters<typeof updateScheduleNode>[3],
|
||
) => Promise<Schedule>;
|
||
deleteScheduleNode: (
|
||
scheduleId: string,
|
||
nodeId: string,
|
||
workflowVersion: number,
|
||
) => Promise<Schedule>;
|
||
createScheduleEdge: (
|
||
scheduleId: string,
|
||
input: Parameters<typeof createScheduleEdge>[2],
|
||
) => Promise<Schedule>;
|
||
deleteScheduleEdge: (
|
||
scheduleId: string,
|
||
edgeId: string,
|
||
workflowVersion: number,
|
||
) => Promise<Schedule>;
|
||
validateSchedule: (
|
||
scheduleId: string,
|
||
) => Promise<DagValidation & { schedule_id: string; workflow_version: number }>;
|
||
previewCron: (
|
||
input: Parameters<typeof previewCron>[1],
|
||
) => Promise<CronPreview>;
|
||
runScheduleNow: (scheduleId: string) => Promise<ScheduleRunDetail>;
|
||
listScheduleRuns: (
|
||
input?: Parameters<typeof listScheduleRuns>[1],
|
||
) => Promise<ScheduleRunSummary[]>;
|
||
getScheduleRun: (runId: string) => Promise<ScheduleRunDetail>;
|
||
getScheduleNodeRunArtifacts: (
|
||
runId: string,
|
||
nodeRunId: string,
|
||
) => Promise<ScheduleNodeRunArtifacts>;
|
||
// Workspace (Project) Management - 系统管理接口
|
||
listWorkspaces: () => Promise<Workspace[]>;
|
||
createWorkspace: (input: WorkspaceCreatePayload) => Promise<Workspace>;
|
||
updateWorkspace: (
|
||
workspaceId: string,
|
||
input: WorkspaceUpdatePayload,
|
||
) => Promise<Workspace>;
|
||
deleteWorkspace: (workspaceId: string) => Promise<{ workspace_id: string; deleted: boolean }>;
|
||
listWorkspaceMembers: (workspaceId: string) => Promise<WorkspaceMember[]>;
|
||
addWorkspaceMember: (
|
||
workspaceId: string,
|
||
input: WorkspaceMemberAddPayload,
|
||
) => Promise<WorkspaceMember>;
|
||
updateWorkspaceMember: (
|
||
workspaceId: string,
|
||
userId: string,
|
||
input: WorkspaceMemberUpdatePayload,
|
||
) => Promise<WorkspaceMember>;
|
||
deleteWorkspaceMember: (
|
||
workspaceId: string,
|
||
userId: string,
|
||
) => Promise<{ user_id: string; deleted: boolean }>;
|
||
};
|