// Two-step EJS rendering: views/pages/.ejs -> `body` -> views/layout.ejs. // No express-ejs-layouts; the vendored layout is a wrapper file that expects `body`. const fs = require('fs'); const path = require('path'); const ejs = require('ejs'); const { navLinks } = require('./nav'); const ROOT = path.join(__dirname, '..'); const VIEWS_DIR = path.join(ROOT, 'views'); const PAGES_DIR = path.join(VIEWS_DIR, 'pages'); const LAYOUT = path.join(VIEWS_DIR, 'layout.ejs'); const STUB = path.join(PAGES_DIR, '_stub.ejs'); const CACHE = process.env.NODE_ENV === 'production'; // Cache-busting token: -, computed once. const assetVersion = (function computeAssetVersion() { let version = '0.0.0'; try { version = require(path.join(ROOT, 'package.json')).version || '0.0.0'; } catch (_) { /* keep default */ } let sha = ''; try { const raw = fs.readFileSync(path.join(ROOT, 'public/web_template/VERSION'), 'utf8').trim(); sha = (raw.split(/\s+/)[1] || '').trim(); } catch (_) { /* VERSION not vendored yet */ } return sha ? `${version}-${sha}` : version; })(); const APP_NAME = process.env.APP_NAME || 'PULSE'; const APP_SUBTITLE = process.env.APP_SUBTITLE || 'Worker Orchestration // LotusGuild'; function viewPath(view) { const file = path.join(PAGES_DIR, `${view}.ejs`); return fs.existsSync(file) ? file : STUB; } // Render a page view inside the shared layout. async function renderPage(req, res, view, locals = {}) { const data = Object.assign({ user: req.user || null, nonce: res.locals && res.locals.nonce, appName: APP_NAME, appSubtitle: APP_SUBTITLE, csrfToken: '', navLinks, pageTitle: '', activeNav: '', pageStyles: [], pageScripts: [], pageConfig: {}, assetVersion }, locals); const opts = { cache: CACHE, filename: viewPath(view) }; const body = await ejs.renderFile(viewPath(view), data, opts); const html = await ejs.renderFile(LAYOUT, Object.assign({}, data, { body }), { cache: CACHE, filename: LAYOUT }); res.set('Content-Type', 'text/html; charset=utf-8'); res.send(html); return html; } function esc(s) { return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } // Small self-contained themed page for 401/403/404/500. function renderError(req, res, status, title, message) { const html = ` ${esc(status)} — ${esc(APP_NAME)}
${esc(status)} — ${esc(title)}

${esc(message)}

Return to dashboard

`; res.status(status).set('Content-Type', 'text/html; charset=utf-8').send(html); } module.exports = { renderPage, renderError, assetVersion };