Rounds out the per-instance file bridge to the agent's full jailed file
manager so a real file browser can be built on it: POST :id/files/
{delete,rename,mkdir,mkfile,move,copy}, all via requestScoped (license-
scoped reply) on the new agent {op,path} protocol. files.manage. The
broken legacy VueFinder /api/files (retired Go fm_* protocol, wrong
subject, default _INBOX) is superseded by this — frontend rewrite next.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
import { Controller, Post, Get, Put, Body, Param, Query } from '@nestjs/common';
|
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
|
import { CurrentTenant } from '../../common/decorators/current-tenant.decorator';
|
|
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
|
import { InstancesService, LifecycleFunc } from './instances.service';
|
|
|
|
@ApiTags('instances')
|
|
@ApiBearerAuth()
|
|
@Controller('instances')
|
|
export class InstancesController {
|
|
constructor(private readonly instances: InstancesService) {}
|
|
|
|
@Post(':id/lifecycle')
|
|
@RequirePermission('server.manage')
|
|
@ApiOperation({ summary: 'Send a lifecycle command to a game instance (start/stop/restart/status/steam_update)' })
|
|
async lifecycle(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { action: LifecycleFunc },
|
|
) {
|
|
return this.instances.lifecycle(licenseId, id, body.action);
|
|
}
|
|
|
|
@Post(':id/rcon')
|
|
@RequirePermission('server.console')
|
|
@ApiOperation({ summary: 'Send an RCON/console command to a game instance' })
|
|
async rcon(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { command: string },
|
|
) {
|
|
return this.instances.rcon(licenseId, id, body.command);
|
|
}
|
|
|
|
@Get(':id/files')
|
|
@RequirePermission('files.view')
|
|
@ApiOperation({ summary: 'List a directory in the instance (jailed to its root)' })
|
|
async listFiles(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Query('path') path?: string,
|
|
) {
|
|
return this.instances.listFiles(licenseId, id, path ?? '');
|
|
}
|
|
|
|
@Get(':id/file')
|
|
@RequirePermission('files.view')
|
|
@ApiOperation({ summary: 'Read a text file from the instance (jailed, 5 MiB cap)' })
|
|
async readFile(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Query('path') path: string,
|
|
) {
|
|
return this.instances.readFile(licenseId, id, path);
|
|
}
|
|
|
|
@Put(':id/file')
|
|
@RequirePermission('files.manage')
|
|
@ApiOperation({ summary: 'Write a text file in the instance (jailed)' })
|
|
async writeFile(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { path: string; content: string },
|
|
) {
|
|
return this.instances.writeFile(licenseId, id, body.path, body.content ?? '');
|
|
}
|
|
|
|
@Post(':id/files/delete')
|
|
@RequirePermission('files.manage')
|
|
@ApiOperation({ summary: 'Delete a file or directory (jailed)' })
|
|
async deleteFile(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { path: string },
|
|
) {
|
|
return this.instances.deleteFile(licenseId, id, body.path);
|
|
}
|
|
|
|
@Post(':id/files/rename')
|
|
@RequirePermission('files.manage')
|
|
@ApiOperation({ summary: 'Rename a file/directory within its parent (jailed)' })
|
|
async renameFile(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { path: string; name: string },
|
|
) {
|
|
return this.instances.renameFile(licenseId, id, body.path, body.name);
|
|
}
|
|
|
|
@Post(':id/files/mkdir')
|
|
@RequirePermission('files.manage')
|
|
@ApiOperation({ summary: 'Create a directory (jailed)' })
|
|
async mkdir(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { path: string },
|
|
) {
|
|
return this.instances.mkdir(licenseId, id, body.path);
|
|
}
|
|
|
|
@Post(':id/files/mkfile')
|
|
@RequirePermission('files.manage')
|
|
@ApiOperation({ summary: 'Create an empty file (jailed)' })
|
|
async mkfile(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { path: string },
|
|
) {
|
|
return this.instances.mkfile(licenseId, id, body.path);
|
|
}
|
|
|
|
@Post(':id/files/move')
|
|
@RequirePermission('files.manage')
|
|
@ApiOperation({ summary: 'Move a file/directory (jailed)' })
|
|
async moveFile(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { path: string; dest: string },
|
|
) {
|
|
return this.instances.moveFile(licenseId, id, body.path, body.dest);
|
|
}
|
|
|
|
@Post(':id/files/copy')
|
|
@RequirePermission('files.manage')
|
|
@ApiOperation({ summary: 'Copy a file/directory (jailed)' })
|
|
async copyFile(
|
|
@CurrentTenant() licenseId: string,
|
|
@Param('id') id: string,
|
|
@Body() body: { path: string; dest: string },
|
|
) {
|
|
return this.instances.copyFile(licenseId, id, body.path, body.dest);
|
|
}
|
|
}
|