195 lines
4.5 KiB
JavaScript
195 lines
4.5 KiB
JavaScript
/**
|
|
* CLQMS V2 Frontend - Alpine.js Components
|
|
*/
|
|
|
|
document.addEventListener('alpine:init', () => {
|
|
|
|
/**
|
|
* Toast Store
|
|
*/
|
|
Alpine.store('toast', {
|
|
messages: [],
|
|
|
|
show(message, type = 'info', duration = 4000) {
|
|
const id = Date.now();
|
|
this.messages.push({ id, message, type });
|
|
setTimeout(() => this.dismiss(id), duration);
|
|
},
|
|
|
|
dismiss(id) {
|
|
this.messages = this.messages.filter(m => m.id !== id);
|
|
},
|
|
|
|
success(msg) { this.show(msg, 'success'); },
|
|
error(msg) { this.show(msg, 'error', 6000); },
|
|
info(msg) { this.show(msg, 'info'); }
|
|
});
|
|
|
|
/**
|
|
* API Response Store
|
|
*/
|
|
Alpine.store('apiResponse', {
|
|
hasResponse: false,
|
|
status: '',
|
|
statusClass: '',
|
|
time: 0,
|
|
body: '',
|
|
|
|
set(status, body, time) {
|
|
this.hasResponse = true;
|
|
this.status = status;
|
|
this.statusClass = status >= 200 && status < 300 ? 'badge-success' : 'badge-error';
|
|
this.time = time;
|
|
this.body = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* API Tester Component
|
|
*/
|
|
Alpine.data('apiTester', () => ({
|
|
method: 'GET',
|
|
url: '/api/patient',
|
|
body: '{\n \n}',
|
|
loading: false,
|
|
|
|
setEndpoint(method, url) {
|
|
this.method = method;
|
|
this.url = url;
|
|
},
|
|
|
|
async sendRequest() {
|
|
this.loading = true;
|
|
const startTime = performance.now();
|
|
|
|
try {
|
|
const options = {
|
|
method: this.method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
credentials: 'include'
|
|
};
|
|
|
|
if (this.method === 'POST' || this.method === 'PATCH') {
|
|
try {
|
|
options.body = this.body;
|
|
JSON.parse(this.body); // Validate JSON
|
|
} catch (e) {
|
|
Alpine.store('toast').error('Invalid JSON body');
|
|
this.loading = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
const response = await fetch(this.url, options);
|
|
const endTime = performance.now();
|
|
|
|
let data;
|
|
const contentType = response.headers.get('content-type');
|
|
if (contentType && contentType.includes('application/json')) {
|
|
data = await response.json();
|
|
} else {
|
|
data = await response.text();
|
|
}
|
|
|
|
Alpine.store('apiResponse').set(
|
|
response.status,
|
|
data,
|
|
Math.round(endTime - startTime)
|
|
);
|
|
|
|
} catch (err) {
|
|
Alpine.store('toast').error('Request failed: ' + err.message);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
}));
|
|
|
|
/**
|
|
* DB Browser Component
|
|
*/
|
|
Alpine.data('dbBrowser', () => ({
|
|
tables: [],
|
|
loadingTables: true,
|
|
selectedTable: null,
|
|
tableData: null,
|
|
loadingData: false,
|
|
|
|
init() {
|
|
this.loadTables();
|
|
},
|
|
|
|
async loadTables() {
|
|
this.loadingTables = true;
|
|
try {
|
|
const res = await fetch('/v2/api/tables', { credentials: 'include' });
|
|
const data = await res.json();
|
|
this.tables = data.tables || [];
|
|
} catch (e) {
|
|
Alpine.store('toast').error('Failed to load tables');
|
|
} finally {
|
|
this.loadingTables = false;
|
|
}
|
|
},
|
|
|
|
async selectTable(table) {
|
|
this.selectedTable = table;
|
|
this.loadingData = true;
|
|
this.tableData = null;
|
|
|
|
try {
|
|
const res = await fetch(`/v2/api/table/${table}`, { credentials: 'include' });
|
|
this.tableData = await res.json();
|
|
} catch (e) {
|
|
Alpine.store('toast').error('Failed to load table data');
|
|
} finally {
|
|
this.loadingData = false;
|
|
}
|
|
}
|
|
}));
|
|
|
|
/**
|
|
* Logs Viewer Component
|
|
*/
|
|
Alpine.data('logsViewer', () => ({
|
|
logs: [],
|
|
loading: true,
|
|
expandedLogs: [],
|
|
|
|
init() {
|
|
this.loadLogs();
|
|
},
|
|
|
|
async loadLogs() {
|
|
this.loading = true;
|
|
try {
|
|
const res = await fetch('/v2/api/logs', { credentials: 'include' });
|
|
const data = await res.json();
|
|
this.logs = data.logs || [];
|
|
} catch (e) {
|
|
Alpine.store('toast').error('Failed to load logs');
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
toggleLog(name) {
|
|
if (this.expandedLogs.includes(name)) {
|
|
this.expandedLogs = this.expandedLogs.filter(n => n !== name);
|
|
} else {
|
|
this.expandedLogs.push(name);
|
|
}
|
|
},
|
|
|
|
formatSize(bytes) {
|
|
if (bytes < 1024) return bytes + ' B';
|
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
}
|
|
}));
|
|
|
|
});
|