Building Dashboards
This guide walks you through building a complete dashboard with the GluonDB SDK.
Dashboard Structure
Every dashboard is a self-contained HTML file with three parts:
html
<!DOCTYPE html>
<html>
<head>
<style>
/* Your CSS styles */
</style>
</head>
<body>
<!-- Your HTML markup -->
<script>
// Your JavaScript using the gluon SDK
</script>
</body>
</html>
> Note: Chart.js and Plotly are pre-loaded. No need to add tags for them.
Step 1: Define Your Datasource
Set a constant for your datasource name at the top of your script:
const DATASOURCE = 'my_database';
Step 2: Create a Query Function
Wrap the SDK call in a helper function with error handling:
async function runQuery(sql) {
try {
const rows = await gluon.query(sql, {
datasource: DATASOURCE,
limit: 5000
});
return { ok: true, rows };
} catch (err) {
console.error('Query error:', err);
return { ok: false, error: err.message };
}
}
Step 3: Build KPI Cards
KPI cards show key metrics at a glance:
HTML:
<div class="kpi-card">
<div class="kpi-label">Total Users</div>
<div class="kpi-value" id="kpi-users">-</div>
</div>
JavaScript:
async function loadKPIs() {
const sql = `
SELECT
COUNT(*) as total_users,
COUNT(*) FILTER (WHERE created_at > NOW() - INTERVAL '30 days') as new_users
FROM users
`;
const res = await runQuery(sql);
if (res.ok && res.rows.length > 0) {
const data = res.rows[0];
document.getElementById('kpi-users').textContent = data.total_users.toLocaleString();
}
}
Step 4: Create Charts with Chart.js
HTML:
<div class="chart-container">
<canvas id="myChart"></canvas>
</div>
JavaScript:
let chartInstance = null;
async function loadChart() {
const sql = `
SELECT
DATE(created_at) as date,
COUNT(*) as count
FROM orders
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY date ASC
`;
const res = await runQuery(sql);
if (!res.ok) return;
const labels = res.rows.map(r => new Date(r.date).toLocaleDateString());
const data = res.rows.map(r => r.count);
// Destroy previous instance if it exists
if (chartInstance) chartInstance.destroy();
const ctx = document.getElementById('myChart').getContext('2d');
chartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Orders',
data: data,
borderColor: '#4f46e5',
backgroundColor: 'rgba(79, 70, 229, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: { beginAtZero: true }
}
}
});
}
Step 5: Build Data Tables
HTML:
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody id="table-body">
<tr><td colspan="3">Loading...</td></tr>
</tbody>
</table>
JavaScript:
async function loadTable() {
const sql = `
SELECT name, email, status
FROM users
ORDER BY created_at DESC
LIMIT 20
`;
const res = await runQuery(sql);
const tbody = document.getElementById('table-body');
if (!res.ok) {
tbody.innerHTML = `<tr><td colspan="3">Error: ${res.error}</td></tr>`;
return;
}
if (res.rows.length === 0) {
tbody.innerHTML = '<tr><td colspan="3">No data found</td></tr>';
return;
}
tbody.innerHTML = res.rows.map(row => `
<tr>
<td>${row.name}</td>
<td>${row.email}</td>
<td><span class="badge badge-${row.status}">${row.status}</span></td>
</tr>
`).join('');
}
Step 6: Add Interactive Filters
HTML:
<select id="dateRange">
<option value="7">Last 7 Days</option>
<option value="30" selected>Last 30 Days</option>
<option value="90">Last 90 Days</option>
</select>
JavaScript:
let filters = {
dateRange: 30
};
async function refreshDashboard() {
await Promise.all([
loadKPIs(),
loadChart(),
loadTable()
]);
}
// Event listener
document.getElementById('dateRange').addEventListener('change', (e) => {
filters.dateRange = parseInt(e.target.value);
refreshDashboard();
});
// Use filters in queries
async function loadKPIs() {
const sql = `
SELECT COUNT(*) as total
FROM users
WHERE created_at > NOW() - INTERVAL '${filters.dateRange} days'
`;
// ... rest of function
}
Step 7: Initialize on Page Load
document.addEventListener('DOMContentLoaded', () => {
refreshDashboard();
});
Complete Example
Here's a minimal but complete dashboard:
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: system-ui, sans-serif;
background: #1a1a2e;
color: #eee;
padding: 20px;
}
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.kpi-card {
background: #252540;
border-radius: 12px;
padding: 24px;
}
.kpi-label {
font-size: 12px;
text-transform: uppercase;
color: #888;
margin-bottom: 8px;
}
.kpi-value {
font-size: 32px;
font-weight: bold;
color: #6366f1;
}
.chart-container {
background: #252540;
border-radius: 12px;
padding: 24px;
height: 400px;
}
</style>
</head>
<body>
<h1>๐ My Dashboard</h1>
<div class="kpi-grid">
<div class="kpi-card">
<div class="kpi-label">Total Users</div>
<div class="kpi-value" id="kpi-users">-</div>
</div>
<div class="kpi-card">
<div class="kpi-label">Total Orders</div>
<div class="kpi-value" id="kpi-orders">-</div>
</div>
</div>
<div class="chart-container">
<canvas id="ordersChart"></canvas>
</div>
<script>
const DATASOURCE = 'my_database';
let chart = null;
async function runQuery(sql) {
try {
const rows = await gluon.query(sql, { datasource: DATASOURCE });
return { ok: true, rows };
} catch (err) {
return { ok: false, error: err.message };
}
}
async function loadKPIs() {
const res = await runQuery(`
SELECT
(SELECT COUNT(*) FROM users) as users,
(SELECT COUNT(*) FROM orders) as orders
`);
if (res.ok && res.rows[0]) {
document.getElementById('kpi-users').textContent = res.rows[0].users?.toLocaleString() || '0';
document.getElementById('kpi-orders').textContent = res.rows[0].orders?.toLocaleString() || '0';
}
}
async function loadChart() {
const res = await runQuery(`
SELECT DATE(created_at) as date, COUNT(*) as count
FROM orders
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY date
`);
if (!res.ok) return;
if (chart) chart.destroy();
chart = new Chart(document.getElementById('ordersChart'), {
type: 'line',
data: {
labels: res.rows.map(r => new Date(r.date).toLocaleDateString()),
datasets: [{
label: 'Orders',
data: res.rows.map(r => r.count),
borderColor: '#6366f1',
backgroundColor: 'rgba(99, 102, 241, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
}
document.addEventListener('DOMContentLoaded', async () => {
await loadKPIs();
await loadChart();
});
</script>
</body>
</html>
Tips
- Dark themes work best โ Dashboards are often viewed in dark mode
- Use CSS Grid โ Makes responsive layouts easy
- Destroy charts before re-rendering โ Prevents memory leaks
- Show loading states โ Users appreciate feedback
- Handle empty data โ Always check if
rows.length === 0