diff --git a/adminui/css/style.css b/adminui/css/style.css new file mode 100644 index 0000000..d65284b --- /dev/null +++ b/adminui/css/style.css @@ -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); +} diff --git a/adminui/index.html b/adminui/index.html index 30bd6d0..a067850 100644 --- a/adminui/index.html +++ b/adminui/index.html @@ -1,5 +1,353 @@ - - -

GoHttpd Running...

- - \ No newline at end of file + + + + + + + + HTTP服务器管理控制台 + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+ +

+ HTTP服务器管理控制台 +

+
+
+ + 导出配置 + + + 导入配置 + + + + + 暗色主题 + 亮色主题 + 科技主题 + + +
+
+ + + + + + +
+ + 服务器配置 + + + 添加网站 + +
+ +
+
+
+

{{ site.name }}

+

{{ site.domain }}:{{ site.port }}

+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ URL路由配置 +

+
+ + 添加路由 + +
+
+ +
+
+
+
+ {{ route.path }} + → {{ route.target }} +
+
+ + +
+
+
+
+
+
+
+ + + +
+ + 操作日志 + +
+ + + + + + +
+
+ +
+ + + + + + +
+ +
+
+ [{{ log.timestamp }}] + + {{ log.message }} + +
+
+ 无匹配日志记录 +
+
+
+
+ + + + +
+ 服务器状态 +
+ + + + +
{{ requestCount }}
+
访问量
+
+
+ + +
{{ goroutineCount }}
+
Goroutines
+
+
+
+ +
+

访问量趋势

+ +
+ +
+

Goroutine变化

+ +
+
+ + +
+ 快速操作 +
+ + + + 启动 + + + + + 停止 + + + + + 重启 + + + + + 日志 + + + +
+ + +
+ 性能监控 +
+
+ +

性能监控功能正在开发中

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + + + + + 静态资源 + 反向代理 + + + + + + + + 取消 + 确定 + + +
+ + + + + + + + + + + diff --git a/adminui/js/app.js b/adminui/js/app.js new file mode 100644 index 0000000..0925781 --- /dev/null +++ b/adminui/js/app.js @@ -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(); + } + } + } +}); diff --git a/adminui/js/chart.js b/adminui/js/chart.js new file mode 100644 index 0000000..7490701 --- /dev/null +++ b/adminui/js/chart.js @@ -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); + } + } +} diff --git a/adminui/js/logger.js b/adminui/js/logger.js new file mode 100644 index 0000000..a1a4590 --- /dev/null +++ b/adminui/js/logger.js @@ -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'; + } + } +}