### Start EasyUKey Server with Docker Source: https://github.com/hang666/easyukey/blob/main/README.md Starts the EasyUKey server and its dependencies using Docker Compose. Supports using an external MySQL database (recommended for production) or an integrated MySQL for development. ```bash # For production (using external MySQL): # Edit .env file to configure external database connection details docker-compose up -d # For development/testing (using built-in MySQL): docker-compose -f docker-compose.db.yml up -d ``` -------------------------------- ### Configure and Run EasyUKey Server (Traditional) Source: https://github.com/hang666/easyukey/blob/main/README.md Copies the server configuration example, prompts to edit it for database settings, and then runs the compiled server executable. ```bash # Copy server configuration cp server/config.example.yaml server/config.yaml # Edit the configuration file, set database connection, etc. # Run the server cd build ./easyukey-server ``` -------------------------------- ### Configure Environment Variables Source: https://github.com/hang666/easyukey/blob/main/README.md Copies the example environment file and instructs users to edit it, specifically setting the EASYUKEY_SECURITY_ENCRYPTION_KEY for server configuration. ```bash # Copy environment variables example file cp .env.example .env # Edit .env file, set the required EASYUKEY_SECURITY_ENCRYPTION_KEY # You can use the following command to generate a 32-byte random key: # openssl rand -hex 32 ``` -------------------------------- ### Clone EasyUKey Project Source: https://github.com/hang666/easyukey/blob/main/README.md Clones the EasyUKey repository from GitHub to your local machine. This is the first step for both Docker and traditional installations. ```bash git clone https://github.com/hang666/EasyUKey.git cd EasyUKey ``` -------------------------------- ### PIN Setup JavaScript Function Source: https://github.com/hang666/easyukey/blob/main/client/template/pin.html This JavaScript function manages the PIN input and submission logic for EasyUKey. It handles user input, validation for a 6-digit PIN, and asynchronous submission to a '/pin-setup' endpoint. It also includes logic for displaying success or error messages and closing the window after a delay. ```javascript function pinSetup() { return { pin: "", loading: false, completed: false, success: false, resultMessage: "", errorMessage: "", get isComplete() { return this.pin.length === 6; }, init() { // No specific initialization needed for the single pin input }, onInput(e) { this.pin = e.target.value.replace(/\D/g, "").slice(0, 6); this.errorMessage = ""; }, async submitPIN() { if (!this.isComplete) { this.errorMessage = "请输入完整6位PIN"; return; } this.loading = true; this.errorMessage = ""; try { const response = await fetch("/pin-setup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ pin: this.pin }), }); const result = await response.json(); if (!response.ok) throw new Error(result.message || "设置PIN失败"); this.showResult(result.message || "PIN设置成功", true); } catch (error) { this.showResult(error.message || "PIN设置失败,请重试", false); } }, showResult(message, isSuccess) { this.loading = false; this.completed = true; this.success = isSuccess; this.resultMessage = message; setTimeout(() => window.close(), 3000); }, }; } ``` -------------------------------- ### Server Configuration File Source: https://github.com/hang666/easyukey/blob/main/README.md Example structure of the EasyUKey server configuration file, typically in YAML format. It includes settings for database connections, ports, and security parameters. ```yaml # Example server/config.yaml structure (actual content may vary) server: port: 8888 database: type: mysql host: 127.0.0.1 port: 3306 user: root password: "" dbname: easyukey security: encryption_key: "YOUR_32_BYTE_ENCRYPTION_KEY" token_ttl: "24h" # Other configurations like logging, CORS, etc. ``` -------------------------------- ### PIN Input UI Styling Source: https://github.com/hang666/easyukey/blob/main/client/template/pin.html CSS styles for the PIN input fields and related UI elements. This includes styling for focus states, filled states, and animations for a pulsing ring effect, likely used for visual feedback during the PIN setup process. ```css @keyframes pulse-ring { 0% { transform: translate(-50%, -50%) scale(0.7); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1.3); opacity: 0; } } .pulse-ring::before { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; height: 100%; border: 2px solid #3b82f6; border-radius: 50%; animation: pulse-ring 2s infinite; } [x-cloak] { display: none !important; } .pin-input { width: 3rem; height: 3rem; text-align: center; font-size: 1.5rem; font-weight: bold; border: 2px solid #e5e7eb; border-radius: 0.5rem; outline: none; transition: all 0.2s; } .pin-input:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .pin-input.filled { border-color: #10b981; background-color: #f0fdf4; } ``` -------------------------------- ### Build EasyUKey Client Source: https://github.com/hang666/easyukey/blob/main/README.md Builds the EasyUKey client binary using Make, allowing customization of the encryption key and server address during the build process. ```makefile make client ENCRYPT_KEY_STR=123456789 SERVER_ADDR=http://localhost:8888 ``` -------------------------------- ### Build EasyUKey Server (Traditional) Source: https://github.com/hang666/easyukey/blob/main/README.md Builds the EasyUKey server binary using the Make tool. This is an alternative to Docker deployment. ```makefile make server ``` -------------------------------- ### Manage Docker Services Source: https://github.com/hang666/easyukey/blob/main/README.md Commands to manage the EasyUKey services running via Docker Compose. Includes checking service status and viewing logs. ```bash # Check service status docker-compose ps # View logs (follow logs for the server) docker-compose logs -f server ``` -------------------------------- ### API Key Creation Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html Creates a new API key for administrative access. Requires a name and description for the key. ```javascript async createApiKey() { // Assumes this.newKey is populated with { name: "", description: "", is_admin: false } // Assumes this.api is a method to make API calls try { const result = await this.api("/api/v1/admin/apikeys", { method: "POST", body: JSON.stringify(this.newKey), }); if (result.success) { await this.loadApiKeys(); // Reload keys this.showModal = false; // Close modal this.newKey = { name: "", description: "", is_admin: false }; // Reset form this.showMsg("API密钥创建成功"); // Show success message } } catch (error) { this.showMsg("创建失败: " + error.message, "error"); // Show error message } finally { this.loading = false; // Ensure loading state is reset } } ``` -------------------------------- ### EasyUKey UI 样式 Source: https://github.com/hang666/easyukey/blob/main/client/template/auth.html 包含用于认证界面的CSS动画和样式定义,如脉冲环动画和PIN输入框样式。 ```css @keyframes pulse-ring { 0% { transform: translate(-50%, -50%) scale(0.7); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1.3); opacity: 0; } } .pulse-ring::before { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; height: 100%; border: 2px solid #3b82f6; border-radius: 50%; animation: pulse-ring 2s infinite; } [x-cloak] { display: none !important; } .pin-input { width: 3rem; height: 3rem; text-align: center; font-size: 1.5rem; font-weight: bold; border: 2px solid #e5e7eb; border-radius: 0.5rem; outline: none; transition: all 0.2s; } .pin-input:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .pin-input.filled { border-color: #10b981; background-color: #f0fdf4; } ``` -------------------------------- ### Admin API Endpoints Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html API endpoints for the admin panel, covering authentication, device statistics, and management of devices, users, and API keys. Includes details on request methods, parameters, and expected response structures. ```APIDOC POST /api/v1/admin/verify Description: Verifies the provided admin key for authentication. Request Body: admin_key: string (The administrator key to verify) Responses: 200 OK: content: application/json: schema: type: object properties: success: { type: boolean, description: "Indicates if the verification was successful." } message: { type: string, description: "A message detailing the result of the verification." } GET /api/v1/admin/devices/statistics Description: Retrieves statistics for devices, including total, online, active, and bound counts. Authentication: Required (via admin token) Responses: 200 OK: content: application/json: schema: type: object properties: success: { type: boolean, description: "Indicates if the statistics were retrieved successfully." } data: type: object properties: total_devices: { type: integer, description: "Total number of devices." } online_devices: { type: integer, description: "Number of currently online devices." } active_devices: { type: integer, description: "Number of active devices." } bound_devices: { type: integer, description: "Number of bound devices." } GET /api/v1/admin/devices Description: Retrieves a paginated list of all devices. Authentication: Required (via admin token) Query Parameters: page: { type: integer, description: "The page number to retrieve (1-based).", default: 1 } page_size: { type: integer, description: "The number of items per page.", default: 10 } Responses: 200 OK: content: application/json: schema: type: object properties: success: { type: boolean, description: "Indicates if the devices were retrieved successfully." } data: type: array items: type: object properties: id: { type: integer } name: { type: string } // ... other device properties total: { type: integer, description: "Total number of devices available." } GET /api/v1/admin/users Description: Retrieves a paginated list of all users. Authentication: Required (via admin token) Query Parameters: page: { type: integer, description: "The page number to retrieve (1-based).", default: 1 } page_size: { type: integer, description: "The number of items per page.", default: 10 } Responses: 200 OK: content: application/json: schema: type: object properties: success: { type: boolean, description: "Indicates if the users were retrieved successfully." } data: type: array items: type: object properties: id: { type: integer } username: { type: string } is_active: { type: boolean } // ... other user properties total: { type: integer, description: "Total number of users available." } GET /api/v1/admin/apikeys Description: Retrieves a paginated list of all API keys. Authentication: Required (via admin token) Query Parameters: page: { type: integer, description: "The page number to retrieve (1-based).", default: 1 } page_size: { type: integer, description: "The number of items per page.", default: 10 } Responses: 200 OK: content: application/json: schema: type: object properties: success: { type: boolean, description: "Indicates if the API keys were retrieved successfully." } data: type: array items: type: object properties: id: { type: integer } name: { type: string } description: { type: string } is_admin: { type: boolean } // ... other API key properties total: { type: integer, description: "Total number of API keys available." } POST /api/v1/admin/apikeys Description: Creates a new API key. Authentication: Required (via admin token) Request Body: name: { type: string, description: "The name for the new API key." } description: { type: string, description: "A description for the API key." } is_admin: { type: boolean, description: "Whether the API key grants admin privileges." } Responses: 200 OK: content: application/json: schema: type: object properties: success: { type: boolean, description: "Indicates if the API key was created successfully." } message: { type: string, description: "A message detailing the result of the creation." } ``` -------------------------------- ### Device Management API Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html Manages devices, including updating device properties and linking/unlinking devices to users. ```APIDOC PUT /api/v1/admin/devices/{deviceId} Description: Updates an existing device. Parameters: deviceId: { type: string, in: path, description: "The ID of the device to update" } Request Body: contentType: application/json schema: type: object properties: name: { type: string, description: "The name of the device" } permissions: { type: array, items: { type: string }, description: "List of permissions associated with the device" } is_active: { type: boolean, description: "Whether the device is active" } Responses: 200 OK: Device updated successfully. 404 Not Found: Device with the specified ID not found. 500 Internal Server Error: Server error during update. Example: await this.api(`/api/v1/admin/devices/${this.selectedDevice.id}`, { method: "PUT", body: JSON.stringify({ name: "NewDeviceName", permissions: [], is_active: true }), }); POST /api/v1/admin/devices/{deviceId}/user Description: Links a device to a user. Parameters: deviceId: { type: string, in: path, description: "The ID of the device to link" } Request Body: contentType: application/json schema: type: object properties: user_id: { type: integer, description: "The ID of the user to link the device to" } Responses: 200 OK: Device linked successfully. 404 Not Found: Device or user not found. 500 Internal Server Error: Server error during linking. Example: await this.api(`/api/v1/admin/devices/${this.selectedDevice.id}/user`, { method: "POST", body: JSON.stringify({ user_id: parseInt(this.linkUserId) }), }); DELETE /api/v1/admin/devices/{deviceId}/user Description: Unlinks a device from its associated user. Parameters: deviceId: { type: string, in: path, description: "The ID of the device to unlink" } Responses: 200 OK: Device unlinked successfully. 404 Not Found: Device not found or not linked to any user. 500 Internal Server Error: Server error during unlinking. Example: if (confirm(`确定要解绑设备 ${device.id} 吗?`)) { await this.api(`/api/v1/admin/devices/${device.id}/user`, { method: "DELETE", }); } POST /api/v1/admin/devices/{deviceId}/offline Description: Sets a device to offline status. Parameters: deviceId: { type: string, in: path, description: "The ID of the device to set offline" } Responses: 200 OK: Device set to offline successfully. 400 Bad Request: Device is already offline. 404 Not Found: Device not found. 500 Internal Server Error: Server error. Example: if (!device.is_online) return this.showMsg("设备已离线", "error"); await this.api(`/api/v1/admin/devices/${device.id}/offline`, { method: "POST", }); ``` -------------------------------- ### API Response Handling and Utility Functions Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html This snippet includes functions for processing API responses, formatting dates for display, and showing user messages. It demonstrates common patterns for interacting with backend services and providing user feedback. ```javascript async () => { return await response.json(); }, formatTime(dateString) { return dateString ? new Date(dateString).toLocaleString("zh-CN") : "从未"; }, showMsg(message, type = "success") { alert(message); // 可替换为更好的提示组件 } ``` -------------------------------- ### User and Device Modal Management Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html This section covers functions related to managing user and device data, including opening modals for new entries, editing existing ones, and resetting form states. It highlights state management for UI interactions. ```javascript openNewUserModal() { this.selectedUser = null; this.resetUserForm(); this.showUserModal = true; }, editUser(user) { this.selectedUser = user; this.userForm = { username: user.username, permissions: JSON.stringify(user.permissions || ["login"]), is_active: user.is_active, }; this.showUserModal = true; }, editDevice(device) { this.selectedDevice = device; this.deviceForm = { name: device.name || "", permissions: JSON.stringify(device.permissions || []), is_active: device.is_active, }; this.showUserModal = true; }, async openLinkModal(device) { this.selectedDevice = device; this.linkUserId = ""; this.showLinkModal = true; // 刷新用户列表 await this.loadUsers(); }, resetUserForm() { this.userForm = { username: "", permissions: '["login"]', is_active: true, }; }, resetDeviceForm() { this.deviceForm = { name: "", permissions: "[]", is_active: true, }; } ``` -------------------------------- ### Generate Encryption Key Source: https://github.com/hang666/easyukey/blob/main/README.md Generates a 32-byte (64 hex characters) random encryption key using OpenSSL. This key is essential for configuring the EasyUKey server's security settings. ```bash openssl rand -hex 32 ``` -------------------------------- ### Admin Panel Component Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html JavaScript code defining the structure and logic for an admin panel. It manages application state, handles user authentication, tab switching, data loading for devices, users, and API keys, and includes methods for pagination and initiating data operations. ```javascript function adminPanel() { return { // 状态 currentTab: "dashboard", loading: false, isAuthenticated: false, showModal: false, showUserModal: false, showDeviceModal: false, showLinkModal: false, loginError: "", authToken: null, adminKey: "", // 分页状态 pagination: { devices: { page: 1, pageSize: 10, total: 0 }, users: { page: 1, pageSize: 10, total: 0 }, apikeys: { page: 1, pageSize: 10, total: 0 }, }, // 配置 tabs: [ { id: "dashboard", name: "仪表盘", desc: "系统概览和统计信息", icon: "fas fa-chart-line" }, { id: "devices", name: "设备管理", desc: "查看和管理连接的设备", icon: "fa-brands fa-usb" }, { id: "users", name: "用户管理", desc: "管理系统用户", icon: "fas fa-users" }, { id: "apikeys", name: "API密钥管理", desc: "管理API访问密钥", icon: "fas fa-key" }, ], statsCards: [ { key: "onlineDevices", name: "在线设备", icon: "fa-brands fa-usb", bgColor: "bg-green-100", iconColor: "text-green-600" }, { key: "totalDevices", name: "总设备数", icon: "fas fa-microchip", bgColor: "bg-blue-100", iconColor: "text-blue-600" }, { key: "activeDevices", name: "活跃设备", icon: "fas fa-bolt", bgColor: "bg-yellow-100", iconColor: "text-yellow-600" }, { key: "boundDevices", name: "已绑定设备", icon: "fas fa-link", bgColor: "bg-purple-100", iconColor: "text-purple-600" }, ], // 数据 stats: { onlineDevices: 0, totalDevices: 0, activeDevices: 0, boundDevices: 0 }, devices: [], users: [], apikeys: [], newKey: { name: "", description: "", is_admin: false }, selectedDevice: null, selectedUser: null, linkUserId: "", userForm: { username: "", permissions: '["login"]', is_active: true }, deviceForm: { name: "", permissions: "[]", is_active: true }, // 计算属性 get currentTabData() { return ( this.tabs.find((tab) => tab.id === this.currentTab) || this.tabs[0] ); }, init() { const token = localStorage.getItem("admin_token"); if (token) { this.authToken = token; this.adminKey = token; try { this.loadData(); this.isAuthenticated = true; } catch (error) { localStorage.removeItem("admin_token"); this.authToken = null; this.adminKey = ""; } } }, async login() { if (!this.adminKey) return; this.loading = true; this.loginError = ""; try { const result = await this.api("/api/v1/admin/verify", { method: "POST", body: JSON.stringify({ admin_key: this.adminKey }), }); if (result.success) { localStorage.setItem("admin_token", this.adminKey); this.authToken = this.adminKey; this.isAuthenticated = true; await this.loadData(); } else { this.loginError = result.message || "登录失败"; } } catch (error) { this.loginError = "网络错误,请稍后重试"; } finally { this.loading = false; } }, logout() { this.isAuthenticated = false; this.authToken = null; this.adminKey = ""; localStorage.removeItem("admin_token"); this.resetData(); }, resetData() { this.stats = { onlineDevices: 0, totalDevices: 0, activeDevices: 0, boundDevices: 0, }; this.devices = []; this.users = []; this.apikeys = []; this.pagination = { devices: { page: 1, pageSize: 10, total: 0 }, users: { page: 1, pageSize: 10, total: 0 }, apikeys: { page: 1, pageSize: 10, total: 0 }, }; }, switchTab(tabId) { this.currentTab = tabId; this.loadData(); }, async loadData() { if (!this.isAuthenticated) return; this.loading = true; try { if (this.currentTab === "dashboard") { const statsData = await this.api( "/api/v1/admin/devices/statistics" ); if (statsData.success) { this.stats = { totalDevices: statsData.data.total_devices || 0, onlineDevices: statsData.data.online_devices || 0, activeDevices: statsData.data.active_devices || 0, boundDevices: statsData.data.bound_devices || 0, }; } } else if (this.currentTab === "devices") { await this.loadDevices(); } else if (this.currentTab === "users") { await this.loadUsers(); } else if (this.currentTab === "apikeys") { await this.loadApiKeys(); } } catch (error) { this.showMsg("加载数据失败: " + error.message, "error"); } finally { this.loading = false; } }, async loadDevices() { const p = this.pagination.devices; const result = await this.api( `/api/v1/admin/devices?page=${p.page}&page_size=${p.pageSize}` ); if (result.success) { this.devices = result.data || []; this.pagination.devices.total = result.total || 0; } }, async loadUsers() { const p = this.pagination.users; const result = await this.api( `/api/v1/admin/users?page=${p.page}&page_size=${p.pageSize}` ); if (result.success) { this.users = result.data || []; this.pagination.users.total = result.total || 0; } }, async loadApiKeys() { const p = this.pagination.apikeys; const result = await this.api( `/api/v1/admin/apikeys?page=${p.page}&page_size=${p.pageSize}` ); if (result.success) { this.apikeys = result.data || []; this.pagination.apikeys.total = result.total || 0; } }, async changePage(type, page) { if (page < 1) return; this.pagination[type].page = page; if (type === "devices") await this.loadDevices(); else if (type === "users") await this.loadUsers(); else if (type === "apikeys") await this.loadApiKeys(); }, async createKey() { if (!this.newKey.name) return; this.loading = true; try { const result = await this.api("/api/v1/admin/apikeys", { method: "POST", body: JSON.stringify(this.newKey), }); if (result.success) { this.showMsg("API密钥创建成功", "success"); this.newKey = { name: "", description: "", is_admin: false }; await this.loadApiKeys(); } else { this.showMsg("API密钥创建失败: " + result.message, "error"); } } catch (error) { this.showMsg("网络错误,请稍后重试", "error"); } finally { this.loading = false; } }, // Placeholder for the api method, assuming it handles fetch requests api(endpoint, options) { // This is a placeholder for the actual API call implementation // It should include headers like Authorization: Bearer ${this.authToken} console.log(`API Call: ${endpoint}`, options); // Example mock response for login: if (endpoint === "/api/v1/admin/verify") { return Promise.resolve({ success: true, message: "Verified" }); } // Example mock response for statistics: if (endpoint === "/api/v1/admin/devices/statistics") { return Promise.resolve({ success: true, data: { total_devices: 100, online_devices: 50, active_devices: 75, bound_devices: 60 }, }); } // Example mock response for devices: if (endpoint.startsWith("/api/v1/admin/devices")) { return Promise.resolve({ success: true, data: [{ id: 1, name: "Device 1" }, { id: 2, name: "Device 2" }], total: 2, }); } // Example mock response for users: if (endpoint.startsWith("/api/v1/admin/users")) { return Promise.resolve({ success: true, data: [{ id: 1, username: "User 1" }, { id: 2, username: "User 2" }], total: 2, }); } // Example mock response for apikeys: if (endpoint.startsWith("/api/v1/admin/apikeys")) { return Promise.resolve({ success: true, data: [{ id: 1, name: "Key 1" }, { id: 2, name: "Key 2" }], total: 2, }); } return Promise.resolve({ success: false, message: "Not Implemented" }); }, // Placeholder for showMsg method showMsg(message, type) { console.log(`Message (${type}): ${message}`); } }; } ``` -------------------------------- ### EasyUKey 认证流程 JavaScript Source: https://github.com/hang666/easyukey/blob/main/client/template/auth.html JavaScript函数用于管理EasyUKey的认证流程,包括计时器、PIN输入处理、提交认证请求和显示结果。 ```javascript function authFlow(initialRemaining, rawRequest) { return { initialTime: initialRemaining, remaining: initialRemaining, rawRequest: rawRequest, expired: initialRemaining <= 0, loading: false, currentStep: "auth", // 'auth', 'pin', 'result' resultMessage: "", confirmStatus: "", // true or false resultStatus: "", // 'success' or 'error' pin: "", pinErrorMessage: "", get isPinComplete() { return this.pin.length === 6; }, onInput(e) { this.pin = e.target.value.replace(/\D/g, "").slice(0, 6); this.pinErrorMessage = ""; }, init() { if (!this.expired) { this.startTimer(); } }, startTimer() { const interval = setInterval(() => { this.remaining--; if (this.remaining <= 0) { this.remaining = 0; this.expired = true; clearInterval(interval); } }, 1000); }, // 进入PIN输入步骤 requirePIN() { if (this.expired || this.loading) return; this.currentStep = "pin"; // 聚焦到第一个PIN输入框 this.$nextTick(() => { // No specific focus logic needed here as pin is a single input }); }, // 返回认证页面 backToAuth() { this.currentStep = "auth"; this.pin = ""; // Clear the pin input this.pinErrorMessage = ""; }, // 清空PIN clearPIN() { this.pin = ""; this.pinErrorMessage = ""; }, // 带PIN的认证提交 async submitWithPIN() { if (!this.isPinComplete || this.expired || this.loading) return; this.loading = true; this.pinErrorMessage = ""; try { const response = await fetch("/confirm", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ action: "confirm", request: this.rawRequest, pin: this.pin, }), }); const result = await response.json(); if (!response.ok) { throw new Error(result.message || "认证失败"); } // 显示真正的认证结果 const isSuccess = result.status === "success" && result.confirmStatus === true; this.showResult(result.message, isSuccess, result.status); } catch (error) { console.error("认证失败:", error); this.pinErrorMessage = error.message || "PIN验证失败,请重试"; this.loading = false; } }, // 拒绝认证 async submit(action) { if (action === "reject") { if (this.expired || this.loading) return; this.loading = true; try { const response = await fetch("/confirm", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ action: action, request: this.rawRequest, }), }); const result = await response.json(); if (!response.ok) { throw new Error(result.message || "发生未知错误"); } this.showResult( result.message, result.confirmStatus, result.status ); } catch (error) { console.error("操作失败:", error); this.showResult( error.message || "操作失败,请重试", false, "error" ); } } }, showResult(message, confirmStatus, resultStatus) { this.loading = false; this.currentStep = "result"; this.resultMessage = message; this.confirmStatus = confirmStatus; this.resultStatus = resultStatus; this.clearPIN(); // 清空PIN setTimeout(() => { window.close(); }, 10000); }, }; } ``` -------------------------------- ### User Management API Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html Handles CRUD operations for users, including creation, update, and deletion. Includes permission parsing and error handling. ```APIDOC POST /api/v1/admin/users Description: Creates a new user. Request Body: contentType: application/json schema: type: object properties: username: { type: string, description: "Unique username for the user" } permissions: { type: array, items: { type: string }, description: "List of user permissions, e.g., ["login", "read"]" } Responses: 200 OK: User created successfully. 400 Bad Request: Invalid input data or permissions format. 500 Internal Server Error: Server error during creation. Example: await this.api("/api/v1/admin/users", { method: "POST", body: JSON.stringify({ username: "testuser", permissions: ["login"] }), }); PUT /api/v1/admin/users/{userId} Description: Updates an existing user. Parameters: userId: { type: string, in: path, description: "The ID of the user to update" } Request Body: contentType: application/json schema: type: object properties: username: { type: string, description: "Updated username" } permissions: { type: array, items: { type: string }, description: "Updated list of user permissions" } is_active: { type: boolean, description: "Whether the user account is active" } Responses: 200 OK: User updated successfully. 404 Not Found: User with the specified ID not found. 500 Internal Server Error: Server error during update. Example: await this.api(`/api/v1/admin/users/${this.selectedUser.id}`, { method: "PUT", body: JSON.stringify({ username: "updateduser", permissions: ["read"], is_active: false }), }); DELETE /api/v1/admin/users/{userId} Description: Deletes a user. Parameters: userId: { type: string, in: path, description: "The ID of the user to delete" } Responses: 200 OK: User deleted successfully. 404 Not Found: User with the specified ID not found. 500 Internal Server Error: Server error during deletion. Example: if (confirm(`确定要删除用户 ${user.username} 吗?`)) { await this.api(`/api/v1/admin/users/${user.id}`, { method: "DELETE", }); } ``` -------------------------------- ### API Client Utility Source: https://github.com/hang666/easyukey/blob/main/server/template/admin.html Provides a utility method for copying text to the clipboard and a base API request handler. ```javascript async copyKey(key) { if (!key.api_key) return this.showMsg("API密钥不可用", "error"); try { await navigator.clipboard.writeText(key.api_key); this.showMsg("已复制到剪贴板"); } catch (error) { this.showMsg("复制失败", "error"); } }, async api(url, options = {}) { const headers = { "Content-Type": "application/json", ...options.headers, }; if (this.authToken) { headers["X-API-Key"] = `${this.authToken}`; } const response = await fetch(url, { headers, ...options, }); if (!response.ok) { if (response.status === 401) { this.logout(); throw new Error("认证失败,请重新登录"); } throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.json(); // Assuming API returns JSON } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.