fix admin panel
This commit is contained in:
@@ -553,10 +553,50 @@ func (s *Server) handleAdminGetAdmins(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
// handleAdminChangePassword handles admin password change
|
||||
func (s *Server) handleAdminChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
admin, err := s.authenticateAdmin(r)
|
||||
if err != nil || admin == nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ID int `json:"id"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Password == "" {
|
||||
http.Error(w, "Password required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.db.UpdateAdminPassword(req.ID, hashAPIKey(req.Password)); err != nil {
|
||||
http.Error(w, "Failed to update password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "Password changed successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// handleAdminUI serves the admin panel UI
|
||||
func (s *Server) handleAdminUI(w http.ResponseWriter, r *http.Request) {
|
||||
// Serve the embedded admin UI HTML
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(adminPanelHTML))
|
||||
}
|
||||
|
||||
@@ -586,9 +626,12 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
.btn-danger:hover { background: #c0392b; }
|
||||
.btn-success { background: #27ae60; }
|
||||
.btn-success:hover { background: #229954; }
|
||||
.btn-sm { padding: 6px 12px; font-size: 12px; }
|
||||
.btn-warning { background: #f39c12; }
|
||||
.btn-warning:hover { background: #d68910; }
|
||||
.btn-sm { padding: 6px 12px; font-size: 12px; margin: 2px; }
|
||||
.error { color: #e74c3c; margin-bottom: 20px; text-align: center; }
|
||||
.hidden { display: none; }
|
||||
.success { color: #27ae60; margin-bottom: 20px; text-align: center; }
|
||||
.hidden { display: none !important; }
|
||||
.card { background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
||||
.card-header { padding: 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
|
||||
.card-header h3 { color: #2c3e50; }
|
||||
@@ -605,11 +648,12 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; }
|
||||
.stat-card h4 { color: #666; font-size: 14px; margin-bottom: 10px; }
|
||||
.stat-card .value { font-size: 32px; font-weight: bold; color: #2c3e50; }
|
||||
.modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
||||
.modal-content { background: white; padding: 30px; border-radius: 8px; max-width: 500px; width: 90%; }
|
||||
.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
||||
.modal-content { background: white; padding: 30px; border-radius: 8px; max-width: 500px; width: 90%; max-height: 90vh; overflow-y: auto; }
|
||||
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.modal-header h3 { color: #2c3e50; }
|
||||
.close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: #666; }
|
||||
.close-btn { background: none; border: none; font-size: 28px; cursor: pointer; color: #666; line-height: 1; }
|
||||
.close-btn:hover { color: #333; }
|
||||
.tabs { display: flex; gap: 10px; margin-bottom: 20px; }
|
||||
.tab { padding: 10px 20px; background: white; border: none; border-radius: 4px; cursor: pointer; color: #666; }
|
||||
.tab.active { background: #3498db; color: white; }
|
||||
@@ -619,26 +663,27 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
</head>
|
||||
<body>
|
||||
<div id="login-page" class="login-container">
|
||||
<h2>🔐 Admin Login</h2>
|
||||
<h2>Admin Login</h2>
|
||||
<div id="login-error" class="error hidden"></div>
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" id="username" required>
|
||||
<input type="text" id="username" required autocomplete="username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" id="password" required>
|
||||
<input type="password" id="password" required autocomplete="current-password">
|
||||
</div>
|
||||
<button type="submit" class="btn" style="width: 100%;">Login</button>
|
||||
</form>
|
||||
<p style="margin-top: 20px; text-align: center; color: #666; font-size: 12px;">Default: admin / admin123</p>
|
||||
</div>
|
||||
|
||||
<div id="admin-page" class="container hidden">
|
||||
<div class="header">
|
||||
<h1>📦 ZFS Backup Admin Panel</h1>
|
||||
<h1>ZFS Backup Admin Panel</h1>
|
||||
<div>
|
||||
<span id="admin-user"></span>
|
||||
<span id="admin-user" style="margin-right: 15px;"></span>
|
||||
<button onclick="logout()">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -646,16 +691,16 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
<div class="stats-grid" id="stats-grid"></div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab active" onclick="showTab('clients')">Clients</button>
|
||||
<button class="tab" onclick="showTab('snapshots')">Snapshots</button>
|
||||
<button class="tab" onclick="showTab('admins')">Admins</button>
|
||||
<button class="tab active" data-tab="clients" onclick="showTab('clients')">Clients</button>
|
||||
<button class="tab" data-tab="snapshots" onclick="showTab('snapshots')">Snapshots</button>
|
||||
<button class="tab" data-tab="admins" onclick="showTab('admins')">Admins</button>
|
||||
</div>
|
||||
|
||||
<div id="clients-tab">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Clients</h3>
|
||||
<button class="btn btn-success" onclick="showAddClientModal()">+ Add Client</button>
|
||||
<button class="btn btn-success" onclick="showModal('add-client-modal')">+ Add Client</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table>
|
||||
@@ -706,7 +751,7 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Admin Users</h3>
|
||||
<button class="btn btn-success" onclick="showAddAdminModal()">+ Add Admin</button>
|
||||
<button class="btn btn-success" onclick="showModal('add-admin-modal')">+ Add Admin</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table>
|
||||
@@ -727,11 +772,11 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
</div>
|
||||
|
||||
<!-- Add Client Modal -->
|
||||
<div id="add-client-modal" class="modal hidden">
|
||||
<div class="modal-content">
|
||||
<div id="add-client-modal" class="modal-overlay hidden" onclick="closeModalOnOverlay(event, 'add-client-modal')">
|
||||
<div class="modal-content" onclick="event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<h3>Add New Client</h3>
|
||||
<button class="close-btn" onclick="closeModal('add-client-modal')">×</button>
|
||||
<button type="button" class="close-btn" onclick="closeModal('add-client-modal')">×</button>
|
||||
</div>
|
||||
<form id="add-client-form">
|
||||
<div class="form-group">
|
||||
@@ -755,7 +800,7 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Quota (GB)</label>
|
||||
<input type="number" id="new-client-quota" value="100">
|
||||
<input type="number" id="new-client-quota" value="100" min="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Enabled</label>
|
||||
@@ -765,19 +810,19 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<div class="form-group">
|
||||
<label>Keep Hourly</label>
|
||||
<input type="number" id="new-client-hourly" value="24">
|
||||
<input type="number" id="new-client-hourly" value="24" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Keep Daily</label>
|
||||
<input type="number" id="new-client-daily" value="7">
|
||||
<input type="number" id="new-client-daily" value="7" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Keep Weekly</label>
|
||||
<input type="number" id="new-client-weekly" value="4">
|
||||
<input type="number" id="new-client-weekly" value="4" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Keep Monthly</label>
|
||||
<input type="number" id="new-client-monthly" value="12">
|
||||
<input type="number" id="new-client-monthly" value="12" min="0">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success" style="width: 100%; margin-top: 20px;">Create Client</button>
|
||||
@@ -785,12 +830,68 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Client Modal -->
|
||||
<div id="edit-client-modal" class="modal-overlay hidden" onclick="closeModalOnOverlay(event, 'edit-client-modal')">
|
||||
<div class="modal-content" onclick="event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<h3>Edit Client</h3>
|
||||
<button type="button" class="close-btn" onclick="closeModal('edit-client-modal')">×</button>
|
||||
</div>
|
||||
<form id="edit-client-form">
|
||||
<input type="hidden" id="edit-client-id">
|
||||
<div class="form-group">
|
||||
<label>New API Key (leave empty to keep current)</label>
|
||||
<input type="text" id="edit-client-apikey" placeholder="Leave empty to keep current">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Storage Type</label>
|
||||
<select id="edit-client-storage">
|
||||
<option value="s3">S3</option>
|
||||
<option value="local">Local ZFS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Target Dataset</label>
|
||||
<input type="text" id="edit-client-dataset">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Quota (GB)</label>
|
||||
<input type="number" id="edit-client-quota" min="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Enabled</label>
|
||||
<input type="checkbox" id="edit-client-enabled">
|
||||
</div>
|
||||
<h4 style="margin: 20px 0 10px; color: #666;">Rotation Policy</h4>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<div class="form-group">
|
||||
<label>Keep Hourly</label>
|
||||
<input type="number" id="edit-client-hourly" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Keep Daily</label>
|
||||
<input type="number" id="edit-client-daily" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Keep Weekly</label>
|
||||
<input type="number" id="edit-client-weekly" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Keep Monthly</label>
|
||||
<input type="number" id="edit-client-monthly" min="0">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success" style="width: 100%; margin-top: 20px;">Update Client</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Admin Modal -->
|
||||
<div id="add-admin-modal" class="modal hidden">
|
||||
<div class="modal-content">
|
||||
<div id="add-admin-modal" class="modal-overlay hidden" onclick="closeModalOnOverlay(event, 'add-admin-modal')">
|
||||
<div class="modal-content" onclick="event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<h3>Add New Admin</h3>
|
||||
<button class="close-btn" onclick="closeModal('add-admin-modal')">×</button>
|
||||
<button type="button" class="close-btn" onclick="closeModal('add-admin-modal')">×</button>
|
||||
</div>
|
||||
<form id="add-admin-form">
|
||||
<div class="form-group">
|
||||
@@ -812,6 +913,32 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Password Modal -->
|
||||
<div id="change-password-modal" class="modal-overlay hidden" onclick="closeModalOnOverlay(event, 'change-password-modal')">
|
||||
<div class="modal-content" onclick="event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<h3>Change Password</h3>
|
||||
<button type="button" class="close-btn" onclick="closeModal('change-password-modal')">×</button>
|
||||
</div>
|
||||
<form id="change-password-form">
|
||||
<input type="hidden" id="change-password-admin-id">
|
||||
<div class="form-group">
|
||||
<label>Admin Username</label>
|
||||
<input type="text" id="change-password-username" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>New Password</label>
|
||||
<input type="password" id="change-password-new" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Confirm New Password</label>
|
||||
<input type="password" id="change-password-confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success" style="width: 100%; margin-top: 20px;">Change Password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentTab = 'clients';
|
||||
|
||||
@@ -890,7 +1017,7 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
|
||||
const tbody = document.getElementById('clients-table');
|
||||
tbody.innerHTML = clients.map(c => {
|
||||
const usedPercent = (c.current_usage / c.max_size_bytes * 100).toFixed(1);
|
||||
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>' +
|
||||
@@ -904,6 +1031,7 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
'<td>' + c.snapshot_count + '</td>' +
|
||||
'<td>' + (c.enabled ? '<span class="badge badge-success">Enabled</span>' : '<span class="badge badge-danger">Disabled</span>') + '</td>' +
|
||||
'<td>' +
|
||||
'<button class="btn btn-sm btn-warning" onclick="editClient(\'' + c.client_id + '\')">Edit</button>' +
|
||||
'<button class="btn btn-sm btn-danger" onclick="deleteClient(\'' + c.client_id + '\')">Delete</button>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
@@ -960,7 +1088,10 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
'<td><strong>' + a.username + '</strong></td>' +
|
||||
'<td><span class="badge badge-info">' + a.role + '</span></td>' +
|
||||
'<td>' + new Date(a.created_at).toLocaleDateString() + '</td>' +
|
||||
'<td><button class="btn btn-sm btn-danger" onclick="deleteAdmin(' + a.id + ')">Delete</button></td>' +
|
||||
'<td>' +
|
||||
'<button class="btn btn-sm btn-warning" onclick="showChangePassword(' + a.id + ', \'' + a.username + '\')">Change Password</button>' +
|
||||
'<button class="btn btn-sm btn-danger" onclick="deleteAdmin(' + a.id + ')">Delete</button>' +
|
||||
'</td>' +
|
||||
'</tr>'
|
||||
).join('');
|
||||
} catch (e) {
|
||||
@@ -972,7 +1103,7 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
function showTab(tab) {
|
||||
currentTab = tab;
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
document.querySelector('.tab[data-tab="' + tab + '"]').classList.add('active');
|
||||
|
||||
document.getElementById('clients-tab').classList.add('hidden');
|
||||
document.getElementById('snapshots-tab').classList.add('hidden');
|
||||
@@ -984,18 +1115,47 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
}
|
||||
|
||||
// Modal functions
|
||||
function showAddClientModal() {
|
||||
document.getElementById('add-client-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showAddAdminModal() {
|
||||
document.getElementById('add-admin-modal').classList.remove('hidden');
|
||||
function showModal(id) {
|
||||
document.getElementById(id).classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
document.getElementById(id).classList.add('hidden');
|
||||
}
|
||||
|
||||
function closeModalOnOverlay(event, id) {
|
||||
if (event.target === event.currentTarget) {
|
||||
closeModal(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Edit client
|
||||
async function editClient(clientId) {
|
||||
try {
|
||||
const res = await fetch('/admin/client?client_id=' + clientId);
|
||||
const data = await res.json();
|
||||
const c = data.client;
|
||||
|
||||
document.getElementById('edit-client-id').value = c.client_id;
|
||||
document.getElementById('edit-client-apikey').value = '';
|
||||
document.getElementById('edit-client-storage').value = c.storage_type;
|
||||
document.getElementById('edit-client-dataset').value = c.dataset;
|
||||
document.getElementById('edit-client-quota').value = Math.round(c.max_size_bytes / (1024*1024*1024));
|
||||
document.getElementById('edit-client-enabled').checked = c.enabled;
|
||||
|
||||
if (c.rotation_policy) {
|
||||
document.getElementById('edit-client-hourly').value = c.rotation_policy.keep_hourly || 0;
|
||||
document.getElementById('edit-client-daily').value = c.rotation_policy.keep_daily || 0;
|
||||
document.getElementById('edit-client-weekly').value = c.rotation_policy.keep_weekly || 0;
|
||||
document.getElementById('edit-client-monthly').value = c.rotation_policy.keep_monthly || 0;
|
||||
}
|
||||
|
||||
showModal('edit-client-modal');
|
||||
} catch (e) {
|
||||
alert('Failed to load client data');
|
||||
}
|
||||
}
|
||||
|
||||
// Add client
|
||||
document.getElementById('add-client-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -1008,10 +1168,10 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
max_size_bytes: parseInt(document.getElementById('new-client-quota').value) * 1024 * 1024 * 1024,
|
||||
enabled: document.getElementById('new-client-enabled').checked,
|
||||
rotation_policy: {
|
||||
keep_hourly: parseInt(document.getElementById('new-client-hourly').value),
|
||||
keep_daily: parseInt(document.getElementById('new-client-daily').value),
|
||||
keep_weekly: parseInt(document.getElementById('new-client-weekly').value),
|
||||
keep_monthly: parseInt(document.getElementById('new-client-monthly').value)
|
||||
keep_hourly: parseInt(document.getElementById('new-client-hourly').value) || 0,
|
||||
keep_daily: parseInt(document.getElementById('new-client-daily').value) || 0,
|
||||
keep_weekly: parseInt(document.getElementById('new-client-weekly').value) || 0,
|
||||
keep_monthly: parseInt(document.getElementById('new-client-monthly').value) || 0
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1025,6 +1185,7 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
|
||||
if (data.success) {
|
||||
closeModal('add-client-modal');
|
||||
document.getElementById('add-client-form').reset();
|
||||
loadClients();
|
||||
loadStats();
|
||||
} else {
|
||||
@@ -1035,6 +1196,45 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
}
|
||||
});
|
||||
|
||||
// Edit client form
|
||||
document.getElementById('edit-client-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const client = {
|
||||
client_id: document.getElementById('edit-client-id').value,
|
||||
api_key: document.getElementById('edit-client-apikey').value,
|
||||
storage_type: document.getElementById('edit-client-storage').value,
|
||||
dataset: document.getElementById('edit-client-dataset').value,
|
||||
max_size_bytes: parseInt(document.getElementById('edit-client-quota').value) * 1024 * 1024 * 1024,
|
||||
enabled: document.getElementById('edit-client-enabled').checked,
|
||||
rotation_policy: {
|
||||
keep_hourly: parseInt(document.getElementById('edit-client-hourly').value) || 0,
|
||||
keep_daily: parseInt(document.getElementById('edit-client-daily').value) || 0,
|
||||
keep_weekly: parseInt(document.getElementById('edit-client-weekly').value) || 0,
|
||||
keep_monthly: parseInt(document.getElementById('edit-client-monthly').value) || 0
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('/admin/client/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(client)
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
closeModal('edit-client-modal');
|
||||
loadClients();
|
||||
loadStats();
|
||||
} else {
|
||||
alert(data.message || 'Failed to update client');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed to update client');
|
||||
}
|
||||
});
|
||||
|
||||
// Add admin
|
||||
document.getElementById('add-admin-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -1055,6 +1255,7 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
|
||||
if (data.success) {
|
||||
closeModal('add-admin-modal');
|
||||
document.getElementById('add-admin-form').reset();
|
||||
loadAdmins();
|
||||
} else {
|
||||
alert(data.message || 'Failed to create admin');
|
||||
@@ -1064,6 +1265,46 @@ const adminPanelHTML = `<!DOCTYPE html>
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
function showChangePassword(adminId, username) {
|
||||
document.getElementById('change-password-admin-id').value = adminId;
|
||||
document.getElementById('change-password-username').value = username;
|
||||
document.getElementById('change-password-new').value = '';
|
||||
document.getElementById('change-password-confirm').value = '';
|
||||
showModal('change-password-modal');
|
||||
}
|
||||
|
||||
document.getElementById('change-password-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const adminId = document.getElementById('change-password-admin-id').value;
|
||||
const newPassword = document.getElementById('change-password-new').value;
|
||||
const confirmPassword = document.getElementById('change-password-confirm').value;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/admin/admin/password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: parseInt(adminId), 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');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed to change password');
|
||||
}
|
||||
});
|
||||
|
||||
// Delete functions
|
||||
async function deleteClient(clientId) {
|
||||
if (!confirm('Are you sure you want to delete client "' + clientId + '" and all its snapshots?')) return;
|
||||
|
||||
Reference in New Issue
Block a user