gohttp/adminui/index.html

354 lines
19 KiB
HTML

<!-- 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>