diff --git a/groups/RODA/index.html b/groups/RODA/index.html
index dfcf655..71a8e09 100644
--- a/groups/RODA/index.html
+++ b/groups/RODA/index.html
@@ -78,7 +78,9 @@
dashboardRuntimeFetch: 'always',
dashboardTargetUrl: 'https://dashboard.informatika.untan.ac.id/index.php?penelitian',
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}}'
]
};
@@ -1631,6 +1633,22 @@
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() {
const grid = document.getElementById('riset-grid');
if (grid) {
@@ -1681,28 +1699,15 @@
return;
}
- for (const getProxyUrl of proxyServers) {
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 15000);
-
- try {
- const proxyUrl = getProxyUrl(targetUrl);
-
- const response = await fetch(proxyUrl, { signal: controller.signal });
- clearTimeout(timeoutId);
-
- 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...
- }
+ 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
diff --git a/vite.config.js b/vite.config.js
index e65cacf..f076183 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -6,8 +6,13 @@ import { defineConfig } from 'vite';
const root = path.dirname(fileURLToPath(import.meta.url));
const excludedDirs = new Set(['.git', 'dist', 'node_modules']);
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 rodaDashboardDevCache = { html: '', timestamp: 0 };
function collectHtmlInputs(dir) {
const inputs = {};
@@ -50,33 +55,37 @@ function copyStaticAssets() {
};
}
-async function fetchRodaDashboardHtml() {
- if (rodaDashboardHtmlCache !== undefined) {
- return rodaDashboardHtmlCache;
- }
-
+async function requestRodaDashboardHtml() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 12000);
try {
const response = await fetch(rodaDashboardUrl, {
signal: controller.signal,
- headers: {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
- },
+ headers: dashboardRequestHeaders,
});
if (!response.ok) {
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) {
const message = error instanceof Error ? error.message : String(error);
console.warn(`[roda-dashboard] Build-time dashboard fetch skipped: ${message}`);
rodaDashboardHtmlCache = '';
- } finally {
- clearTimeout(timeoutId);
}
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({
- plugins: [embedRodaDashboardData(), copyStaticAssets()],
+ plugins: [serveRodaDashboardApi(), embedRodaDashboardData(), copyStaticAssets()],
build: {
rollupOptions: {
input: collectHtmlInputs(root),