| {% extends "base.html" %}
|
|
|
| {% block title %}用户管理{% endblock %}
|
|
|
| {% block content %}
|
| <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
|
| <div class="flex justify-between items-center mb-6">
|
| <h2 class="text-2xl font-bold text-gray-800">用户列表</h2>
|
| <button id="sycGatewayBtn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">
|
| 一键同步GPT网关
|
| </button>
|
| <button id="addUserBtn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">
|
| 添加用户
|
| </button>
|
| </div>
|
|
|
|
|
| <table class="min-w-full">
|
| <thead>
|
| <tr>
|
| <th
|
| class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
| 用户名
|
| </th>
|
| <th
|
| class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
| 角色
|
| </th>
|
| <th
|
| class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
| 绑定GPT账号
|
| </th>
|
| <th
|
| class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
| GPT过期时间
|
| </th>
|
| <th
|
| class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
| 绑定Claude账号
|
| </th>
|
| <th
|
| class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
| Claude过期时间
|
| </th>
|
| <th
|
| class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
| 操作
|
| </th>
|
| </tr>
|
| </thead>
|
| <tbody id="userTableBody">
|
|
|
| </tbody>
|
| </table>
|
|
|
|
|
| <div id="userModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden">
|
| <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
| <div class="mt-3">
|
| <h3 class="text-lg font-medium text-gray-900 mb-4">用户信息</h3>
|
| <form id="userForm">
|
| <input type="hidden" id="userId">
|
| <div class="mb-4">
|
| <label class="block text-gray-700 text-sm font-bold mb-2" for="username">
|
| 用户名
|
| </label>
|
| <input
|
| class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
| id="username" type="text" required>
|
| </div>
|
| <div class="mb-4">
|
| <label class="block text-gray-700 text-sm font-bold mb-2" for="password">
|
| 密码
|
| </label>
|
| <input
|
| class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
| id="password" type="password">
|
| </div>
|
| <div class="mb-4">
|
| <label class="block text-gray-700 text-sm font-bold mb-2" for="role">
|
| 角色
|
| </label>
|
| <select
|
| class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
| id="role" required>
|
| <option value="admin">管理员</option>
|
| <option value="user">普通用户</option>
|
| </select>
|
| </div>
|
|
|
| <div class="mb-4">
|
| <label class="block text-gray-700 text-sm font-bold mb-2" for="expirationDate">
|
| GPT过期时间
|
| </label>
|
| <input
|
| class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
| id="expirationDate" type="datetime-local">
|
| </div>
|
|
|
| <div class="mb-4">
|
| <label class="block text-gray-700 text-sm font-bold mb-2" for="expirationClaude">
|
| Claude过期时间
|
| </label>
|
| <input
|
| class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
| id="expirationClaude" type="datetime-local">
|
| </div>
|
| <div class="flex items-center justify-end">
|
| <button type="button" onclick="closeModal()"
|
| class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2">
|
| 取消
|
| </button>
|
| <button type="submit"
|
| class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
|
| 保存
|
| </button>
|
| </div>
|
| </form>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div id="bindModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden">
|
| <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
| <div class="mt-3">
|
| <h3 class="text-lg font-medium text-gray-900 mb-4">请选择要绑定的账号</h3>
|
| <form id="bindForm">
|
| <input type="hidden" id="userId">
|
| <div class="mb-4">
|
| <select
|
| class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
| id="email">
|
| </select>
|
| </div>
|
| <div class="mb-4">
|
| <select
|
| class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
| id="claudeEmail">
|
| </select>
|
| </div>
|
| <div class="flex items-center justify-end">
|
| <button type="button" onclick="closeBindModal()"
|
| class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2">
|
| 取消
|
| </button>
|
| <button type="submit"
|
| class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
|
| 保存
|
| </button>
|
| </div>
|
| </form>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| </div>
|
| {% endblock %}
|
| {% block extra_js %}
|
| <script>
|
| let users = [];
|
| const bindModal = document.getElementById('bindModal');
|
| const modal = document.getElementById('userModal');
|
| const userForm = document.getElementById('userForm');
|
| const bindForm = document.getElementById('bindForm');
|
| const addUserBtn = document.getElementById('addUserBtn');
|
| const sycGateway = document.getElementById('sycGatewayBtn');
|
|
|
| function loadUsers() {
|
| fetch('/api/users')
|
| .then(response => response.json())
|
| .then(data => {
|
| users = data;
|
| renderUsers();
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
|
|
|
|
| function renderUsers() {
|
| const tbody = document.getElementById('userTableBody');
|
| tbody.innerHTML = '';
|
| users.forEach(user => {
|
| const tr = document.createElement('tr');
|
| tr.innerHTML = `
|
| <td class="px-6 py-4 whitespace-nowrap">${user.username}</td>
|
| <td class="px-6 py-4 whitespace-nowrap">${user.role}</td>
|
| <td class="px-6 py-4 whitespace-nowrap">${user.bind_email}</td>
|
| <td class="px-6 py-4 whitespace-nowrap">${user.expiration_time}</td>
|
| <td class="px-6 py-4 whitespace-nowrap">${user.bind_claude_email}</td>
|
| <td class="px-6 py-4 whitespace-nowrap">${user.claude_expiration_time}</td>
|
| <td class="px-6 py-4 whitespace-nowrap">
|
| <button onclick="bindChatGPT('${user.id}')" class="text-blue-600 hover:text-blue-900 mr-4">绑定GPT</button>
|
| <button onclick="bindClaude('${user.id}')" class="text-blue-600 hover:text-blue-900 mr-4">绑定Claude</button>
|
| <button onclick="editUser('${user.id}')" class="text-blue-600 hover:text-blue-900 mr-4">编辑</button>
|
| <button onclick="deleteUser('${user.id}')" class="text-red-600 hover:text-red-900">删除</button>
|
| <button onclick="deleteBind('${user.id}')" class="text-red-600 hover:text-red-900">解绑GPT</button>
|
| <button onclick="deleteClaudeBind('${user.id}')" class="text-red-600 hover:text-red-900">解绑Claude</button>
|
| </td>
|
| `;
|
| tbody.appendChild(tr);
|
| });
|
| }
|
|
|
|
|
| function openModal(user = null) {
|
| const form = document.getElementById('userForm');
|
| if (user) {
|
| document.getElementById('userId').value = user.id;
|
| document.getElementById('username').value = user.username;
|
| document.getElementById('password').value = '';
|
| document.getElementById('role').value = user.role;
|
| document.getElementById('expirationDate').value = user.expiration_time;
|
| document.getElementById('expirationClaude').value = user.claude_expiration_time;
|
| } else {
|
| form.reset();
|
| document.getElementById('userId').value = '';
|
| }
|
| modal.classList.remove('hidden');
|
| }
|
|
|
|
|
| function closeModal() {
|
| modal.classList.add('hidden');
|
| }
|
|
|
|
|
| function editUser(userId) {
|
| const user = users.find(u => u.id === userId);
|
| if (user) {
|
| openModal(user);
|
| }
|
| }
|
|
|
|
|
| function openBindModal(user = null,type) {
|
| if(type == 1){
|
| url = "/api/all_email"
|
| fetch(url)
|
| .then(response => response.json())
|
| .then(data => {
|
| const emailSelect = document.getElementById('email');
|
| document.getElementById('email').classList.remove('hidden')
|
| document.getElementById('claudeEmail').classList.add('hidden')
|
| emailSelect.innerHTML = '';
|
| document.getElementById('userId').value = user.id;
|
| data.forEach(email => {
|
| const option = document.createElement('option');
|
| option.value = email;
|
| option.textContent = email;
|
| emailSelect.appendChild(option);
|
| });
|
|
|
| if (user && user.email) {
|
| emailSelect.value = user.email;
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }else{
|
| url = "/api/all_claude_email"
|
| fetch(url)
|
| .then(response => response.json())
|
| .then(data => {
|
| const emailSelect = document.getElementById('claudeEmail');
|
| document.getElementById('claudeEmail').classList.remove('hidden')
|
| document.getElementById('email').classList.add('hidden')
|
| emailSelect.innerHTML = '';
|
| document.getElementById('userId').value = user.id;
|
| data.forEach(email => {
|
| const option = document.createElement('option');
|
| option.value = email;
|
| option.textContent = email;
|
| emailSelect.appendChild(option);
|
| });
|
|
|
| if (user && user.email) {
|
| emailSelect.value = user.email;
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
|
|
| bindModal.classList.remove('hidden');
|
| }
|
|
|
|
|
| function closeBindModal() {
|
| bindModal.classList.add('hidden');
|
| }
|
|
|
|
|
| function bindChatGPT(userId) {
|
| const user = users.find(u => u.id === userId);
|
| if (user) {
|
| openBindModal(user,1);
|
| }
|
| }
|
|
|
| function bindClaude(userId) {
|
| const user = users.find(u => u.id === userId);
|
| if (user) {
|
| openBindModal(user,0);
|
| }
|
| }
|
|
|
|
|
|
|
| function deleteUser(userId) {
|
| if (confirm('确定要删除这个用户吗?')) {
|
| fetch(`/api/users/${userId}`, {
|
| method: 'DELETE'
|
| })
|
| .then(response => response.json())
|
| .then(data => {
|
| if (data.success) {
|
| loadUsers();
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
| }
|
|
|
|
|
| function deleteBind(userId) {
|
| if (confirm('确定要解除这个用户绑定的ChatGPT账号吗?')) {
|
| fetch(`/api/del_bind/${userId}`, {
|
| method: 'DELETE'
|
| })
|
| .then(response => response.json())
|
| .then(data => {
|
| if (data.success) {
|
| loadUsers();
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
| }
|
|
|
|
|
| function deleteClaudeBind(userId) {
|
| if (confirm('确定要解除这个用户绑定的Claude账号吗?')) {
|
| fetch(`/api/del_bindClaude/${userId}`, {
|
| method: 'DELETE'
|
| })
|
| .then(response => response.json())
|
| .then(data => {
|
| if (data.success) {
|
| loadUsers();
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
| }
|
|
|
|
|
| userForm.addEventListener('submit', function (e) {
|
| e.preventDefault();
|
| const userId = document.getElementById('userId').value;
|
| const userData = {
|
| username: document.getElementById('username').value,
|
| password: document.getElementById('password').value,
|
| expiration_time: document.getElementById('expirationDate').value,
|
| claude_expiration_time: document.getElementById('expirationClaude').value,
|
| role: document.getElementById('role').value
|
| };
|
|
|
| const url = userId ? `/api/users/${userId}` : '/api/users';
|
| const method = userId ? 'PUT' : 'POST';
|
|
|
| fetch(url, {
|
| method: method,
|
| headers: {
|
| 'Content-Type': 'application/json'
|
| },
|
| body: JSON.stringify(userData)
|
| })
|
| .then(response => response.json())
|
| .then(data => {
|
| if (data.success) {
|
| closeModal();
|
| loadUsers();
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| });
|
|
|
|
|
|
|
| bindForm.addEventListener('submit', function (e) {
|
| e.preventDefault();
|
| const userId = document.getElementById('userId').value;
|
| const emailSelect = document.getElementById('email');
|
| const emailSelectClaude = document.getElementById('claudeEmail');
|
| const bindEmail = {email: emailSelect.value};
|
| const bindClaudeEmail = {email: emailSelectClaude.value};
|
|
|
| const method = 'PUT';
|
| if(document.getElementById('claudeEmail').classList.contains('hidden')){
|
| url = `/api/bind/${userId}`;
|
| fetch(url, {
|
| method: method,
|
| headers: {
|
| 'Content-Type': 'application/json'
|
| },
|
| body: JSON.stringify(bindEmail)
|
| })
|
| .then(response => response.json())
|
| .then(data => {
|
| if (data.success) {
|
| closeBindModal();
|
| loadUsers();
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }else{
|
| url = `/api/bindClaude/${userId}`;
|
| fetch(url, {
|
| method: method,
|
| headers: {
|
| 'Content-Type': 'application/json'
|
| },
|
| body: JSON.stringify(bindClaudeEmail)
|
| })
|
| .then(response => response.json())
|
| .then(data => {
|
| if (data.success) {
|
| closeBindModal();
|
| loadUsers();
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
|
|
| });
|
|
|
|
|
| addUserBtn.addEventListener('click', () => openModal());
|
|
|
|
|
| function syc() {
|
| if (confirm('确定是否同步网关?')) {
|
| fetch(`/api/syc`, {
|
| method: 'GET'
|
| })
|
| .then(response => response.json())
|
| .then(data => {
|
| if (data.success) {
|
| loadUsers();
|
| alert('同步成功!');
|
| } else {
|
| alert('同步失败!');
|
| }
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
| }
|
|
|
|
|
|
|
| sycGateway.addEventListener('click', () => syc());
|
|
|
|
|
|
|
| loadUsers();
|
| </script>
|
| {% endblock %} |