117 lines
3.8 KiB
JavaScript
117 lines
3.8 KiB
JavaScript
const fs = require('fs').promises;
|
|
const pathModule = require('path');
|
|
const { logDebug, logError } = require('../../../infrastructure/logger');
|
|
|
|
const ADMIN_FRONTEND_DIR = pathModule.join(__dirname, '..', '..', 'admin-frontend');
|
|
const TAILWIND_CSS = pathModule.join(__dirname, '..', '..', '..', 'css', 'tailwind.css');
|
|
|
|
const MIME_TYPES = {
|
|
'.css': 'text/css; charset=utf-8',
|
|
'.js': 'text/javascript; charset=utf-8',
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.svg': 'image/svg+xml',
|
|
'.ico': 'image/x-icon',
|
|
'.png': 'image/png',
|
|
'.json': 'application/json',
|
|
'.webmanifest': 'application/manifest+json'
|
|
};
|
|
|
|
function isPathInsideDir(candidate, baseDir) {
|
|
const resolvedBase = pathModule.resolve(baseDir);
|
|
const resolvedCandidate = pathModule.resolve(candidate);
|
|
return resolvedCandidate === resolvedBase
|
|
|| resolvedCandidate.startsWith(resolvedBase + pathModule.sep);
|
|
}
|
|
|
|
async function serveFile(filePath, res) {
|
|
const ext = pathModule.extname(filePath).toLowerCase();
|
|
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
const body = await fs.readFile(filePath);
|
|
res.writeHead(200, { 'Content-Type': contentType });
|
|
res.end(body);
|
|
}
|
|
|
|
async function serveAdminFrontendAsset(urlPath, res) {
|
|
if (!/^\/(?:ui\/[\w.-]+\.js|branding\/[\w.-]+\.png|[\w.-]+\.(?:css|js|svg|ico|png|json|webmanifest))$/.test(urlPath)) {
|
|
return false;
|
|
}
|
|
|
|
const relativePath = urlPath.replace(/^\//, '');
|
|
const filePath = pathModule.join(ADMIN_FRONTEND_DIR, relativePath);
|
|
if (!isPathInsideDir(filePath, ADMIN_FRONTEND_DIR)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
await serveFile(filePath, res);
|
|
return true;
|
|
} catch (err) {
|
|
if (err.code === 'ENOENT') {
|
|
return false;
|
|
}
|
|
logError('Admin', `Failed to serve ${urlPath}: ${err.message}`);
|
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
res.end('Failed to load asset');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
async function handleStaticRoutes(req, res) {
|
|
const urlPath = req.urlPath || new URL(req.url, `https://${req.headers.host}`).pathname;
|
|
const method = req.method;
|
|
|
|
const tabs = ['domains', 'host', 'local-dns', 'entries', 'peers', 'certs', 'interfaces', 'logs', 'settings', 'stats', 'backups', 'plugins'];
|
|
if (method === 'GET' && urlPath.startsWith('/') && tabs.includes(urlPath.substring(1))) {
|
|
res.writeHead(302, { 'Location': `/#${urlPath.substring(1)}` });
|
|
res.end();
|
|
return true;
|
|
}
|
|
|
|
if (method === 'GET' && urlPath === '/') {
|
|
logDebug('Admin', 'Serving admin panel HTML');
|
|
try {
|
|
await serveFile(pathModule.join(ADMIN_FRONTEND_DIR, 'index.html'), res);
|
|
} catch (err) {
|
|
logError('Admin', `Failed to serve index.html: ${err.message}`);
|
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
res.end('Failed to load admin panel');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (method === 'GET' && urlPath === '/tailwind.css') {
|
|
try {
|
|
await serveFile(TAILWIND_CSS, res);
|
|
} catch (err) {
|
|
logError('Admin', `Failed to serve tailwind.css: ${err.message}`);
|
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
res.end('Failed to load Tailwind CSS');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (method === 'GET' && (urlPath === '/favicon.ico' || urlPath === '/apple-touch-icon.png')) {
|
|
const faviconFile = urlPath === '/favicon.ico'
|
|
? pathModule.join(ADMIN_FRONTEND_DIR, 'favicon.ico')
|
|
: pathModule.join(ADMIN_FRONTEND_DIR, 'branding', 'p2ns-favicon-192.png');
|
|
try {
|
|
await serveFile(faviconFile, res);
|
|
} catch (err) {
|
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
res.end('Not Found');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (method === 'GET') {
|
|
const served = await serveAdminFrontendAsset(urlPath, res);
|
|
if (served) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
module.exports = { handleStaticRoutes };
|