- 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
192 lines
7.1 KiB
HTML
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>
|