test/moxie/admin/templates/users.html
Z User 1f9535d683 Add complete MOXIE web UI with authentication and user management
- New web UI with OpenWebUI-like interface using Tailwind CSS
- SQLite-based authentication with session management
- User registration, login, and profile pages
- Chat interface with conversation history
- Streaming responses with visible thinking phase
- File attachments support
- User document uploads in profile
- Rate limiting (5 requests/day for free users)
- Admin panel user management with promote/demote/delete
- Custom color theme matching balloon logo design
- Compatible with Nuitka build system
2026-03-24 05:15:50 +00:00

192 lines
7.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Management - MOXIE Admin</title>
<link rel="stylesheet" href="/{{ settings.admin_path }}/static/admin.css">
<style>
.user-card {
background: #1e1e1e;
border: 1px solid #333;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.user-card:hover {
border-color: #4F9AC3;
}
.admin-badge {
background: linear-gradient(135deg, #EA744C, #ECDC67);
color: #0B0C1C;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: bold;
}
.btn-small {
padding: 0.25rem 0.75rem;
font-size: 0.875rem;
}
.limit-input {
width: 80px;
padding: 0.25rem 0.5rem;
background: #0B0C1C;
border: 1px solid #333;
border-radius: 4px;
color: #F0F0F0;
}
</style>
</head>
<body>
<nav class="navbar">
<div class="nav-brand">MOXIE Admin</div>
<div class="nav-links">
<a href="/{{ settings.admin_path }}/">Dashboard</a>
<a href="/{{ settings.admin_path }}/endpoints">Endpoints</a>
<a href="/{{ settings.admin_path }}/documents">Documents</a>
<a href="/{{ settings.admin_path }}/comfyui">ComfyUI</a>
<a href="/{{ settings.admin_path }}/users" class="active">Users</a>
</div>
</nav>
<main class="container">
<h1>User Management</h1>
<div class="info-section">
<p>Manage user accounts, permissions, and request limits.</p>
</div>
<div class="users-grid">
{% for user in users %}
<div class="user-card">
<div class="flex justify-between items-start mb-3">
<div>
<h3 style="margin: 0; display: flex; align-items: center; gap: 0.5rem;">
{{ user.username }}
{% if user.is_admin %}
<span class="admin-badge">ADMIN</span>
{% endif %}
</h3>
<p style="margin: 0.25rem 0 0 0; color: #888; font-size: 0.875rem;">{{ user.email }}</p>
</div>
<div style="text-align: right;">
<div style="font-size: 0.75rem; color: #666;">Requests today</div>
<div style="font-size: 1.25rem; font-weight: bold; color: #4F9AC3;">{{ user.request_count }}/{{ user.request_limit }}</div>
</div>
</div>
<div class="flex justify-between items-center">
<div style="font-size: 0.875rem; color: #666;">
Created: {{ user.created_at[:10] if user.created_at else 'N/A' }}
</div>
<div class="flex gap-2 items-center">
{% if not user.is_admin %}
<button onclick="promoteUser('{{ user.id }}')" class="btn-small btn-primary">Make Admin</button>
{% else %}
{% if user.username != 'admin' %}
<button onclick="demoteUser('{{ user.id }}')" class="btn-small btn-secondary">Remove Admin</button>
{% endif %}
{% endif %}
<form onsubmit="updateLimit(event, '{{ user.id }}')" style="display: inline-flex; align-items: center; gap: 0.25rem;">
<input type="number" name="limit" value="{{ user.request_limit }}" min="1" max="999999" class="limit-input">
<button type="submit" class="btn-small btn-secondary">Set Limit</button>
</form>
{% if user.username != 'admin' %}
<button onclick="deleteUser('{{ user.id }}', '{{ user.username }}')" class="btn-small btn-danger">Delete</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% if not users %}
<div class="info-section">
<p>No users found.</p>
</div>
{% endif %}
</main>
<script>
async function promoteUser(userId) {
if (!confirm('Promote this user to admin?')) return;
try {
const response = await fetch(`/{{ settings.admin_path }}/users/${userId}/promote`, {
method: 'POST'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to promote user');
}
} catch (error) {
alert('An error occurred');
}
}
async function demoteUser(userId) {
if (!confirm('Remove admin privileges from this user?')) return;
try {
const response = await fetch(`/{{ settings.admin_path }}/users/${userId}/demote`, {
method: 'POST'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to demote user');
}
} catch (error) {
alert('An error occurred');
}
}
async function updateLimit(event, userId) {
event.preventDefault();
const limit = event.target.limit.value;
try {
const response = await fetch(`/{{ settings.admin_path }}/users/${userId}/limit`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `limit=${limit}`
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to update limit');
}
} catch (error) {
alert('An error occurred');
}
}
async function deleteUser(userId, username) {
if (!confirm(`Delete user "${username}"? This cannot be undone.`)) return;
try {
const response = await fetch(`/{{ settings.admin_path }}/users/${userId}`, {
method: 'DELETE'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to delete user');
}
} catch (error) {
alert('An error occurred');
}
}
</script>
</body>
</html>