feat: major dashboard overhaul and polish
- Switched from mock data to real backend APIs. - Implemented unified ApiClient for consistent frontend data fetching. - Refactored dashboard structure and styles for a modern SaaS aesthetic. - Fixed Axum 0.8+ routing and parameter syntax issues. - Implemented real client creation/deletion and provider health monitoring. - Synchronized WebSocket event structures between backend and frontend.
This commit is contained in:
90
static/js/api.js
Normal file
90
static/js/api.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// Unified API client for the dashboard
|
||||
|
||||
class ApiClient {
|
||||
constructor() {
|
||||
this.baseUrl = '/api';
|
||||
}
|
||||
|
||||
async request(path, options = {}) {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
};
|
||||
|
||||
// Add auth token if available
|
||||
if (window.authManager && window.authManager.token) {
|
||||
headers['Authorization'] = `Bearer ${window.authManager.token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.error || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error(`API Request failed (${path}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async get(path) {
|
||||
return this.request(path, { method: 'GET' });
|
||||
}
|
||||
|
||||
async post(path, body) {
|
||||
return this.request(path, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
}
|
||||
|
||||
async put(path, body) {
|
||||
return this.request(path, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
}
|
||||
|
||||
async delete(path) {
|
||||
return this.request(path, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
// Helper for formatting large numbers
|
||||
formatNumber(num) {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
}
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
// Helper for formatting currency
|
||||
formatCurrency(amount) {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 4
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
// Helper for relative time
|
||||
formatTimeAgo(dateStr) {
|
||||
if (!dateStr) return 'Never';
|
||||
const date = luxon.DateTime.fromISO(dateStr);
|
||||
return date.toRelative();
|
||||
}
|
||||
}
|
||||
|
||||
window.api = new ApiClient();
|
||||
Reference in New Issue
Block a user