1. 增加了请求框架
2. 增加了删除虚拟环境的脚本
This commit is contained in:
1
request/static/.openapi_cache
Normal file
1
request/static/.openapi_cache
Normal file
@@ -0,0 +1 @@
|
||||
0dc715b603074fca864c0c5074317e6a
|
||||
799
request/static/doc.html
Normal file
799
request/static/doc.html
Normal 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
4
request/static/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
request/static/fontawesome-webfont.ttf
Normal file
BIN
request/static/fontawesome-webfont.ttf
Normal file
Binary file not shown.
BIN
request/static/fontawesome-webfont.woff
Normal file
BIN
request/static/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
request/static/fontawesome-webfont.woff2
Normal file
BIN
request/static/fontawesome-webfont.woff2
Normal file
Binary file not shown.
175
request/static/index.html
Normal file
175
request/static/index.html
Normal 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
697
request/static/log.html
Normal 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
474
request/static/status.html
Normal 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>
|
||||
|
||||
4
request/static/vendor/font-awesome/font-awesome.min.css
vendored
Normal file
4
request/static/vendor/font-awesome/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
request/static/vendor/font-awesome/fonts/fontawesome-webfont.ttf
vendored
Normal file
BIN
request/static/vendor/font-awesome/fonts/fontawesome-webfont.ttf
vendored
Normal file
Binary file not shown.
BIN
request/static/vendor/font-awesome/fonts/fontawesome-webfont.woff
vendored
Normal file
BIN
request/static/vendor/font-awesome/fonts/fontawesome-webfont.woff
vendored
Normal file
Binary file not shown.
BIN
request/static/vendor/font-awesome/fonts/fontawesome-webfont.woff2
vendored
Normal file
BIN
request/static/vendor/font-awesome/fonts/fontawesome-webfont.woff2
vendored
Normal file
Binary file not shown.
140
request/static/vendor/icons.js
vendored
Normal file
140
request/static/vendor/icons.js
vendored
Normal 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),
|
||||
};
|
||||
3
request/static/vendor/swagger-ui-bundle.js
vendored
Normal file
3
request/static/vendor/swagger-ui-bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3
request/static/vendor/swagger-ui.css
vendored
Normal file
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
65
request/static/vendor/tailwind.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user