chore: init lms adm fe
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
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[];
|
||||
};
|
||||
|
||||
const LMS_API_BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:4400/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 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>;
|
||||
}
|
||||
|
||||
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})`;
|
||||
}
|
||||
Reference in New Issue
Block a user