feat: auth
This commit is contained in:
+423
-215
@@ -1,67 +1,15 @@
|
||||
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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
// 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";
|
||||
@@ -132,22 +80,40 @@ export class ApiRequestError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
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 response = await fetch(path, {
|
||||
const finalPath = workspaceId ? appendWorkspaceId(path, workspaceId) : path;
|
||||
const response = await fetch(finalPath, {
|
||||
...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,
|
||||
},
|
||||
});
|
||||
|
||||
// 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;
|
||||
@@ -171,8 +137,8 @@ async function apiRequest<T>(
|
||||
return (payload as ApiEnvelope<T>).data;
|
||||
}
|
||||
|
||||
export async function listScripts(): Promise<ScriptItem[]> {
|
||||
return apiRequest<ScriptItem[]>("/api/v1/scripts");
|
||||
export async function listScripts(workspaceId: string): Promise<ScriptItem[]> {
|
||||
return apiRequest<ScriptItem[]>("/api/v1/scripts", {}, workspaceId);
|
||||
}
|
||||
|
||||
function initialContent(scriptType: ScriptType): string {
|
||||
@@ -197,14 +163,14 @@ function initialContent(scriptType: ScriptType): string {
|
||||
{
|
||||
cell_type: "markdown",
|
||||
metadata: {},
|
||||
source: ["# 新建模型实验\\n", "在这里开始数据探索与模型构建。"],
|
||||
source: ["# 新建模型实验\n", "在这里开始数据探索与模型构建。"],
|
||||
},
|
||||
{
|
||||
cell_type: "code",
|
||||
execution_count: null,
|
||||
metadata: {},
|
||||
outputs: [],
|
||||
source: ["print('Hello, Model Platform!')\\n"],
|
||||
source: ["print('Hello, Model Platform!')\n"],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
@@ -226,25 +192,33 @@ function initialContent(scriptType: ScriptType): string {
|
||||
);
|
||||
}
|
||||
|
||||
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 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",
|
||||
@@ -261,38 +235,64 @@ export async function uploadScript(
|
||||
headers: { "Content-Type": "application/octet-stream" },
|
||||
body: file,
|
||||
},
|
||||
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" });
|
||||
return apiRequest(
|
||||
`/api/v1/scripts/${scriptId}`,
|
||||
{ method: "DELETE" },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function listWorkspaceDirectories(): Promise<
|
||||
WorkspaceDirectory[]
|
||||
> {
|
||||
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,
|
||||
}),
|
||||
});
|
||||
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;
|
||||
@@ -301,9 +301,11 @@ export async function deleteWorkspaceDirectory(
|
||||
versions_preserved: boolean;
|
||||
}> {
|
||||
const parameters = new URLSearchParams({ path });
|
||||
return apiRequest(`/api/v1/workspace-directories?${parameters.toString()}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
return apiRequest(
|
||||
`/api/v1/workspace-directories?${parameters.toString()}`,
|
||||
{ method: "DELETE" },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export type FileLockSession = {
|
||||
@@ -353,12 +355,20 @@ export type StableVersion = {
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
// Note: the file-lock and jupyter-ticket endpoints are not yet
|
||||
// implemented in the backend (see the cookie+JWT auth refactor plan).
|
||||
// They are retained here so the editor UI keeps its existing call
|
||||
// sites, but they will return 404 until the backend ships the
|
||||
// corresponding routes.
|
||||
|
||||
export async function acquireFileLock(
|
||||
workspaceId: string,
|
||||
script: ScriptItem,
|
||||
): Promise<ActiveEditSession> {
|
||||
const session = await apiRequest<FileLockSession>(
|
||||
`/api/v1/files/${script.current_object_id}/lock`,
|
||||
{ method: "POST" },
|
||||
workspaceId,
|
||||
);
|
||||
if (!session.lock_token) {
|
||||
throw new Error("加锁成功响应缺少 lock_token");
|
||||
@@ -372,6 +382,7 @@ export async function acquireFileLock(
|
||||
}
|
||||
|
||||
export async function heartbeatFileLock(
|
||||
workspaceId: string,
|
||||
session: ActiveEditSession,
|
||||
): Promise<FileLockSession> {
|
||||
return apiRequest<FileLockSession>(
|
||||
@@ -380,10 +391,12 @@ export async function heartbeatFileLock(
|
||||
method: "POST",
|
||||
body: JSON.stringify({ lock_token: session.lock_token }),
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function releaseFileLock(
|
||||
workspaceId: string,
|
||||
session: ActiveEditSession,
|
||||
): Promise<FileLockSession> {
|
||||
return apiRequest<FileLockSession>(
|
||||
@@ -392,47 +405,64 @@ export async function releaseFileLock(
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ lock_token: session.lock_token }),
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
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("-", ""),
|
||||
export function releaseFileLockOnUnload(
|
||||
workspaceId: string,
|
||||
session: ActiveEditSession,
|
||||
): void {
|
||||
void fetch(
|
||||
`/api/v1/file-locks/${session.edit_session_id}?workspace_id=${
|
||||
encodeURIComponent(workspaceId)
|
||||
}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
keepalive: true,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ lock_token: session.lock_token }),
|
||||
},
|
||||
body: JSON.stringify({ lock_token: session.lock_token }),
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
export async function createJupyterAccessTicket(
|
||||
workspaceId: string,
|
||||
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,
|
||||
}),
|
||||
});
|
||||
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,
|
||||
}),
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function listScriptVersions(
|
||||
workspaceId: string,
|
||||
scriptId: string,
|
||||
): Promise<StableVersion[]> {
|
||||
return apiRequest<StableVersion[]>(`/api/v1/scripts/${scriptId}/versions`);
|
||||
return apiRequest<StableVersion[]>(
|
||||
`/api/v1/scripts/${scriptId}/versions`,
|
||||
{},
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function publishScriptVersion(input: {
|
||||
script: ScriptItem;
|
||||
releaseNote: string;
|
||||
visibility: Visibility;
|
||||
}): Promise<StableVersion> {
|
||||
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`,
|
||||
{
|
||||
@@ -443,6 +473,7 @@ export async function publishScriptVersion(input: {
|
||||
visibility: input.visibility,
|
||||
}),
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -595,31 +626,43 @@ export type ScheduleRunDetail = ScheduleRunSummary & {
|
||||
node_runs: ScheduleNodeRun[];
|
||||
};
|
||||
|
||||
export async function listSchedules(): Promise<Schedule[]> {
|
||||
return apiRequest<Schedule[]>("/api/v1/schedules");
|
||||
export async function listSchedules(workspaceId: string): Promise<Schedule[]> {
|
||||
return apiRequest<Schedule[]>("/api/v1/schedules", {}, workspaceId);
|
||||
}
|
||||
|
||||
export async function getSchedule(scheduleId: string): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}`);
|
||||
export async function getSchedule(
|
||||
workspaceId: string,
|
||||
scheduleId: string,
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(
|
||||
`/api/v1/schedules/${scheduleId}`,
|
||||
{},
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
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 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;
|
||||
@@ -633,55 +676,72 @@ export async function updateSchedule(
|
||||
failure_policy?: "stop" | "continue";
|
||||
},
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
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 }),
|
||||
});
|
||||
return apiRequest(
|
||||
`/api/v1/schedules/${scheduleId}`,
|
||||
{ method: "DELETE", body: JSON.stringify({ workflow_version: workflowVersion }) },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function listScheduleArtifacts(): Promise<ScheduleArtifact[]> {
|
||||
return apiRequest<ScheduleArtifact[]>("/api/v1/schedule-artifacts");
|
||||
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",
|
||||
});
|
||||
return apiRequest(
|
||||
`/api/v1/versions/${versionsId}`,
|
||||
{ method: "DELETE" },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function listEmployees(): Promise<Employee[]> {
|
||||
return apiRequest<Employee[]>("/api/v1/admin/employees");
|
||||
export async function listEmployees(workspaceId: string): Promise<Employee[]> {
|
||||
return apiRequest<Employee[]>("/api/v1/admin/employees", {}, workspaceId);
|
||||
}
|
||||
|
||||
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 createEmployee(
|
||||
workspaceId: string,
|
||||
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) },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateEmployee(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
input: {
|
||||
display_name?: string;
|
||||
@@ -690,21 +750,26 @@ export async function updateEmployee(
|
||||
status?: "active" | "disabled" | "locked";
|
||||
},
|
||||
): Promise<Employee> {
|
||||
return apiRequest<Employee>(`/api/v1/admin/employees/${userId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
return apiRequest<Employee>(
|
||||
`/api/v1/admin/employees/${userId}`,
|
||||
{ method: "PATCH", body: JSON.stringify(input) },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteEmployee(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
): Promise<{ user_id: string; deleted: boolean }> {
|
||||
return apiRequest(`/api/v1/admin/employees/${userId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
return apiRequest(
|
||||
`/api/v1/admin/employees/${userId}`,
|
||||
{ method: "DELETE" },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function createScheduleNode(
|
||||
workspaceId: string,
|
||||
scheduleId: string,
|
||||
input: {
|
||||
workflow_version: number;
|
||||
@@ -720,13 +785,15 @@ export async function createScheduleNode(
|
||||
env_refs_json?: Record<string, string>;
|
||||
},
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}/nodes`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
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: {
|
||||
@@ -744,28 +811,26 @@ export async function updateScheduleNode(
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(
|
||||
`/api/v1/schedules/${scheduleId}/nodes/${nodeId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: JSON.stringify(input),
|
||||
},
|
||||
{ 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 }),
|
||||
},
|
||||
{ method: "DELETE", body: JSON.stringify({ workflow_version: workflowVersion }) },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function createScheduleEdge(
|
||||
workspaceId: string,
|
||||
scheduleId: string,
|
||||
input: {
|
||||
workflow_version: number;
|
||||
@@ -774,50 +839,58 @@ export async function createScheduleEdge(
|
||||
condition_expr?: string | null;
|
||||
},
|
||||
): Promise<Schedule> {
|
||||
return apiRequest<Schedule>(`/api/v1/schedules/${scheduleId}/edges`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
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 }),
|
||||
},
|
||||
{ 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",
|
||||
});
|
||||
return apiRequest(
|
||||
`/api/v1/schedules/${scheduleId}/validate`,
|
||||
{ method: "POST" },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
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 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>(
|
||||
@@ -829,25 +902,160 @@ export async function runScheduleNow(
|
||||
},
|
||||
body: JSON.stringify({ reason: "manual_run" }),
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function listScheduleRuns(input: {
|
||||
scheduleId?: string;
|
||||
status?: ScheduleRunStatus;
|
||||
limit?: number;
|
||||
} = {}): Promise<ScheduleRunSummary[]> {
|
||||
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}`);
|
||||
return apiRequest<ScheduleRunDetail>(
|
||||
`/api/v1/schedule-runs/${runId}`,
|
||||
{},
|
||||
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>[1],
|
||||
) => Promise<ScriptItem>;
|
||||
deleteScript: (
|
||||
scriptId: string,
|
||||
) => Promise<{ script_id: string; status: string; versions_preserved: boolean }>;
|
||||
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>;
|
||||
listScriptVersions: (scriptId: string) => Promise<StableVersion[]>;
|
||||
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[]>;
|
||||
createEmployee: (
|
||||
input: Parameters<typeof createEmployee>[1],
|
||||
) => Promise<Employee>;
|
||||
updateEmployee: (
|
||||
userId: string,
|
||||
input: Parameters<typeof updateEmployee>[2],
|
||||
) => Promise<Employee>;
|
||||
deleteEmployee: (
|
||||
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>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user