mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-28 00:11:54 +00:00
- Implemented client-side pagination in `dashboard/index.html` (20 items per page). - Added pagination controls and CSS utilities in `dashboard/styles.css`. - Fixed `src/dashboard.mjs` to serve `.mjs` files with `text/javascript` MIME type. - Verified `npx torch-lock init` functionality (no changes needed to landing page). Co-authored-by: PR0M3TH3AN <[email protected]>
94 lines
3.2 KiB
JavaScript
94 lines
3.2 KiB
JavaScript
import fs from 'node:fs';
|
|
import fsp from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import http from 'node:http';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { DEFAULT_DASHBOARD_PORT } from './constants.mjs';
|
|
|
|
export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT) {
|
|
// Resolve package root relative to this file (src/dashboard.mjs)
|
|
// this file is in <root>/src/dashboard.mjs, so '..' goes to <root>
|
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
const server = http.createServer(async (req, res) => {
|
|
// URL parsing
|
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
let pathname = url.pathname;
|
|
|
|
// Redirect / to /dashboard/
|
|
if (pathname === '/' || pathname === '/dashboard') {
|
|
res.writeHead(302, { 'Location': '/dashboard/' });
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
async function statSafe(p) {
|
|
try {
|
|
return await fsp.stat(p);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Special case: /torch-config.json
|
|
// Priority: User's CWD > Package default
|
|
if (pathname === '/torch-config.json') {
|
|
const userConfigPath = path.resolve(process.cwd(), 'torch-config.json');
|
|
if (await statSafe(userConfigPath)) {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
fs.createReadStream(userConfigPath).pipe(res);
|
|
return;
|
|
}
|
|
// If not found in CWD, fall through to serve from packageRoot (if it exists there)
|
|
// or return empty object if missing?
|
|
// Falling through means it looks for packageRoot/torch-config.json
|
|
}
|
|
|
|
// Security check: prevent directory traversal
|
|
const safePath = path.normalize(pathname).replace(new RegExp('^(\\.\\.[\\/\\\\])+'), '');
|
|
let filePath = path.join(packageRoot, safePath);
|
|
|
|
// If directory, try index.html
|
|
let fileStat = await statSafe(filePath);
|
|
if (fileStat && fileStat.isDirectory()) {
|
|
filePath = path.join(filePath, 'index.html');
|
|
fileStat = await statSafe(filePath);
|
|
}
|
|
|
|
// Check if file exists and is a file
|
|
if (!fileStat || !fileStat.isFile()) {
|
|
res.writeHead(404);
|
|
res.end('Not Found');
|
|
return;
|
|
}
|
|
|
|
// MIME types
|
|
const extname = path.extname(filePath);
|
|
let contentType = 'text/plain';
|
|
switch (extname) {
|
|
case '.html': contentType = 'text/html'; break;
|
|
case '.js': contentType = 'text/javascript'; break;
|
|
case '.mjs': contentType = 'text/javascript'; break;
|
|
case '.css': contentType = 'text/css'; break;
|
|
case '.json': contentType = 'application/json'; break;
|
|
case '.png': contentType = 'image/png'; break;
|
|
case '.jpg': contentType = 'image/jpeg'; break;
|
|
case '.svg': contentType = 'image/svg+xml'; break;
|
|
case '.ico': contentType = 'image/x-icon'; break;
|
|
case '.md': contentType = 'text/markdown'; break;
|
|
}
|
|
|
|
res.writeHead(200, { 'Content-Type': contentType });
|
|
fs.createReadStream(filePath).pipe(res);
|
|
});
|
|
|
|
server.listen(port, () => {
|
|
console.log(`Dashboard running at http://localhost:${port}/dashboard/`);
|
|
console.log(`Serving files from ${packageRoot}`);
|
|
console.log(`Using configuration from ${process.cwd()}`);
|
|
});
|
|
|
|
// Keep process alive
|
|
return new Promise(() => {});
|
|
}
|