Add initial dashboard entrypoint server side rendering routes

This commit is contained in:
Johannes Zellner
2025-07-11 11:16:03 +02:00
parent 148c0d9213
commit 66d1de0821
9 changed files with 87 additions and 7 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<link rel="icon" id="favicon" href="/api/v1/cloudron/avatar" />
<title>Cloudron Setup</title>
<title>Cloudron Admin Setup</title>
<style>
@media (prefers-color-scheme: dark) {
body {
+2 -1
View File
@@ -11,7 +11,8 @@
<link rel="icon" id="favicon" href="/api/v1/cloudron/avatar" />
<title>Cloudron Dashboard</title>
<title><%= cloudronName %> Dashboard</title>
<style>
@media (prefers-color-scheme: dark) {
body {
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
<title>Cloudron Password Reset</title>
<title><%= cloudronName %> Password Reset</title>
<link rel="icon" href="/api/v1/cloudron/avatar">
<link rel="apple-touch-icon" href="/api/v1/cloudron/avatar">
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
<title>Cloudron Account Setup</title>
<title><%= cloudronName %> Account Setup</title>
<link rel="icon" href="/api/v1/cloudron/avatar">
<link rel="apple-touch-icon" href="/api/v1/cloudron/avatar">
+1 -1
View File
@@ -235,7 +235,7 @@ onMounted(async () => {
features.value = result.features;
avatarUrl.value = `https://${config.value.adminFqdn}/api/v1/cloudron/avatar`;
window.document.title = result.cloudronName;
// window.document.title = result.cloudronName;
document.getElementById('favicon').href = `${API_ORIGIN}/api/v1/cloudron/avatar?ts=${Date.now()}`;
if (config.value.mandatory2FA && !profile.value.twoFactorAuthenticationEnabled) window.location.hash = `/${VIEWS.PROFILE}?setup2fa`;
+8 -1
View File
@@ -278,9 +278,16 @@ server {
return 302 /;
}
location ~ ^/(logs|filemanager|terminal|passwordreset|setupaccount).html {
proxy_pass http://127.0.0.1:3000;
}
location = / {
proxy_pass http://127.0.0.1:3000;
}
location / {
root <%= sourceDir %>/dashboard/dist;
index index.html index.htm;
error_page 404 = @dashboarderrorredirect;
}
+1
View File
@@ -22,6 +22,7 @@ exports = module.exports = {
INFRA_VERSION_FILE: path.join(baseDir(), 'platformdata/INFRA_VERSION'),
CRON_SEED_FILE: path.join(baseDir(), 'platformdata/CRON_SEED'),
TRANSLATIONS_DIR: path.join(dashboardDir(), 'translation'),
DASHBOARD_DIR: dashboardDir(),
PROVIDER_FILE: '/etc/cloudron/PROVIDER',
+64 -1
View File
@@ -4,15 +4,27 @@ exports = module.exports = {
getConfig,
startPrepareLocation,
changeLocation
changeLocation,
renderIndex,
renderFilemanager,
renderLogs,
renderTerminal,
renderPasswordreset,
renderSetupaccount,
};
const AuditSource = require('../auditsource.js'),
BoxError = require('../boxerror.js'),
branding = require('../branding.js'),
constants = require('../constants.js'),
dashboard = require('../dashboard.js'),
ejs = require('ejs'),
fs = require('fs'),
HttpError = require('@cloudron/connect-lastmile').HttpError,
HttpSuccess = require('@cloudron/connect-lastmile').HttpSuccess,
path = require('path'),
paths = require('../paths.js'),
safe = require('safetydance');
async function getConfig(req, res, next) {
@@ -39,3 +51,54 @@ async function changeLocation(req, res, next) {
next(new HttpSuccess(204, {}));
}
async function renderIndex(req, res) {
const template = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'index.html'), 'utf-8');
const cloudronName = await branding.getCloudronName();
const html = ejs.render(template, { cloudronName });
res.send(html);
}
async function renderFilemanager(req, res) {
const template = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'filemanager.html'), 'utf-8');
const html = template;
res.send(html);
}
async function renderLogs(req, res) {
const template = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'logs.html'), 'utf-8');
const html = template;
res.send(html);
}
async function renderTerminal(req, res) {
const template = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'terminal.html'), 'utf-8');
const html = template;
res.send(html);
}
async function renderPasswordreset(req, res) {
const template = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'passwordreset.html'), 'utf-8');
const cloudronName = await branding.getCloudronName();
const html = ejs.render(template, { cloudronName });
res.send(html);
}
async function renderSetupaccount(req, res) {
const template = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'setupaccount.html'), 'utf-8');
const cloudronName = await branding.getCloudronName();
const html = ejs.render(template, { cloudronName });
res.send(html);
}
+8
View File
@@ -447,6 +447,14 @@ async function initializeExpressSync() {
router.post('/api/v1/oidc/clients/:clientId', json, token, authorizeAdmin, routes.oidcClients.load, routes.oidcClients.update);
router.del ('/api/v1/oidc/clients/:clientId', token, authorizeAdmin, routes.oidcClients.load, routes.oidcClients.del);
// dashboard assets. Only entry points are served up here, rest is via static assets from nginx
router.get ('/', routes.dashboard.renderIndex);
router.get ('/filemanager.html', routes.dashboard.renderFilemanager);
router.get ('/logs.html', routes.dashboard.renderLogs);
router.get ('/terminal.html', routes.dashboard.renderTerminal);
router.get ('/passwordreset.html', routes.dashboard.renderPasswordreset);
router.get ('/setupaccount.html', routes.dashboard.renderSetupaccount);
// upgrade handler
httpServer.on('upgrade', function (req, socket, head) {
// create a node response object for express