From 478d48c237a018970ed502bdb6cca9dab80646ba Mon Sep 17 00:00:00 2001 From: Raditya_Indra_Putranto Date: Thu, 4 Jun 2026 19:21:53 +0700 Subject: [PATCH] fix: embed RODA dashboard data at build --- groups/RODA/index.html | 144 +++++++++++++++++++++++++---------------- vite.config.js | 70 +++++++++++++++++++- 2 files changed, 159 insertions(+), 55 deletions(-) diff --git a/groups/RODA/index.html b/groups/RODA/index.html index d3d46c4..6f8cfc0 100644 --- a/groups/RODA/index.html +++ b/groups/RODA/index.html @@ -75,6 +75,7 @@ + @@ -1484,6 +1486,7 @@ 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 === '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 ')}).`; 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 === ''; } + 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 atau ) + 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 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() { const grid = document.getElementById('riset-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 proxyTemplates = Array.isArray(window.APP_CONFIG?.dashboardProxyTemplates) ? window.APP_CONFIG.dashboardProxyTemplates.filter(Boolean) @@ -1583,8 +1671,6 @@ return; } - let lastError = null; - for (const getProxyUrl of proxyServers) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 15000); @@ -1600,58 +1686,8 @@ const responseData = await response.text(); const html = extractDashboardHtml(responseData); const rows = parseDashboardRows(html); - - const newData = []; - 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 atau ) - 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 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'); + applyDashboardRows(rows); + return; // Berhasil! Keluar dari loop } catch (error) { clearTimeout(timeoutId); lastError = error; diff --git a/vite.config.js b/vite.config.js index 891b74f..e65cacf 100644 --- a/vite.config.js +++ b/vite.config.js @@ -5,6 +5,9 @@ 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'; + +let rodaDashboardHtmlCache; function collectHtmlInputs(dir) { 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('', ''); + } + + const dashboardHtml = await fetchRodaDashboardHtml(); + const payload = ``; + + return html.replace('', payload); + }, + }; +} + export default defineConfig({ - plugins: [copyStaticAssets()], + plugins: [embedRodaDashboardData(), copyStaticAssets()], build: { rollupOptions: { input: collectHtmlInputs(root),