Compare commits
2 Commits
56210416a6
...
b92ca64cf8
Author | SHA1 | Date |
---|---|---|
|
b92ca64cf8 | |
|
9822db29d9 |
164
admin/admin.go
164
admin/admin.go
|
@ -3,128 +3,148 @@ package admin
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"git.pyer.club/kingecg/gohttpd/model"
|
||||
"git.pyer.club/kingecg/gohttpd/server"
|
||||
"github.com/gin-gonic/gin" // 添加Gin框架导入
|
||||
)
|
||||
|
||||
type RunStatus struct {
|
||||
Goroutines int `json:"goroutines"`
|
||||
}
|
||||
|
||||
func about(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("About Page"))
|
||||
func about(c *gin.Context) {
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
c.Writer.Write([]byte("About Page"))
|
||||
|
||||
}
|
||||
|
||||
func setConfig(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
func setConfig(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
data, ok := ctxData["data"]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
c.Writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
t := data.(model.HttpServerConfig)
|
||||
if t.Name == "admin" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
c.Writer.WriteHeader(http.StatusForbidden)
|
||||
resp := server.NewErrorResult(errors.New("不能通过api设置管理服务器"))
|
||||
w.Write(resp)
|
||||
}
|
||||
err := model.SetServerConfig(&t)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write(server.NewErrorResult(err))
|
||||
c.Writer.Write(resp)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
os.Exit(0)
|
||||
}()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(server.NewSuccessResult(t))
|
||||
er := model.SetServerConfig(&t)
|
||||
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(t))
|
||||
}
|
||||
|
||||
func getServerConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
id, ok := ctxData["id"]
|
||||
if ok {
|
||||
if id.(string) == "admin" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
func getServerConfigure(c *gin.Context) {
|
||||
// ctx := c.Request.Context()
|
||||
// get url param
|
||||
// g.
|
||||
//ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
id := c.Param("name")
|
||||
if id != "" {
|
||||
if id == "admin" {
|
||||
c.Writer.WriteHeader(http.StatusForbidden)
|
||||
resp := server.NewErrorResult(errors.New("不能通过api获取管理服务器配置信息"))
|
||||
w.Write(resp)
|
||||
c.Writer.Write(resp)
|
||||
return
|
||||
}
|
||||
data := model.GetServerConfig(id.(string))
|
||||
/// configContent, _ := json.Marshal(data)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(server.NewSuccessResult(data))
|
||||
data := model.GetServerConfig(id)
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
c.Writer.Write(server.NewSuccessResult(data))
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
http.NotFound(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func getStatus(w http.ResponseWriter, r *http.Request) {
|
||||
//获取当前进程的goroutines数量
|
||||
func getStatus(c *gin.Context) {
|
||||
runtime := runtime.NumGoroutine()
|
||||
ret := RunStatus{
|
||||
Goroutines: runtime.NumGoroutine(),
|
||||
Goroutines: runtime,
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(server.NewSuccessResult(ret))
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
c.Writer.Write(server.NewSuccessResult(ret))
|
||||
}
|
||||
|
||||
func stop(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
name, ok := ctxData["name"]
|
||||
if ok {
|
||||
serverConf := model.GetServerConfig(name.(string))
|
||||
server.StopServer(name.(string), serverConf.Port)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
func stop(c *gin.Context) {
|
||||
// ctx := c.Request.Context()
|
||||
// ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
name := c.Param("name")
|
||||
if name != "" {
|
||||
serverConf := model.GetServerConfig(name)
|
||||
server.StopServer(name, serverConf.Port)
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
data := "stopped"
|
||||
w.Write(server.NewSuccessResult(data))
|
||||
c.Writer.Write(server.NewSuccessResult(data))
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
http.NotFound(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
func start(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
name, ok := ctxData["name"]
|
||||
if ok {
|
||||
serverConf := model.GetServerConfig(name.(string))
|
||||
server.StartServer(name.(string), serverConf.Port)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
func start(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name != "" {
|
||||
serverConf := model.GetServerConfig(name)
|
||||
server.StartServer(name, serverConf.Port)
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
data := "started"
|
||||
w.Write(server.NewSuccessResult(data))
|
||||
c.Writer.Write(server.NewSuccessResult(data))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var AdminServerMux *server.RestMux
|
||||
var AdminServerMux *gin.Engine
|
||||
|
||||
// InitAdminApi 初始化管理API路由
|
||||
// 参数:
|
||||
//
|
||||
// conf: 指向HttpServerConfig的指针,包含服务器配置信息。
|
||||
//
|
||||
// 返回值:
|
||||
//
|
||||
// 无直接返回值,初始化全局AdminServerMux变量。
|
||||
func InitAdminApi(conf *model.HttpServerConfig) {
|
||||
// 创建Gin路由器实例
|
||||
router := gin.Default()
|
||||
|
||||
AdminServerMux = server.NewRestMux("/api")
|
||||
// 根据认证类型添加中间件
|
||||
if conf.AuthType == "jwt" {
|
||||
AdminServerMux.Use(server.JwtAuth)
|
||||
router.Use(jwtAuth) // 使用JWT认证中间件
|
||||
} else if conf.AuthType == "basic" {
|
||||
AdminServerMux.Use(server.BasicAuth)
|
||||
router.Use(ginBashiAuth) // 使用基础认证中间件
|
||||
}
|
||||
// AdminServerMux.Use(server.JwtAuth)
|
||||
AdminServerMux.HandleFunc("GET", "/about", http.HandlerFunc(about))
|
||||
postConfigRoute := AdminServerMux.HandleFunc("POST", "/config", http.HandlerFunc(setConfig))
|
||||
postConfigRoute.Add(server.Parse[model.HttpServerConfig])
|
||||
AdminServerMux.HandleFunc("GET", "/config/:id", http.HandlerFunc(getServerConfigure))
|
||||
AdminServerMux.HandleFunc("GET", "/status", http.HandlerFunc(getStatus))
|
||||
AdminServerMux.HandleFunc("Post", "/stop/:name", http.HandlerFunc(stop))
|
||||
AdminServerMux.HandleFunc("Post", "/start/:name", http.HandlerFunc(start))
|
||||
loginRoute := AdminServerMux.HandleFunc("POST", "/login", http.HandlerFunc(login))
|
||||
loginRoute.Add(server.Parse[LoginModel])
|
||||
// AdminServerMux.Use(server.BasicAuth)
|
||||
|
||||
// 注册路由处理函数
|
||||
api := router.Group("/")
|
||||
{
|
||||
api.GET("about", about)
|
||||
|
||||
configGroup := api.Group("config")
|
||||
{
|
||||
configGroup.POST("", setConfig)
|
||||
configGroup.GET(":name", getServerConfigure)
|
||||
}
|
||||
|
||||
api.GET("status", getStatus)
|
||||
|
||||
serverGroup := api.Group("server")
|
||||
{
|
||||
serverGroup.POST("stop/:name", stop)
|
||||
serverGroup.POST("start/:name", start)
|
||||
}
|
||||
|
||||
api.POST("login", login)
|
||||
}
|
||||
|
||||
// 将路由器赋值给全局变量
|
||||
AdminServerMux = router
|
||||
}
|
||||
|
|
135
admin/login.go
135
admin/login.go
|
@ -3,11 +3,14 @@ package admin
|
|||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.pyer.club/kingecg/gohttpd/model"
|
||||
"git.pyer.club/kingecg/gohttpd/server"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
|
@ -16,46 +19,63 @@ type LoginModel struct {
|
|||
Encrypt string `json:"password"`
|
||||
}
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
data, ok := ctxData["data"]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
// login 处理用户登录请求,验证凭证并生成会话令牌。
|
||||
// 参数:
|
||||
//
|
||||
// w: HTTP响应写入器,用于返回响应状态和数据。
|
||||
// r: 指向HTTP请求的指针,包含请求上下文和数据。
|
||||
//
|
||||
// 返回值:
|
||||
//
|
||||
// 无直接返回值,通过w写入HTTP响应。
|
||||
func login(c *gin.Context) {
|
||||
// 从请求上下文中获取请求数据
|
||||
// ctx := c.Request.Context()
|
||||
// ctxData := ctx.Value(server.RequestCtxKey("data")).(map[string]interface{})
|
||||
// data, ok := ctxData["data"]
|
||||
loginModel := LoginModel{}
|
||||
// 绑定请求体到LoginModel结构体
|
||||
if err := c.ShouldBindJSON(&loginModel); err != nil {
|
||||
c.Writer.WriteHeader(http.StatusBadRequest)
|
||||
c.Writer.Write(server.NewErrorResult(err))
|
||||
return
|
||||
}
|
||||
t := data.(LoginModel)
|
||||
if t.Username == "admin" {
|
||||
decryptText, _ := Decrypt(t.Encrypt)
|
||||
|
||||
// 验证是否为管理员账号
|
||||
if loginModel.Username == "admin" {
|
||||
// 解密密码并验证与配置文件中密码是否匹配
|
||||
decryptText, _ := Decrypt(loginModel.Encrypt)
|
||||
if decryptText == model.GetConfig().Admin.Password {
|
||||
token, err := GenerateToken(t.Username)
|
||||
// 生成JWT令牌
|
||||
token, err := GenerateToken(loginModel.Username)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write(server.NewErrorResult(err))
|
||||
c.Writer.WriteHeader(http.StatusInternalServerError)
|
||||
c.Writer.Write(server.NewErrorResult(err))
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "token",
|
||||
Value: token,
|
||||
Path: "/",
|
||||
// HttpOnly: true,
|
||||
// Secure: true,
|
||||
// 设置认证Cookie并返回成功响应
|
||||
cookie := &http.Cookie{
|
||||
Name: "token",
|
||||
Value: token,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Expires: time.Now().Add(time.Hour * 24 * 7),
|
||||
})
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
w.Write(server.NewSuccessResult("Login success"))
|
||||
}
|
||||
c.Writer.Header().Set("Set-Cookie", cookie.String())
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
c.Writer.Write(server.NewSuccessResult("Login success"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
// 非管理员账号返回禁止访问响应
|
||||
c.Writer.WriteHeader(http.StatusForbidden)
|
||||
resp := server.NewErrorResult(errors.New("not allowed user/password"))
|
||||
w.Write(resp)
|
||||
c.Writer.Write(resp)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write(server.NewErrorResult(errors.New("not allowed user/password")))
|
||||
// 默认返回未授权响应
|
||||
c.Writer.WriteHeader(http.StatusUnauthorized)
|
||||
c.Writer.Write(server.NewErrorResult(errors.New("not allowed user/password")))
|
||||
}
|
||||
|
||||
// 实现非对称加密
|
||||
|
@ -106,3 +126,66 @@ func GenerateToken(username string) (string, error) {
|
|||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
|
||||
return token.SignedString([]byte(secret))
|
||||
}
|
||||
|
||||
func ginBashiAuth(c *gin.Context) {
|
||||
// first get Url
|
||||
url := c.Request.URL.Path
|
||||
// check if url is login
|
||||
if strings.Contains(url, "login") || strings.Contains(url, "logout") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
accountMap := map[string]string{}
|
||||
accountMap[model.GetConfig().Admin.Username] = model.GetConfig().Admin.Password
|
||||
handler := gin.BasicAuth(accountMap) // 使用gin的BasicAuth中间件进行认证
|
||||
handler(c) // 调用认证处理函数
|
||||
}
|
||||
|
||||
func jwtAuth(c *gin.Context) {
|
||||
|
||||
config := model.GetConfig().Admin
|
||||
if config == nil || config.Jwt == nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, errors.New("Jwt config error"))
|
||||
// http.Error(w, "Jwt config error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
url := c.Request.URL.Path
|
||||
// check if url is login
|
||||
if strings.Contains(url, "login") || strings.Contains(url, "logout") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
jwtConfig := config.Jwt
|
||||
if jwtConfig.Secret == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
// 从cookie中获取token
|
||||
tokenCookie, err := c.Cookie("auth_token") //r.Cookie("auth_token")
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
tokenString := tokenCookie
|
||||
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// 确保签名方法是正确的
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(jwtConfig.Secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
// l.Error("Failed to parse JWT: %v", err)
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if claims, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid {
|
||||
// 验证通过,将用户信息存储在请求上下文中
|
||||
// ctx := context.WithValue(r.Context(), "user", claims)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
c.Set("user", claims)
|
||||
return
|
||||
}
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
// http.Error(w, "Unauthorized.", http.StatusUnauthorized)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
31
go.mod
31
go.mod
|
@ -16,24 +16,35 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.7.4 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.9.0 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/signalsciences/ac v1.2.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.6 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.14 // indirect
|
||||
golang.org/x/arch v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
53
go.sum
53
go.sum
|
@ -4,25 +4,48 @@ git.pyer.club/kingecg/godaemon v0.0.0-20231210104221-3a72649c6511 h1:qgNgm8ewkZd
|
|||
git.pyer.club/kingecg/godaemon v0.0.0-20231210104221-3a72649c6511/go.mod h1:6cL7tPbT1m6EaTvrbDg/YhoZG0Mms8WBD10yRGhng4o=
|
||||
git.pyer.club/kingecg/gologger v1.0.1 h1:snCb0ePlfDUglX+CHwNzq5MRK5uNTnPUks1Dnapl/p8=
|
||||
git.pyer.club/kingecg/gologger v1.0.1/go.mod h1:SNSl2jRHPzIpHSzdKOoVG798rtYMjPDPFyxUrEgivkY=
|
||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
|
||||
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
|
@ -37,6 +60,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
|
@ -48,9 +75,13 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -59,6 +90,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nanmu42/gzip v1.2.0 h1:pZoKNTlnJQJ4xM5Zi/EuIch77/x/9ww9PLsA3zEHLlU=
|
||||
github.com/nanmu42/gzip v1.2.0/go.mod h1:ubXkuAEakeUraJOokoM5/XuDdcjotF4Q+TvFSCgPSEg=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
@ -70,16 +103,27 @@ github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXn
|
|||
github.com/signalsciences/ac v1.2.0 h1:6UcueKRSJn7iHhq1vKU7R0EVhzCJf77tD6HjAGcGDSs=
|
||||
github.com/signalsciences/ac v1.2.0/go.mod h1:jnlGjtNM8dyGcnOdZjY35vHmUtOn5M5K4U+BzcVPjN0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||
github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw=
|
||||
github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
|
@ -93,6 +137,8 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -102,6 +148,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -120,6 +167,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
|||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
@ -132,3 +181,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"git.pyer.club/kingecg/gohttpd/model"
|
||||
"git.pyer.club/kingecg/gologger"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
|
@ -88,14 +89,14 @@ func NewMiddlewareLink() *MiddlewareLink {
|
|||
}
|
||||
|
||||
func BasicAuth(w http.ResponseWriter, r *http.Request, next http.Handler) {
|
||||
config := model.GetConfig()
|
||||
config := getServerConfig(r)
|
||||
|
||||
if config.Admin.Username == "" || config.Admin.Password == "" {
|
||||
if config.Username == "" || config.Password == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if ok && user == config.Admin.Username && pass == config.Admin.Password {
|
||||
if ok && user == config.Username && pass == config.Password {
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
|
@ -232,3 +233,19 @@ func IPAccessControl(w http.ResponseWriter, r *http.Request, next http.Handler)
|
|||
func Done(w http.ResponseWriter, r *http.Request, next http.Handler) {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GinJwtAuth 返回Gin框架兼容的JWT认证中间件
|
||||
func GinJwtAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 实现JWT认证逻辑
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// GinBasicAuth 返回Gin框架兼容的基础认证中间件
|
||||
func GinBasicAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 实现基础认证逻辑
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue