const modulename = 'WebServer:FXServerCommands'; import { AuthedCtx } from '@modules/WebServer/ctxTypes'; import consoleFactory from '@lib/console'; import { ApiToastResp } from '@shared/genericApiTypes'; import { txEnv } from '@core/globalData'; const console = consoleFactory(modulename); //Helper functions const delay = async (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; /** * Handle all the server commands * @param {object} ctx */ export default async function FXServerCommands(ctx: AuthedCtx) { if ( typeof ctx.request.body.action === 'undefined' || typeof ctx.request.body.parameter === 'undefined' ) { return ctx.send({ type: 'error', msg: 'Invalid request.', }); } const action = ctx.request.body.action; const parameter = ctx.request.body.parameter; //Ignore commands when the server is offline if (!txCore.fxRunner.child?.isAlive) { return ctx.send({ type: 'error', msg: 'The server is not running.', }); } //Block starting/restarting the 'runcode' resource const unsafeActions = ['restart_res', 'start_res', 'ensure_res']; if (unsafeActions.includes(action) && parameter.includes('runcode')) { return ctx.send({ type: 'error', msg: 'The resource "runcode" might be unsafe.
If you know what you are doing, run it via the Live Console.', }); } //============================================== //DEBUG: Only available in the /advanced page //FIXME: move to the advanced route, give button for profiling, saving mem snapshot, verbose, etc. if (action == 'profile_monitor') { if (!ensurePermission(ctx, 'all_permissions')) return false; ctx.admin.logAction('Profiling txAdmin instance.'); const profileDuration = 5; const savePath = `${txEnv.profilePath}/data/txProfile.bin`; ExecuteCommand('profiler record start'); await delay(profileDuration * 1000); ExecuteCommand('profiler record stop'); await delay(150); ExecuteCommand(`profiler save "${savePath}"`); await delay(150); console.ok(`Profile saved to: ${savePath}`); txCore.fxRunner.sendCommand('profiler', ['view', savePath], ctx.admin.name); return ctx.send({ type: 'success', msg: 'Check your live console in a few seconds.', }); //============================================== } else if (action == 'admin_broadcast') { if (!ensurePermission(ctx, 'announcement')) return false; const message = (parameter ?? '').trim(); // Dispatch `txAdmin:events:announcement` txCore.fxRunner.sendEvent('announcement', { message, author: ctx.admin.name, }); ctx.admin.logAction(`Sending announcement: ${parameter}`); // Sending discord announcement const publicAuthor = txCore.adminStore.getAdminPublicName(ctx.admin.name, 'message'); txCore.discordBot.sendAnnouncement({ type: 'info', title: { key: 'nui_menu.misc.announcement_title', data: { author: publicAuthor } }, description: message }); return ctx.send({ type: 'success', msg: 'Announcement command sent.', }); //============================================== } else if (action == 'kick_all') { if (!ensurePermission(ctx, 'control.server')) return false; const kickReason = (parameter ?? '').trim() || txCore.translator.t('kick_messages.unknown_reason'); const dropMessage = txCore.translator.t( 'kick_messages.everyone', { reason: kickReason } ); ctx.admin.logAction(`Kicking all players: ${kickReason}`); // Dispatch `txAdmin:events:playerKicked` txCore.fxRunner.sendEvent('playerKicked', { target: -1, author: ctx.admin.name, reason: kickReason, dropMessage, }); return ctx.send({ type: 'success', msg: 'Kick All command sent.', }); //============================================== } else if (action == 'restart_res') { if (!ensurePermission(ctx, 'commands.resources')) return false; ctx.admin.logAction(`Restarted resource "${parameter}"`); txCore.fxRunner.sendCommand('restart', [parameter], ctx.admin.name); return ctx.send({ type: 'warning', msg: 'Resource restart command sent.', }); //============================================== } else if (action == 'start_res') { if (!ensurePermission(ctx, 'commands.resources')) return false; ctx.admin.logAction(`Started resource "${parameter}"`); txCore.fxRunner.sendCommand('start', [parameter], ctx.admin.name); return ctx.send({ type: 'warning', msg: 'Resource start command sent.', }); //============================================== } else if (action == 'ensure_res') { if (!ensurePermission(ctx, 'commands.resources')) return false; ctx.admin.logAction(`Ensured resource "${parameter}"`); txCore.fxRunner.sendCommand('ensure', [parameter], ctx.admin.name); return ctx.send({ type: 'warning', msg: 'Resource ensure command sent.', }); //============================================== } else if (action == 'stop_res') { if (!ensurePermission(ctx, 'commands.resources')) return false; ctx.admin.logAction(`Stopped resource "${parameter}"`); txCore.fxRunner.sendCommand('stop', [parameter], ctx.admin.name); return ctx.send({ type: 'warning', msg: 'Resource stop command sent.', }); //============================================== } else if (action == 'refresh_res') { if (!ensurePermission(ctx, 'commands.resources')) return false; ctx.admin.logAction(`Refreshed resources`); txCore.fxRunner.sendCommand('refresh', [], ctx.admin.name); return ctx.send({ type: 'warning', msg: 'Refresh command sent.', }); //============================================== } else { return ctx.send({ type: 'error', msg: 'Unknown Action.', }); } }; //================================================================ /** * Wrapper function to check permission and give output if denied */ function ensurePermission(ctx: AuthedCtx, perm: string) { if (ctx.admin.testPermission(perm, modulename)) { return true; } else { ctx.send({ type: 'error', msg: 'You don\'t have permission to execute this action.', }); return false; } }