223 lines
6.6 KiB
JavaScript
223 lines
6.6 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const app = document.getElementById('app');
|
|
if (!app) return;
|
|
|
|
const ionApp = document.createElement('ion-app');
|
|
const ionContent = document.createElement('ion-content');
|
|
ionContent.classList.add('ion-padding');
|
|
|
|
const mainDiv = document.createElement('div');
|
|
|
|
ionContent.appendChild(mainDiv);
|
|
ionApp.appendChild(ionContent);
|
|
app.appendChild(ionApp);
|
|
|
|
let jobs = [];
|
|
let filteredJobs = [];
|
|
let filters = {};
|
|
let sortField = null;
|
|
let sortAsc = true;
|
|
|
|
const columns = ['id', 'kind', 'host', 'database', 'user'];
|
|
const displayNames = ['ID', 'Kind', 'Host', 'Database', 'User'];
|
|
|
|
const headerGrid = document.createElement('ion-grid');
|
|
const filterRow = document.createElement('ion-row');
|
|
const headerRow = document.createElement('ion-row');
|
|
|
|
filterRow.style.alignItems = 'center';
|
|
headerRow.style.alignItems = 'center';
|
|
|
|
columns.forEach((col, idx) => {
|
|
// Filters row with ion-searchbar filter
|
|
const filterCol = document.createElement('ion-col');
|
|
filterCol.size = '2';
|
|
|
|
const filterInput = document.createElement('ion-searchbar');
|
|
filterInput.placeholder = `Filter ${displayNames[idx]}`;
|
|
filterInput.style.setProperty('--min-height', '32px');
|
|
filterInput.value = filters[col] || '';
|
|
filterInput.addEventListener('ionInput', e => {
|
|
const val = e.target.value.trim().toLowerCase();
|
|
if (val) filters[col] = val;
|
|
else delete filters[col];
|
|
applyFilterSortRender();
|
|
});
|
|
filterCol.appendChild(filterInput);
|
|
filterRow.appendChild(filterCol);
|
|
|
|
// Header row labels with sorting
|
|
const headerCol = document.createElement('ion-col');
|
|
headerCol.size = '2';
|
|
headerCol.style.fontWeight = 'bold';
|
|
headerCol.style.cursor = 'pointer';
|
|
headerCol.textContent = displayNames[idx];
|
|
headerCol.addEventListener('click', () => {
|
|
if (sortField === col) {
|
|
sortAsc = !sortAsc;
|
|
} else {
|
|
sortField = col;
|
|
sortAsc = true;
|
|
}
|
|
applyFilterSortRender();
|
|
});
|
|
if (sortField === col) {
|
|
headerCol.textContent += sortAsc ? ' ▲' : ' ▼';
|
|
}
|
|
headerCol.classList.add('ion-text-center');
|
|
headerRow.appendChild(headerCol);
|
|
});
|
|
|
|
// Actions column header and filter column (empty)
|
|
const actionsFilterCol = document.createElement('ion-col');
|
|
actionsFilterCol.size = '2';
|
|
filterRow.appendChild(actionsFilterCol);
|
|
|
|
const actionsHeaderCol = document.createElement('ion-col');
|
|
actionsHeaderCol.size = '2';
|
|
actionsHeaderCol.style.fontWeight = 'bold';
|
|
actionsHeaderCol.textContent = 'Actions';
|
|
actionsHeaderCol.classList.add('ion-text-center'); // Center the header text
|
|
headerRow.appendChild(actionsHeaderCol);
|
|
|
|
headerGrid.appendChild(filterRow);
|
|
headerGrid.appendChild(headerRow);
|
|
|
|
const ionList = document.createElement('ion-list');
|
|
|
|
mainDiv.appendChild(headerGrid);
|
|
mainDiv.appendChild(ionList);
|
|
|
|
async function loadJobs() {
|
|
try {
|
|
const res = await fetch('/list');
|
|
jobs = await res.json();
|
|
applyFilterSortRender();
|
|
} catch {
|
|
await presentAlert('Failed to load jobs.');
|
|
}
|
|
}
|
|
|
|
function applyFilterSortRender() {
|
|
filteredJobs = jobs.filter(job =>
|
|
Object.entries(filters).every(([k, v]) =>
|
|
(job[k] || '').toLowerCase().includes(v)
|
|
)
|
|
);
|
|
if (sortField) {
|
|
filteredJobs.sort((a, b) => {
|
|
if (a[sortField] < b[sortField]) return sortAsc ? -1 : 1;
|
|
if (a[sortField] > b[sortField]) return sortAsc ? 1 : -1;
|
|
return 0;
|
|
});
|
|
}
|
|
renderJobList();
|
|
}
|
|
|
|
// Show an Ionic alert with OK button
|
|
async function presentAlert(message) {
|
|
return new Promise(async (resolve) => {
|
|
const alert = document.createElement('ion-alert');
|
|
alert.header = 'Result';
|
|
alert.message = message;
|
|
alert.buttons = ['OK'];
|
|
document.body.appendChild(alert);
|
|
await alert.present();
|
|
alert.addEventListener('didDismiss', () => {
|
|
alert.remove();
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
// Show Ionic confirmation alert, resolves boolean
|
|
async function presentConfirm(message) {
|
|
return new Promise(async (resolve) => {
|
|
const alert = document.createElement('ion-alert');
|
|
alert.header = 'Confirm';
|
|
alert.message = message;
|
|
alert.buttons = [
|
|
{
|
|
text: 'Cancel',
|
|
role: 'cancel',
|
|
handler: () => resolve(false),
|
|
},
|
|
{
|
|
text: 'OK',
|
|
handler: () => resolve(true),
|
|
},
|
|
];
|
|
document.body.appendChild(alert);
|
|
await alert.present();
|
|
alert.addEventListener('didDismiss', () => {
|
|
alert.remove();
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderJobList() {
|
|
ionList.innerHTML = '';
|
|
filteredJobs.forEach(job => {
|
|
const ionItem = document.createElement('ion-item');
|
|
|
|
const grid = document.createElement('ion-grid');
|
|
const row = document.createElement('ion-row');
|
|
|
|
columns.forEach(col => {
|
|
const colElem = document.createElement('ion-col');
|
|
colElem.size = '2';
|
|
colElem.textContent = job[col] || '';
|
|
colElem.classList.add('ion-text-center'); // Center column text
|
|
row.appendChild(colElem);
|
|
});
|
|
|
|
const actionCol = document.createElement('ion-col');
|
|
actionCol.size = '2';
|
|
actionCol.classList.add('ion-text-center'); // Center alignment
|
|
|
|
const dumpBtn = document.createElement('ion-button');
|
|
dumpBtn.color = 'primary';
|
|
dumpBtn.size = 'small';
|
|
dumpBtn.textContent = 'Dump';
|
|
dumpBtn.addEventListener('click', () => triggerAction(job.id, 'dump'));
|
|
|
|
const restoreBtn = document.createElement('ion-button');
|
|
restoreBtn.color = 'danger';
|
|
restoreBtn.size = 'small';
|
|
restoreBtn.textContent = 'Restore';
|
|
restoreBtn.addEventListener('click', async () => {
|
|
const confirmed = await presentConfirm(`Confirm restore job ${job.id}?`);
|
|
if (confirmed) {
|
|
triggerAction(job.id, 'restore');
|
|
}
|
|
});
|
|
|
|
actionCol.appendChild(dumpBtn);
|
|
actionCol.appendChild(restoreBtn);
|
|
row.appendChild(actionCol);
|
|
|
|
grid.appendChild(row);
|
|
ionItem.appendChild(grid);
|
|
|
|
ionList.appendChild(ionItem);
|
|
});
|
|
}
|
|
|
|
async function triggerAction(jobId, action) {
|
|
try {
|
|
const res = await fetch(`/${action}`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({id: jobId}),
|
|
});
|
|
const result = await res.json();
|
|
await presentAlert(`${action.toUpperCase()} result: ${result.result || 'Unknown'}`);
|
|
loadJobs();
|
|
} catch {
|
|
await presentAlert(`Error running ${action}`);
|
|
}
|
|
}
|
|
|
|
loadJobs();
|
|
});
|