Files
lms-adm-fe/src/lib/platforms.ts
T

295 lines
9.2 KiB
TypeScript

export type InstructorPlatform = {
id: string;
masterPlatformId: string;
name: string;
type: string;
status: string;
battery: number;
uptime: string;
speed: string;
errors: number;
group: string | null;
user: string | null;
emergencyStop: boolean;
vehicleType: string;
wheelbaseMeters: number;
maxSpeedMetersPerSecond: number;
maxSteeringAngleDegrees: number;
sensors: PlatformSettingItem[];
drivers: PlatformSettingItem[];
algorithms: PlatformAlgorithms;
controlDurationMinutes: number;
controlWarningMinutes: number;
agent: InstructorPlatformAgentBinding | null;
};
export type InstructorPlatformAgentBinding = {
nodeId: string | null;
hostname: string | null;
ownerEmail: string | null;
ip: string | null;
connected: boolean;
lastSeenAt: string | null;
};
export type PlatformInUseSession = {
id: string;
holderUserId: string;
holderName: string | null;
holderEmail: string | null;
startedAt: string;
endsAt: string;
};
export class PlatformInUseError extends Error {
activeSession: PlatformInUseSession;
constructor(message: string, activeSession: PlatformInUseSession) {
super(message);
this.name = 'PlatformInUseError';
this.activeSession = activeSession;
}
}
export type TeachingAgent = {
id: string;
hostname: string;
name: string;
user: string;
tags: string[];
ip: string;
sshUser?: string | null;
workspaceRoot?: string | null;
connected: boolean;
expired: boolean;
lastSeenAt: string | null;
};
export type PlatformSettingItem = {
title: string;
description: string;
enabled?: boolean;
installed?: boolean;
};
export type PlatformAlgorithms = {
slam: string;
planning: string;
control: string;
};
export type MasterPlatform = {
id: string;
name: string;
description: string;
iconKey: string;
};
export type InstructorPlatformsSummary = {
totalPlatforms: number;
activePlatforms: number;
standbyPlatforms: number;
errorPlatforms: number;
};
export type InstructorPlatformsResponse = {
summary: InstructorPlatformsSummary;
platforms: InstructorPlatform[];
masterPlatforms: MasterPlatform[];
agents: TeachingAgent[];
};
export type InstructorPlatformRegistrationResponse = {
code: string;
expiresAt: string | null;
};
const LMS_API_BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? 'https://workspace.autonomystudio.net/lms-api/api').replace(/\/+$/, '');
export async function listInstructorPlatforms(accessToken: string): Promise<InstructorPlatformsResponse> {
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`플랫폼 현황을 불러오지 못했습니다. (${response.status})`);
}
return response.json() as Promise<InstructorPlatformsResponse>;
}
export async function listInstructorPlatformAgents(accessToken: string): Promise<{ agents: TeachingAgent[] }> {
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms/agents`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`에이전트 목록을 불러오지 못했습니다. (${response.status})`);
}
return response.json() as Promise<{ agents: TeachingAgent[] }>;
}
export async function createInstructorPlatform(
accessToken: string,
platformId: string,
displayName?: string,
agent?: TeachingAgent | null,
ssh?: { user?: string; port?: string },
): Promise<InstructorPlatformsResponse> {
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'content-type': 'application/json',
},
body: JSON.stringify({
platformId,
displayName,
agentNodeId: agent?.id,
agentHostname: agent?.hostname || agent?.name,
agentOwnerEmail: agent?.user,
sshUser: ssh?.user ?? agent?.sshUser ?? undefined,
sshPort: ssh?.port,
workspaceRoot: agent?.workspaceRoot ?? undefined,
}),
});
if (!response.ok) throw new Error(await platformErrorMessage(response, '플랫폼을 등록하지 못했습니다.'));
return response.json() as Promise<InstructorPlatformsResponse>;
}
export async function issueInstructorPlatformRegistration(
accessToken: string,
): Promise<InstructorPlatformRegistrationResponse> {
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platform-registration-code`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'content-type': 'application/json',
},
});
if (!response.ok) throw new Error(await platformErrorMessage(response, '등록 키를 발급하지 못했습니다.'));
return response.json() as Promise<InstructorPlatformRegistrationResponse>;
}
export async function updateInstructorPlatform(
accessToken: string,
platformId: string,
displayName: string,
vehicleType: string,
wheelbaseMeters: number,
maxSpeedMetersPerSecond: number,
maxSteeringAngleDegrees: number,
sensors: PlatformSettingItem[],
algorithms: PlatformAlgorithms,
controlDurationMinutes: number,
controlWarningMinutes: number,
): Promise<InstructorPlatformsResponse> {
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms/${platformId}`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'content-type': 'application/json',
},
body: JSON.stringify({
displayName,
vehicleType,
wheelbaseMeters,
maxSpeedMetersPerSecond,
maxSteeringAngleDegrees,
sensors,
algorithms,
controlDurationMinutes,
controlWarningMinutes,
}),
});
if (!response.ok) throw new Error(await platformErrorMessage(response, '플랫폼 설정을 저장하지 못했습니다.'));
return response.json() as Promise<InstructorPlatformsResponse>;
}
export async function deleteInstructorPlatform(
accessToken: string,
platformId: string,
options?: { force?: boolean },
): Promise<InstructorPlatformsResponse> {
const query = options?.force ? '?force=true' : '';
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms/${platformId}${query}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const payload = await response.json().catch((): null => null);
if (response.status === 409 && payload && typeof payload === 'object' && (payload as { code?: unknown }).code === 'PLATFORM_IN_USE') {
const activeSession = (payload as { activeSession?: PlatformInUseSession }).activeSession;
if (activeSession) {
throw new PlatformInUseError(String((payload as { message?: unknown }).message ?? '현재 학생이 플랫폼을 사용 중입니다.'), activeSession);
}
}
throw new Error(platformErrorMessageFromPayload(response, payload, '플랫폼을 삭제하지 못했습니다.'));
}
return response.json() as Promise<InstructorPlatformsResponse>;
}
export async function assignInstructorPlatform(
accessToken: string,
platformId: string,
groupId: string,
options?: { force?: boolean },
): Promise<InstructorPlatformsResponse> {
const query = options?.force ? '?force=true' : '';
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms/${platformId}/assignment${query}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'content-type': 'application/json',
},
body: JSON.stringify({ groupId }),
});
if (!response.ok) {
const payload = await response.json().catch((): null => null);
if (response.status === 409 && payload && typeof payload === 'object' && (payload as { code?: unknown }).code === 'PLATFORM_IN_USE') {
const activeSession = (payload as { activeSession?: PlatformInUseSession }).activeSession;
if (activeSession) {
throw new PlatformInUseError(String((payload as { message?: unknown }).message ?? '현재 학생이 플랫폼을 사용 중입니다.'), activeSession);
}
}
throw new Error(platformErrorMessageFromPayload(response, payload, '플랫폼을 배정하지 못했습니다.'));
}
return response.json() as Promise<InstructorPlatformsResponse>;
}
export async function syncInstructorPlatformAccess(accessToken: string): Promise<{ ok: boolean; assignmentCount: number; syncedAt: string }> {
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms/access-sync`, {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!response.ok) {
throw new Error(await platformErrorMessage(response, '플랫폼 접근 설정을 동기화하지 못했습니다.'));
}
return response.json() as Promise<{ ok: boolean; assignmentCount: number; syncedAt: string }>;
}
async function platformErrorMessage(response: Response, fallback: string) {
const payload = await response.json().catch((): null => null);
return platformErrorMessageFromPayload(response, payload, fallback);
}
function platformErrorMessageFromPayload(response: Response, payload: unknown, fallback: string) {
if (payload && typeof payload === 'object' && 'message' in payload) {
return String((payload as { message?: unknown }).message);
}
return `${fallback} (${response.status})`;
}