1. 增加了请求框架

2. 增加了删除虚拟环境的脚本
This commit is contained in:
2026-01-12 14:20:44 +08:00
parent 45276a7787
commit bda8f13446
68 changed files with 9067 additions and 1 deletions

View File

@@ -0,0 +1 @@
0dc715b603074fca864c0c5074317e6a

799
request/static/doc.html Normal file
View File

@@ -0,0 +1,799 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>X-Request API Framework - 接口文档</title>
<!-- 本地Swagger UI资源 -->
<link rel="stylesheet" href="/vendor/swagger-ui.css">
<script src="/vendor/swagger-ui-bundle.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #f8fafc;
}
.nav {
background: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
height: 64px;
display: flex;
align-items: center;
padding: 0 24px;
position: sticky;
top: 0;
z-index: 100;
}
.nav-brand {
display: flex;
align-items: center;
font-size: 20px;
font-weight: 600;
color: #1f2937;
}
.nav-brand i {
color: #3b82f6;
margin-right: 8px;
}
.nav-links {
display: flex;
margin-left: 24px;
gap: 8px;
}
.nav-link {
padding: 8px 12px;
border-radius: 6px;
text-decoration: none;
font-size: 14px;
color: #6b7280;
transition: all 0.2s;
}
.nav-link:hover {
background: rgba(59, 130, 246, 0.1);
color: #3b82f6;
}
.nav-link.active {
background: rgba(59, 130, 246, 0.1);
color: #3b82f6;
border-bottom: 2px solid #3b82f6;
}
.nav-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 16px;
}
.api-version {
font-size: 14px;
color: #6b7280;
}
.api-version span {
font-weight: 500;
color: #1f2937;
}
.main {
padding: 16px 24px;
}
.doc-container {
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
min-height: calc(100vh - 120px);
}
/* Swagger UI 样式覆盖 */
.swagger-ui .topbar {
display: none;
}
.swagger-ui .info {
margin: 20px 0;
}
.swagger-ui .scheme-container {
background: #f8fafc;
padding: 15px;
box-shadow: none;
}
/* Font Awesome 图标 (内联) */
.fa {
display: inline-block;
font-style: normal;
}
.fa-file-text-o:before { content: "📄"; }
.fa-file-text:before { content: "📝"; }
.fa-book:before { content: "📖"; }
.fa-home:before { content: "🏠"; }
</style>
</head>
<body>
<nav class="nav">
<div class="nav-brand">
<i class="fa fa-file-text-o"></i>
X-Request 管理系统
</div>
<div class="nav-links">
<a href="/log.html" class="nav-link">
<i class="fa fa-file-text"></i> 日志管理
</a>
<a href="/doc.html" class="nav-link active">
<i class="fa fa-book"></i> 接口文档
</a>
<a href="/" class="nav-link">
<i class="fa fa-home"></i> 首页
</a>
</div>
<div class="nav-right">
<div class="api-version">
API版本: <span>1.0.0</span>
</div>
</div>
</nav>
<main class="main">
<div class="doc-container">
<div id="swagger-ui"></div>
</div>
</main>
<script>
// 内嵌的OpenAPI规范
const spec = {
"openapi": "3.1.0",
"info": {
"title": "X-Request API Framework",
"description": "高性能、高并发的请求框架",
"version": "1.0.0"
},
"paths": {
"/": {
"get": {
"summary": "Root",
"description": "根路径 - 重定向到前端监控界面",
"operationId": "root__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/dashboard": {
"get": {
"summary": "Dashboard",
"description": "前端监控界面",
"operationId": "dashboard_dashboard_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/health": {
"get": {
"summary": "Health Check",
"description": "健康检查",
"operationId": "health_check_health_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/info": {
"get": {
"summary": "App Info",
"description": "应用信息",
"operationId": "app_info_info_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/routes": {
"get": {
"summary": "Get Routes Info",
"description": "获取所有路由信息",
"operationId": "get_routes_info_routes_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/monitoring/logs/batch/delete": {
"post": {
"tags": [
"Monitoring"
],
"summary": "批量删除日志文件",
"description": "批量删除日志文件\n\nArgs:\n request: 批量删除请求,包含日志文件路径列表",
"operationId": "batch_delete_logs_monitoring_logs_batch_delete_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BatchDeleteRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/monitoring/logs/download": {
"post": {
"tags": [
"Monitoring"
],
"summary": "批量下载日志文件",
"description": "批量下载日志文件\n\nArgs:\n request: 批量下载请求,包含日志文件路径列表",
"operationId": "batch_download_logs_monitoring_logs_download_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BatchDownloadRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/monitoring/logs/{log_name}": {
"delete": {
"tags": [
"Monitoring"
],
"summary": "删除单个日志文件",
"description": "删除单个日志文件\n\nArgs:\n log_name: 日志文件名支持带日期路径如YYYY-MM-DD/filename.log",
"operationId": "delete_log_monitoring_logs__log_name__delete",
"parameters": [
{
"name": "log_name",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Log Name"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
},
"get": {
"tags": [
"Monitoring"
],
"summary": "获取日志内容",
"description": "获取日志文件内容\n\nArgs:\n log_name: 日志文件名\n lines: 返回的行数默认100行",
"operationId": "get_log_content_monitoring_logs__log_name__get",
"parameters": [
{
"name": "log_name",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Log Name"
}
},
{
"name": "lines",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"default": 100,
"title": "Lines"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/monitoring/logs/{date}/{log_name}": {
"delete": {
"tags": [
"Monitoring"
],
"summary": "删除指定日期的单个日志文件",
"description": "删除指定日期的单个日志文件\n\nArgs:\n date: 日期格式为YYYY-MM-DD\n log_name: 日志文件名",
"operationId": "delete_log_by_date_monitoring_logs__date___log_name__delete",
"parameters": [
{
"name": "date",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Date"
}
},
{
"name": "log_name",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Log Name"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
},
"get": {
"tags": [
"Monitoring"
],
"summary": "获取指定日期的日志内容",
"description": "获取指定日期的日志内容\n\nArgs:\n date: 日期格式为YYYY-MM-DD\n log_name: 日志文件名\n entries: 返回的日志条目数量默认10个\n mode: 显示模式latest表示最新的N个条目显示在顶部oldest表示最早的N个条目显示在顶部默认latest",
"operationId": "get_log_content_by_date_monitoring_logs__date___log_name__get",
"parameters": [
{
"name": "date",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Date"
}
},
{
"name": "log_name",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Log Name"
}
},
{
"name": "entries",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"default": 10,
"title": "Entries"
}
},
{
"name": "mode",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "latest",
"title": "Mode"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/monitoring/logs/{log_name}/download": {
"get": {
"tags": [
"Monitoring"
],
"summary": "下载日志文件",
"description": "下载日志文件\n\nArgs:\n log_name: 日志文件名支持带日期路径如YYYY-MM-DD/filename.log",
"operationId": "download_log_monitoring_logs__log_name__download_get",
"parameters": [
{
"name": "log_name",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Log Name"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/monitoring/logs/{date}/{log_name}/download": {
"get": {
"tags": [
"Monitoring"
],
"summary": "下载指定日期的日志文件",
"description": "下载指定日期的日志文件\n\nArgs:\n date: 日期格式为YYYY-MM-DD\n log_name: 日志文件名",
"operationId": "download_log_by_date_monitoring_logs__date___log_name__download_get",
"parameters": [
{
"name": "date",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Date"
}
},
{
"name": "log_name",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Log Name"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/monitoring/logs": {
"get": {
"tags": [
"Monitoring"
],
"summary": "获取日志列表",
"description": "获取日志列表,支持按时间分类\n\nArgs:\n date: 日期格式为YYYY-MM-DD如不提供则返回所有日期文件夹",
"operationId": "get_logs_list_monitoring_logs_get",
"parameters": [
{
"name": "date",
"in": "query",
"required": false,
"schema": {
"type": "string",
"title": "Date"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/monitoring/status": {
"get": {
"tags": [
"Monitoring"
],
"summary": "获取服务器状态",
"description": "获取服务器基本状态信息",
"operationId": "get_server_status_monitoring_status_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"components": {
"schemas": {
"BatchDeleteRequest": {
"properties": {
"files": {
"items": {
"type": "string"
},
"type": "array",
"title": "Files"
}
},
"type": "object",
"required": [
"files"
],
"title": "BatchDeleteRequest",
"description": "批量删除请求模型"
},
"BatchDownloadRequest": {
"properties": {
"files": {
"items": {
"type": "string"
},
"type": "array",
"title": "Files"
}
},
"type": "object",
"required": [
"files"
],
"title": "BatchDownloadRequest",
"description": "批量下载请求模型"
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
}
}
};
window.onload = function() {
SwaggerUIBundle({
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout",
defaultModelsExpandDepth: 1,
defaultModelExpandDepth: 1,
docExpansion: "list",
filter: true,
showExtensions: true,
showCommonExtensions: true
});
};
</script>
</body>
</html>

4
request/static/font-awesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

175
request/static/index.html Normal file
View File

@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>X-Request 管理系统</title>
<!-- 引入Tailwind CSS (离线版本) -->
<script src="vendor/tailwind.min.js"></script>
<!-- 引入内联 SVG 图标系统 (完全离线) -->
<script src="vendor/icons.js"></script>
<style>
.inline-icon, .inline-emoji {
display: inline-block;
vertical-align: middle;
}
.inline-icon svg {
width: 1em;
height: 1em;
fill: currentColor;
}
.inline-emoji {
font-size: 1em;
line-height: 1;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.inline-icon[data-spin="true"], .inline-emoji[data-spin="true"] {
animation: spin 1s linear infinite;
}
</style>
<!-- 配置Tailwind -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#8b5cf6',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
}
/* 全屏样式 */
html, body {
height: 100vh;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
}
main {
flex: 1;
padding: 1rem;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 顶部导航栏 -->
<nav class="bg-white shadow-md h-16" style="display: none;">
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center">
<span class="text-3xl font-bold text-primary">X</span>
<span class="ml-2 text-xl font-semibold text-gray-800">X-Request 管理系统</span>
</div>
<div class="ml-6 flex items-center space-x-4">
<a href="/" class="px-3 py-2 rounded-md text-sm font-medium text-primary bg-primary/10 border-b-2 border-primary">
<i class="fa fa-home mr-1"></i> 首页
</a>
<a href="/log.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-file-text mr-1"></i> 日志管理
</a>
<a href="/doc.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-book mr-1"></i> 接口文档
</a>
<a href="/status.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-heartbeat mr-1"></i> 系统状态
</a>
</div>
</div>
</div>
</div>
</nav>
<!-- 主要内容 -->
<main class="w-full px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-center">
<div class="max-w-4xl w-full">
<div class="bg-white rounded-lg shadow-md p-8 text-center">
<div class="mb-8">
<span class="text-8xl font-bold text-primary mb-4 inline-block">X</span>
<h1 class="text-3xl font-bold text-gray-800 mb-2">X-Request 管理系统</h1>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- 日志管理卡片 -->
<a href="log.html" class="block p-6 h-72 bg-primary/5 border border-primary/20 rounded-lg hover:bg-primary/10 transition-colors transform hover:scale-105">
<div class="flex flex-col items-center justify-center h-full">
<div class="flex items-center justify-center mb-4">
<div class="w-16 h-16 bg-primary/20 rounded-full flex items-center justify-center">
<i class="fa fa-file-text text-2xl text-primary"></i>
</div>
</div>
<h2 class="text-xl font-semibold text-gray-800 mb-2">日志管理</h2>
<p class="text-gray-600 mb-4 text-center">查看、下载和管理系统日志文件</p>
<div class="inline-flex items-center text-primary font-medium mt-auto">
<span>进入日志管理</span>
<i class="fa fa-arrow-right ml-2"></i>
</div>
</div>
</a>
<!-- 接口文档卡片 -->
<a href="doc.html" class="block p-6 h-72 bg-primary/5 border border-primary/20 rounded-lg hover:bg-primary/10 transition-colors transform hover:scale-105">
<div class="flex flex-col items-center justify-center h-full">
<div class="flex items-center justify-center mb-4">
<div class="w-16 h-16 bg-primary/20 rounded-full flex items-center justify-center">
<i class="fa fa-book text-2xl text-primary"></i>
</div>
</div>
<h2 class="text-xl font-semibold text-gray-800 mb-2">接口文档</h2>
<p class="text-gray-600 mb-4 text-center">查看系统API接口文档和规范</p>
<div class="inline-flex items-center text-primary font-medium mt-auto">
<span>进入接口文档</span>
<i class="fa fa-arrow-right ml-2"></i>
</div>
</div>
</a>
<!-- 系统状态卡片 -->
<a href="status.html" class="block p-6 h-72 bg-success/5 border border-success/20 rounded-lg hover:bg-success/10 transition-colors transform hover:scale-105">
<div class="flex flex-col items-center justify-center h-full">
<div class="flex items-center justify-center mb-4">
<div class="w-16 h-16 bg-success/20 rounded-full flex items-center justify-center">
<i class="fa fa-heartbeat text-2xl text-success"></i>
</div>
</div>
<h2 class="text-xl font-semibold text-gray-800 mb-2">系统状态</h2>
<p class="text-gray-600 mb-4 text-center">查看健康检查、应用信息和系统状态</p>
<div class="inline-flex items-center text-success font-medium mt-auto">
<span>进入系统状态</span>
<i class="fa fa-arrow-right ml-2"></i>
</div>
</div>
</a>
</div>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-white border-t border-gray-200 py-4">
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8 text-center text-sm text-gray-500">
<p>X-Request © 2025</p>
</div>
</footer>
</body>
</html>

697
request/static/log.html Normal file
View File

@@ -0,0 +1,697 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>X-Request 日志管理</title>
<!-- 引入Tailwind CSS (离线版本) -->
<script src="vendor/tailwind.min.js"></script>
<!-- 引入内联 SVG 图标系统 (完全离线) -->
<script src="vendor/icons.js"></script>
<style>
.inline-icon, .inline-emoji {
display: inline-block;
vertical-align: middle;
}
.inline-icon svg {
width: 1em;
height: 1em;
fill: currentColor;
}
.inline-emoji {
font-size: 1em;
line-height: 1;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.inline-icon[data-spin="true"], .inline-emoji[data-spin="true"] {
animation: spin 1s linear infinite;
}
</style>
<!-- 配置Tailwind -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#8b5cf6',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
}
/* 全屏样式 */
html, body {
height: 100vh;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
}
main {
flex: 1;
padding: 1rem;
}
.max-w-7xl {
max-width: 100%;
}
/* 调整网格布局高度 */
.h-full {
height: 100%;
}
/* 调整滚动区域最大高度 */
.max-h-screen-content {
max-height: calc(100vh - 200px);
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 顶部导航栏 -->
<nav class="bg-white shadow-md h-16">
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center">
<span class="text-3xl font-bold text-primary">X</span>
<span class="ml-2 text-xl font-semibold text-gray-800">X-Request 管理系统</span>
</div>
<div class="ml-6 flex items-center space-x-4">
<a href="/" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-home mr-1"></i> 首页
</a>
<a href="/log.html" class="px-3 py-2 rounded-md text-sm font-medium text-primary bg-primary/10 border-b-2 border-primary">
<i class="fa fa-file-text mr-1"></i> 日志管理
</a>
<a href="/doc.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-book mr-1"></i> 接口文档
</a>
<a href="/status.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-heartbeat mr-1"></i> 系统状态
</a>
</div>
</div>
<div class="flex items-center">
<div class="mr-4">
<span class="text-sm text-gray-500">更新时间: </span>
<span id="last-updated" class="font-medium text-gray-800">--:--:--</span>
</div>
<button type="button" id="refresh-btn" class="bg-primary hover:bg-primary/90 text-white px-3 py-1.5 rounded-md text-sm font-medium transition-colors mr-2">
<i class="fa fa-refresh mr-1"></i> 刷新
</button>
<button type="button" id="batch-download-btn" onclick="batchDownload()" class="bg-success hover:bg-success/90 text-white px-3 py-1.5 rounded-md text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed mr-2" disabled>
<i class="fa fa-download mr-1"></i> 批量下载
<span id="selected-count-download" class="ml-1 bg-white/20 px-2 py-0.5 rounded-full text-xs">0</span>
</button>
<button type="button" id="batch-delete-btn" onclick="batchDelete()" class="bg-danger hover:bg-danger/90 text-white px-3 py-1.5 rounded-md text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fa fa-trash mr-1"></i> 批量删除
<span id="selected-count-delete" class="ml-1 bg-white/20 px-2 py-0.5 rounded-full text-xs">0</span>
</button>
</div>
</div>
</div>
</nav>
<!-- 主要内容 -->
<main class="w-full px-4 sm:px-6 lg:px-8 py-4">
<!-- 日志管理视图 -->
<div class="bg-white rounded-lg shadow-md p-4 h-full">
<h2 class="text-xl font-bold text-gray-800 mb-4">日志管理</h2>
<!-- 日志分类和内容 -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 h-screen-content">
<!-- 左侧:日期列表 -->
<div class="lg:col-span-2">
<div class="bg-white rounded-lg shadow-sm p-4 h-full flex flex-col">
<h3 class="text-lg font-semibold text-gray-800 mb-4">按日期分类</h3>
<div class="space-y-2 flex-1 flex flex-col">
<div class="flex items-center">
<input type="checkbox" id="select-all-dates" class="rounded text-primary focus:ring-primary h-4 w-4 mr-2">
<label for="select-all-dates" class="text-sm font-medium text-gray-700">全选日期</label>
</div>
<div id="dates-list" class="space-y-1 flex-1 overflow-y-auto mt-2">
<!-- 日期列表将通过JavaScript动态生成 -->
<div class="text-center text-gray-500 py-8">
<i class="fa fa-spinner fa-spin text-xl mb-2"></i>
<p>加载中...</p>
</div>
</div>
</div>
</div>
</div>
<!-- 中间:日志文件列表 -->
<div class="lg:col-span-3">
<div class="bg-white rounded-lg shadow-sm p-4 h-full flex flex-col">
<div class="flex justify-between items-center mb-4">
<h3 id="selected-date-title" class="text-lg font-semibold text-gray-800">选择日期查看日志</h3>
<div class="flex items-center">
<input type="checkbox" id="select-all-logs" class="rounded text-primary focus:ring-primary h-4 w-4 mr-2">
<label for="select-all-logs" class="text-sm font-medium text-gray-700">全选日志</label>
</div>
</div>
<div id="logs-list" class="space-y-2 flex-1 overflow-y-auto">
<!-- 日志文件列表将通过JavaScript动态生成 -->
<div class="text-center text-gray-500 py-8">
<p>请选择左侧日期</p>
</div>
</div>
</div>
</div>
<!-- 右侧:日志内容 -->
<div class="lg:col-span-7">
<div class="bg-white rounded-lg shadow-sm p-4 h-full flex flex-col">
<div class="flex justify-between items-center mb-4">
<h3 id="current-log-title" class="text-lg font-semibold text-gray-800">日志内容</h3>
<div class="flex items-center space-x-2">
<select id="log-mode" class="border border-gray-300 rounded-md text-sm px-2 py-1">
<option value="latest" selected>最新的N条日志顶部</option>
<option value="oldest">最早的N条日志顶部</option>
</select>
<select id="log-lines" class="border border-gray-300 rounded-md text-sm px-2 py-1">
<option value="50">50行</option>
<option value="100" selected>100行</option>
<option value="200">200行</option>
<option value="500">500行</option>
<option value="1000">1000行</option>
<option value="2000">2000行</option>
</select>
</div>
</div>
<div class="bg-gray-900 text-gray-200 rounded-md p-4 flex-1 overflow-auto">
<pre id="log-content" class="text-sm whitespace-pre-wrap">请选择日志文件查看内容...</pre>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- 自定义确认对话框 -->
<div id="confirm-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-2" id="confirm-title">确认操作</h3>
<p class="text-gray-600 mb-6" id="confirm-message">确定要执行此操作吗?</p>
<div class="flex justify-end space-x-3">
<button type="button" id="confirm-cancel" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
取消
</button>
<button type="button" id="confirm-ok" class="px-4 py-2 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors">
确定
</button>
</div>
</div>
</div>
</div>
<!-- JavaScript -->
<script>
// 全局变量
let selectedDate = null;
let currentLogFile = null;
let selectedFiles = new Set(); // 存储选中的日志文件路径
let dates = []; // 存储所有日期
// 自定义确认对话框
let confirmResolver = null;
function showConfirm(title, message) {
return new Promise((resolve) => {
confirmResolver = resolve;
const modal = document.getElementById('confirm-modal');
const titleEl = document.getElementById('confirm-title');
const messageEl = document.getElementById('confirm-message');
titleEl.textContent = title;
messageEl.textContent = message;
modal.classList.remove('hidden');
modal.classList.add('flex');
});
}
// 确认对话框事件监听
document.getElementById('confirm-cancel').addEventListener('click', () => {
const modal = document.getElementById('confirm-modal');
modal.classList.add('hidden');
modal.classList.remove('flex');
if (confirmResolver) {
confirmResolver(false);
confirmResolver = null;
}
});
document.getElementById('confirm-ok').addEventListener('click', () => {
const modal = document.getElementById('confirm-modal');
modal.classList.add('hidden');
modal.classList.remove('flex');
if (confirmResolver) {
confirmResolver(true);
confirmResolver = null;
}
});
// 工具函数
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// 获取日期列表
async function fetchDates() {
try {
const response = await fetch('/monitoring/logs');
const data = await response.json();
if (data.success) {
dates = data.data.dates || [];
renderDatesList();
return true;
}
} catch (error) {
console.error('获取日期列表失败:', error);
document.getElementById('dates-list').innerHTML = '<div class="text-center text-red-500 py-8"><i class="fa fa-exclamation-circle text-xl mb-2"></i><p>获取日期列表失败</p></div>';
}
return false;
}
// 渲染日期列表
function renderDatesList() {
const datesList = document.getElementById('dates-list');
if (dates.length === 0) {
datesList.innerHTML = '<div class="text-center text-gray-500 py-8"><p>没有找到日志日期</p></div>';
return;
}
let html = '';
for (const date of dates) {
const isSelected = selectedDate === date.date;
html += `
<div class="flex items-center p-2 rounded-md border border-gray-200 cursor-pointer hover:bg-gray-100 transition-colors ${isSelected ? 'bg-primary/10 border-l-4 border-primary' : ''}">
<input type="checkbox" data-date="${date.date}" class="date-checkbox rounded text-primary focus:ring-primary h-4 w-4 mr-2">
<div class="flex-1" onclick="selectDate('${date.date}')">
<div class="font-medium text-gray-800">${date.date}</div>
<div class="text-xs text-gray-500">${date.log_count} 个日志文件</div>
</div>
<div class="text-xs text-gray-500">${new Date(date.last_modified * 1000).toLocaleString()}</div>
</div>
`;
}
datesList.innerHTML = html;
// 添加日期复选框事件监听
document.querySelectorAll('.date-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', handleDateCheckboxChange);
});
}
// 选择日期
async function selectDate(date) {
selectedDate = date;
currentLogFile = null;
selectedFiles.clear();
updateSelectedCount();
// 更新日期列表样式
renderDatesList();
// 更新日志列表
await fetchLogsByDate(date);
// 清空日志内容
document.getElementById('current-log-title').textContent = '日志内容';
document.getElementById('log-content').textContent = '请选择日志文件查看内容...';
}
// 获取指定日期的日志文件
async function fetchLogsByDate(date) {
try {
const response = await fetch(`/monitoring/logs?date=${date}`);
const data = await response.json();
if (data.success) {
const logs = data.data.logs || [];
renderLogsList(logs, date);
return true;
}
} catch (error) {
console.error('获取日志文件列表失败:', error);
document.getElementById('logs-list').innerHTML = '<div class="text-center text-red-500 py-8"><i class="fa fa-exclamation-circle text-xl mb-2"></i><p>获取日志文件列表失败</p></div>';
}
return false;
}
// 渲染日志文件列表
function renderLogsList(logs, date) {
const logsList = document.getElementById('logs-list');
const selectedDateTitle = document.getElementById('selected-date-title');
selectedDateTitle.textContent = `${date} 日志文件 (${logs.length} 个)`;
if (logs.length === 0) {
logsList.innerHTML = '<div class="text-center text-gray-500 py-8"><p>该日期没有日志文件</p></div>';
return;
}
let html = '';
for (const log of logs) {
const isSelected = selectedFiles.has(log.relative_path);
// 将Windows风格的路径分隔符替换为Unix风格
const displayPath = log.relative_path.replace(/\\/g, '/');
html += `
<div class="flex items-center p-2 rounded-md border border-gray-200 hover:bg-gray-100 transition-colors">
<input type="checkbox" data-path="${log.relative_path}" class="log-checkbox rounded text-primary focus:ring-primary h-4 w-4 mr-2" ${isSelected ? 'checked' : ''}>
<div class="flex-1" onclick="viewLog('${displayPath}', '${log.name}')">
<div class="font-medium text-gray-800">${log.name}</div>
<div class="text-xs text-gray-500">${formatBytes(log.size)} · ${new Date(log.modified_at * 1000).toLocaleString()}</div>
</div>
<a href="/monitoring/logs/${log.relative_path}/download" target="_blank" class="text-primary hover:text-primary/80 mr-2" title="下载">
<i class="fa fa-download"></i>
</a>
<button type="button" onclick="deleteLog('${displayPath}', '${log.name}')" class="text-danger hover:text-danger/80" title="删除">
<i class="fa fa-trash"></i>
</button>
</div>
`;
}
logsList.innerHTML = html;
// 添加日志复选框事件监听
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', handleLogCheckboxChange);
});
}
// 查看日志内容
async function viewLog(filePath, fileName) {
currentLogFile = filePath;
// 更新标题
document.getElementById('current-log-title').textContent = `日志内容 - ${fileName}`;
try {
const entries = document.getElementById('log-lines').value;
const mode = document.getElementById('log-mode').value;
// 将Windows风格的路径分隔符替换为Unix风格
const normalizedPath = filePath.replace(/\\/g, '/');
const parts = normalizedPath.split('/');
const date = parts[0];
const logName = parts.slice(1).join('/');
const response = await fetch(`/monitoring/logs/${date}/${logName}?entries=${entries}&mode=${mode}`);
const data = await response.json();
if (data.success) {
document.getElementById('log-content').textContent = data.data.content;
// 根据显示模式滚动
const logContent = document.getElementById('log-content');
logContent.scrollTop = 0;
} else {
document.getElementById('log-content').textContent = `获取日志内容失败: ${data.message}`;
}
} catch (error) {
console.error('获取日志内容失败:', error);
document.getElementById('log-content').textContent = `获取日志内容失败: ${error.message}`;
}
}
// 处理日期复选框变化
async function handleDateCheckboxChange(e) {
const date = e.target.dataset.date;
const isChecked = e.target.checked;
if (isChecked) {
// 选中该日期下的所有日志文件
await fetchLogsByDate(date);
// 勾选该日期下的所有日志文件
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
const path = checkbox.dataset.path;
if (path && path.startsWith(date)) {
checkbox.checked = true;
selectedFiles.add(path);
}
});
} else {
// 取消选中该日期下的所有日志文件
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
const path = checkbox.dataset.path;
if (path && path.startsWith(date)) {
checkbox.checked = false;
selectedFiles.delete(path);
}
});
}
updateSelectedCount();
}
// 处理日志复选框变化
function handleLogCheckboxChange(e) {
const filePath = e.target.dataset.path;
const isChecked = e.target.checked;
if (isChecked) {
selectedFiles.add(filePath);
} else {
selectedFiles.delete(filePath);
}
updateSelectedCount();
}
// 更新选中计数
function updateSelectedCount() {
const count = selectedFiles.size;
// 更新批量下载的计数
document.getElementById('selected-count-download').textContent = count;
// 更新批量删除的计数
document.getElementById('selected-count-delete').textContent = count;
// 更新按钮状态
document.getElementById('batch-download-btn').disabled = count === 0;
document.getElementById('batch-delete-btn').disabled = count === 0;
}
// 删除单个日志文件
async function deleteLog(filePath, fileName) {
const confirmed = await showConfirm('删除确认', `确定要删除日志文件 "${fileName}" 吗?此操作不可恢复。`);
if (!confirmed) {
return;
}
try {
const response = await fetch(`/monitoring/logs/${filePath}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
// 更新日志列表
await fetchLogsByDate(selectedDate);
// 清空日志内容如果当前显示的是被删除的日志
if (currentLogFile === filePath) {
document.getElementById('current-log-title').textContent = '日志内容';
document.getElementById('log-content').textContent = '请选择日志文件查看内容...';
currentLogFile = null;
}
// 更新选中文件集合
selectedFiles.delete(filePath);
updateSelectedCount();
// 显示成功提示
alert(`日志文件 "${fileName}" 删除成功`);
} else {
alert(`删除失败: ${data.message}`);
}
} catch (error) {
console.error('删除日志失败:', error);
alert(`删除失败: ${error.message}`);
}
}
// 批量删除日志文件
async function batchDelete() {
if (selectedFiles.size === 0) {
alert('请先选择日志文件');
return;
}
const confirmed = await showConfirm('批量删除确认', `确定要删除选中的 ${selectedFiles.size} 个日志文件吗?此操作不可恢复。`);
if (!confirmed) {
return;
}
try {
const response = await fetch('/monitoring/logs/batch/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
files: Array.from(selectedFiles)
})
});
const data = await response.json();
if (data.success) {
// 更新日志列表
await fetchLogsByDate(selectedDate);
// 清空当前日志内容
document.getElementById('current-log-title').textContent = '日志内容';
document.getElementById('log-content').textContent = '请选择日志文件查看内容...';
currentLogFile = null;
// 清空选中文件集合
selectedFiles.clear();
updateSelectedCount();
// 显示成功提示
alert(`批量删除完成。成功删除 ${data.data.deleted} 个文件,失败 ${data.data.failed} 个文件。`);
} else {
alert(`批量删除失败: ${data.message}`);
}
} catch (error) {
console.error('批量删除失败:', error);
alert(`批量删除失败: ${error.message}`);
}
}
// 全选/取消全选日期
function toggleSelectAllDates() {
const selectAllCheckbox = document.getElementById('select-all-dates');
const isChecked = selectAllCheckbox.checked;
document.querySelectorAll('.date-checkbox').forEach(checkbox => {
checkbox.checked = isChecked;
// 触发change事件调用handleDateCheckboxChange处理日志文件联动
checkbox.dispatchEvent(new Event('change'));
});
}
// 全选/取消全选日志
function toggleSelectAllLogs() {
const selectAllCheckbox = document.getElementById('select-all-logs');
const isChecked = selectAllCheckbox.checked;
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
checkbox.checked = isChecked;
checkbox.dispatchEvent(new Event('change'));
});
}
// 批量下载日志
async function batchDownload() {
if (selectedFiles.size === 0) {
alert('请先选择日志文件');
return;
}
try {
const response = await fetch('/monitoring/logs/download', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
files: Array.from(selectedFiles)
})
});
if (response.ok) {
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `logs_${new Date().toISOString().slice(0, 10)}_batch.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} else {
const data = await response.json();
alert(`批量下载失败: ${data.message}`);
}
} catch (error) {
console.error('批量下载失败:', error);
alert(`批量下载失败: ${error.message}`);
}
}
// 刷新所有数据
async function refreshAll() {
await fetchDates();
if (selectedDate) {
await fetchLogsByDate(selectedDate);
}
// 更新最后更新时间
const now = new Date();
document.getElementById('last-updated').textContent = now.toLocaleTimeString();
}
// 初始化
async function init() {
// 初始加载数据
await refreshAll();
// 事件监听
document.getElementById('refresh-btn').addEventListener('click', refreshAll);
document.getElementById('select-all-dates').addEventListener('change', toggleSelectAllDates);
document.getElementById('select-all-logs').addEventListener('change', toggleSelectAllLogs);
// 日志行数或显示模式变化事件
document.getElementById('log-lines').addEventListener('change', () => {
if (currentLogFile) {
// 直接调用viewLog使用当前的filePath和fileName
viewLog(currentLogFile, document.getElementById('current-log-title').textContent.replace('日志内容 - ', ''));
}
});
// 显示模式变化事件
document.getElementById('log-mode').addEventListener('change', () => {
if (currentLogFile) {
// 直接调用viewLog使用当前的filePath和fileName
viewLog(currentLogFile, document.getElementById('current-log-title').textContent.replace('日志内容 - ', ''));
}
});
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>

474
request/static/status.html Normal file
View File

@@ -0,0 +1,474 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统状态 - X-Request</title>
<!-- 引入Tailwind CSS (离线版本) -->
<script src="vendor/tailwind.min.js"></script>
<!-- 引入内联 SVG 图标系统 (完全离线) -->
<script src="vendor/icons.js"></script>
<style>
.inline-icon, .inline-emoji {
display: inline-block;
vertical-align: middle;
}
.inline-icon svg {
width: 1em;
height: 1em;
fill: currentColor;
}
.inline-emoji {
font-size: 1em;
line-height: 1;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.inline-icon[data-spin="true"], .inline-emoji[data-spin="true"] {
animation: spin 1s linear infinite;
}
</style>
<!-- 配置Tailwind -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#8b5cf6',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
}
/* 全屏样式 */
html, body {
height: 100vh;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
}
main {
flex: 1;
padding: 1rem;
}
.status-card {
transition: all 0.3s ease;
}
.status-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.pulse-animation {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .5;
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 顶部导航栏 -->
<nav class="bg-white shadow-md h-16">
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center">
<span class="text-3xl font-bold text-primary">X</span>
<span class="ml-2 text-xl font-semibold text-gray-800">X-Request 管理系统</span>
</div>
<div class="ml-6 flex items-center space-x-4">
<a href="/" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-home mr-1"></i> 首页
</a>
<a href="/log.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-file-text mr-1"></i> 日志管理
</a>
<a href="/doc.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
<i class="fa fa-book mr-1"></i> 接口文档
</a>
<a href="/status.html" class="px-3 py-2 rounded-md text-sm font-medium text-primary bg-primary/10 border-b-2 border-primary">
<i class="fa fa-heartbeat mr-1"></i> 系统状态
</a>
</div>
</div>
<div class="flex items-center space-x-4">
<button id="refresh-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors flex items-center">
<i class="fa fa-refresh mr-2"></i>
<span>刷新</span>
</button>
<a href="index.html" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors flex items-center">
<i class="fa fa-home mr-2"></i>
<span>返回首页</span>
</a>
</div>
</div>
</div>
</nav>
<!-- 主要内容 -->
<main class="w-full px-4 sm:px-6 lg:px-8 py-4">
<div class="max-w-6xl mx-auto">
<!-- 健康检查卡片 -->
<div class="bg-white rounded-lg shadow-md p-6 mb-6 status-card">
<div class="flex items-center justify-between mb-4">
<h2 class="text-2xl font-bold text-gray-800 flex items-center">
<i class="fa fa-heartbeat text-primary mr-3"></i>
健康检查
</h2>
<div id="health-status" class="flex items-center">
<span class="px-3 py-1 rounded-full text-sm font-medium bg-gray-200 text-gray-700">
<i class="fa fa-spinner fa-spin mr-2"></i>
检查中...
</span>
</div>
</div>
<div id="health-content" class="space-y-3">
<div class="flex items-center justify-center py-8">
<i class="fa fa-spinner fa-spin text-3xl text-primary"></i>
<span class="ml-3 text-gray-600">正在加载健康检查信息...</span>
</div>
</div>
</div>
<!-- 应用信息卡片 -->
<div class="bg-white rounded-lg shadow-md p-6 mb-6 status-card">
<div class="flex items-center justify-between mb-4">
<h2 class="text-2xl font-bold text-gray-800 flex items-center">
<i class="fa fa-info-circle text-primary mr-3"></i>
应用信息
</h2>
<div class="text-sm text-gray-500">
<i class="fa fa-clock-o mr-1"></i>
<span id="last-updated">--</span>
</div>
</div>
<div id="info-content" class="space-y-3">
<div class="flex items-center justify-center py-8">
<i class="fa fa-spinner fa-spin text-3xl text-primary"></i>
<span class="ml-3 text-gray-600">正在加载应用信息...</span>
</div>
</div>
</div>
<!-- 系统状态卡片 -->
<div class="bg-white rounded-lg shadow-md p-6 status-card">
<div class="flex items-center justify-between mb-4">
<h2 class="text-2xl font-bold text-gray-800 flex items-center">
<i class="fa fa-server text-primary mr-3"></i>
系统状态
</h2>
</div>
<div id="system-content" class="space-y-3">
<div class="flex items-center justify-center py-8">
<i class="fa fa-spinner fa-spin text-3xl text-primary"></i>
<span class="ml-3 text-gray-600">正在加载系统状态...</span>
</div>
</div>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-white border-t border-gray-200 py-4 mt-6">
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8 text-center text-sm text-gray-500">
<p>X-Request © 2025</p>
</div>
</footer>
<script>
// 工具函数
function formatTimestamp(timestamp) {
if (!timestamp) return '--';
const date = new Date(timestamp * 1000);
return date.toLocaleString('zh-CN');
}
function formatDateTime(date) {
return date.toLocaleString('zh-CN');
}
// 获取健康检查信息
async function fetchHealth() {
try {
const response = await fetch('/health');
const data = await response.json();
const healthStatus = document.getElementById('health-status');
const healthContent = document.getElementById('health-content');
if (data.status === 1 && data.response) {
// 健康状态
healthStatus.innerHTML = `
<span class="px-3 py-1 rounded-full text-sm font-medium bg-success/20 text-success border border-success/30">
<i class="fa fa-check-circle mr-2"></i>
健康
</span>
`;
// 健康信息内容
healthContent.innerHTML = `
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-success/5 border border-success/20 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-check-circle text-success text-xl mr-2"></i>
<span class="font-semibold text-gray-800">服务状态</span>
</div>
<p class="text-2xl font-bold text-success">${data.response.status || 'healthy'}</p>
</div>
<div class="bg-primary/5 border border-primary/20 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-cog text-primary text-xl mr-2"></i>
<span class="font-semibold text-gray-800">服务名称</span>
</div>
<p class="text-lg font-semibold text-gray-700">${data.response.service || 'Unknown'}</p>
</div>
</div>
<div class="mt-4 pt-4 border-t border-gray-200">
<div class="text-sm text-gray-500">
<i class="fa fa-clock-o mr-1"></i>
检查时间: ${data.time || '--'}
</div>
</div>
`;
} else {
// 不健康状态
healthStatus.innerHTML = `
<span class="px-3 py-1 rounded-full text-sm font-medium bg-danger/20 text-danger border border-danger/30">
<i class="fa fa-exclamation-circle mr-2"></i>
异常
</span>
`;
healthContent.innerHTML = `
<div class="bg-danger/5 border border-danger/20 rounded-lg p-4">
<p class="text-danger font-semibold">健康检查失败</p>
<p class="text-sm text-gray-600 mt-2">${data.response?.error || '未知错误'}</p>
</div>
`;
}
} catch (error) {
const healthStatus = document.getElementById('health-status');
const healthContent = document.getElementById('health-content');
healthStatus.innerHTML = `
<span class="px-3 py-1 rounded-full text-sm font-medium bg-danger/20 text-danger border border-danger/30">
<i class="fa fa-times-circle mr-2"></i>
错误
</span>
`;
healthContent.innerHTML = `
<div class="bg-danger/5 border border-danger/20 rounded-lg p-4">
<p class="text-danger font-semibold">无法获取健康检查信息</p>
<p class="text-sm text-gray-600 mt-2">${error.message}</p>
</div>
`;
}
}
// 获取应用信息
async function fetchInfo() {
try {
const response = await fetch('/info');
const data = await response.json();
const infoContent = document.getElementById('info-content');
if (data.status === 1 && data.response) {
const info = data.response;
infoContent.innerHTML = `
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="bg-primary/5 border border-primary/20 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-tag text-primary text-xl mr-2"></i>
<span class="font-semibold text-gray-800">应用名称</span>
</div>
<p class="text-lg font-semibold text-gray-700">${info.app_name || 'Unknown'}</p>
</div>
<div class="bg-secondary/5 border border-secondary/20 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-code-fork text-secondary text-xl mr-2"></i>
<span class="font-semibold text-gray-800">版本号</span>
</div>
<p class="text-lg font-semibold text-gray-700">${info.version || 'Unknown'}</p>
</div>
<div class="bg-${info.debug ? 'warning' : 'success'}/5 border border-${info.debug ? 'warning' : 'success'}/20 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-${info.debug ? 'bug' : 'shield'} text-${info.debug ? 'warning' : 'success'} text-xl mr-2"></i>
<span class="font-semibold text-gray-800">调试模式</span>
</div>
<p class="text-lg font-semibold text-${info.debug ? 'warning' : 'success'}">${info.debug ? '已启用' : '已禁用'}</p>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-server text-gray-600 text-xl mr-2"></i>
<span class="font-semibold text-gray-800">主机地址</span>
</div>
<p class="text-lg font-semibold text-gray-700">${info.host || 'Unknown'}</p>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-plug text-gray-600 text-xl mr-2"></i>
<span class="font-semibold text-gray-800">端口号</span>
</div>
<p class="text-lg font-semibold text-gray-700">${info.port || 'Unknown'}</p>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-link text-gray-600 text-xl mr-2"></i>
<span class="font-semibold text-gray-800">访问地址</span>
</div>
<p class="text-lg font-semibold text-gray-700">http://${info.host === '0.0.0.0' ? 'localhost' : info.host}:${info.port || 'Unknown'}</p>
</div>
</div>
<div class="mt-4 pt-4 border-t border-gray-200">
<div class="text-sm text-gray-500">
<i class="fa fa-clock-o mr-1"></i>
更新时间: ${data.time || '--'}
</div>
</div>
`;
} else {
infoContent.innerHTML = `
<div class="bg-danger/5 border border-danger/20 rounded-lg p-4">
<p class="text-danger font-semibold">无法获取应用信息</p>
<p class="text-sm text-gray-600 mt-2">${data.response?.error || '未知错误'}</p>
</div>
`;
}
} catch (error) {
const infoContent = document.getElementById('info-content');
infoContent.innerHTML = `
<div class="bg-danger/5 border border-danger/20 rounded-lg p-4">
<p class="text-danger font-semibold">无法获取应用信息</p>
<p class="text-sm text-gray-600 mt-2">${error.message}</p>
</div>
`;
}
}
// 获取系统状态
async function fetchSystemStatus() {
try {
const response = await fetch('/monitoring/status');
const data = await response.json();
const systemContent = document.getElementById('system-content');
if (data.success && data.data && data.data.system) {
const system = data.data.system;
systemContent.innerHTML = `
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-desktop text-gray-600 text-xl mr-2"></i>
<span class="font-semibold text-gray-800">主机名</span>
</div>
<p class="text-lg font-semibold text-gray-700">${system.hostname || 'Unknown'}</p>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-linux text-gray-600 text-xl mr-2"></i>
<span class="font-semibold text-gray-800">系统类型</span>
</div>
<p class="text-lg font-semibold text-gray-700">${system.system_type || 'Unknown'}</p>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-python text-gray-600 text-xl mr-2"></i>
<span class="font-semibold text-gray-800">Python 版本</span>
</div>
<p class="text-lg font-semibold text-gray-700">${system.python_version || 'Unknown'}</p>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div class="flex items-center mb-2">
<i class="fa fa-clock-o text-gray-600 text-xl mr-2"></i>
<span class="font-semibold text-gray-800">时间戳</span>
</div>
<p class="text-lg font-semibold text-gray-700">${formatTimestamp(system.timestamp)}</p>
</div>
</div>
`;
} else {
systemContent.innerHTML = `
<div class="bg-warning/5 border border-warning/20 rounded-lg p-4">
<p class="text-warning font-semibold">系统状态信息不可用</p>
</div>
`;
}
} catch (error) {
const systemContent = document.getElementById('system-content');
systemContent.innerHTML = `
<div class="bg-warning/5 border border-warning/20 rounded-lg p-4">
<p class="text-warning font-semibold">无法获取系统状态</p>
<p class="text-sm text-gray-600 mt-2">${error.message}</p>
</div>
`;
}
}
// 刷新所有信息
async function refreshAll() {
const lastUpdated = document.getElementById('last-updated');
lastUpdated.textContent = formatDateTime(new Date());
await Promise.all([
fetchHealth(),
fetchInfo(),
fetchSystemStatus()
]);
}
// 初始化
async function init() {
// 初始加载数据
await refreshAll();
// 刷新按钮事件
document.getElementById('refresh-btn').addEventListener('click', async () => {
const btn = document.getElementById('refresh-btn');
const icon = btn.querySelector('i');
icon.classList.add('fa-spin');
btn.disabled = true;
await refreshAll();
icon.classList.remove('fa-spin');
btn.disabled = false;
});
// 自动刷新每30秒
setInterval(refreshAll, 30000);
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

140
request/static/vendor/icons.js vendored Normal file
View File

@@ -0,0 +1,140 @@
/**
* Emoji Icons - Beautiful Emoji Icons (Offline)
* Modern, beautiful emoji icons, completely offline
* Compatible with existing fa-* class names
*/
const Icons = {
// 文档相关图标
'document-text': '📄',
'document': '📄',
'file-text': '📝',
'book-open': '📖',
'book': '📖',
// 健康/状态相关图标
'heart-pulse': '💓',
'heart': '❤️',
'check-circle': '✅',
'x-circle': '❌',
'exclamation-triangle': '⚠️',
'information-circle': '',
// 导航相关图标
'home': '🏠',
'arrow-right': '➡️',
'arrow-left': '⬅️',
// 操作相关图标
'arrow-downTray': '⬇️',
'download': '⬇️',
'trash': '🗑️',
'folder': '📁',
'magnifying-glass': '🔍',
'eye': '👁️',
'check-square': '☑️',
'square': '⬜',
'ellipsis-horizontal': '⋯',
// 系统相关图标
'server': '🖥️',
'chart-bar': '📊',
'calendar': '📅',
'clock': '🕐',
'user': '👤',
'cpu-chip': '💾',
'circle-stack': '🗃️',
'globe-alt': '🌐',
// 设置相关图标
'cog-6-tooth': '⚙️',
'bell': '🔔',
'inbox': '📥',
// 新增:文件操作
'folder-plus': '📁➕',
'document-plus': '📝➕',
'arrow-path': '🔄',
};
// 创建图标元素的辅助函数
function createIcon(name, className = '') {
const emoji = Icons[name];
if (!emoji) {
console.warn(`Icon "${name}" not found`);
return '';
}
return `<span class="inline-emoji ${className}" data-icon="${name}">${emoji}</span>`;
}
// 初始化:替换所有 <i class="fa fa-"> 为 Emoji 图标
function initIcons() {
// 映射旧图标名到新图标名
const iconMapping = {
'fa-file-text-o': 'document-text',
'fa-file-text': 'document-text',
'fa-book': 'book-open',
'fa-book-o': 'book-open',
'fa-heartbeat': 'heart-pulse',
'fa-home': 'home',
'fa-arrow-right': 'arrow-right',
'fa-download': 'download',
'fa-trash': 'trash',
'fa-check-circle': 'check-circle',
'fa-times-circle': 'x-circle',
'fa-info-circle': 'information-circle',
'fa-exclamation-circle': 'exclamation-triangle',
'fa-refresh': 'arrow-path',
'fa-server': 'server',
'fa-clock-o': 'clock',
'fa-eye': 'eye',
};
// 查找所有 Font Awesome 图标
document.querySelectorAll('i.fa').forEach(icon => {
const classes = Array.from(icon.classList);
const iconName = classes.find(cls => cls.startsWith('fa-') && cls !== 'fa');
if (iconName) {
// 映射到新图标名
const mappedName = iconMapping[iconName] || iconName.replace('fa-', '');
const emoji = Icons[mappedName];
if (emoji) {
// 保留原有的类名(除了 fa 和 fa-*
const otherClasses = classes.filter(cls => !cls.startsWith('fa'));
const newElement = document.createElement('span');
newElement.className = `inline-emoji ${otherClasses.join(' ')}`;
newElement.textContent = emoji;
newElement.style.display = 'inline-block';
newElement.style.verticalAlign = 'middle';
// 处理旋转动画
if (classes.includes('fa-spin')) {
newElement.setAttribute('data-spin', 'true');
}
// 复制样式
const computedStyle = window.getComputedStyle(icon);
if (computedStyle.fontSize) {
newElement.style.fontSize = computedStyle.fontSize;
}
icon.parentNode.replaceChild(newElement, icon);
}
}
});
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initIcons);
} else {
initIcons();
}
// 添加便捷方法到全局
window.Icons = {
create: createIcon,
render: (name, className = '') => createIcon(name, className),
};

File diff suppressed because one or more lines are too long

3
request/static/vendor/swagger-ui.css vendored Normal file

File diff suppressed because one or more lines are too long

65
request/static/vendor/tailwind.min.js vendored Normal file

File diff suppressed because one or more lines are too long