Compare commits
11 Commits
52f6588316
...
54a317d147
| Author | SHA1 | Date | |
|---|---|---|---|
| 54a317d147 | |||
| 00611a921b | |||
| d772f4573b | |||
| 3d2f0b23ad | |||
| e18641ff52 | |||
| f5dfb68bcc | |||
| 1a90411509 | |||
| 9755149741 | |||
| afe51be562 | |||
| 06fcc936e5 | |||
| 7957e6d50f |
+19
-48
@@ -76,12 +76,7 @@
|
|||||||
window.APP_CONFIG = window.APP_CONFIG || {
|
window.APP_CONFIG = window.APP_CONFIG || {
|
||||||
debugLogs: false,
|
debugLogs: false,
|
||||||
dashboardRuntimeFetch: 'always',
|
dashboardRuntimeFetch: 'always',
|
||||||
dashboardTargetUrl: 'https://dashboard.informatika.untan.ac.id/index.php?penelitian',
|
dashboardApiUrl: 'https://proxykbk.netlify.app/.netlify/functions/dashboard',
|
||||||
dashboardProxyTemplates: [
|
|
||||||
'https://api.codetabs.com/v1/proxy?quest={{url}}',
|
|
||||||
'https://api.allorigins.win/get?url={{url}}&ts={{ts}}',
|
|
||||||
'https://thingproxy.freeboard.io/fetch/{{rawUrl}}'
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<!-- RODA_DASHBOARD_DATA -->
|
<!-- RODA_DASHBOARD_DATA -->
|
||||||
@@ -1661,57 +1656,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
|
|
||||||
|
if (isDashboardRuntimeFetchAllowed()) {
|
||||||
|
const dashboardApiUrl = window.APP_CONFIG?.dashboardApiUrl?.trim();
|
||||||
|
|
||||||
|
if (dashboardApiUrl) {
|
||||||
|
try {
|
||||||
|
const rows = await fetchProxyRows(dashboardApiUrl);
|
||||||
|
applyDashboardRows(rows);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback ke data yang di-embed saat build jika runtime fetch gagal atau dinonaktifkan
|
||||||
const embeddedHtml = readEmbeddedDashboardHtml();
|
const embeddedHtml = readEmbeddedDashboardHtml();
|
||||||
if (embeddedHtml) {
|
if (embeddedHtml) {
|
||||||
try {
|
try {
|
||||||
applyDashboardRows(parseDashboardRows(embeddedHtml));
|
applyDashboardRows(parseDashboardRows(embeddedHtml));
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error;
|
lastError = lastError || error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDashboardRuntimeFetchAllowed()) {
|
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(lastError || new Error('DASHBOARD_RUNTIME_FETCH_DISABLED')), 'Coba Lagi', 'fetchDashboardData()');
|
||||||
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(lastError || new Error('DASHBOARD_RUNTIME_FETCH_DISABLED')), 'Coba Lagi', 'fetchDashboardData()');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetUrl = window.APP_CONFIG?.dashboardTargetUrl?.trim();
|
|
||||||
const proxyTemplates = Array.isArray(window.APP_CONFIG?.dashboardProxyTemplates)
|
|
||||||
? window.APP_CONFIG.dashboardProxyTemplates.filter(Boolean)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Endpoint server.js hanya ada saat development lokal.
|
|
||||||
const proxyServers = shouldUseLocalDashboardProxy() ? [() => '/api/dashboard'] : [];
|
|
||||||
|
|
||||||
// Deployment static memakai proxy publik karena tidak menjalankan server.js.
|
|
||||||
if (targetUrl) {
|
|
||||||
proxyTemplates.forEach((template) => {
|
|
||||||
proxyServers.push((url) => template
|
|
||||||
.replaceAll('{{url}}', encodeURIComponent(url))
|
|
||||||
.replaceAll('{{rawUrl}}', url)
|
|
||||||
.replaceAll('{{ts}}', String(Date.now())));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!proxyServers.length) {
|
|
||||||
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(new Error('DASHBOARD_PROXY_NOT_CONFIGURED')), 'Coba Lagi', 'fetchDashboardData()');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const proxyUrls = proxyServers.map((getProxyUrl) => getProxyUrl(targetUrl));
|
|
||||||
const rows = await Promise.any(proxyUrls.map((proxyUrl) => fetchProxyRows(proxyUrl)));
|
|
||||||
applyDashboardRows(rows);
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
lastError = error instanceof AggregateError && error.errors?.length
|
|
||||||
? error.errors[error.errors.length - 1]
|
|
||||||
: error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jika semua proxy gagal
|
|
||||||
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(lastError), 'Coba Lagi', 'fetchDashboardData()');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Generated
-13
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "roda",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "roda",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "ISC"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,3 @@
|
|||||||
{
|
{
|
||||||
"name": "roda",
|
"type": "commonjs"
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Project website untuk menampilkan sorotan dan prestasi karya mahasiswa program studi Informatika Universitas Tanjungpura.",
|
|
||||||
"main": "server.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"start": "node server.js",
|
|
||||||
"dev": "node server.js"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
|
||||||
}
|
}
|
||||||
+38
-85
@@ -6,64 +6,21 @@ const { URL } = require('url');
|
|||||||
const ROOT_DIR = __dirname;
|
const ROOT_DIR = __dirname;
|
||||||
const PARENT_DIR = path.join(__dirname, '..', '..');
|
const PARENT_DIR = path.join(__dirname, '..', '..');
|
||||||
const INDEX_PATH = path.join(ROOT_DIR, 'index.html');
|
const INDEX_PATH = path.join(ROOT_DIR, 'index.html');
|
||||||
const ENV_PATH = path.join(ROOT_DIR, '.env');
|
const HOST = process.env.HOST || '127.0.0.1';
|
||||||
const HOST = '127.0.0.1';
|
|
||||||
const PORT = Number(process.env.PORT || 3000);
|
const PORT = Number(process.env.PORT || 3000);
|
||||||
|
const DASHBOARD_TARGET_URL = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian';
|
||||||
|
const RODA_PATH_PREFIX = '/groups/RODA';
|
||||||
|
|
||||||
function logServerError(scope, error, extra = {}) {
|
function logServerError(scope, error, extra = {}) {
|
||||||
console.error(`[${scope}]`, error && error.stack ? error.stack : error, extra);
|
console.error(`[${scope}]`, error && error.stack ? error.stack : error, extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEnvFile(filePath) {
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const env = {};
|
|
||||||
const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
||||||
|
|
||||||
for (const rawLine of lines) {
|
|
||||||
const line = rawLine.trim();
|
|
||||||
if (!line || line.startsWith('#')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const separatorIndex = line.indexOf('=');
|
|
||||||
if (separatorIndex === -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = line.slice(0, separatorIndex).trim();
|
|
||||||
let value = line.slice(separatorIndex + 1).trim();
|
|
||||||
|
|
||||||
if (
|
|
||||||
(value.startsWith('"') && value.endsWith('"')) ||
|
|
||||||
(value.startsWith("'") && value.endsWith("'"))
|
|
||||||
) {
|
|
||||||
value = value.slice(1, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
env[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppConfig() {
|
function getAppConfig() {
|
||||||
const env = {
|
|
||||||
...parseEnvFile(ENV_PATH),
|
|
||||||
...process.env
|
|
||||||
};
|
|
||||||
|
|
||||||
const dashboardProxyTemplates = [
|
|
||||||
env.DASHBOARD_PROXY_1,
|
|
||||||
env.DASHBOARD_PROXY_2,
|
|
||||||
env.DASHBOARD_PROXY_3
|
|
||||||
].filter(Boolean);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dashboardTargetUrl: env.DASHBOARD_TARGET_URL || '',
|
debugLogs: false,
|
||||||
dashboardProxyTemplates
|
dashboardRuntimeFetch: 'always',
|
||||||
|
dashboardApiUrl: 'api/dashboard',
|
||||||
|
dashboardTargetUrl: DASHBOARD_TARGET_URL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,10 +28,6 @@ const DASHBOARD_CACHE_TTL_MS = 5 * 60 * 1000;
|
|||||||
let dashboardCache = { html: null, timestamp: 0 };
|
let dashboardCache = { html: null, timestamp: 0 };
|
||||||
|
|
||||||
async function fetchDashboardHtml() {
|
async function fetchDashboardHtml() {
|
||||||
const targetUrl = getAppConfig().dashboardTargetUrl;
|
|
||||||
if (!targetUrl) {
|
|
||||||
throw new Error('DASHBOARD_TARGET_URL kosong');
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (dashboardCache.html && (now - dashboardCache.timestamp) < DASHBOARD_CACHE_TTL_MS) {
|
if (dashboardCache.html && (now - dashboardCache.timestamp) < DASHBOARD_CACHE_TTL_MS) {
|
||||||
@@ -84,7 +37,7 @@ async function fetchDashboardHtml() {
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 12000);
|
const timeoutId = setTimeout(() => controller.abort(), 12000);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(targetUrl, {
|
const response = await fetch(DASHBOARD_TARGET_URL, {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36'
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36'
|
||||||
@@ -106,39 +59,14 @@ async function fetchDashboardHtml() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateAppConfig(config) {
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
if (!config.dashboardTargetUrl) {
|
|
||||||
errors.push('DASHBOARD_TARGET_URL belum diisi.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(config.dashboardProxyTemplates) || !config.dashboardProxyTemplates.length) {
|
|
||||||
errors.push('Minimal satu DASHBOARD_PROXY_* harus diisi.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectConfig(html) {
|
function injectConfig(html) {
|
||||||
const configScript = `<script>window.APP_CONFIG = ${JSON.stringify(getAppConfig())};</script>`;
|
const configScript = `<script>window.APP_CONFIG = ${JSON.stringify(getAppConfig())};</script>`;
|
||||||
const defaultConfigScript = '<script>\n window.APP_CONFIG = window.APP_CONFIG || {\n dashboardTargetUrl: \'\',\n dashboardProxyTemplates: []\n };\n </script>';
|
|
||||||
|
|
||||||
if (html.includes(defaultConfigScript)) {
|
|
||||||
return html.replace(defaultConfigScript, configScript);
|
|
||||||
}
|
|
||||||
|
|
||||||
return html.replace('</head>', ` ${configScript}\n</head>`);
|
return html.replace('</head>', ` ${configScript}\n</head>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendHtml(res) {
|
function sendHtml(res) {
|
||||||
try {
|
try {
|
||||||
const html = fs.readFileSync(INDEX_PATH, 'utf8');
|
const html = fs.readFileSync(INDEX_PATH, 'utf8');
|
||||||
const configErrors = validateAppConfig(getAppConfig());
|
|
||||||
if (configErrors.length) {
|
|
||||||
logServerError('config', new Error('Invalid app config'), { configErrors });
|
|
||||||
}
|
|
||||||
|
|
||||||
const withConfig = injectConfig(html);
|
const withConfig = injectConfig(html);
|
||||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||||
res.end(withConfig);
|
res.end(withConfig);
|
||||||
@@ -195,28 +123,53 @@ function sendNotFound(res) {
|
|||||||
res.end('Not found');
|
res.end('Not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRodaStaticPathname(pathname) {
|
||||||
|
if (pathname === RODA_PATH_PREFIX || pathname === `${RODA_PATH_PREFIX}/` || pathname === `${RODA_PATH_PREFIX}/index.html`) {
|
||||||
|
return '/index.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith(`${RODA_PATH_PREFIX}/`)) {
|
||||||
|
return pathname.slice(RODA_PATH_PREFIX.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathname;
|
||||||
|
}
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
try {
|
try {
|
||||||
const requestUrl = new URL(req.url, `http://${req.headers.host}`);
|
const requestUrl = new URL(req.url, `http://${req.headers.host}`);
|
||||||
const pathname = requestUrl.pathname;
|
const pathname = requestUrl.pathname;
|
||||||
|
|
||||||
if (pathname === '/' || pathname === '/index.html') {
|
if (pathname === '/' || pathname === '/index.html' || pathname === RODA_PATH_PREFIX || pathname === `${RODA_PATH_PREFIX}/` || pathname === `${RODA_PATH_PREFIX}/index.html`) {
|
||||||
sendHtml(res);
|
sendHtml(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathname === '/api/dashboard') {
|
if (pathname === '/api/dashboard' || pathname === `${RODA_PATH_PREFIX}/api/dashboard`) {
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.writeHead(204, {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET',
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fetchDashboardHtml()
|
fetchDashboardHtml()
|
||||||
.then(({ html, cached }) => {
|
.then(({ html, cached }) => {
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
'Content-Type': 'text/html; charset=utf-8',
|
'Content-Type': 'text/html; charset=utf-8',
|
||||||
'X-Cache': cached
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'X-Cache': cached,
|
||||||
});
|
});
|
||||||
res.end(html);
|
res.end(html);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logServerError('api-dashboard', error);
|
logServerError('api-dashboard', error);
|
||||||
res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' });
|
res.writeHead(502, {
|
||||||
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
});
|
||||||
res.end('DASHBOARD_FETCH_FAILED');
|
res.end('DASHBOARD_FETCH_FAILED');
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -237,7 +190,7 @@ const server = http.createServer((req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Serve static files from ROOT_DIR
|
// Serve static files from ROOT_DIR
|
||||||
const filepath = path.join(ROOT_DIR, pathname);
|
const filepath = path.join(ROOT_DIR, getRodaStaticPathname(pathname));
|
||||||
const normalizedPath = path.normalize(filepath);
|
const normalizedPath = path.normalize(filepath);
|
||||||
|
|
||||||
// Security check: ensure the requested file is within ROOT_DIR
|
// Security check: ensure the requested file is within ROOT_DIR
|
||||||
|
|||||||
+3
-1
@@ -5,7 +5,9 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Student-safe static page assignment kit for Informatika UNTAN",
|
"description": "Student-safe static page assignment kit for Informatika UNTAN",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "node scripts/dev.js",
|
||||||
|
"dev:vite": "vite",
|
||||||
|
"dev:roda": "node groups/RODA/server.js",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"test:tapops": "node --test groups/Tapops/tests/*.test.mjs",
|
"test:tapops": "node --test groups/Tapops/tests/*.test.mjs",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
|
||||||
|
export function createDevProcessSpecs({
|
||||||
|
nodeCommand = process.execPath,
|
||||||
|
env = process.env,
|
||||||
|
} = {}) {
|
||||||
|
return {
|
||||||
|
roda: {
|
||||||
|
command: nodeCommand,
|
||||||
|
args: ['groups/RODA/server.js'],
|
||||||
|
options: {
|
||||||
|
env: {
|
||||||
|
...env,
|
||||||
|
HOST: env.RODA_HOST || '127.0.0.1',
|
||||||
|
PORT: env.RODA_PORT || '3000',
|
||||||
|
},
|
||||||
|
stdio: ['ignore', 'ignore', 'pipe'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
command: nodeCommand,
|
||||||
|
args: ['node_modules/vite/bin/vite.js'],
|
||||||
|
options: {
|
||||||
|
env: { ...env },
|
||||||
|
stdio: 'inherit',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startDevServers({
|
||||||
|
spawnProcess = spawn,
|
||||||
|
specs = createDevProcessSpecs(),
|
||||||
|
} = {}) {
|
||||||
|
const roda = spawnProcess(specs.roda.command, specs.roda.args, specs.roda.options);
|
||||||
|
const vite = spawnProcess(specs.vite.command, specs.vite.args, specs.vite.options);
|
||||||
|
let shuttingDown = false;
|
||||||
|
let rodaErrorOutput = '';
|
||||||
|
|
||||||
|
roda.stderr?.on('data', (chunk) => {
|
||||||
|
const message = String(chunk);
|
||||||
|
rodaErrorOutput += message;
|
||||||
|
if (!message.includes('EADDRINUSE')) {
|
||||||
|
process.stderr.write(`[roda] ${message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function stopChildren() {
|
||||||
|
shuttingDown = true;
|
||||||
|
if (!roda.killed) {
|
||||||
|
roda.kill();
|
||||||
|
}
|
||||||
|
if (!vite.killed) {
|
||||||
|
vite.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vite.on('exit', (code, signal) => {
|
||||||
|
shuttingDown = true;
|
||||||
|
if (!roda.killed) {
|
||||||
|
roda.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal) {
|
||||||
|
process.exitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exitCode = code ?? 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
roda.on('exit', (code, signal) => {
|
||||||
|
if (shuttingDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rodaErrorOutput.includes('EADDRINUSE')) {
|
||||||
|
process.stderr.write('[roda] Port 3000 is already in use; continuing with the existing RODA server.\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reason = signal ? `signal ${signal}` : `exit code ${code}`;
|
||||||
|
process.stderr.write(`[roda] server stopped unexpectedly (${reason})\n`);
|
||||||
|
if (!vite.killed) {
|
||||||
|
vite.kill();
|
||||||
|
}
|
||||||
|
process.exitCode = code || 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.once('SIGINT', stopChildren);
|
||||||
|
process.once('SIGTERM', stopChildren);
|
||||||
|
|
||||||
|
return { roda, vite };
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { startDevServers } from './dev-runner.js';
|
||||||
|
|
||||||
|
startDevServers();
|
||||||
+16
-55
@@ -10,9 +10,11 @@ const rodaDashboardCacheTtlMs = 5 * 60 * 1000;
|
|||||||
const dashboardRequestHeaders = {
|
const dashboardRequestHeaders = {
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
|
||||||
};
|
};
|
||||||
|
const rodaHost = process.env.RODA_HOST || '127.0.0.1';
|
||||||
|
const rodaPort = process.env.RODA_PORT || '3000';
|
||||||
|
const rodaTarget = `http://${rodaHost}:${rodaPort}`;
|
||||||
|
|
||||||
let rodaDashboardHtmlCache;
|
let rodaDashboardHtmlCache;
|
||||||
let rodaDashboardDevCache = { html: '', timestamp: 0 };
|
|
||||||
|
|
||||||
function collectHtmlInputs(dir) {
|
function collectHtmlInputs(dir) {
|
||||||
const inputs = {};
|
const inputs = {};
|
||||||
@@ -124,61 +126,20 @@ function embedRodaDashboardData() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function serveRodaDashboardApi() {
|
|
||||||
return {
|
|
||||||
name: 'serve-roda-dashboard-api',
|
|
||||||
configureServer(server) {
|
|
||||||
server.middlewares.use(async (req, res, next) => {
|
|
||||||
const requestUrl = new URL(req.url || '/', 'http://localhost');
|
|
||||||
if (requestUrl.pathname !== '/api/dashboard') {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
||||||
res.writeHead(405, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
||||||
res.end('METHOD_NOT_ALLOWED');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
try {
|
|
||||||
let html = rodaDashboardDevCache.html;
|
|
||||||
let cacheStatus = 'HIT';
|
|
||||||
|
|
||||||
if (!html || (now - rodaDashboardDevCache.timestamp) >= rodaDashboardCacheTtlMs) {
|
|
||||||
html = await requestRodaDashboardHtml();
|
|
||||||
rodaDashboardDevCache = { html, timestamp: Date.now() };
|
|
||||||
cacheStatus = 'MISS';
|
|
||||||
}
|
|
||||||
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'text/html; charset=utf-8',
|
|
||||||
'X-Cache': cacheStatus,
|
|
||||||
});
|
|
||||||
res.end(req.method === 'HEAD' ? '' : html);
|
|
||||||
} catch (error) {
|
|
||||||
if (rodaDashboardDevCache.html) {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'text/html; charset=utf-8',
|
|
||||||
'X-Cache': 'STALE',
|
|
||||||
});
|
|
||||||
res.end(req.method === 'HEAD' ? '' : rodaDashboardDevCache.html);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
console.warn(`[roda-dashboard] Dev dashboard fetch failed: ${message}`);
|
|
||||||
res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
||||||
res.end('DASHBOARD_FETCH_FAILED');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [serveRodaDashboardApi(), embedRodaDashboardData(), copyStaticAssets()],
|
plugins: [embedRodaDashboardData(), copyStaticAssets()],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/groups/RODA/api/dashboard': {
|
||||||
|
target: rodaTarget,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/api/dashboard': {
|
||||||
|
target: rodaTarget,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: collectHtmlInputs(root),
|
input: collectHtmlInputs(root),
|
||||||
|
|||||||
Reference in New Issue
Block a user