fix: embed RODA dashboard data at build

This commit is contained in:
Raditya_Indra_Putranto
2026-06-04 19:21:53 +07:00
parent 56c34eddc3
commit 478d48c237
2 changed files with 159 additions and 55 deletions
+90 -54
View File
@@ -75,6 +75,7 @@
<script> <script>
window.APP_CONFIG = window.APP_CONFIG || { window.APP_CONFIG = window.APP_CONFIG || {
debugLogs: false, debugLogs: false,
dashboardRuntimeFetch: 'local',
dashboardTargetUrl: 'https://dashboard.informatika.untan.ac.id/index.php?penelitian', dashboardTargetUrl: 'https://dashboard.informatika.untan.ac.id/index.php?penelitian',
dashboardProxyTemplates: [ dashboardProxyTemplates: [
'https://api.allorigins.win/get?url={{url}}&ts={{ts}}', 'https://api.allorigins.win/get?url={{url}}&ts={{ts}}',
@@ -83,6 +84,7 @@
] ]
}; };
</script> </script>
<!-- RODA_DASHBOARD_DATA -->
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
@@ -1484,6 +1486,7 @@
if (error.message === 'EMPTY_RESPONSE') return 'Dashboard mengembalikan data kosong atau struktur halaman berubah.'; if (error.message === 'EMPTY_RESPONSE') return 'Dashboard mengembalikan data kosong atau struktur halaman berubah.';
if (error.message === 'NO_ROWS_FOUND') return 'Data penelitian tidak ditemukan pada halaman dashboard.'; if (error.message === 'NO_ROWS_FOUND') return 'Data penelitian tidak ditemukan pada halaman dashboard.';
if (error.message === 'DASHBOARD_PROXY_NOT_CONFIGURED') return 'Proxy dashboard belum dikonfigurasi.'; if (error.message === 'DASHBOARD_PROXY_NOT_CONFIGURED') return 'Proxy dashboard belum dikonfigurasi.';
if (error.message === 'DASHBOARD_RUNTIME_FETCH_DISABLED') return 'Data publikasi belum tersedia pada build ini.';
if (error.message?.startsWith('HTTP_ERROR_')) return `Proxy merespons gagal (${error.message.replace('HTTP_ERROR_', 'HTTP ')}).`; if (error.message?.startsWith('HTTP_ERROR_')) return `Proxy merespons gagal (${error.message.replace('HTTP_ERROR_', 'HTTP ')}).`;
return 'Gagal mengambil data. Server dashboard mungkin sedang sibuk atau memblokir akses.'; return 'Gagal mengambil data. Server dashboard mungkin sedang sibuk atau memblokir akses.';
} }
@@ -1549,6 +1552,75 @@
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === ''; return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '';
} }
function isDashboardRuntimeFetchAllowed() {
const mode = window.APP_CONFIG?.dashboardRuntimeFetch;
return mode === true || mode === 'always' || (mode === 'local' && shouldUseLocalDashboardProxy());
}
function readEmbeddedDashboardHtml() {
const source = document.getElementById('roda-dashboard-html');
if (!source) return '';
try {
return JSON.parse(source.textContent || '""') || '';
} catch (error) {
return source.textContent || '';
}
}
function applyDashboardRows(rows) {
const newData = [];
rows.forEach((row) => {
const cols = row.querySelectorAll('td');
if (cols.length >= 3) {
const indexVal = cols[0].innerText.trim();
// Mencari elemen judul (biasanya di dalam <a> atau <b>)
const linkEl = cols[1].querySelector('a');
const titleEl = linkEl || cols[1].querySelector('b') || cols[1];
const url = linkEl ? linkEl.getAttribute('href') : '#';
const title = titleEl.innerText.trim();
// Skip jika baris ini adalah header atau bukan data penelitian (berdasarkan index atau judul)
if (isNaN(parseInt(indexVal)) || title === "Bidang Riset" || title === "Thn Jlh") {
return;
}
// Mencari elemen penulis (biasanya di dalam tag <font> dengan size 11px)
const authorsEl = cols[1].querySelector('font');
const authors = authorsEl ? authorsEl.innerText.trim() : 'Peneliti Informatika';
// Tahun biasanya ada di kolom berikutnya
const year = cols[2]?.innerText.trim() || '2025';
const kbk = categorizeByKeywords(title);
const kbkInfo = kbkMeta[kbk];
newData.push({
id: parseInt(indexVal) || (newData.length + 1),
kbk: kbk,
title_id: title,
title_en: title,
authors: authors,
year: year,
url: url,
venue: 'Dashboard Informatika',
badge_id: kbkInfo.label_id,
badge_en: kbkInfo.label_en
});
}
});
if (!newData.length) {
throw new Error('NO_ROWS_FOUND');
}
risetData = newData;
isLoadingData = false;
renderRisetCards();
updateStatPub(newData.length);
}
async function fetchDashboardData() { async function fetchDashboardData() {
const grid = document.getElementById('riset-grid'); const grid = document.getElementById('riset-grid');
if (grid) { if (grid) {
@@ -1560,6 +1632,22 @@
`; `;
} }
let lastError = null;
const embeddedHtml = readEmbeddedDashboardHtml();
if (embeddedHtml) {
try {
applyDashboardRows(parseDashboardRows(embeddedHtml));
return;
} catch (error) {
lastError = error;
}
}
if (!isDashboardRuntimeFetchAllowed()) {
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(lastError || new Error('DASHBOARD_RUNTIME_FETCH_DISABLED')), 'Coba Lagi', 'fetchDashboardData()');
return;
}
const targetUrl = window.APP_CONFIG?.dashboardTargetUrl?.trim(); const targetUrl = window.APP_CONFIG?.dashboardTargetUrl?.trim();
const proxyTemplates = Array.isArray(window.APP_CONFIG?.dashboardProxyTemplates) const proxyTemplates = Array.isArray(window.APP_CONFIG?.dashboardProxyTemplates)
? window.APP_CONFIG.dashboardProxyTemplates.filter(Boolean) ? window.APP_CONFIG.dashboardProxyTemplates.filter(Boolean)
@@ -1583,8 +1671,6 @@
return; return;
} }
let lastError = null;
for (const getProxyUrl of proxyServers) { for (const getProxyUrl of proxyServers) {
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); const timeoutId = setTimeout(() => controller.abort(), 15000);
@@ -1600,58 +1686,8 @@
const responseData = await response.text(); const responseData = await response.text();
const html = extractDashboardHtml(responseData); const html = extractDashboardHtml(responseData);
const rows = parseDashboardRows(html); const rows = parseDashboardRows(html);
applyDashboardRows(rows);
const newData = []; return; // Berhasil! Keluar dari loop
rows.forEach((row, index) => {
const cols = row.querySelectorAll('td');
if (cols.length >= 3) {
const indexVal = cols[0].innerText.trim();
// Mencari elemen judul (biasanya di dalam <a> atau <b>)
const linkEl = cols[1].querySelector('a');
const titleEl = linkEl || cols[1].querySelector('b') || cols[1];
const url = linkEl ? linkEl.getAttribute('href') : '#';
const title = titleEl.innerText.trim();
// Skip jika baris ini adalah header atau bukan data penelitian (berdasarkan index atau judul)
if (isNaN(parseInt(indexVal)) || title === "Bidang Riset" || title === "Thn Jlh") {
return;
}
// Mencari elemen penulis (biasanya di dalam tag <font> dengan size 11px)
const authorsEl = cols[1].querySelector('font');
const authors = authorsEl ? authorsEl.innerText.trim() : 'Peneliti Informatika';
// Tahun biasanya ada di kolom berikutnya
const year = cols[2]?.innerText.trim() || '2025';
const kbk = categorizeByKeywords(title);
const kbkInfo = kbkMeta[kbk];
newData.push({
id: parseInt(indexVal) || (newData.length + 1),
kbk: kbk,
title_id: title,
title_en: title,
authors: authors,
year: year,
url: url,
venue: 'Dashboard Informatika',
badge_id: kbkInfo.label_id,
badge_en: kbkInfo.label_en
});
}
});
if (newData.length > 0) {
risetData = newData;
isLoadingData = false;
renderRisetCards();
updateStatPub(newData.length);
return; // Berhasil! Keluar dari loop
}
throw new Error('NO_ROWS_FOUND');
} catch (error) { } catch (error) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
lastError = error; lastError = error;
+69 -1
View File
@@ -5,6 +5,9 @@ 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';
let rodaDashboardHtmlCache;
function collectHtmlInputs(dir) { function collectHtmlInputs(dir) {
const inputs = {}; const inputs = {};
@@ -47,8 +50,73 @@ function copyStaticAssets() {
}; };
} }
async function fetchRodaDashboardHtml() {
if (rodaDashboardHtmlCache !== undefined) {
return rodaDashboardHtmlCache;
}
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',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
rodaDashboardHtmlCache = await response.text();
} 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;
}
function escapeJsonScriptContent(value) {
return JSON.stringify(value)
.replaceAll('<', '\\u003c')
.replaceAll('>', '\\u003e')
.replaceAll('&', '\\u0026')
.replaceAll('\u2028', '\\u2028')
.replaceAll('\u2029', '\\u2029');
}
function embedRodaDashboardData() {
let command = 'serve';
return {
name: 'embed-roda-dashboard-data',
configResolved(config) {
command = config.command;
},
async transformIndexHtml(html, context) {
const filename = context?.filename ? path.resolve(context.filename) : '';
const rodaIndex = path.join(root, 'groups', 'RODA', 'index.html');
if (filename !== rodaIndex || command !== 'build') {
return html.replace('<!-- RODA_DASHBOARD_DATA -->', '');
}
const dashboardHtml = await fetchRodaDashboardHtml();
const payload = `<script type="application/json" id="roda-dashboard-html">${escapeJsonScriptContent(dashboardHtml)}</script>`;
return html.replace('<!-- RODA_DASHBOARD_DATA -->', payload);
},
};
}
export default defineConfig({ export default defineConfig({
plugins: [copyStaticAssets()], plugins: [embedRodaDashboardData(), copyStaticAssets()],
build: { build: {
rollupOptions: { rollupOptions: {
input: collectHtmlInputs(root), input: collectHtmlInputs(root),