const modulename = 'WebServer:Resources'; import path from 'node:path'; import slash from 'slash'; import slug from 'slug'; import consoleFactory from '@lib/console'; import { SYM_SYSTEM_AUTHOR } from '@lib/symbols'; const console = consoleFactory(modulename); //Helper functions const isUndefined = (x) => (x === undefined); const breakPath = (inPath) => slash(path.normalize(inPath)).split('/').filter(String); const dynamicSort = (prop) => { let sortOrder = 1; if (prop[0] === '-') { sortOrder = -1; prop = prop.substr(1); } return function (a, b) { const result = (a[prop] < b[prop]) ? -1 : (a[prop] > b[prop]) ? 1 : 0; return result * sortOrder; }; }; const getResourceSubPath = (resPath) => { if (resPath.indexOf('system_resources') >= 0) return 'system_resources'; if (!path.isAbsolute(resPath)) return resPath; let serverDataPathArr = breakPath(`${txConfig.server.dataPath}/resources`); let resPathArr = breakPath(resPath); for (let i = 0; i < serverDataPathArr.length; i++) { if (isUndefined(resPathArr[i])) break; if (serverDataPathArr[i].toLowerCase() == resPathArr[i].toLowerCase()) { delete resPathArr[i]; } } resPathArr.pop(); resPathArr = resPathArr.filter(String); if (resPathArr.length) { return resPathArr.join('/'); } else { return 'root'; } }; /** * Returns the resources list * @param {object} ctx */ export default async function Resources(ctx) { if (!txCore.fxRunner.child?.isAlive) { return ctx.utils.render('main/message', { message: 'The resources list is only available when the server is running.', }); } const timeoutMessage = `Couldn't load the resources list.
- Make sure the server is online (try to join it).
- Make sure you don't have more than 1000 resources.
- Make sure you are not running the FXServer outside of txAdmin.
- Check the Live Console for any errors which may indicate that some resource has a malformed fxmanifest.lua file.`; //Send command request const cmdSuccess = txCore.fxRunner.sendCommand('txaReportResources', [], SYM_SYSTEM_AUTHOR); if (!cmdSuccess) { return ctx.utils.render('main/message', {message: timeoutMessage}); } //Timer fot list delivery let tListTimer; let tErrorTimer; const tList = new Promise((resolve, reject) => { tListTimer = setInterval(() => { if ( txCore.fxResources.resourceReport && (new Date() - txCore.fxResources.resourceReport.ts) <= 1000 && Array.isArray(txCore.fxResources.resourceReport.resources) ) { clearTimeout(tListTimer); clearTimeout(tErrorTimer); const resGroups = processResources(txCore.fxResources.resourceReport.resources); const renderData = { headerTitle: 'Resources', resGroupsJS: JSON.stringify(resGroups), resGroups, disableActions: (ctx.admin.hasPermission('commands.resources')) ? '' : 'disabled', }; resolve(['main/resources', renderData]); } }, 100); }); //Timer for timing out const tError = new Promise((resolve, reject) => { tErrorTimer = setTimeout(() => { clearTimeout(tListTimer); resolve(['main/message', {message: timeoutMessage}]); }, 1000); }); //Start race and give output const [view, renderData] = await Promise.race([tList, tError]); return ctx.utils.render(view, renderData); }; //================================================================ /** * Returns the Processed Resource list. * @param {array} resList */ function processResources(resList) { //Clean resource data and add it so an object separated by subpaths const resGroupList = {}; resList.forEach((resource) => { if (isUndefined(resource.name) || isUndefined(resource.status) || isUndefined(resource.path) || resource.path === '') { return; } const subPath = getResourceSubPath(resource.path); const resData = { name: resource.name, divName: slug(resource.name), status: resource.status, statusClass: (resource.status === 'started') ? 'success' : 'danger', // path: slash(path.normalize(resource.path)), version: (resource.version) ? `(${resource.version.trim()})` : '', author: (resource.author) ? `${resource.author.trim()}` : '', description: (resource.description) ? resource.description.trim() : '', }; if (resGroupList.hasOwnProperty(subPath)) { resGroupList[subPath].push(resData); } else { resGroupList[subPath] = [resData]; } }); //Generate final array with subpaths and div ids const finalList = []; Object.keys(resGroupList).forEach((subPath) => { const subPathData = { subPath: subPath, divName: slug(subPath), resources: resGroupList[subPath].sort(dynamicSort('name')), }; finalList.push(subPathData); }); // finalList = JSON.stringify(finalList, null, 2) // console.log(finalList) // return [] return finalList; }