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),