feat: rework the machine actions
this also fixes the registration regression introduced in 0.5.8
This commit is contained in:
parent
6bc20bd0e6
commit
d1f395acfd
9 changed files with 215 additions and 222 deletions
|
|
@ -1,11 +1,8 @@
|
|||
import type { ActionFunctionArgs } from 'react-router';
|
||||
import { type ActionFunctionArgs, data, redirect } from 'react-router';
|
||||
import type { LoadContext } from '~/server';
|
||||
import { Capabilities } from '~/server/web/roles';
|
||||
import { Machine } from '~/types';
|
||||
import log from '~/utils/log';
|
||||
import { data400, data403, data404, send } from '~/utils/res';
|
||||
|
||||
// TODO: Clean this up like dns-actions and user-actions
|
||||
export async function machineAction({
|
||||
request,
|
||||
context,
|
||||
|
|
@ -16,14 +13,33 @@ export async function machineAction({
|
|||
Capabilities.write_machines,
|
||||
);
|
||||
|
||||
const apiKey = session.get('api_key')!;
|
||||
const formData = await request.formData();
|
||||
const apiKey = session.get('api_key')!;
|
||||
|
||||
// TODO: Rename this to 'action_id' and 'node_id'
|
||||
const action = formData.get('_method')?.toString();
|
||||
const nodeId = formData.get('id')?.toString();
|
||||
if (!action || !nodeId) {
|
||||
return data400('Missing required parameters: _method and id');
|
||||
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[] }>(
|
||||
|
|
@ -33,215 +49,192 @@ export async function machineAction({
|
|||
|
||||
const node = nodes.find((node) => node.id === nodeId);
|
||||
if (!node) {
|
||||
return data404(`Node with ID ${nodeId} not found`);
|
||||
throw data(`Machine with ID ${nodeId} not found`, {
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const subject = session.get('user')!.subject;
|
||||
if (node.user.providerId?.split('/').pop() !== subject) {
|
||||
if (!check) {
|
||||
return data403('You do not have permission to act on this machine');
|
||||
}
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Split up into methods
|
||||
switch (action) {
|
||||
case 'rename': {
|
||||
return renameMachine(formData, apiKey, nodeId, context);
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
await context.client.delete(`v1/node/${nodeId}`, session.get('api_key')!);
|
||||
return { message: 'Machine removed' };
|
||||
return deleteMachine(apiKey, nodeId, context);
|
||||
}
|
||||
|
||||
case 'expire': {
|
||||
await context.client.post(
|
||||
`v1/node/${nodeId}/expire`,
|
||||
session.get('api_key')!,
|
||||
);
|
||||
return { message: 'Machine expired' };
|
||||
return expireMachine(apiKey, nodeId, context);
|
||||
}
|
||||
|
||||
case 'rename': {
|
||||
if (!formData.has('name')) {
|
||||
return send(
|
||||
{ message: 'No name provided' },
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const name = String(formData.get('name'));
|
||||
await context.client.post(
|
||||
`v1/node/${nodeId}/rename/${name}`,
|
||||
session.get('api_key')!,
|
||||
);
|
||||
return { message: 'Machine renamed' };
|
||||
case 'update_tags': {
|
||||
return updateTags(formData, apiKey, nodeId, context);
|
||||
}
|
||||
|
||||
case 'routes': {
|
||||
if (!formData.has('route') || !formData.has('enabled')) {
|
||||
return send(
|
||||
{ message: 'No route or enabled provided' },
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const route = String(formData.get('route'));
|
||||
const enabled = formData.get('enabled') === 'true';
|
||||
const postfix = enabled ? 'enable' : 'disable';
|
||||
|
||||
await context.client.post(
|
||||
`v1/routes/${route}/${postfix}`,
|
||||
session.get('api_key')!,
|
||||
);
|
||||
return { message: 'Route updated' };
|
||||
case 'update_routes': {
|
||||
return updateRoutes(formData, apiKey, nodeId, context);
|
||||
}
|
||||
|
||||
case 'exit-node': {
|
||||
if (!formData.has('routes') || !formData.has('enabled')) {
|
||||
return send(
|
||||
{ message: 'No route or enabled provided' },
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const routes = formData.get('routes')?.toString().split(',') ?? [];
|
||||
const enabled = formData.get('enabled') === 'true';
|
||||
const postfix = enabled ? 'enable' : 'disable';
|
||||
|
||||
await Promise.all(
|
||||
routes.map(async (route) => {
|
||||
await context.client.post(
|
||||
`v1/routes/${route}/${postfix}`,
|
||||
session.get('api_key')!,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
return { message: 'Exit node updated' };
|
||||
case 'reassign': {
|
||||
return reassignMachine(formData, apiKey, nodeId, context);
|
||||
}
|
||||
|
||||
case 'move': {
|
||||
if (!formData.has('to')) {
|
||||
return send(
|
||||
{ message: 'No destination provided' },
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const to = String(formData.get('to'));
|
||||
|
||||
try {
|
||||
await context.client.post(
|
||||
`v1/node/${nodeId}/user`,
|
||||
session.get('api_key')!,
|
||||
{
|
||||
user: to,
|
||||
},
|
||||
);
|
||||
|
||||
return { message: `Moved node ${nodeId} to ${to}` };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return send(
|
||||
{ message: `Failed to move node ${nodeId} to ${to}` },
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
case 'tags': {
|
||||
const tags =
|
||||
formData
|
||||
.get('tags')
|
||||
?.toString()
|
||||
.split(',')
|
||||
.filter((tag) => tag.trim() !== '') ?? [];
|
||||
|
||||
try {
|
||||
await context.client.post(
|
||||
`v1/node/${nodeId}/tags`,
|
||||
session.get('api_key')!,
|
||||
{
|
||||
tags,
|
||||
},
|
||||
);
|
||||
|
||||
return { message: 'Tags updated' };
|
||||
} catch (error) {
|
||||
log.debug('api', 'Failed to update tags: %s', error);
|
||||
return send(
|
||||
{ message: 'Failed to update tags' },
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
case 'register': {
|
||||
const key = formData.get('mkey')?.toString();
|
||||
const user = formData.get('user')?.toString();
|
||||
|
||||
if (!key) {
|
||||
return send(
|
||||
{ message: 'No machine key provided' },
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return send(
|
||||
{ message: 'No user provided' },
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const qp = new URLSearchParams();
|
||||
qp.append('user', user);
|
||||
qp.append('key', key);
|
||||
|
||||
const url = `v1/node/register?${qp.toString()}`;
|
||||
await context.client.post(url, session.get('api_key')!, {
|
||||
user,
|
||||
key,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Machine registered',
|
||||
};
|
||||
} catch {
|
||||
return send(
|
||||
{
|
||||
success: false,
|
||||
message: 'Failed to register machine',
|
||||
},
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
return send(
|
||||
{ message: 'Invalid method' },
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
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' };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue