apigateway / templates /index.html
jebin2's picture
table viewer
d72816f
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Database Viewer</title>
<style>
:root {
--primary-color: #6366f1;
--primary-hover: #4f46e5;
--bg-color: #f3f4f6;
--sidebar-bg: #ffffff;
--text-color: #1f2937;
--border-color: #e5e7eb;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar */
#sidebar {
width: 250px;
background-color: var(--sidebar-bg);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: 1rem;
overflow-y: auto;
}
#sidebar h2 {
font-size: 1.25rem;
margin-bottom: 1rem;
color: var(--primary-color);
}
.table-list {
list-style: none;
padding: 0;
margin: 0;
}
.table-item {
padding: 0.75rem 1rem;
margin-bottom: 0.5rem;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 500;
}
.table-item:hover {
background-color: #eff6ff;
color: var(--primary-color);
}
.table-item.active {
background-color: var(--primary-color);
color: white;
}
/* Main Content */
#main-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 1.5rem;
overflow: hidden;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
h1 {
font-size: 1.5rem;
margin: 0;
}
/* Table Container */
#table-container {
flex: 1;
background: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
overflow: auto;
position: relative;
}
table {
width: 100%;
border-collapse: collapse;
white-space: nowrap;
}
th, td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
th {
background-color: #f9fafb;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
}
tr:hover {
background-color: #f9fafb;
}
/* Pagination */
#pagination {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
padding: 0.5rem;
background: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.btn {
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
background-color: white;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.2s;
}
.btn:hover:not(:disabled) {
background-color: #f3f4f6;
border-color: #d1d5db;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#page-info {
font-size: 0.875rem;
color: #6b7280;
}
/* Loading & Empty States */
.loading, .empty {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: #6b7280;
font-size: 1.125rem;
}
</style>
</head>
<body>
<div id="sidebar">
<h2>Tables</h2>
<ul id="table-list" class="table-list">
<!-- Tables will be injected here -->
</ul>
</div>
<div id="main-content">
<header>
<h1 id="current-table-name">Select a Table</h1>
<div id="pagination-controls" style="display: none;">
<!-- Pagination controls -->
</div>
</header>
<div id="table-container">
<div class="empty">Select a table from the sidebar to view data.</div>
</div>
<div id="pagination" style="display: none;">
<button id="prev-btn" class="btn">Previous</button>
<span id="page-info">Page 1 of 1</span>
<button id="next-btn" class="btn">Next</button>
</div>
</div>
<script>
const API_BASE = '/api/schema';
let currentTable = null;
let currentPage = 1;
let totalPages = 1;
const perPage = 50;
// DOM Elements
const tableListEl = document.getElementById('table-list');
const currentTableNameEl = document.getElementById('current-table-name');
const tableContainerEl = document.getElementById('table-container');
const paginationEl = document.getElementById('pagination');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const pageInfoEl = document.getElementById('page-info');
// Initialize
async function init() {
try {
const response = await fetch(`${API_BASE}/tables`);
const tables = await response.json();
renderTableList(tables);
} catch (error) {
console.error('Failed to fetch tables:', error);
tableContainerEl.innerHTML = '<div class="empty">Error loading tables.</div>';
}
}
function renderTableList(tables) {
tableListEl.innerHTML = tables.map(table => `
<li class="table-item" onclick="loadTable('${table}')">${table}</li>
`).join('');
}
async function loadTable(tableName, page = 1) {
currentTable = tableName;
currentPage = page;
// Update UI
document.querySelectorAll('.table-item').forEach(el => {
el.classList.toggle('active', el.textContent === tableName);
});
currentTableNameEl.textContent = tableName;
tableContainerEl.innerHTML = '<div class="loading">Loading...</div>';
paginationEl.style.display = 'none';
try {
const response = await fetch(`${API_BASE}/table/${tableName}?page=${page}&per_page=${perPage}`);
const data = await response.json();
renderTableData(data);
} catch (error) {
console.error(`Failed to fetch data for ${tableName}:`, error);
tableContainerEl.innerHTML = `<div class="empty">Error loading data for ${tableName}.</div>`;
}
}
function renderTableData(data) {
if (!data.data || data.data.length === 0) {
tableContainerEl.innerHTML = '<div class="empty">No data found in this table.</div>';
paginationEl.style.display = 'none';
return;
}
// Calculate pagination
totalPages = Math.ceil(data.total / data.per_page);
updatePaginationUI(data.total);
// Build Table
const columns = data.columns;
const rows = data.data;
let html = '<table><thead><tr>';
columns.forEach(col => {
html += `<th>${col}</th>`;
});
html += '</tr></thead><tbody>';
rows.forEach(row => {
html += '<tr>';
columns.forEach(col => {
let val = row[col];
if (val === null) val = '<span style="color: #9ca3af;">null</span>';
else if (typeof val === 'object') val = JSON.stringify(val);
html += `<td>${val}</td>`;
});
html += '</tr>';
});
html += '</tbody></table>';
tableContainerEl.innerHTML = html;
}
function updatePaginationUI(totalItems) {
if (totalItems === 0) {
paginationEl.style.display = 'none';
return;
}
paginationEl.style.display = 'flex';
pageInfoEl.textContent = `Page ${currentPage} of ${totalPages} (${totalItems} items)`;
prevBtn.disabled = currentPage <= 1;
nextBtn.disabled = currentPage >= totalPages;
}
// Event Listeners
prevBtn.addEventListener('click', () => {
if (currentPage > 1) loadTable(currentTable, currentPage - 1);
});
nextBtn.addEventListener('click', () => {
if (currentPage < totalPages) loadTable(currentTable, currentPage + 1);
});
// Start
init();
</script>
</body>
</html>