forked from izu/student-web-if-development-kit
fix: embed RODA dashboard data at build
This commit is contained in:
+87
-51
@@ -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,60 +1552,25 @@
|
|||||||
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '';
|
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchDashboardData() {
|
function isDashboardRuntimeFetchAllowed() {
|
||||||
const grid = document.getElementById('riset-grid');
|
const mode = window.APP_CONFIG?.dashboardRuntimeFetch;
|
||||||
if (grid) {
|
return mode === true || mode === 'always' || (mode === 'local' && shouldUseLocalDashboardProxy());
|
||||||
grid.innerHTML = `
|
|
||||||
<div class="md:col-span-2 lg:col-span-3 py-20 text-center">
|
|
||||||
<div class="inline-block animate-spin w-10 h-10 border-4 border-untan-blue border-t-transparent rounded-full mb-4"></div>
|
|
||||||
<p class="text-slate-500 font-bold">Mengambil data dari Dashboard...</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetUrl = window.APP_CONFIG?.dashboardTargetUrl?.trim();
|
function readEmbeddedDashboardHtml() {
|
||||||
const proxyTemplates = Array.isArray(window.APP_CONFIG?.dashboardProxyTemplates)
|
const source = document.getElementById('roda-dashboard-html');
|
||||||
? window.APP_CONFIG.dashboardProxyTemplates.filter(Boolean)
|
if (!source) return '';
|
||||||
: [];
|
|
||||||
|
|
||||||
// Endpoint server.js hanya ada saat development lokal.
|
|
||||||
const proxyServers = shouldUseLocalDashboardProxy() ? [() => '/api/dashboard'] : [];
|
|
||||||
|
|
||||||
// Deployment static memakai proxy publik karena tidak menjalankan server.js.
|
|
||||||
if (targetUrl) {
|
|
||||||
proxyTemplates.forEach((template) => {
|
|
||||||
proxyServers.push((url) => template
|
|
||||||
.replaceAll('{{url}}', encodeURIComponent(url))
|
|
||||||
.replaceAll('{{rawUrl}}', url)
|
|
||||||
.replaceAll('{{ts}}', String(Date.now())));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!proxyServers.length) {
|
|
||||||
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(new Error('DASHBOARD_PROXY_NOT_CONFIGURED')), 'Coba Lagi', 'fetchDashboardData()');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastError = null;
|
|
||||||
|
|
||||||
for (const getProxyUrl of proxyServers) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 15000);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const proxyUrl = getProxyUrl(targetUrl);
|
return JSON.parse(source.textContent || '""') || '';
|
||||||
|
} catch (error) {
|
||||||
const response = await fetch(proxyUrl, { signal: controller.signal });
|
return source.textContent || '';
|
||||||
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);
|
|
||||||
|
|
||||||
|
function applyDashboardRows(rows) {
|
||||||
const newData = [];
|
const newData = [];
|
||||||
rows.forEach((row, index) => {
|
rows.forEach((row) => {
|
||||||
const cols = row.querySelectorAll('td');
|
const cols = row.querySelectorAll('td');
|
||||||
if (cols.length >= 3) {
|
if (cols.length >= 3) {
|
||||||
const indexVal = cols[0].innerText.trim();
|
const indexVal = cols[0].innerText.trim();
|
||||||
@@ -1643,15 +1611,83 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newData.length > 0) {
|
if (!newData.length) {
|
||||||
|
throw new Error('NO_ROWS_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
risetData = newData;
|
risetData = newData;
|
||||||
isLoadingData = false;
|
isLoadingData = false;
|
||||||
renderRisetCards();
|
renderRisetCards();
|
||||||
updateStatPub(newData.length);
|
updateStatPub(newData.length);
|
||||||
return; // Berhasil! Keluar dari loop
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('NO_ROWS_FOUND');
|
async function fetchDashboardData() {
|
||||||
|
const grid = document.getElementById('riset-grid');
|
||||||
|
if (grid) {
|
||||||
|
grid.innerHTML = `
|
||||||
|
<div class="md:col-span-2 lg:col-span-3 py-20 text-center">
|
||||||
|
<div class="inline-block animate-spin w-10 h-10 border-4 border-untan-blue border-t-transparent rounded-full mb-4"></div>
|
||||||
|
<p class="text-slate-500 font-bold">Mengambil data dari Dashboard...</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Endpoint server.js hanya ada saat development lokal.
|
||||||
|
const proxyServers = shouldUseLocalDashboardProxy() ? [() => '/api/dashboard'] : [];
|
||||||
|
|
||||||
|
// Deployment static memakai proxy publik karena tidak menjalankan server.js.
|
||||||
|
if (targetUrl) {
|
||||||
|
proxyTemplates.forEach((template) => {
|
||||||
|
proxyServers.push((url) => template
|
||||||
|
.replaceAll('{{url}}', encodeURIComponent(url))
|
||||||
|
.replaceAll('{{rawUrl}}', url)
|
||||||
|
.replaceAll('{{ts}}', String(Date.now())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!proxyServers.length) {
|
||||||
|
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(new Error('DASHBOARD_PROXY_NOT_CONFIGURED')), 'Coba Lagi', 'fetchDashboardData()');
|
||||||
|
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) {
|
} catch (error) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
lastError = error;
|
lastError = error;
|
||||||
|
|||||||
+69
-1
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user