241 lines
5.2 KiB
TypeScript
241 lines
5.2 KiB
TypeScript
import { type ActionFunctionArgs, data, redirect } from 'react-router';
|
|
import type { LoadContext } from '~/server';
|
|
import { Capabilities } from '~/server/web/roles';
|
|
import { Machine } from '~/types';
|
|
|
|
export async function machineAction({
|
|
request,
|
|
context,
|
|
}: ActionFunctionArgs<LoadContext>) {
|
|
const session = await context.sessions.auth(request);
|
|
const check = await context.sessions.check(
|
|
request,
|
|
Capabilities.write_machines,
|
|
);
|
|
|
|
const formData = await request.formData();
|
|
const apiKey = session.get('api_key')!;
|
|
|
|
const action = formData.get('action_id')?.toString();
|
|
if (!action) {
|
|
throw data('Missing `action_id` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
// Fast track register since it doesn't require an existing machine
|
|
if (action === 'register') {
|
|
if (!check) {
|
|
throw data('You do not have permission to manage machines', {
|
|
status: 403,
|
|
});
|
|
}
|
|
|
|
return registerMachine(formData, apiKey, context);
|
|
}
|
|
|
|
// Check if the user has permission to manage this machine
|
|
const nodeId = formData.get('node_id')?.toString();
|
|
if (!nodeId) {
|
|
throw data('Missing `node_id` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const { nodes } = await context.client.get<{ nodes: Machine[] }>(
|
|
'v1/node',
|
|
apiKey,
|
|
);
|
|
|
|
const node = nodes.find((node) => node.id === nodeId);
|
|
if (!node) {
|
|
throw data(`Machine with ID ${nodeId} not found`, {
|
|
status: 404,
|
|
});
|
|
}
|
|
|
|
if (
|
|
node.user.providerId?.split('/').pop() !== session.get('user')!.subject &&
|
|
!check
|
|
) {
|
|
throw data('You do not have permission to act on this machine', {
|
|
status: 403,
|
|
});
|
|
}
|
|
|
|
switch (action) {
|
|
case 'rename': {
|
|
return renameMachine(formData, apiKey, nodeId, context);
|
|
}
|
|
|
|
case 'delete': {
|
|
return deleteMachine(apiKey, nodeId, context);
|
|
}
|
|
|
|
case 'expire': {
|
|
return expireMachine(apiKey, nodeId, context);
|
|
}
|
|
|
|
case 'update_tags': {
|
|
return updateTags(formData, apiKey, nodeId, context);
|
|
}
|
|
|
|
case 'update_routes': {
|
|
return updateRoutes(formData, apiKey, nodeId, context);
|
|
}
|
|
|
|
case 'reassign': {
|
|
return reassignMachine(formData, apiKey, nodeId, context);
|
|
}
|
|
|
|
default:
|
|
throw data('Invalid action', {
|
|
status: 400,
|
|
});
|
|
}
|
|
}
|
|
|
|
async function registerMachine(
|
|
formData: FormData,
|
|
apiKey: string,
|
|
context: LoadContext,
|
|
) {
|
|
const registrationKey = formData.get('register_key')?.toString();
|
|
if (!registrationKey) {
|
|
throw data('Missing `register_key` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const user = formData.get('user')?.toString();
|
|
if (!user) {
|
|
throw data('Missing `user` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const qp = new URLSearchParams();
|
|
qp.append('user', user);
|
|
qp.append('key', registrationKey);
|
|
const url = `v1/node/register?${qp.toString()}`;
|
|
const { node } = await context.client.post<{ node: Machine }>(url, apiKey, {
|
|
user,
|
|
key: registrationKey,
|
|
});
|
|
|
|
return redirect(`/machines/${node.id}`);
|
|
}
|
|
|
|
async function renameMachine(
|
|
formData: FormData,
|
|
apiKey: string,
|
|
nodeId: string,
|
|
context: LoadContext,
|
|
) {
|
|
const newName = formData.get('name')?.toString();
|
|
if (!newName) {
|
|
throw data('Missing `name` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const name = String(formData.get('name'));
|
|
await context.client.post(`v1/node/${nodeId}/rename/${name}`, apiKey);
|
|
return { message: 'Machine renamed' };
|
|
}
|
|
|
|
async function deleteMachine(
|
|
apiKey: string,
|
|
nodeId: string,
|
|
context: LoadContext,
|
|
) {
|
|
await context.client.delete(`v1/node/${nodeId}`, apiKey);
|
|
return redirect('/machines');
|
|
}
|
|
|
|
async function expireMachine(
|
|
apiKey: string,
|
|
nodeId: string,
|
|
context: LoadContext,
|
|
) {
|
|
await context.client.post(`v1/node/${nodeId}/expire`, apiKey);
|
|
return { message: 'Machine expired' };
|
|
}
|
|
|
|
async function updateTags(
|
|
formData: FormData,
|
|
apiKey: string,
|
|
nodeId: string,
|
|
context: LoadContext,
|
|
) {
|
|
const tags = formData.get('tags')?.toString().split(',') ?? [];
|
|
if (tags.length === 0) {
|
|
throw data('Missing `tags` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
await context.client.post(`v1/node/${nodeId}/tags`, apiKey, {
|
|
tags: tags.map((tag) => tag.trim()).filter((tag) => tag !== ''),
|
|
});
|
|
|
|
return { message: 'Tags updated' };
|
|
}
|
|
|
|
async function updateRoutes(
|
|
formData: FormData,
|
|
apiKey: string,
|
|
nodeId: string,
|
|
context: LoadContext,
|
|
) {
|
|
const routes = formData.get('routes')?.toString();
|
|
if (!routes) {
|
|
throw data('Missing `routes` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const allRoutes = routes.split(',').map((route) => route.trim());
|
|
if (allRoutes.length === 0) {
|
|
throw data('No routes provided to update', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const enabled = formData.get('enabled')?.toString();
|
|
if (enabled === undefined) {
|
|
throw data('Missing `enabled` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const postfix = enabled === 'true' ? 'enable' : 'disable';
|
|
await Promise.all(
|
|
allRoutes.map(async (route) => {
|
|
await context.client.post(`v1/routes/${route}/${postfix}`, apiKey);
|
|
}),
|
|
);
|
|
|
|
return { message: 'Routes updated' };
|
|
}
|
|
|
|
async function reassignMachine(
|
|
formData: FormData,
|
|
apiKey: string,
|
|
nodeId: string,
|
|
context: LoadContext,
|
|
) {
|
|
const user = formData.get('user')?.toString();
|
|
if (!user) {
|
|
throw data('Missing `user` in the form data.', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
await context.client.post(`v1/node/${nodeId}/user`, apiKey, {
|
|
user,
|
|
});
|
|
|
|
return { message: 'Machine reassigned' };
|
|
}
|