fix gradients

This commit is contained in:
2026-02-14 00:11:59 +01:00
parent 1e126734b0
commit 05c916e9a9
9 changed files with 729 additions and 442 deletions

View File

@@ -25,9 +25,42 @@ async function loadStats() {
const res = await fetch('/admin/stats');
const data = await res.json();
document.getElementById('stats-grid').innerHTML =
'<div class="bg-white p-4 rounded-lg shadow text-center"><h4 class="text-gray-500 text-sm mb-2">Clients</h4><div class="text-2xl font-bold text-slate-800">' + data.client_count + '</div></div>' +
'<div class="bg-white p-4 rounded-lg shadow text-center"><h4 class="text-gray-500 text-sm mb-2">Total Snapshots</h4><div class="text-2xl font-bold text-slate-800">' + data.total_snapshots + '</div></div>' +
'<div class="bg-white p-4 rounded-lg shadow text-center"><h4 class="text-gray-500 text-sm mb-2">Total Storage</h4><div class="text-2xl font-bold text-slate-800">' + data.total_storage_gb.toFixed(2) + ' GB</div></div>';
'<div class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">' +
'<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>' +
'<div class="relative">' +
'<div class="flex items-center justify-between mb-4">' +
'<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">Clients</span>' +
'<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">' +
'<i class="fas fa-users text-violet-600 dark:text-violet-400"></i>' +
'</div>' +
'</div>' +
'<div class="text-3xl font-bold text-slate-900 dark:text-white">' + data.client_count + '</div>' +
'</div>' +
'</div>' +
'<div class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">' +
'<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>' +
'<div class="relative">' +
'<div class="flex items-center justify-between mb-4">' +
'<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">Total Snapshots</span>' +
'<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">' +
'<i class="fas fa-camera text-violet-600 dark:text-violet-400"></i>' +
'</div>' +
'</div>' +
'<div class="text-3xl font-bold text-slate-900 dark:text-white">' + data.total_snapshots + '</div>' +
'</div>' +
'</div>' +
'<div class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">' +
'<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>' +
'<div class="relative">' +
'<div class="flex items-center justify-between mb-4">' +
'<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">Total Storage</span>' +
'<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">' +
'<i class="fas fa-hard-drive text-violet-600 dark:text-violet-400"></i>' +
'</div>' +
'</div>' +
'<div class="text-3xl font-bold text-slate-900 dark:text-white">' + data.total_storage_gb.toFixed(2) + ' GB</div>' +
'</div>' +
'</div>';
} catch (e) {
console.error('Failed to load stats:', e);
}
@@ -44,21 +77,21 @@ async function loadClients() {
const usedPercent = c.max_size_bytes > 0 ? (c.current_usage / c.max_size_bytes * 100).toFixed(1) : 0;
const usedGB = (c.current_usage / (1024*1024*1024)).toFixed(2);
const maxGB = (c.max_size_bytes / (1024*1024*1024)).toFixed(0);
return '<tr class="border-b hover:bg-gray-50">' +
'<td class="py-2 px-2 font-semibold">' + c.client_id + '</td>' +
'<td class="py-2 px-2"><span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">' + c.storage_type + '</span></td>' +
'<td class="py-2 px-2">' + maxGB + ' GB</td>' +
'<td class="py-2 px-2">' +
'<div>' + usedGB + ' GB (' + usedPercent + '%)</div>' +
'<div class="w-24 h-2 bg-gray-200 rounded overflow-hidden mt-1"><div class="h-full bg-primary transition-all" style="width: ' + Math.min(usedPercent, 100) + '%"></div></div>' +
return '<tr class="border-b border-slate-100 dark:border-slate-700/30 hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">' +
'<td class="py-4 px-4 font-semibold text-slate-900 dark:text-white">' + c.client_id + '</td>' +
'<td class="py-4 px-4"><span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30">' + c.storage_type + '</span></td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + maxGB + ' GB</td>' +
'<td class="py-4 px-4">' +
'<div class="text-slate-600 dark:text-slate-300">' + usedGB + ' GB (' + usedPercent + '%)</div>' +
'<div class="w-24 h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden mt-2"><div class="h-full bg-gradient-to-r from-violet-500 to-fuchsia-500 transition-all rounded-full" style="width: ' + Math.min(usedPercent, 100) + '%"></div></div>' +
'</td>' +
'<td class="py-2 px-2">' + c.snapshot_count + '</td>' +
'<td class="py-2 px-2">' + (c.enabled ? '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-800">Enabled</span>' : '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-red-100 text-red-800">Disabled</span>') + '</td>' +
'<td class="py-2 px-2 whitespace-nowrap">' +
'<button class="px-2 py-1 text-xs rounded bg-warning hover:bg-yellow-600 text-white mr-1" data-action="edit-client" data-client-id="' + c.client_id + '">Edit</button>' +
'<button class="px-2 py-1 text-xs rounded bg-purple-500 hover:bg-purple-600 text-white mr-1" data-action="set-client-key" data-client-id="' + c.client_id + '">Set Key</button>' +
'<button class="px-2 py-1 text-xs rounded bg-orange-500 hover:bg-orange-600 text-white mr-1" data-action="reset-client-key" data-client-id="' + c.client_id + '">Reset Key</button>' +
'<button class="px-2 py-1 text-xs rounded bg-danger hover:bg-red-600 text-white" data-action="delete-client" data-client-id="' + c.client_id + '">Delete</button>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + c.snapshot_count + '</td>' +
'<td class="py-4 px-4">' + (c.enabled ? '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30">Enabled</span>' : '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400 border border-red-200 dark:border-red-500/30">Disabled</span>') + '</td>' +
'<td class="py-4 px-4 whitespace-nowrap">' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-400 hover:to-orange-400 text-white mr-1.5 shadow-lg shadow-amber-500/20 transition-all" data-action="edit-client" data-client-id="' + c.client_id + '"><i class="fas fa-edit mr-1"></i>Edit</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-purple-500 to-violet-500 hover:from-purple-400 hover:to-violet-400 text-white mr-1.5 shadow-lg shadow-purple-500/20 transition-all" data-action="set-client-key" data-client-id="' + c.client_id + '"><i class="fas fa-key mr-1"></i>Set Key</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-400 hover:to-amber-400 text-white mr-1.5 shadow-lg shadow-orange-500/20 transition-all" data-action="reset-client-key" data-client-id="' + c.client_id + '"><i class="fas fa-rotate mr-1"></i>Reset</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-red-500 to-rose-500 hover:from-red-400 hover:to-rose-400 text-white shadow-lg shadow-red-500/20 transition-all" data-action="delete-client" data-client-id="' + c.client_id + '"><i class="fas fa-trash mr-1"></i>Delete</button>' +
'</td>' +
'</tr>';
}).join('');
@@ -84,16 +117,16 @@ async function loadSnapshots() {
const tbody = document.getElementById('snapshots-table');
tbody.innerHTML = snapshots.map(s => {
const sizeGB = (s.size_bytes / (1024*1024*1024)).toFixed(2);
return '<tr class="border-b hover:bg-gray-50">' +
'<td class="py-2 px-2">' + s.client_id + '</td>' +
'<td class="py-2 px-2">' + s.snapshot_id + '</td>' +
'<td class="py-2 px-2">' + new Date(s.timestamp).toLocaleString() + '</td>' +
'<td class="py-2 px-2">' + sizeGB + ' GB</td>' +
'<td class="py-2 px-2">' +
(s.incremental ? '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">Incremental</span>' : '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-800">Full</span>') +
(s.compressed ? ' <span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">LZ4</span>' : '') +
return '<tr class="border-b border-slate-100 dark:border-slate-700/30 hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + s.client_id + '</td>' +
'<td class="py-4 px-4 font-semibold text-slate-900 dark:text-white">' + s.snapshot_id + '</td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + new Date(s.timestamp).toLocaleString() + '</td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + sizeGB + ' GB</td>' +
'<td class="py-4 px-4">' +
(s.incremental ? '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30">Incremental</span>' : '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30">Full</span>') +
(s.compressed ? ' <span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-purple-100 text-purple-700 dark:bg-purple-500/20 dark:text-purple-400 border border-purple-200 dark:border-purple-500/30">LZ4</span>' : '') +
'</td>' +
'<td class="py-2 px-2"><button class="px-2 py-1 text-xs rounded bg-danger hover:bg-red-600 text-white" data-action="delete-snapshot" data-client-id="' + s.client_id + '" data-snapshot-id="' + s.snapshot_id + '">Delete</button></td>' +
'<td class="py-4 px-4"><button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-red-500 to-rose-500 hover:from-red-400 hover:to-rose-400 text-white shadow-lg shadow-red-500/20 transition-all" data-action="delete-snapshot" data-client-id="' + s.client_id + '" data-snapshot-id="' + s.snapshot_id + '"><i class="fas fa-trash mr-1"></i>Delete</button></td>' +
'</tr>';
}).join('');
} catch (e) {
@@ -109,14 +142,14 @@ async function loadAdmins() {
const tbody = document.getElementById('admins-table');
tbody.innerHTML = admins.map(a =>
'<tr class="border-b hover:bg-gray-50">' +
'<td class="py-2 px-2">' + a.id + '</td>' +
'<td class="py-2 px-2 font-semibold">' + a.username + '</td>' +
'<td class="py-2 px-2"><span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">' + a.role + '</span></td>' +
'<td class="py-2 px-2">' + new Date(a.created_at).toLocaleDateString() + '</td>' +
'<td class="py-2 px-2 whitespace-nowrap">' +
'<button class="px-2 py-1 text-xs rounded bg-warning hover:bg-yellow-600 text-white mr-1" data-action="change-admin-password" data-admin-id="' + a.id + '" data-admin-username="' + a.username + '">Change Password</button>' +
'<button class="px-2 py-1 text-xs rounded bg-danger hover:bg-red-600 text-white" data-action="delete-admin" data-admin-id="' + a.id + '">Delete</button>' +
'<tr class="border-b border-slate-100 dark:border-slate-700/30 hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">' +
'<td class="py-4 px-4 text-slate-400 dark:text-slate-500">' + a.id + '</td>' +
'<td class="py-4 px-4 font-semibold text-slate-900 dark:text-white">' + a.username + '</td>' +
'<td class="py-4 px-4"><span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30">' + a.role + '</span></td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + new Date(a.created_at).toLocaleDateString() + '</td>' +
'<td class="py-4 px-4 whitespace-nowrap">' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-400 hover:to-orange-400 text-white mr-1.5 shadow-lg shadow-amber-500/20 transition-all" data-action="change-admin-password" data-admin-id="' + a.id + '" data-admin-username="' + a.username + '"><i class="fas fa-key mr-1"></i>Change Password</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-red-500 to-rose-500 hover:from-red-400 hover:to-rose-400 text-white shadow-lg shadow-red-500/20 transition-all" data-action="delete-admin" data-admin-id="' + a.id + '"><i class="fas fa-trash mr-1"></i>Delete</button>' +
'</td>' +
'</tr>'
).join('');
@@ -129,11 +162,12 @@ async function loadAdmins() {
function showTab(tab) {
currentTab = tab;
document.querySelectorAll('[data-tab]').forEach(t => {
t.classList.remove('bg-primary', 'text-white');
t.classList.add('bg-white', 'text-gray-600');
t.classList.remove('bg-gradient-to-r', 'from-violet-600', 'to-fuchsia-600', 'text-white', 'shadow-lg', 'shadow-violet-500/25');
t.classList.add('bg-slate-100', 'dark:bg-slate-800', 'text-slate-600', 'dark:text-slate-400', 'hover:bg-slate-200', 'dark:hover:bg-slate-700', 'hover:text-slate-900', 'dark:hover:text-white', 'border', 'border-slate-200', 'dark:border-slate-700');
});
document.querySelector('[data-tab="' + tab + '"]').classList.remove('bg-white', 'text-gray-600');
document.querySelector('[data-tab="' + tab + '"]').classList.add('bg-primary', 'text-white');
const activeTab = document.querySelector('[data-tab="' + tab + '"]');
activeTab.classList.remove('bg-slate-100', 'dark:bg-slate-800', 'text-slate-600', 'dark:text-slate-400', 'hover:bg-slate-200', 'dark:hover:bg-slate-700', 'hover:text-slate-900', 'dark:hover:text-white', 'border', 'border-slate-200', 'dark:border-slate-700');
activeTab.classList.add('bg-gradient-to-r', 'from-violet-600', 'to-fuchsia-600', 'text-white', 'shadow-lg', 'shadow-violet-500/25');
document.getElementById('clients-tab').classList.add('hidden');
document.getElementById('snapshots-tab').classList.add('hidden');
@@ -507,16 +541,18 @@ document.getElementById('change-password-form').addEventListener('submit', async
}
try {
const res = await fetch('/admin/admin/password', {
const res = await fetch('/admin/admin/change-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ admin_id: parseInt(adminId), new_password: newPassword })
body: JSON.stringify({
admin_id: parseInt(adminId),
new_password: newPassword
})
});
const data = await res.json();
if (data.success) {
closeModal('change-password-modal');
alert('Password changed successfully');
} else {
alert(data.message || 'Failed to change password');
}
@@ -539,24 +575,27 @@ document.getElementById('client-password-form').addEventListener('submit', async
}
try {
const res = await fetch('/admin/client/password', {
const res = await fetch('/admin/client/set-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ client_id: clientId, api_key: newKey })
body: JSON.stringify({
client_id: clientId,
new_api_key: newKey
})
});
const data = await res.json();
if (data.success) {
closeModal('client-password-modal');
alert('API key changed successfully for ' + clientId);
} else {
alert(data.message || 'Failed to change API key');
alert(data.message || 'Failed to set API key');
}
} catch (e) {
alert('Failed to change API key');
alert('Failed to set API key');
}
});
// Initialize
checkAuth();
loadStats();
loadClients();