diff --git a/admin/admin.go b/admin/admin.go index cf9026e..94a878f 100644 --- a/admin/admin.go +++ b/admin/admin.go @@ -5,6 +5,7 @@ import ( "net/http" "runtime" + "git.kingecg.top/kingecg/gohttpd/handler" "git.kingecg.top/kingecg/gohttpd/model" "git.kingecg.top/kingecg/gohttpd/server" "github.com/gin-gonic/gin" // 添加Gin框架导入 @@ -131,6 +132,166 @@ func start(c *gin.Context) { } } +func restart(c *gin.Context) { + name := c.Param("name") + if name != "" { + serverConf := model.GetServerConfig(name) + if serverConf == nil { + c.Writer.WriteHeader(http.StatusNotFound) + c.Writer.Write(server.NewErrorResult(errors.New("server not found"))) + return + } + // 先停止再启动 + server.StopServer(name, serverConf.Port) + server.StartServer(name, serverConf.Port) + c.Writer.WriteHeader(http.StatusOK) + data := "restarted" + c.Writer.Write(server.NewSuccessResult(data)) + return + } +} + +func deleteServer(c *gin.Context) { + name := c.Param("name") + if name != "" { + serverConf := model.GetServerConfig(name) + if serverConf == nil { + c.Writer.WriteHeader(http.StatusNotFound) + c.Writer.Write(server.NewErrorResult(errors.New("server not found"))) + return + } + // 先停止服务器 + server.StopServer(name, serverConf.Port) + // 从配置中删除 + err := model.DeleteServerConfig(name) + if err != nil { + c.Writer.WriteHeader(http.StatusInternalServerError) + c.Writer.Write(server.NewErrorResult(err)) + return + } + c.Writer.WriteHeader(http.StatusOK) + c.Writer.Write(server.NewSuccessResult("deleted")) + return + } +} + +// TCP代理相关API + +func listTcpProxy(c *gin.Context) { + data := model.GetConfig().TcpProxys + c.JSON(http.StatusOK, server.NewResultObj(data)) +} + +func setTcpProxy(c *gin.Context) { + ctx := c.Request.Context() + ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{}) + data, ok := ctxData["data"] + if !ok { + c.Writer.WriteHeader(http.StatusBadRequest) + return + } + tcpProxy, ok := data.(model.TcpProxyConfig) + if !ok { + c.Writer.WriteHeader(http.StatusBadRequest) + c.Writer.Write(server.NewErrorResult(errors.New("invalid tcp proxy config"))) + return + } + er := model.SetTcpProxyConfig(&tcpProxy) + if er != nil { + c.Writer.WriteHeader(http.StatusBadRequest) + c.Writer.Write(server.NewErrorResult(er)) + return + } + c.Writer.WriteHeader(http.StatusOK) + c.Writer.Write(server.NewSuccessResult(tcpProxy)) +} + +func getTcpProxy(c *gin.Context) { + name := c.Param("name") + if name != "" { + data := model.GetTcpProxyConfig(name) + if data == nil { + c.Writer.WriteHeader(http.StatusNotFound) + c.Writer.Write(server.NewErrorResult(errors.New("tcp proxy not found"))) + return + } + c.Writer.WriteHeader(http.StatusOK) + c.Writer.Write(server.NewSuccessResult(data)) + return + } + http.NotFound(c.Writer, c.Request) +} + +func deleteTcpProxy(c *gin.Context) { + name := c.Param("name") + if name != "" { + tcpProxy := model.GetTcpProxyConfig(name) + if tcpProxy == nil { + c.Writer.WriteHeader(http.StatusNotFound) + c.Writer.Write(server.NewErrorResult(errors.New("tcp proxy not found"))) + return + } + // 如果正在运行,先停止 + if proxy, ok := server.TcpProxyManager[name]; ok { + proxy.Stop() + delete(server.TcpProxyManager, name) + } + // 从配置中删除 + err := model.DeleteTcpProxyConfig(name) + if err != nil { + c.Writer.WriteHeader(http.StatusInternalServerError) + c.Writer.Write(server.NewErrorResult(err)) + return + } + c.Writer.WriteHeader(http.StatusOK) + c.Writer.Write(server.NewSuccessResult("deleted")) + return + } + http.NotFound(c.Writer, c.Request) +} + +func startTcpProxy(c *gin.Context) { + name := c.Param("name") + if name != "" { + tcpProxy := model.GetTcpProxyConfig(name) + if tcpProxy == nil { + c.Writer.WriteHeader(http.StatusNotFound) + c.Writer.Write(server.NewErrorResult(errors.New("tcp proxy not found"))) + return + } + if _, ok := server.TcpProxyManager[name]; ok { + c.Writer.WriteHeader(http.StatusOK) + c.Writer.Write(server.NewSuccessResult("already started")) + return + } + proxy := handler.NewTcpProxyHandler(tcpProxy.Name, tcpProxy.Listen, tcpProxy.Target) + server.AddTcpProxy(name, proxy) + proxy.Start() + c.Writer.WriteHeader(http.StatusOK) + c.Writer.Write(server.NewSuccessResult("started")) + return + } + http.NotFound(c.Writer, c.Request) +} + +func stopTcpProxy(c *gin.Context) { + name := c.Param("name") + if name != "" { + proxy, ok := server.TcpProxyManager[name] + if !ok { + c.Writer.WriteHeader(http.StatusNotFound) + c.Writer.Write(server.NewErrorResult(errors.New("tcp proxy not found or not running"))) + return + } + proxy.Stop() + delete(server.TcpProxyManager, name) + c.Writer.WriteHeader(http.StatusOK) + c.Writer.Write(server.NewSuccessResult("stopped")) + return + } + http.NotFound(c.Writer, c.Request) +} + var AdminServerMux *gin.Engine // InitAdminApi 初始化管理API路由 @@ -170,6 +331,19 @@ func InitAdminApi(conf *model.HttpServerConfig) { { serverGroup.POST("stop/:name", stop) serverGroup.POST("start/:name", start) + serverGroup.POST("restart/:name", restart) + serverGroup.DELETE(":name", deleteServer) + } + + // TCP代理路由 + tcpGroup := api.Group("tcp") + { + tcpGroup.GET("", listTcpProxy) + tcpGroup.POST("", setTcpProxy) + tcpGroup.GET(":name", getTcpProxy) + tcpGroup.DELETE(":name", deleteTcpProxy) + tcpGroup.POST("start/:name", startTcpProxy) + tcpGroup.POST("stop/:name", stopTcpProxy) } api.POST("login", login) diff --git a/adminui/css/style.css b/adminui/css/style.css index d65284b..e72285b 100644 --- a/adminui/css/style.css +++ b/adminui/css/style.css @@ -152,20 +152,30 @@ body { /* 表格样式 */ .el-table { - background-color: transparent; + background-color: rgba(0, 0, 0, 0.3); color: var(--light); } .el-table th { - background-color: rgba(0, 0, 0, 0.2); + background-color: rgba(0, 0, 0, 0.4); color: var(--light); + border-bottom: 1px solid var(--glass-border); } .el-table tr { - background-color: rgba(0, 0, 0, 0.1); + background-color: transparent; +} + +.el-table td { + background-color: rgba(0, 0, 0, 0.2); + border-bottom: 1px solid var(--glass-border); } .el-table--enable-row-hover .el-table__body tr:hover>td { + background-color: rgba(64, 158, 255, 0.15); +} + +.el-table__empty-block { background-color: rgba(0, 0, 0, 0.3); } @@ -297,3 +307,12 @@ body { box-shadow: 0 10px 40px 0 rgba(31, 38, 135, 0.37); transform: translateY(-3px); } + +.el-table th.el-table__cell, .el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell { + background-color: transparent; + +} + +.el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell { + background-color: #02270c; +} \ No newline at end of file diff --git a/adminui/index.html b/adminui/index.html index a067850..05efad8 100644 --- a/adminui/index.html +++ b/adminui/index.html @@ -1,353 +1,264 @@ - - - HTTP服务器管理控制台 - + GoHttp 管理控制台 - - - - - - - - -
-
-

- HTTP服务器管理控制台 + GoHttp 管理控制台

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

{{ site.name }}

-

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

-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-

- URL路由配置 -

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

访问量趋势

- -
- -
-

Goroutine变化

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

性能监控功能正在开发中

-
-
-
-
+ + + + + + + +
{{ goroutines }}
+
Goroutines
+
+
+
+
+
- - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 路由配置 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + 添加路由 +
+ - 取消 - 确定 + 取消 + 保存
- - - - - + + + + + - - - 静态资源 - 反向代理 - + + - - + + + - 取消 - 确定 + 取消 + 保存
- - - + + - - - - - + \ No newline at end of file diff --git a/adminui/js/app.js b/adminui/js/app.js index d5ecf28..43cf93e 100644 --- a/adminui/js/app.js +++ b/adminui/js/app.js @@ -1,535 +1,353 @@ - -// http_server_management_console/frontend/js/app.js +// GoHttp 管理控制台 - 前端应用 new Vue({ el: '#app', data() { return { - serverConfig: JSON.parse(localStorage.getItem('serverConfig')) || { - sites: [ - { - - name: '示例网站', - server: 'example.com', - port: 8080, - enable_ssl: false, - collapsed: false, - paths: [ - { - 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: { + loading: false, + activeTab: 'servers', + servers: [], + tcpProxies: [], + runningTcpProxies: [], + goroutines: 0, + serverDialog: { visible: false, - title: '添加网站', - form: { - id: null, - name: '', - domain: '', - port: 80, - ssl: false, - collapsed: false - }, - editIndex: null + title: '添加服务器', + isEdit: false, + saving: false, + form: this.getDefaultServerForm() }, - routeDialog: { + tcpDialog: { 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); + title: '添加TCP代理', + isEdit: false, + saving: false, + form: this.getDefaultTcpForm() } - - 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); - } + this.refreshAll() + this.startStatusPolling() }, methods: { - // 站点配置管理 - toggleCollapse(index) { - this.serverConfig.sites[index].collapsed = !this.serverConfig.sites[index].collapsed; - this.saveConfig(); + // 获取默认服务器表单 + getDefaultServerForm() { + return { + name: '', + server: [], + port: 8080, + enable_ssl: false, + certfile: '', + keyfile: '', + paths: [] + } }, - saveConfig() { - localStorage.setItem('serverConfig', JSON.stringify(this.serverConfig)); - this.logAction('保存了服务器配置', 'success'); + + // 获取默认TCP代理表单 + getDefaultTcpForm() { + return { + name: '', + listen: '', + target: '', + enabled: true + } }, - addSite() { - this.siteDialog = { - visible: true, - title: '添加网站', - form: { - id: Date.now(), - name: '新网站', - domain: 'new-site.com', - port: 80, - ssl: false, - collapsed: false - }, - editIndex: null - }; + + // 重置服务器表单 + resetServerForm() { + this.serverDialog.form = this.getDefaultServerForm() + this.serverDialog.isEdit = false }, - editSite(index) { - const site = this.serverConfig.sites[index]; - this.siteDialog = { - visible: true, - title: '编辑网站', - form: JSON.parse(JSON.stringify(site)), - editIndex: index - }; + + // 重置TCP表单 + resetTcpForm() { + this.tcpDialog.form = this.getDefaultTcpForm() + this.tcpDialog.isEdit = false }, - saveSite() { - if (this.siteDialog.editIndex !== null) { - this.serverConfig.sites.splice(this.siteDialog.editIndex, 1, this.siteDialog.form); - this.logAction(`更新了网站 ${this.siteDialog.form.name} 的配置`, 'success'); + + // API请求封装 + async request(method, url, data) { + try { + const options = { + method: method, + headers: { 'Content-Type': 'application/json' } + } + if (data && (method === 'POST' || method === 'PUT' || method === 'DELETE')) { + options.body = JSON.stringify({ data }) + } + const response = await fetch(url, options) + const result = await response.json() + if (result.code !== 200) { + this.$message.error(result.error || '操作失败') + return null + } + return result + } catch (error) { + console.error('Request error:', error) + this.$message.error('网络错误: ' + error.message) + return null + } + }, + + // 刷新所有数据 + async refreshAll() { + this.loading = true + await Promise.all([ + this.fetchServers(), + this.fetchTcpProxies(), + this.fetchStatus() + ]) + this.loading = false + }, + + // 获取服务器列表 + async fetchServers() { + const result = await this.request('GET', '/api/config') + if (result) { + this.servers = result.data || [] + } + }, + + // 获取TCP代理列表 + async fetchTcpProxies() { + const result = await this.request('GET', '/api/tcp') + if (result) { + this.tcpProxies = result.data || [] + } + }, + + // 获取系统状态 + async fetchStatus() { + const result = await this.request('GET', '/api/status') + if (result && result.data) { + this.goroutines = result.data.goroutines || 0 + } + }, + + // 定时获取状态 + startStatusPolling() { + setInterval(() => { + if (this.activeTab === 'status') { + this.fetchStatus() + } + }, 5000) + }, + + // Tab切换 + handleTabClick(tab) { + if (tab.name === 'status') { + this.fetchStatus() + } + }, + + // 判断TCP代理是否运行中 + isTcpRunning(name) { + return this.runningTcpProxies.includes(name) + }, + + // 显示服务器对话框 + showServerDialog(server) { + if (server) { + this.serverDialog.title = '编辑服务器' + this.serverDialog.isEdit = true + // 转换后端数据格式到前端 + this.serverDialog.form = { + name: server.name, + server: server.server || [], + port: server.port, + enable_ssl: server.enable_ssl || false, + certfile: server.certfile || '', + keyfile: server.keyfile || '', + paths: (server.paths || []).map(p => ({ + path: p.path, + type: p.root ? 'static' : (p.upstreams && p.upstreams.length > 0 ? 'proxy' : 'static'), + root: p.root || '', + default: p.default || '', + upstreams: p.upstreams || [] + })) + } } else { - this.serverConfig.sites.push({ - ...this.siteDialog.form, - routes: [] - }); - this.logAction(`添加了网站 ${this.siteDialog.form.name}`, 'success'); + this.serverDialog.title = '添加服务器' + this.serverDialog.isEdit = false + this.serverDialog.form = this.getDefaultServerForm() } - - 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: '已取消删除' - }); - }); + this.serverDialog.visible = true }, - //get site - getSites() { - //visit REST API to get sites, url is /api/config - //return this.serverConfig.sites; - this.$axios.get('/api/config').then(response => { - this.sites = response.data; - this.siteIndex = siteIndex; - this.getRoutes(siteIndex); - }); + // 添加路由 + addPath() { + this.serverDialog.form.paths.push({ + path: '', + type: 'static', + root: '', + default: 'index.html', + upstreams: [] + }) + }, + // 移除路由 + removePath(index) { + this.serverDialog.form.paths.splice(index, 1) }, - // 路由管理 - 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'); + + // 路由类型变更 + onPathTypeChange(path) { + if (path.type === 'static') { + path.upstreams = [] } else { - site.routes.push(this.routeDialog.form); - this.logAction(`添加了路由 ${this.routeDialog.form.path}`, 'success'); + path.root = '' + path.default = '' } - - 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); + // 保存服务器 + async saveServer() { + const form = this.serverDialog.form + if (!form.name || !form.port) { + this.$message.warning('请填写必填项') + return } - - 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; + // 转换前端数据格式到后端 + const serverConfig = { + name: form.name, + server: form.server, + port: form.port, + enable_ssl: form.enable_ssl, + certfile: form.certfile, + keyfile: form.keyfile, + paths: form.paths.map(p => ({ + path: p.path, + root: p.type === 'static' ? p.root : '', + default: p.type === 'static' ? p.default : '', + upstreams: p.type === 'proxy' ? p.upstreams : [], + directives: p.directives || [] + })) + } + + this.serverDialog.saving = true + const result = await this.request('POST', '/api/config', serverConfig) + this.serverDialog.saving = false + + if (result) { + this.$message.success('保存成功') + this.serverDialog.visible = false + await this.fetchServers() } - - 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('正在显示日志'); + // 重启服务器 + async restartServer(name) { + try { + const result = await this.request('POST', `/api/server/restart/${name}`) + if (result) { + this.$message.success(`服务器 ${name} 已重启`) + } + } catch (e) { + this.$message.error('重启失败') + } }, - // 图表功能 - 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 - } + // 删除服务器 + async deleteServer(name) { + try { + await this.$confirm(`确定要删除服务器 "${name}" 吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + + const result = await this.request('DELETE', `/api/server/${name}`) + if (result) { + this.$message.success('删除成功') + await this.fetchServers() + } + } catch (e) { + if (e !== 'cancel') { + this.$message.error('删除失败') + } + } + }, + + // 显示TCP代理对话框 + showTcpDialog(tcp) { + if (tcp) { + this.tcpDialog.title = '编辑TCP代理' + this.tcpDialog.isEdit = true + this.tcpDialog.form = { + name: tcp.name, + listen: tcp.listen, + target: tcp.target, + enabled: tcp.enabled !== false + } + } else { + this.tcpDialog.title = '添加TCP代理' + this.tcpDialog.isEdit = false + this.tcpDialog.form = this.getDefaultTcpForm() + } + this.tcpDialog.visible = true + }, + + // 保存TCP代理 + async saveTcpProxy() { + const form = this.tcpDialog.form + if (!form.name || !form.listen || !form.target) { + this.$message.warning('请填写必填项') + return + } + + this.tcpDialog.saving = true + const result = await this.request('POST', '/api/tcp', form) + this.tcpDialog.saving = false + + if (result) { + this.$message.success('保存成功') + this.tcpDialog.visible = false + await this.fetchTcpProxies() + } + }, + + // 切换TCP代理状态 + async toggleTcpProxy(tcp) { + const action = this.isTcpRunning(tcp.name) ? 'stop' : 'start' + try { + const result = await this.request('POST', `/api/tcp/${action}/${tcp.name}`) + if (result) { + this.$message.success(`TCP代理已${action === 'stop' ? '停止' : '启动'}`) + // 更新运行状态 + if (action === 'start') { + if (!this.runningTcpProxies.includes(tcp.name)) { + this.runningTcpProxies.push(tcp.name) } + } else { + this.runningTcpProxies = this.runningTcpProxies.filter(n => n !== tcp.name) } } - }); - - // 初始化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); + } catch (e) { + this.$message.error('操作失败') + } }, - 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(); - } + // 删除TCP代理 + async deleteTcpProxy(name) { + try { + await this.$confirm(`确定要删除TCP代理 "${name}" 吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) - 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(); + const result = await this.request('DELETE', `/api/tcp/${name}`) + if (result) { + this.$message.success('删除成功') + this.runningTcpProxies = this.runningTcpProxies.filter(n => n !== name) + await this.fetchTcpProxies() + } + } catch (e) { + if (e !== 'cancel') { + this.$message.error('删除失败') + } } } } -}); +}) \ No newline at end of file diff --git a/config.json b/config.json index 5d79034..a115e66 100644 --- a/config.json +++ b/config.json @@ -21,6 +21,18 @@ } } }, + "admin":{ + "name":"admin", + "server":["localhost"], + "port":8080, + "paths":[ + { + "path":"/", + "root":"./adminui", + "default":"index.html" + } + ] + }, "includs":"./include", "servers": [ diff --git a/model/model.go b/model/model.go index 3bdbb61..b63b350 100644 --- a/model/model.go +++ b/model/model.go @@ -2,6 +2,7 @@ package model import ( "encoding/json" + "errors" "os" "git.kingecg.top/kingecg/gohttpd/utils" @@ -149,3 +150,56 @@ func GetServerConfig(name string) *HttpServerConfig { } return nil } + +func DeleteServerConfig(name string) error { + l := gologger.GetLogger("model") + for i, s := range Config.Servers { + if s.Name == name { + Config.Servers = append(Config.Servers[:i], Config.Servers[i+1:]...) + configFile := utils.NormalizePath("./config.json") + configsStr, err := json.Marshal(Config) + if err != nil { + l.Error("Save config error:", err) + return err + } + werr := os.WriteFile(configFile, configsStr, 0644) + if werr != nil { + l.Error("Save config error:", werr) + return werr + } + return nil + } + } + return errors.New("server not found") +} + +func GetTcpProxyConfig(name string) *TcpProxyConfig { + for _, s := range Config.TcpProxys { + if s.Name == name { + return s + } + } + return nil +} + +func DeleteTcpProxyConfig(name string) error { + l := gologger.GetLogger("model") + for i, s := range Config.TcpProxys { + if s.Name == name { + Config.TcpProxys = append(Config.TcpProxys[:i], Config.TcpProxys[i+1:]...) + configFile := utils.NormalizePath("./config.json") + configsStr, err := json.Marshal(Config) + if err != nil { + l.Error("Save config error:", err) + return err + } + werr := os.WriteFile(configFile, configsStr, 0644) + if werr != nil { + l.Error("Save config error:", werr) + return werr + } + return nil + } + } + return errors.New("tcp proxy not found") +}