Merge pull request 'prod' (#22) from Rayhan/roda-student-web-if-development-kit:prod into main

Reviewed-on: izu/student-web-if-development-kit#22
Approved
This commit is contained in:
izu
2026-06-05 09:22:25 +00:00
2 changed files with 102 additions and 35 deletions
+28 -23
View File
@@ -78,7 +78,9 @@
dashboardRuntimeFetch: 'always', dashboardRuntimeFetch: 'always',
dashboardTargetUrl: 'https://dashboard.informatika.untan.ac.id/index.php?penelitian', dashboardTargetUrl: 'https://dashboard.informatika.untan.ac.id/index.php?penelitian',
dashboardProxyTemplates: [ dashboardProxyTemplates: [
'https://api.codetabs.com/v1/proxy?quest={{url}}' '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>
@@ -1631,6 +1633,22 @@
updateStatPub(newData.length); updateStatPub(newData.length);
} }
async function fetchProxyRows(proxyUrl) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000);
try {
const response = await fetch(proxyUrl, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP_ERROR_${response.status}`);
const responseData = await response.text();
const html = extractDashboardHtml(responseData);
return parseDashboardRows(html);
} finally {
clearTimeout(timeoutId);
}
}
async function fetchDashboardData() { async function fetchDashboardData() {
const grid = document.getElementById('riset-grid'); const grid = document.getElementById('riset-grid');
if (grid) { if (grid) {
@@ -1681,28 +1699,15 @@
return; return;
} }
for (const getProxyUrl of proxyServers) { try {
const controller = new AbortController(); const proxyUrls = proxyServers.map((getProxyUrl) => getProxyUrl(targetUrl));
const timeoutId = setTimeout(() => controller.abort(), 15000); const rows = await Promise.any(proxyUrls.map((proxyUrl) => fetchProxyRows(proxyUrl)));
applyDashboardRows(rows);
try { return;
const proxyUrl = getProxyUrl(targetUrl); } catch (error) {
lastError = error instanceof AggregateError && error.errors?.length
const response = await fetch(proxyUrl, { signal: controller.signal }); ? error.errors[error.errors.length - 1]
clearTimeout(timeoutId); : error;
if (!response.ok) throw new Error(`HTTP_ERROR_${response.status}`);
const responseData = await response.text();
const html = extractDashboardHtml(responseData);
const rows = parseDashboardRows(html);
applyDashboardRows(rows);
return; // Berhasil! Keluar dari loop
} catch (error) {
clearTimeout(timeoutId);
lastError = error;
// Lanjut ke proxy berikutnya...
}
} }
// Jika semua proxy gagal // Jika semua proxy gagal
+74 -12
View File
@@ -6,8 +6,13 @@ import { defineConfig } from 'vite';
const root = path.dirname(fileURLToPath(import.meta.url)); const root = path.dirname(fileURLToPath(import.meta.url));
const excludedDirs = new Set(['.git', 'dist', 'node_modules']); const excludedDirs = new Set(['.git', 'dist', 'node_modules']);
const rodaDashboardUrl = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian'; const rodaDashboardUrl = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian';
const rodaDashboardCacheTtlMs = 5 * 60 * 1000;
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',
};
let rodaDashboardHtmlCache; let rodaDashboardHtmlCache;
let rodaDashboardDevCache = { html: '', timestamp: 0 };
function collectHtmlInputs(dir) { function collectHtmlInputs(dir) {
const inputs = {}; const inputs = {};
@@ -50,33 +55,37 @@ function copyStaticAssets() {
}; };
} }
async function fetchRodaDashboardHtml() { async function requestRodaDashboardHtml() {
if (rodaDashboardHtmlCache !== undefined) {
return rodaDashboardHtmlCache;
}
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(rodaDashboardUrl, { const response = await fetch(rodaDashboardUrl, {
signal: controller.signal, signal: controller.signal,
headers: { headers: dashboardRequestHeaders,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
},
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}`); throw new Error(`HTTP ${response.status}`);
} }
rodaDashboardHtmlCache = await response.text(); return response.text();
} finally {
clearTimeout(timeoutId);
}
}
async function fetchRodaDashboardHtml() {
if (rodaDashboardHtmlCache !== undefined) {
return rodaDashboardHtmlCache;
}
try {
rodaDashboardHtmlCache = await requestRodaDashboardHtml();
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
console.warn(`[roda-dashboard] Build-time dashboard fetch skipped: ${message}`); console.warn(`[roda-dashboard] Build-time dashboard fetch skipped: ${message}`);
rodaDashboardHtmlCache = ''; rodaDashboardHtmlCache = '';
} finally {
clearTimeout(timeoutId);
} }
return rodaDashboardHtmlCache; return rodaDashboardHtmlCache;
@@ -115,8 +124,61 @@ 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: [embedRodaDashboardData(), copyStaticAssets()], plugins: [serveRodaDashboardApi(), embedRodaDashboardData(), copyStaticAssets()],
build: { build: {
rollupOptions: { rollupOptions: {
input: collectHtmlInputs(root), input: collectHtmlInputs(root),