This commit is contained in:
kingecg 2025-06-13 21:47:34 +08:00
parent 56210416a6
commit 9822db29d9
5 changed files with 1414 additions and 5 deletions

299
adminui/css/style.css Normal file
View File

@ -0,0 +1,299 @@
/* http_server_management_console/frontend/css/style.css */
:root {
--primary: #409EFF;
--secondary: #67C23A;
--danger: #F56C6C;
--warning: #E6A23C;
--info: #909399;
--dark: #1a1a2e;
--light: #f8f9fa;
--bg-gradient: linear-gradient(135deg, var(--dark) 0%, #16213e 100%);
--glass-bg: rgba(255, 255, 255, 0.05);
--glass-border: rgba(255, 255, 255, 0.1);
--glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
body {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
background: var(--bg-gradient);
color: var(--light);
min-height: 100vh;
margin: 0;
padding: 0;
}
/* 路由列表展开收起动画 */
.route-list {
transition: all 0.3s ease;
overflow: hidden;
max-height: 0;
opacity: 0;
}
.route-list.expanded {
max-height: 1000px;
opacity: 1;
}
.route-list.collapsed {
max-height: 0;
opacity: 0;
}
.toggle-icon {
transition: transform 0.3s ease;
}
.toggle-icon.collapsed {
transform: rotate(-90deg);
}
/* Element UI 风格按钮 */
.el-button {
border-radius: 4px;
padding: 10px 20px;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
border: 1px solid transparent;
}
.el-button--primary {
background-color: var(--primary);
color: white;
}
.el-button--primary:hover {
background-color: #66b1ff;
border-color: #66b1ff;
}
.el-button--success {
background-color: var(--secondary);
color: white;
}
.el-button--success:hover {
background-color: #85ce61;
border-color: #85ce61;
}
.el-button--danger {
background-color: var(--danger);
color: white;
}
.el-button--danger:hover {
background-color: #f78989;
border-color: #f78989;
}
.el-button--warning {
background-color: var(--warning);
color: white;
}
.el-button--warning:hover {
background-color: #ebb563;
border-color: #ebb563;
}
/* 卡片样式 */
.el-card {
border-radius: 4px;
border: 1px solid var(--glass-border);
background-color: var(--glass-bg);
color: var(--light);
overflow: hidden;
transition: all 0.3s;
}
.el-card__header {
padding: 18px 20px;
border-bottom: 1px solid var(--glass-border);
box-sizing: border-box;
background-color: rgba(0, 0, 0, 0.1);
}
.el-card__body {
padding: 20px;
}
/* 表单元素 */
.el-form-item {
margin-bottom: 22px;
}
.el-form-item__label {
color: var(--light);
font-size: 14px;
line-height: 40px;
padding: 0 12px 0 0;
box-sizing: border-box;
}
.el-input__inner {
background-color: rgba(0, 0, 0, 0.2);
border: 1px solid var(--glass-border);
color: var(--light);
border-radius: 4px;
padding: 0 15px;
height: 40px;
line-height: 40px;
transition: all 0.3s;
}
.el-input__inner:focus {
border-color: var(--primary);
outline: 0;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
/* 表格样式 */
.el-table {
background-color: transparent;
color: var(--light);
}
.el-table th {
background-color: rgba(0, 0, 0, 0.2);
color: var(--light);
}
.el-table tr {
background-color: rgba(0, 0, 0, 0.1);
}
.el-table--enable-row-hover .el-table__body tr:hover>td {
background-color: rgba(0, 0, 0, 0.3);
}
/* 对话框样式 */
.el-dialog {
background: rgba(26, 26, 46, 0.9);
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
border: 1px solid var(--glass-border);
}
.el-dialog__header {
padding: 20px;
border-bottom: 1px solid var(--glass-border);
background-color: rgba(0, 0, 0, 0.1);
}
.el-dialog__title {
color: var(--light);
font-size: 18px;
line-height: 24px;
}
.el-dialog__body {
padding: 20px;
color: var(--light);
}
.el-dialog__footer {
padding: 10px 20px;
border-top: 1px solid var(--glass-border);
text-align: right;
background-color: rgba(0, 0, 0, 0.1);
}
/* 科技感主题 */
.theme-tech {
--primary: #00f7ff;
--secondary: #00c8ff;
--danger: #ff3e3e;
--warning: #ffcc00;
--dark: #0f0f23;
--light: #e0e0ff;
--bg-gradient: linear-gradient(135deg, #0f0f23 0%, #1e1e3e 100%);
--glass-bg: rgba(0, 199, 255, 0.05);
--glass-border: rgba(0, 199, 255, 0.15);
}
/* 暗色主题 */
.theme-dark {
--primary: #409EFF;
--secondary: #67C23A;
--danger: #F56C6C;
--warning: #E6A23C;
--dark: #121212;
--light: #e0e0e0;
--bg-gradient: linear-gradient(135deg, #121212 0%, #1e1e1e 100%);
}
/* 亮色主题 */
.theme-light {
--primary: #0066ff;
--secondary: #6200ee;
--danger: #ff1744;
--warning: #ff9100;
--dark: #f5f5f5;
--light: #333333;
--bg-gradient: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
--glass-bg: rgba(0, 0, 0, 0.05);
--glass-border: rgba(0, 0, 0, 0.1);
}
/* 响应式布局 */
@media (max-width: 768px) {
.el-card {
margin-bottom: 20px;
}
.el-form-item {
margin-bottom: 15px;
}
.el-button {
padding: 8px 15px;
font-size: 13px;
}
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
}
::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 3px;
}
/* 动画效果 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
/* 玻璃卡片效果 */
.glass-card {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: var(--glass-bg);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.glass-card:hover {
box-shadow: 0 10px 40px 0 rgba(31, 38, 135, 0.37);
transform: translateY(-3px);
}

View File

@ -1,5 +1,353 @@
<html>
<body>
<h1>GoHttpd Running...</h1>
</body>
<!-- http_server_management_console/frontend/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>HTTP服务器管理控制台</title>
<!-- Element UI CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.13/theme-chalk/index.min.css">
<!-- Vue & Element UI JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.13/index.min.js"></script>
<!-- Tailwind CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Google Fonts -->
<link href="https://fonts.loli.net/css?family=Roboto+Mono:400,700&display=swap" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="css/style.css">
<style>
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
</style>
</head>
<body>
<div id="app">
<div class="particles" id="particles-js"></div>
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<el-header class="flex justify-between items-center mb-8">
<div class="flex items-center">
<i class="fas fa-server text-3xl mr-3 text-blue-400"></i>
<h1 class="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-500">
HTTP服务器管理控制台
</h1>
</div>
<div class="flex items-center space-x-4">
<el-button type="primary" @click="exportConfig">
<i class="fas fa-file-export mr-2"></i>导出配置
</el-button>
<el-button type="primary" @click="importConfig">
<i class="fas fa-file-import mr-2"></i>导入配置
</el-button>
<el-dropdown trigger="click" @command="changeTheme">
<el-button type="primary" icon="el-icon-brush"></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="theme-dark">暗色主题</el-dropdown-item>
<el-dropdown-item command="theme-light">亮色主题</el-dropdown-item>
<el-dropdown-item command="theme-tech">科技主题</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-header>
<!-- Main Content -->
<el-row :gutter="20">
<el-col :span="16">
<!-- Server Config Panel -->
<el-card class="glass-card mb-6">
<div slot="header" class="flex justify-between items-center">
<span class="text-xl font-semibold text-blue-300">
<i class="fas fa-cog mr-2"></i>服务器配置
</span>
<el-button type="primary" @click="addSite" size="small">
<i class="fas fa-plus mr-1"></i>添加网站
</el-button>
</div>
<div v-for="(site, index) in serverConfig.sites" :key="site.id" class="glass-card p-4 mb-4">
<div class="flex justify-between items-center mb-3">
<div>
<h3 class="font-medium text-blue-200">{{ site.name }}</h3>
<p class="text-xs text-gray-400">{{ site.domain }}:{{ site.port }}</p>
</div>
<div class="flex space-x-2">
<el-button type="primary" icon="el-icon-edit" circle size="mini" @click="editSite(index)"></el-button>
<el-button type="danger" icon="el-icon-delete" circle size="mini" @click="deleteSite(index)"></el-button>
<el-button type="info" icon="el-icon-arrow-down" circle size="mini"
@click="toggleCollapse(index)"
:class="{'toggle-icon': true, 'collapsed': site.collapsed}"></el-button>
</div>
</div>
<el-form :model="site" label-width="100px" class="mb-4">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="域名">
<el-input v-model="site.domain"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="端口">
<el-input-number v-model="site.port" :min="1" :max="65535"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="网站名称">
<el-input v-model="site.name"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="启用SSL">
<el-switch v-model="site.ssl"></el-switch>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="border-t border-gray-700 pt-3">
<div class="flex justify-between items-center mb-2">
<h4 class="text-sm font-medium text-purple-300">
<i class="fas fa-route mr-1"></i>URL路由配置
</h4>
<div>
<el-button type="primary" size="mini" @click="addRoute(index)">
<i class="fas fa-plus mr-1"></i>添加路由
</el-button>
</div>
</div>
<div class="route-list" :class="site.collapsed ? 'collapsed' : 'expanded'">
<div v-for="(route, routeIndex) in site.routes" :key="routeIndex" class="glass-card p-3 text-sm mb-2">
<div class="flex justify-between items-center">
<div>
<span class="font-medium">{{ route.path }}</span>
<span class="text-gray-400 ml-2">→ {{ route.target }}</span>
</div>
<div class="flex space-x-2">
<el-button type="primary" icon="el-icon-edit" circle size="mini" @click="editRoute(index, routeIndex)"></el-button>
<el-button type="danger" icon="el-icon-delete" circle size="mini" @click="deleteRoute(index, routeIndex)"></el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</el-card>
<!-- Logger Panel -->
<el-card class="glass-card">
<div slot="header" class="flex justify-between items-center">
<span class="text-xl font-semibold text-blue-300">
<i class="fas fa-history mr-2"></i>操作日志
</span>
<div>
<el-button type="info" size="mini" @click="exportLogs" title="导出日志">
<i class="fas fa-download"></i>
</el-button>
<el-button type="danger" size="mini" @click="clearLogs" title="清空日志">
<i class="fas fa-trash"></i>
</el-button>
</div>
</div>
<div class="mb-4 flex items-center space-x-4">
<el-select v-model="filterType" placeholder="日志类型" size="small">
<el-option
v-for="level in logLevels"
:key="level.value"
:label="level.label"
:value="level.value">
</el-option>
</el-select>
<el-input
placeholder="搜索日志"
prefix-icon="el-icon-search"
v-model="searchQuery"
size="small">
</el-input>
</div>
<div class="space-y-2 max-h-60 overflow-y-auto">
<div v-for="(log, index) in filteredLogs" :key="index" class="text-sm p-2 bg-gray-800/30 rounded">
<span class="text-gray-400">[{{ log.timestamp }}]</span>
<span :class="[getTypeColor(log.type)]">
{{ log.message }}
</span>
</div>
<div v-if="filteredLogs.length === 0" class="text-center text-gray-400 p-4">
无匹配日志记录
</div>
</div>
</el-card>
</el-col>
<!-- Server Status Panel -->
<el-col :span="8">
<el-card class="glass-card mb-6">
<div slot="header" class="text-xl font-semibold text-blue-300">
<i class="fas fa-chart-line mr-2"></i>服务器状态
</div>
<el-row :gutter="20" class="mb-6">
<el-col :span="12">
<el-card class="glass-card text-center">
<div class="text-3xl font-bold text-green-400 mb-1">{{ requestCount }}</div>
<div class="text-xs text-gray-400">访问量</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="glass-card text-center">
<div class="text-3xl font-bold text-purple-400 mb-1">{{ goroutineCount }}</div>
<div class="text-xs text-gray-400">Goroutines</div>
</el-card>
</el-col>
</el-row>
<div class="mb-4">
<h3 class="text-sm font-medium text-blue-200 mb-2">访问量趋势</h3>
<canvas id="request-chart" class="w-full h-32"></canvas>
</div>
<div>
<h3 class="text-sm font-medium text-blue-200 mb-2">Goroutine变化</h3>
<canvas id="goroutine-chart" class="w-full h-32"></canvas>
</div>
</el-card>
<el-card class="glass-card mb-6">
<div slot="header" class="text-xl font-semibold text-blue-300">
<i class="fas fa-terminal mr-2"></i>快速操作
</div>
<el-row :gutter="10">
<el-col :span="12">
<el-button type="primary" class="w-full mb-2" @click="startServer">
<i class="fas fa-play mr-1"></i>启动
</el-button>
</el-col>
<el-col :span="12">
<el-button type="danger" class="w-full mb-2" @click="stopServer">
<i class="fas fa-stop mr-1"></i>停止
</el-button>
</el-col>
<el-col :span="12">
<el-button type="warning" class="w-full" @click="restartServer">
<i class="fas fa-redo mr-1"></i>重启
</el-button>
</el-col>
<el-col :span="12">
<el-button type="info" class="w-full" @click="showLogs">
<i class="fas fa-file-alt mr-1"></i>日志
</el-button>
</el-col>
</el-row>
</el-card>
<el-card class="glass-card">
<div slot="header" class="text-xl font-semibold text-blue-300">
<i class="fas fa-server mr-2"></i>性能监控
</div>
<div class="text-center p-8 text-gray-400">
<i class="fas fa-tools text-4xl mb-4"></i>
<p>性能监控功能正在开发中</p>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- Site Dialog -->
<el-dialog :title="siteDialog.title" :visible.sync="siteDialog.visible" width="50%">
<el-form :model="siteDialog.form" label-width="100px">
<el-form-item label="网站名称">
<el-input v-model="siteDialog.form.name"></el-input>
</el-form-item>
<el-form-item label="域名">
<el-input v-model="siteDialog.form.domain"></el-input>
</el-form-item>
<el-form-item label="端口">
<el-input-number v-model="siteDialog.form.port" :min="1" :max="65535"></el-input-number>
</el-form-item>
<el-form-item label="启用SSL">
<el-switch v-model="siteDialog.form.ssl"></el-switch>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="siteDialog.visible = false">取消</el-button>
<el-button type="primary" @click="saveSite">确定</el-button>
</span>
</el-dialog>
<!-- Route Dialog -->
<el-dialog :title="routeDialog.title" :visible.sync="routeDialog.visible" width="50%">
<el-form :model="routeDialog.form" label-width="100px">
<el-form-item label="路由路径">
<el-input v-model="routeDialog.form.path" placeholder="/api"></el-input>
</el-form-item>
<el-form-item label="路由类型">
<el-radio-group v-model="routeDialog.form.type">
<el-radio label="static">静态资源</el-radio>
<el-radio label="proxy">反向代理</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="目标地址" v-if="routeDialog.form.type === 'proxy'">
<el-input v-model="routeDialog.form.target" placeholder="http://backend:3000"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="routeDialog.visible = false">取消</el-button>
<el-button type="primary" @click="saveRoute">确定</el-button>
</span>
</el-dialog>
</div>
<!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<script src="js/app.js"></script>
<script src="js/chart.js"></script>
<script src="js/logger.js"></script>
<script>
// 初始化粒子效果
document.addEventListener('DOMContentLoaded', function() {
if (typeof particlesJS !== 'undefined') {
particlesJS('particles-js', {
particles: {
number: { value: 80, density: { enable: true, value_area: 800 } },
color: { value: "#3a86ff" },
shape: { type: "circle" },
opacity: { value: 0.5, random: true },
size: { value: 3, random: true },
line_linked: { enable: true, distance: 150, color: "#3a86ff", opacity: 0.4, width: 1 },
move: { enable: true, speed: 2, direction: "none", random: true, straight: false, out_mode: "out" }
},
interactivity: {
detect_on: "canvas",
events: {
onhover: { enable: true, mode: "repulse" },
onclick: { enable: true, mode: "push" }
}
}
});
}
// 应用保存的主题
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.body.className = savedTheme;
}
});
</script>
</body>
</html>

524
adminui/js/app.js Normal file
View File

@ -0,0 +1,524 @@
// http_server_management_console/frontend/js/app.js
new Vue({
el: '#app',
data() {
return {
serverConfig: JSON.parse(localStorage.getItem('serverConfig')) || {
sites: [
{
id: Date.now(),
name: '示例网站',
domain: 'example.com',
port: 8080,
ssl: false,
collapsed: false,
routes: [
{
path: '/static',
type: 'static',
target: '静态资源'
},
{
path: '/api',
type: 'proxy',
target: 'http://backend:3000'
}
]
}
]
},
logs: JSON.parse(localStorage.getItem('serverLogs')) || [
{ timestamp: '2025-05-06 23:13:22', message: '添加了路由配置 /api → http://backend:3000', type: 'success' },
{ timestamp: '2025-05-06 23:12:15', message: '修改了网站端口 8080 → 8443', type: 'warning' },
{ timestamp: '2025-05-06 23:10:07', message: '创建了网站配置 example.com', type: 'info' }
],
requestCount: 0,
goroutineCount: 10,
filterType: 'all',
searchQuery: '',
logLevels: [
{ value: 'all', label: '全部' },
{ value: 'info', label: '信息' },
{ value: 'success', label: '成功' },
{ value: 'warning', label: '警告' },
{ value: 'danger', label: '错误' }
],
siteDialog: {
visible: false,
title: '添加网站',
form: {
id: null,
name: '',
domain: '',
port: 80,
ssl: false,
collapsed: false
},
editIndex: null
},
routeDialog: {
visible: false,
title: '添加路由',
form: {
path: '',
type: 'static',
target: ''
},
siteIndex: null,
editIndex: null
},
requestChart: null,
goroutineChart: null,
chartInterval: null
}
},
computed: {
filteredLogs() {
let result = this.logs;
if (this.filterType !== 'all') {
result = result.filter(log => log.type === this.filterType);
}
if (this.searchQuery) {
const query = this.searchQuery.toLowerCase();
result = result.filter(log =>
log.message.toLowerCase().includes(query) ||
log.timestamp.includes(query)
);
}
return result;
}
},
mounted() {
this.requestCount = Math.floor(Math.random() * 1000) + 500;
// 模拟数据更新
setInterval(() => {
this.requestCount += Math.floor(Math.random() * 10);
this.goroutineCount = Math.max(5, this.goroutineCount + Math.floor(Math.random() * 5) - 2);
}, 3000);
// 应用已保存的主题
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.body.className = savedTheme;
}
// 初始化图表
this.initCharts();
},
beforeDestroy() {
if (this.chartInterval) {
clearInterval(this.chartInterval);
}
},
methods: {
// 站点配置管理
toggleCollapse(index) {
this.serverConfig.sites[index].collapsed = !this.serverConfig.sites[index].collapsed;
this.saveConfig();
},
saveConfig() {
localStorage.setItem('serverConfig', JSON.stringify(this.serverConfig));
this.logAction('保存了服务器配置', 'success');
},
addSite() {
this.siteDialog = {
visible: true,
title: '添加网站',
form: {
id: Date.now(),
name: '新网站',
domain: 'new-site.com',
port: 80,
ssl: false,
collapsed: false
},
editIndex: null
};
},
editSite(index) {
const site = this.serverConfig.sites[index];
this.siteDialog = {
visible: true,
title: '编辑网站',
form: JSON.parse(JSON.stringify(site)),
editIndex: index
};
},
saveSite() {
if (this.siteDialog.editIndex !== null) {
this.serverConfig.sites.splice(this.siteDialog.editIndex, 1, this.siteDialog.form);
this.logAction(`更新了网站 ${this.siteDialog.form.name} 的配置`, 'success');
} else {
this.serverConfig.sites.push({
...this.siteDialog.form,
routes: []
});
this.logAction(`添加了网站 ${this.siteDialog.form.name}`, 'success');
}
this.saveConfig();
this.siteDialog.visible = false;
},
deleteSite(index) {
this.$confirm('确定要删除这个网站吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const site = this.serverConfig.sites[index];
this.serverConfig.sites.splice(index, 1);
this.saveConfig();
this.logAction(`删除了网站 ${site.name}`, 'warning');
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 路由管理
addRoute(siteIndex) {
this.routeDialog = {
visible: true,
title: '添加路由',
form: {
path: '',
type: 'static',
target: ''
},
siteIndex,
editIndex: null
};
},
editRoute(siteIndex, routeIndex) {
const route = this.serverConfig.sites[siteIndex].routes[routeIndex];
this.routeDialog = {
visible: true,
title: '编辑路由',
form: JSON.parse(JSON.stringify(route)),
siteIndex,
editIndex: routeIndex
};
},
saveRoute() {
const site = this.serverConfig.sites[this.routeDialog.siteIndex];
if (this.routeDialog.editIndex !== null) {
site.routes.splice(this.routeDialog.editIndex, 1, this.routeDialog.form);
this.logAction(`更新了路由 ${this.routeDialog.form.path}`, 'success');
} else {
site.routes.push(this.routeDialog.form);
this.logAction(`添加了路由 ${this.routeDialog.form.path}`, 'success');
}
this.saveConfig();
this.routeDialog.visible = false;
},
deleteRoute(siteIndex, routeIndex) {
this.$confirm('确定要删除这个路由吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const route = this.serverConfig.sites[siteIndex].routes[routeIndex];
this.serverConfig.sites[siteIndex].routes.splice(routeIndex, 1);
this.saveConfig();
this.logAction(`删除了路由 ${route.path}`, 'warning');
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 配置导入导出
exportConfig() {
const dataStr = JSON.stringify(this.serverConfig, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = `server-config-${new Date().toISOString().slice(0,10)}.json`;
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
this.logAction('导出了服务器配置', 'success');
},
importConfig() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = e => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = event => {
try {
const importedConfig = JSON.parse(event.target.result);
this.$confirm('确定要导入此配置吗?当前配置将被覆盖。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.serverConfig = importedConfig;
this.saveConfig();
this.logAction('导入了服务器配置', 'success');
this.$message({
type: 'success',
message: '导入成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消导入'
});
});
} catch (error) {
this.$message.error('配置文件格式错误: ' + error.message);
}
};
reader.readAsText(file);
};
input.click();
},
// 日志管理
logAction(message, type = 'info') {
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
this.logs.unshift({ timestamp, message, type });
if (this.logs.length > 100) {
this.logs = this.logs.slice(0, 100);
}
localStorage.setItem('serverLogs', JSON.stringify(this.logs));
},
clearLogs() {
this.$confirm('确定要清空所有操作日志吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.logs = [];
localStorage.setItem('serverLogs', JSON.stringify(this.logs));
this.logAction('清空了所有操作日志', 'warning');
this.$message.success('日志已清空');
}).catch(() => {
this.$message.info('已取消清空操作');
});
},
exportLogs() {
const dataStr = JSON.stringify(this.logs, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = `server-logs-${new Date().toISOString().slice(0,10)}.json`;
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
this.logAction('导出了操作日志', 'info');
},
getTypeColor(type) {
const colors = {
info: 'text-blue-400',
success: 'text-green-400',
warning: 'text-yellow-400',
danger: 'text-red-400'
};
return colors[type] || 'text-gray-400';
},
// 主题管理
changeTheme(theme) {
document.body.className = theme;
localStorage.setItem('theme', theme);
// 更新图表颜色以匹配主题
this.updateChartColors(theme);
let themeName = '未知';
switch (theme) {
case 'theme-dark':
themeName = '暗色';
break;
case 'theme-light':
themeName = '亮色';
break;
case 'theme-tech':
themeName = '科技';
break;
}
this.logAction(`切换为${themeName}主题`, 'info');
},
// 服务器控制
startServer() {
this.logAction('启动了HTTP服务器', 'success');
this.$message.success('服务器已启动');
},
stopServer() {
this.logAction('停止了HTTP服务器', 'warning');
this.$message.warning('服务器已停止');
},
restartServer() {
this.logAction('重启了HTTP服务器', 'info');
this.$message.info('服务器正在重启');
},
showLogs() {
this.$message.info('正在显示日志');
},
// 图表功能
initCharts() {
// 初始化请求图表
const requestCtx = document.getElementById('request-chart').getContext('2d');
this.requestChart = new Chart(requestCtx, {
type: 'line',
data: {
labels: Array.from({length: 12}, (_, i) => `${i*5}`),
datasets: [{
label: '访问量',
data: Array.from({length: 12}, () => Math.floor(Math.random() * 100) + 50),
borderColor: 'rgba(58, 134, 255, 0.8)',
backgroundColor: 'rgba(58, 134, 255, 0.1)',
borderWidth: 2,
tension: 0.4,
fill: true,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
display: false,
grid: {
display: false
}
},
y: {
display: false,
grid: {
display: false
}
}
}
}
});
// 初始化Goroutine图表
const goroutineCtx = document.getElementById('goroutine-chart').getContext('2d');
this.goroutineChart = new Chart(goroutineCtx, {
type: 'line',
data: {
labels: Array.from({length: 12}, (_, i) => `${i*5}`),
datasets: [{
label: 'Goroutines',
data: Array.from({length: 12}, () => Math.floor(Math.random() * 20) + 10),
borderColor: 'rgba(131, 56, 236, 0.8)',
backgroundColor: 'rgba(131, 56, 236, 0.1)',
borderWidth: 2,
tension: 0.4,
fill: true,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
display: false,
grid: {
display: false
}
},
y: {
display: false,
grid: {
display: false
}
}
}
}
});
// 模拟实时数据更新
this.chartInterval = setInterval(() => {
// 更新请求图表数据
const requestData = this.requestChart.data.datasets[0].data;
requestData.shift();
requestData.push(Math.floor(Math.random() * 30) + requestData[requestData.length - 1] - 15);
this.requestChart.update();
// 更新Goroutine图表数据
const goroutineData = this.goroutineChart.data.datasets[0].data;
goroutineData.shift();
goroutineData.push(Math.floor(Math.random() * 5) + goroutineData[goroutineData.length - 1] - 2);
this.goroutineChart.update();
}, 5000);
},
updateChartColors(theme) {
let requestColor, goroutineColor;
switch(theme) {
case 'theme-tech':
requestColor = 'rgba(0, 247, 255, 0.8)';
goroutineColor = 'rgba(0, 200, 255, 0.8)';
break;
case 'theme-light':
requestColor = 'rgba(0, 102, 255, 0.8)';
goroutineColor = 'rgba(98, 0, 238, 0.8)';
break;
default: // dark theme
requestColor = 'rgba(58, 134, 255, 0.8)';
goroutineColor = 'rgba(131, 56, 236, 0.8)';
}
if (this.requestChart) {
this.requestChart.data.datasets[0].borderColor = requestColor;
this.requestChart.data.datasets[0].backgroundColor = requestColor.replace('0.8', '0.1');
this.requestChart.update();
}
if (this.goroutineChart) {
this.goroutineChart.data.datasets[0].borderColor = goroutineColor;
this.goroutineChart.data.datasets[0].backgroundColor = goroutineColor.replace('0.8', '0.1');
this.goroutineChart.update();
}
}
}
});

139
adminui/js/chart.js Normal file
View File

@ -0,0 +1,139 @@
// http_server_management_console/frontend/js/chart.js
export default {
methods: {
initCharts() {
// 初始化请求图表
const requestCtx = document.getElementById('request-chart').getContext('2d');
this.requestChart = new Chart(requestCtx, {
type: 'line',
data: {
labels: Array.from({length: 12}, (_, i) => `${i*5}`),
datasets: [{
label: '访问量',
data: Array.from({length: 12}, () => Math.floor(Math.random() * 100) + 50),
borderColor: 'rgba(58, 134, 255, 0.8)',
backgroundColor: 'rgba(58, 134, 255, 0.1)',
borderWidth: 2,
tension: 0.4,
fill: true,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
display: false,
grid: {
display: false
}
},
y: {
display: false,
grid: {
display: false
}
}
}
}
});
// 初始化Goroutine图表
const goroutineCtx = document.getElementById('goroutine-chart').getContext('2d');
this.goroutineChart = new Chart(goroutineCtx, {
type: 'line',
data: {
labels: Array.from({length: 12}, (_, i) => `${i*5}`),
datasets: [{
label: 'Goroutines',
data: Array.from({length: 12}, () => Math.floor(Math.random() * 20) + 10),
borderColor: 'rgba(131, 56, 236, 0.8)',
backgroundColor: 'rgba(131, 56, 236, 0.1)',
borderWidth: 2,
tension: 0.4,
fill: true,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
display: false,
grid: {
display: false
}
},
y: {
display: false,
grid: {
display: false
}
}
}
}
});
// 模拟实时数据更新
this.chartInterval = setInterval(() => {
// 更新请求图表数据
const requestData = this.requestChart.data.datasets[0].data;
requestData.shift();
requestData.push(Math.floor(Math.random() * 30) + requestData[requestData.length - 1] - 15);
this.requestChart.update();
// 更新Goroutine图表数据
const goroutineData = this.goroutineChart.data.datasets[0].data;
goroutineData.shift();
goroutineData.push(Math.floor(Math.random() * 5) + goroutineData[goroutineData.length - 1] - 2);
this.goroutineChart.update();
}, 5000);
},
updateChartColors(theme) {
let requestColor, goroutineColor;
switch(theme) {
case 'theme-tech':
requestColor = 'rgba(0, 247, 255, 0.8)';
goroutineColor = 'rgba(0, 200, 255, 0.8)';
break;
case 'theme-light':
requestColor = 'rgba(0, 102, 255, 0.8)';
goroutineColor = 'rgba(98, 0, 238, 0.8)';
break;
default: // dark theme
requestColor = 'rgba(58, 134, 255, 0.8)';
goroutineColor = 'rgba(131, 56, 236, 0.8)';
}
this.requestChart.data.datasets[0].borderColor = requestColor;
this.requestChart.data.datasets[0].backgroundColor = requestColor.replace('0.8', '0.1');
this.requestChart.update();
this.goroutineChart.data.datasets[0].borderColor = goroutineColor;
this.goroutineChart.data.datasets[0].backgroundColor = goroutineColor.replace('0.8', '0.1');
this.goroutineChart.update();
}
},
mounted() {
this.initCharts();
},
beforeDestroy() {
if (this.chartInterval) {
clearInterval(this.chartInterval);
}
}
}

99
adminui/js/logger.js Normal file
View File

@ -0,0 +1,99 @@
// http_server_management_console/frontend/js/logger.js
export default {
data() {
return {
logs: [],
filterType: 'all',
searchQuery: '',
logLevels: [
{ value: 'all', label: '全部' },
{ value: 'info', label: '信息' },
{ value: 'success', label: '成功' },
{ value: 'warning', label: '警告' },
{ value: 'danger', label: '错误' }
]
}
},
created() {
this.loadLogs();
},
computed: {
filteredLogs() {
let result = this.logs;
if (this.filterType !== 'all') {
result = result.filter(log => log.type === this.filterType);
}
if (this.searchQuery) {
const query = this.searchQuery.toLowerCase();
result = result.filter(log =>
log.message.toLowerCase().includes(query) ||
log.timestamp.includes(query)
);
}
return result;
}
},
methods: {
loadLogs() {
const savedLogs = localStorage.getItem('serverLogs');
this.logs = savedLogs ? JSON.parse(savedLogs) : [
{ timestamp: '2025-06-12 14:40:22', message: '添加了路由配置 /api → http://backend:3000', type: 'success' },
{ timestamp: '2025-06-12 14:38:15', message: '修改了网站端口 8080 → 8443', type: 'warning' },
{ timestamp: '2025-06-12 14:35:07', message: '创建了网站配置 example.com', type: 'info' }
];
},
addLog(message, type = 'info') {
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
this.logs.unshift({ timestamp, message, type });
if (this.logs.length > 1000) {
this.logs = this.logs.slice(0, 1000);
}
this.saveLogs();
},
clearLogs() {
this.$confirm('确定要清空所有操作日志吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.logs = [];
this.saveLogs();
this.addLog('清空了所有操作日志', 'warning');
this.$message.success('日志已清空');
}).catch(() => {
this.$message.info('已取消清空操作');
});
},
exportLogs() {
const dataStr = JSON.stringify(this.logs, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = `server-logs-${new Date().toISOString().slice(0,10)}.json`;
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
this.addLog('导出了操作日志', 'info');
},
saveLogs() {
localStorage.setItem('serverLogs', JSON.stringify(this.logs));
},
getTypeColor(type) {
const colors = {
info: 'text-blue-400',
success: 'text-green-400',
warning: 'text-yellow-400',
danger: 'text-red-400'
};
return colors[type] || 'text-gray-400';
}
}
}