Restructure frontend into templates/ and static/
- Move index.html → templates/index.html (Jinja2, uses url_for) - Extract CSS → static/css/app.css - Split JS into static/js/: utils.js, dashboard.js, shares.js, quotas.js, users.js, main.js - Switch app.py from send_file to render_template Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
822e6b3b87
commit
1118681fd2
10 files changed
+607
-642
No files matched your search
@@ -0,0 +1,80 @@
|
||||
async function loadUsers() {
|
||||
const res = await apiFetch('/api/users');
|
||||
if (!res) return;
|
||||
const users = await res.json();
|
||||
const tb = document.getElementById('usersTableBody');
|
||||
if (!users.length) {
|
||||
tb.innerHTML = `<tr><td colspan="4" style="color:var(--muted)">No users found.</td></tr>`;
|
||||
return;
|
||||
}
|
||||
tb.innerHTML = users.map(u => `
|
||||
<tr>
|
||||
<td>${esc(u.name)}</td>
|
||||
<td style="color:var(--muted)">${u.uid}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill"
|
||||
style="${u.smb ? 'background:#1a3a1f; color:var(--green)' : 'background:var(--surf2); color:var(--muted)'}">
|
||||
${u.smb ? 'Active' : 'No'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm me-1" style="border:1px solid var(--border); color:var(--muted)"
|
||||
onclick="openPwModal(${JSON.stringify(u.name)})">Password</button>
|
||||
<button class="btn btn-sm btn-danger"
|
||||
onclick="deleteUser(${JSON.stringify(u.name)})">Delete</button>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
async function createUser() {
|
||||
const name = document.getElementById('newUsername').value.trim();
|
||||
const password = document.getElementById('newPassword').value;
|
||||
if (!name || !password) return showToast('Username and password are required.', 'danger');
|
||||
|
||||
const res = await apiFetch('/api/users', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name, password}),
|
||||
});
|
||||
if (!res) return;
|
||||
const data = await res.json();
|
||||
if (!res.ok) return showToast('Error: ' + (data.error || res.status), 'danger');
|
||||
showToast(`User "${name}" created.`);
|
||||
document.getElementById('newUsername').value = '';
|
||||
document.getElementById('newPassword').value = '';
|
||||
loadUsers();
|
||||
}
|
||||
|
||||
async function deleteUser(name) {
|
||||
if (!confirm(`Delete user "${name}"? This cannot be undone.`)) return;
|
||||
const res = await apiFetch(`/api/users/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
||||
if (!res) return;
|
||||
const data = await res.json();
|
||||
if (!res.ok) return showToast('Error: ' + (data.error || res.status), 'danger');
|
||||
showToast(`User "${name}" deleted.`);
|
||||
loadUsers();
|
||||
}
|
||||
|
||||
function openPwModal(name) {
|
||||
document.getElementById('pwUsername').value = name;
|
||||
document.getElementById('pwNew').value = '';
|
||||
document.getElementById('pwConfirm').value = '';
|
||||
new bootstrap.Modal(document.getElementById('pwModal')).show();
|
||||
}
|
||||
|
||||
async function submitPassword() {
|
||||
const name = document.getElementById('pwUsername').value;
|
||||
const pw = document.getElementById('pwNew').value;
|
||||
const confirm = document.getElementById('pwConfirm').value;
|
||||
if (pw.length < 8) return showToast('Password must be at least 8 characters.', 'danger');
|
||||
if (pw !== confirm) return showToast('Passwords do not match.', 'danger');
|
||||
|
||||
const res = await apiFetch(`/api/users/${encodeURIComponent(name)}/password`, {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({password: pw}),
|
||||
});
|
||||
if (!res) return;
|
||||
const data = await res.json();
|
||||
bootstrap.Modal.getInstance(document.getElementById('pwModal')).hide();
|
||||
if (!res.ok) return showToast('Error: ' + (data.error || res.status), 'danger');
|
||||
showToast(`Password updated for "${name}".`);
|
||||
}
|
||||
Reference in new issue
Block a user