update pwd change
This commit is contained in:
parent
b91daad8ad
commit
47b6f4903f
13 changed files with 572 additions and 90 deletions
|
|
@ -10,9 +10,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare:
|
||||||
runs-on: docker
|
runs-on: ubuntu:latest
|
||||||
container:
|
|
||||||
image: ubuntu:latest
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -41,9 +39,7 @@ jobs:
|
||||||
export PATH: $PATH:/usr/local/go/bin
|
export PATH: $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: docker
|
runs-on: ubuntu:latest
|
||||||
container:
|
|
||||||
image: ubuntu:latest
|
|
||||||
needs: prepare # 确保这个作业在 prepare 作业完成后运行
|
needs: prepare # 确保这个作业在 prepare 作业完成后运行
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -57,13 +57,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(header_realip) {
|
(header_realip_cf) {
|
||||||
header_up X-Real-IP {remote_host}
|
header_up X-Real-IP {remote_host}
|
||||||
header_up X-Real-IP {http.request.header.CF-Connecting-IP}
|
header_up X-Real-IP {http.request.header.CF-Connecting-IP}
|
||||||
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
|
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
|
||||||
header_up X-Forwarded-Proto {http.request.header.CF-Visitor}
|
header_up X-Forwarded-Proto {http.request.header.CF-Visitor}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(header_realip) {
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
}
|
||||||
|
|
||||||
(tls) {
|
(tls) {
|
||||||
tls {
|
tls {
|
||||||
dns {args[0]} {args[1]}
|
dns {args[0]} {args[1]}
|
||||||
|
|
@ -87,4 +93,3 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
import ./config.d/*
|
import ./config.d/*
|
||||||
#import ./config/*
|
|
||||||
24
api/api.go
24
api/api.go
|
|
@ -31,6 +31,24 @@ func ApiGroup(v0 touka.IRouter, cdb *db.ConfigDB, cfg *config.Config) {
|
||||||
|
|
||||||
api.GET("/config/templates", GetTemplates(cdb)) // 获取可用模板名称
|
api.GET("/config/templates", GetTemplates(cdb)) // 获取可用模板名称
|
||||||
|
|
||||||
|
api.GET("/config/headers-presets", func(c *touka.Context) {
|
||||||
|
c.JSON(200, GetHeaderSetMetadataList())
|
||||||
|
})
|
||||||
|
|
||||||
|
api.GET("/config/headers-presets/:name", func(c *touka.Context) {
|
||||||
|
presetName := c.Param("name")
|
||||||
|
if presetName == "" {
|
||||||
|
c.JSON(400, touka.H{"error": "presetName is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
preset, found := GetHeaderSetByID(presetName)
|
||||||
|
if !found {
|
||||||
|
c.JSON(404, touka.H{"error": "preset not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, preset)
|
||||||
|
})
|
||||||
|
|
||||||
// caddy实例相关
|
// caddy实例相关
|
||||||
api.POST("/caddy/stop", apic.StopCaddy()) // 无需payload
|
api.POST("/caddy/stop", apic.StopCaddy()) // 无需payload
|
||||||
api.POST("/caddy/run", apic.StartCaddy(cfg))
|
api.POST("/caddy/run", apic.StartCaddy(cfg))
|
||||||
|
|
@ -46,10 +64,7 @@ func ApiGroup(v0 touka.IRouter, cdb *db.ConfigDB, cfg *config.Config) {
|
||||||
AuthLogout(c)
|
AuthLogout(c)
|
||||||
})
|
})
|
||||||
auth.GET("/logout", func(c *touka.Context) {
|
auth.GET("/logout", func(c *touka.Context) {
|
||||||
// 重定向到/
|
AuthLogout(c)
|
||||||
c.Redirect(302, "/")
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
auth.GET("/init", func(c *touka.Context) {
|
auth.GET("/init", func(c *touka.Context) {
|
||||||
// 返回是否init管理员
|
// 返回是否init管理员
|
||||||
|
|
@ -76,6 +91,7 @@ func ApiGroup(v0 touka.IRouter, cdb *db.ConfigDB, cfg *config.Config) {
|
||||||
}
|
}
|
||||||
c.JSON(200, touka.H{"message": "admin initialized"})
|
c.JSON(200, touka.H{"message": "admin initialized"})
|
||||||
})
|
})
|
||||||
|
auth.POST("resetpwd", ResetPassword(cdb))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
62
api/auth.go
62
api/auth.go
|
|
@ -16,6 +16,7 @@ var (
|
||||||
"/login": {},
|
"/login": {},
|
||||||
"/login.html": {},
|
"/login.html": {},
|
||||||
"/v0/api/auth/login": {},
|
"/v0/api/auth/login": {},
|
||||||
|
"/v0/api/auth/logout": {},
|
||||||
"/v0/api/auth/init": {},
|
"/v0/api/auth/init": {},
|
||||||
"/init.html": {},
|
"/init.html": {},
|
||||||
"/favicon.ico": {},
|
"/favicon.ico": {},
|
||||||
|
|
@ -98,6 +99,7 @@ func AuthLogin(c *touka.Context, cfg *config.Config, cdb *db.ConfigDB) {
|
||||||
c.JSON(http.StatusBadRequest, touka.H{"error": "Need username and password"})
|
c.JSON(http.StatusBadRequest, touka.H{"error": "Need username and password"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.Infof("user login: %s password: %s", username, password)
|
||||||
|
|
||||||
// 验证账户密码
|
// 验证账户密码
|
||||||
pass, err := user.CheckLogin(username, password, cdb)
|
pass, err := user.CheckLogin(username, password, cdb)
|
||||||
|
|
@ -119,9 +121,65 @@ func AuthLogin(c *touka.Context, cfg *config.Config, cdb *db.ConfigDB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthLogout(c *touka.Context) {
|
func AuthLogout(c *touka.Context) {
|
||||||
|
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
session.Clear()
|
|
||||||
session.Set("authenticated", false)
|
session.Set("authenticated", false)
|
||||||
session.Save()
|
session.Clear()
|
||||||
|
err := session.Save()
|
||||||
|
if err != nil {
|
||||||
|
c.Errorf("Failed to save session: %v", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, touka.H{"error": "Failed to save session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Redirect(http.StatusFound, "/login.html")
|
c.Redirect(http.StatusFound, "/login.html")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResetPassword(cdb *db.ConfigDB) touka.HandlerFunc {
|
||||||
|
return func(c *touka.Context) {
|
||||||
|
username := c.PostForm("username")
|
||||||
|
oldPassword := c.PostForm("old_password")
|
||||||
|
newPassword := c.PostForm("new_password")
|
||||||
|
// 验证是否为空
|
||||||
|
if username == "" || oldPassword == "" || newPassword == "" {
|
||||||
|
c.JSON(400, touka.H{"error": "username and password are required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 验证用户是否存在
|
||||||
|
exist, err := cdb.IsUserExists(username)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, touka.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
//不正确的参数
|
||||||
|
c.JSON(400, touka.H{"error": "user not exist"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 是否可以重置
|
||||||
|
ok, err := user.CheckLogin(username, oldPassword, cdb)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, touka.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// 错误的密码
|
||||||
|
c.JSON(400, touka.H{"error": "current password is not correct"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 更新密码
|
||||||
|
hashpwd, err := user.HashPassword(newPassword)
|
||||||
|
if err != nil {
|
||||||
|
c.Errorf("Failed to hash password: %v", err)
|
||||||
|
c.JSON(500, touka.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = cdb.UpdateUserPassword(username, hashpwd)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, touka.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 进行logout
|
||||||
|
AuthLogout(c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
112
api/headers_presets.go
Normal file
112
api/headers_presets.go
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
// HeaderSet 定义了一个可复用的、完整的HTTP头预设。
|
||||||
|
// JSON标签用于API响应的序列化。
|
||||||
|
type HeaderSet struct {
|
||||||
|
ID string `json:"id"` // 唯一ID, e.g., "real_ip_cloudflare"
|
||||||
|
Name string `json:"name"` // UI上显示的名称 (为简化, 此处不使用i18n key)
|
||||||
|
Description string `json:"description"` // UI上的提示文本
|
||||||
|
Target string `json:"target"` // 目标: "global" 或 "upstream"
|
||||||
|
Headers map[string][]string `json:"headers"` // 预设的请求头键值对
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderSetMetadata 是HeaderSet的轻量级版本, 用于在列表中显示, 不包含具体的Headers数据。
|
||||||
|
type HeaderSetMetadata struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// registry 是一个私有变量, 作为所有Header预设的“数据库”。
|
||||||
|
// 在这里添加、修改或删除预设。
|
||||||
|
var registry = []HeaderSet{
|
||||||
|
{
|
||||||
|
ID: "real_ip_cloudflare",
|
||||||
|
Name: "Cloudflare 真实IP",
|
||||||
|
Description: "添加从Cloudflare获取真实客户端IP所需的请求头。适用于通过Cloudflare代理的流量。",
|
||||||
|
Target: "upstream", // 此预设仅适用于上游请求头
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Forwarded-For": {"{http.request.header.CF-Connecting-IP}"},
|
||||||
|
"X-Forwarded-Proto": {"{http.request.header.CF-Visitor}"},
|
||||||
|
"X-Real-IP": {"{http.request.header.CF-Connecting-IP}"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 真实IP-直接
|
||||||
|
{
|
||||||
|
ID: "real_ip_direct",
|
||||||
|
Name: "真实IP (直接)",
|
||||||
|
Description: "当Caddy直接暴露在互联网上时, 使用此预设获取客户端真实IP。",
|
||||||
|
Target: "upstream",
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Forwarded-For": {"{http.request.remote.host}"},
|
||||||
|
"X-Forwarded-Proto": {"{http.request.scheme}"},
|
||||||
|
"X-Real-IP": {"{http.request.remote.host}"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 真实IP-中间层
|
||||||
|
{
|
||||||
|
ID: "real_ip_intermediate",
|
||||||
|
Name: "真实IP (中间层)",
|
||||||
|
Description: "当Caddy位于反向代理或负载均衡器之后时, 使用此预设获取客户端真实IP。",
|
||||||
|
Target: "upstream",
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Forwarded-For": {"{http.request.header.X-Forwarded-For}"},
|
||||||
|
"X-Forwarded-Proto": {"{http.request.header.X-Forwarded-Proto}"},
|
||||||
|
"X-Real-IP": {"{http.request.header.X-Real-IP}"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "common_security_headers",
|
||||||
|
Name: "通用安全响应头",
|
||||||
|
Description: "添加一系列推荐的HTTP安全头以增强站点安全性 (HSTS, X-Frame-Options等)。",
|
||||||
|
Target: "global", // 此预设适用于全局响应头
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains; preload"},
|
||||||
|
"X-Frame-Options": {"SAMEORIGIN"},
|
||||||
|
"X-Content-Type-Options": {"nosniff"},
|
||||||
|
"Referrer-Policy": {"strict-origin-when-cross-origin"},
|
||||||
|
"Permissions-Policy": {"geolocation=(), microphone=()"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "cors_allow_all",
|
||||||
|
Name: "CORS (允许所有来源)",
|
||||||
|
Description: "添加允许所有来源跨域请求的响应头。警告: 生产环境请谨慎使用。",
|
||||||
|
Target: "global",
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"Access-Control-Allow-Origin": {"*"},
|
||||||
|
"Access-Control-Allow-Methods": {"GET, POST, PUT, DELETE, OPTIONS"},
|
||||||
|
"Access-Control-Allow-Headers": {"Content-Type, Authorization, X-Requested-With"},
|
||||||
|
"Access-Control-Allow-Credentials": {"true"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeaderSetMetadataList 返回所有可用预设的元数据列表。
|
||||||
|
// 这个函数是线程安全的, 因为它只读取全局只读变量 registry。
|
||||||
|
func GetHeaderSetMetadataList() []HeaderSetMetadata {
|
||||||
|
metadata := make([]HeaderSetMetadata, len(registry))
|
||||||
|
for i, set := range registry {
|
||||||
|
metadata[i] = HeaderSetMetadata{
|
||||||
|
ID: set.ID,
|
||||||
|
Name: set.Name,
|
||||||
|
Description: set.Description,
|
||||||
|
Target: set.Target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeaderSetByID 通过其唯一ID查找并返回一个完整的预设。
|
||||||
|
// 返回找到的预设和一个布尔值, 表示是否找到。
|
||||||
|
func GetHeaderSetByID(id string) (*HeaderSet, bool) {
|
||||||
|
for _, set := range registry {
|
||||||
|
if set.ID == id {
|
||||||
|
// 返回该结构体的副本指针, 避免外部修改全局变量
|
||||||
|
foundSet := set
|
||||||
|
return &foundSet, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
@ -55,11 +55,10 @@ After=network.target network-online.target
|
||||||
Requires=network-online.target
|
Requires=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=exec
|
||||||
User=root
|
User=root
|
||||||
Group=root
|
Group=root
|
||||||
ExecStart=/root/data/caddy/caddydash -c ./config/config.toml
|
ExecStart=/root/data/caddy/caddydash -c ./config/config.toml
|
||||||
ExecReload=/root/data/caddy/caddydash
|
|
||||||
WorkingDirectory=/root/data/caddy
|
WorkingDirectory=/root/data/caddy
|
||||||
TimeoutStopSec=5s
|
TimeoutStopSec=5s
|
||||||
LimitNOFILE=1048576
|
LimitNOFILE=1048576
|
||||||
|
|
@ -85,11 +84,10 @@ After=network.target network-online.target
|
||||||
Requires=network-online.target
|
Requires=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=exec
|
||||||
User=root
|
User=root
|
||||||
Group=root
|
Group=root
|
||||||
ExecStart=/root/data/caddy/caddydash -c ./config/config.toml
|
ExecStart=/root/data/caddy/caddydash -c ./config/config.toml
|
||||||
ExecReload=/root/data/caddy/caddydash
|
|
||||||
WorkingDirectory=/root/data/caddy
|
WorkingDirectory=/root/data/caddy
|
||||||
TimeoutStopSec=5s
|
TimeoutStopSec=5s
|
||||||
LimitNOFILE=1048576
|
LimitNOFILE=1048576
|
||||||
|
|
@ -107,22 +105,6 @@ EOF
|
||||||
echo -e "${green}>${white} $mikublue CaddyDash 用户名: ${username}" $white
|
echo -e "${green}>${white} $mikublue CaddyDash 用户名: ${username}" $white
|
||||||
echo -e "${green}>${white} $mikublue CaddyDash 密码: ${password}" $white
|
echo -e "${green}>${white} $mikublue CaddyDash 密码: ${password}" $white
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# 选择安装模式
|
|
||||||
echo -e "${green}>${white} $mikublue 选择安装模式" $white
|
|
||||||
echo -e "${green}1.${white} $mikublue 不使用环境变量配置用户名密码" $white
|
|
||||||
echo -e "${green}2.${white} $mikublue 使用环境变量配置用户名密码" $white
|
|
||||||
read -p "请输入选项 [1/2]: " install_mode
|
|
||||||
|
|
||||||
if [[ "$install_mode" == "2" ]]; then
|
|
||||||
read -p "请输入CaddyDash用户名: " caddydash_username
|
|
||||||
writeSystemdServiceWithPasswdEnv "$caddydash_username"
|
|
||||||
else
|
|
||||||
writeSystemdServiceNoEnv
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 显示免责声明
|
# 显示免责声明
|
||||||
|
|
@ -160,7 +142,7 @@ mkdir -p /root/data/caddy/config
|
||||||
echo -e "${green}>${white} $mikublue 下載主程序" $white
|
echo -e "${green}>${white} $mikublue 下載主程序" $white
|
||||||
input_version="$@" #获取输入的版本号
|
input_version="$@" #获取输入的版本号
|
||||||
if [ -z "$input_version" ]; then
|
if [ -z "$input_version" ]; then
|
||||||
VERSION=$(curl -s https://raw.githubusercontent.com/WJQSERVER/caddy/main/Caddy-VERSION)
|
VERSION=$(curl -s https://raw.githubusercontent.com/WJQSERVER/caddy/main/TEST-VERSION)
|
||||||
else
|
else
|
||||||
VERSION=$input_version
|
VERSION=$input_version
|
||||||
fi
|
fi
|
||||||
|
|
@ -179,7 +161,7 @@ echo -e "${green}>${white} $mikublue 是否创建caddydash账户密码" $white
|
||||||
read -p "是否创建caddydash账户密码? [Y/n]" choice
|
read -p "是否创建caddydash账户密码? [Y/n]" choice
|
||||||
if [[ "$choice" == "" || "$choice" == "Y" || "$choice" == "y" ]]; then
|
if [[ "$choice" == "" || "$choice" == "Y" || "$choice" == "y" ]]; then
|
||||||
read -p "请输入CaddyDash用户名: " caddydash_username
|
read -p "请输入CaddyDash用户名: " caddydash_username
|
||||||
read -p "请输入CaddyDash密码: " caddydash_password
|
read -s -p "请输入CaddyDash密码: " caddydash_password
|
||||||
writeSystemdServiceWithPasswdEnv "$caddydash_username" "$caddydash_password"
|
writeSystemdServiceWithPasswdEnv "$caddydash_username" "$caddydash_password"
|
||||||
else
|
else
|
||||||
writeSystemdServiceNoEnv
|
writeSystemdServiceNoEnv
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,8 @@
|
||||||
</header>
|
</header>
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#" class="active"><i class="fa-solid fa-sitemap"></i> <span>配置管理</span></a></li>
|
<li><a href="/" class="active"><i class="fa-solid fa-sitemap"></i> <span>配置管理</span></a></li>
|
||||||
<li><a href="#"><i class="fa-solid fa-chart-line"></i> <span>仪表盘 (预留)</span></a></li>
|
<li><a href="/settings.html" data-nav-id="settings"><i class="fa-solid fa-gears"></i> <span>面板设置</span></a></li>
|
||||||
<li><a href="#"><i class="fa-solid fa-puzzle-piece"></i> <span>模板管理 (预留)</span></a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sidebar-bottom">
|
<div class="sidebar-bottom">
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@
|
||||||
|
|
||||||
import { state } from './state.js';
|
import { state } from './state.js';
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
import { initTheme } from './theme.js';
|
import { theme, toast, activateNav } from './common.js';
|
||||||
import { notification } from './notifications.js';
|
import { notification } from './notifications.js';
|
||||||
import { DOMElements, switchView, renderConfigList, addKeyValueInput, addSingleInput, fillForm, showRenderedConfig, updateCaddyStatusView, updateSegmentedControl, updateServiceModeView, updateMultiUpstreamView } from './ui.js';
|
import { initCaddyStatus } from './caddy.js';
|
||||||
|
import { DOMElements, switchView, renderConfigList, addKeyValueInput, addSingleInput, fillForm, showRenderedConfig, updateSegmentedControl, updateServiceModeView, updateMultiUpstreamView } from './ui.js';
|
||||||
const POLLING_INTERVAL = 5000;
|
|
||||||
let caddyStatusInterval;
|
|
||||||
|
|
||||||
|
// --- 事件处理与逻辑流 ---
|
||||||
function getFormStateAsString() {
|
function getFormStateAsString() {
|
||||||
const formData = new FormData(DOMElements.configForm);
|
const formData = new FormData(DOMElements.configForm);
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
@ -29,36 +28,6 @@ async function attemptExitForm() {
|
||||||
if (await notification.confirm('您有未保存的更改。确定要放弃吗?')) switchView(DOMElements.configListPanel);
|
if (await notification.confirm('您有未保存的更改。确定要放弃吗?')) switchView(DOMElements.configListPanel);
|
||||||
} else switchView(DOMElements.configListPanel);
|
} else switchView(DOMElements.configListPanel);
|
||||||
}
|
}
|
||||||
async function checkCaddyStatus() {
|
|
||||||
try {
|
|
||||||
const response = await api.get('/caddy/status');
|
|
||||||
updateCaddyStatusView(response.message === 'Caddy is running' ? 'running' : 'stopped', caddyHandlers);
|
|
||||||
} catch (error) { console.error('Error checking Caddy status:', error); updateCaddyStatusView('error', caddyHandlers); }
|
|
||||||
}
|
|
||||||
async function handleStartCaddy() {
|
|
||||||
try {
|
|
||||||
const result = await api.post('/caddy/run');
|
|
||||||
notification.toast(result.message || '启动命令已发送。', 'success');
|
|
||||||
setTimeout(checkCaddyStatus, 500);
|
|
||||||
} catch (error) { notification.toast(`启动失败: ${error.message}`, 'error'); }
|
|
||||||
}
|
|
||||||
async function handleStopCaddy() {
|
|
||||||
if (!await notification.confirm('您确定要停止 Caddy 实例吗?')) return;
|
|
||||||
try {
|
|
||||||
const result = await api.post('/caddy/stop');
|
|
||||||
notification.toast(result.message || '停止命令已发送。', 'info');
|
|
||||||
setTimeout(checkCaddyStatus, 500);
|
|
||||||
} catch(error) { notification.toast(`操作失败: ${error.message}`, 'error'); }
|
|
||||||
}
|
|
||||||
async function handleReloadCaddy() {
|
|
||||||
if (!await notification.confirm('确定要重载 Caddy 配置吗?')) return;
|
|
||||||
try {
|
|
||||||
const result = await api.post('/caddy/restart');
|
|
||||||
notification.toast(result.message || '重载命令已发送。', 'success');
|
|
||||||
setTimeout(checkCaddyStatus, 500);
|
|
||||||
} catch(error) { notification.toast(`重载失败: ${error.message}`, 'error'); }
|
|
||||||
}
|
|
||||||
const caddyHandlers = { handleStartCaddy, handleStopCaddy, handleReloadCaddy };
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
if (!await notification.confirm('您确定要退出登录吗?')) return;
|
if (!await notification.confirm('您确定要退出登录吗?')) return;
|
||||||
notification.toast('正在退出...', 'info');
|
notification.toast('正在退出...', 'info');
|
||||||
|
|
@ -81,11 +50,8 @@ async function handleEditConfig(originalFilename) {
|
||||||
DOMElements.formTitle.textContent = '编辑配置';
|
DOMElements.formTitle.textContent = '编辑配置';
|
||||||
fillForm(config, originalFilename);
|
fillForm(config, originalFilename);
|
||||||
showRenderedConfig(rendered, originalFilename);
|
showRenderedConfig(rendered, originalFilename);
|
||||||
|
|
||||||
// 关键修正: 在填充表单后, 根据数据更新服务模式的fieldset可见性
|
|
||||||
const mode = config.upstream_config?.enable_upstream ? 'reverse_proxy' : (config.file_server_config?.enable_file_server ? 'file_server' : 'none');
|
const mode = config.upstream_config?.enable_upstream ? 'reverse_proxy' : (config.file_server_config?.enable_file_server ? 'file_server' : 'none');
|
||||||
updateServiceModeView(mode);
|
updateServiceModeView(mode);
|
||||||
|
|
||||||
state.initialFormState = getFormStateAsString();
|
state.initialFormState = getFormStateAsString();
|
||||||
} catch(error) { notification.toast(`加载配置详情失败: ${error.message}`, 'error'); }
|
} catch(error) { notification.toast(`加载配置详情失败: ${error.message}`, 'error'); }
|
||||||
}
|
}
|
||||||
|
|
@ -150,12 +116,14 @@ async function handleSaveConfig(e) {
|
||||||
} catch(error) { notification.toast(`保存失败: ${error.message}`, 'error'); }
|
} catch(error) { notification.toast(`保存失败: ${error.message}`, 'error'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 初始化与事件绑定 ---
|
||||||
function init() {
|
function init() {
|
||||||
initTheme(DOMElements.themeToggleInput);
|
theme.init(DOMElements.themeToggleInput);
|
||||||
notification.init(DOMElements.toastContainer, DOMElements.dialogContainer);
|
notification.init(DOMElements.toastContainer, DOMElements.dialogContainer);
|
||||||
|
activateNav('configs');
|
||||||
|
initCaddyStatus(); // 初始化通用Caddy状态检查
|
||||||
|
|
||||||
loadAllConfigs();
|
loadAllConfigs();
|
||||||
checkCaddyStatus();
|
|
||||||
caddyStatusInterval = setInterval(checkCaddyStatus, POLLING_INTERVAL);
|
|
||||||
|
|
||||||
DOMElements.menuToggleBtn.addEventListener('click', () => DOMElements.sidebar.classList.toggle('is-open'));
|
DOMElements.menuToggleBtn.addEventListener('click', () => DOMElements.sidebar.classList.toggle('is-open'));
|
||||||
DOMElements.mainContent.addEventListener('click', () => DOMElements.sidebar.classList.remove('is-open'));
|
DOMElements.mainContent.addEventListener('click', () => DOMElements.sidebar.classList.remove('is-open'));
|
||||||
|
|
|
||||||
98
frontend/js/caddy.js
Normal file
98
frontend/js/caddy.js
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
// js/caddy.js - Caddy 实例状态管理与控制
|
||||||
|
|
||||||
|
import { api } from './api.js';
|
||||||
|
import { notification } from './notifications.js';
|
||||||
|
|
||||||
|
let caddyStatusInterval;
|
||||||
|
const POLLING_INTERVAL = 5000;
|
||||||
|
|
||||||
|
const DOMElements = {
|
||||||
|
caddyStatusIndicator: document.getElementById('caddy-status-indicator'),
|
||||||
|
caddyActionButtonContainer: document.getElementById('caddy-action-button-container'),
|
||||||
|
};
|
||||||
|
|
||||||
|
function createButton(text, className, onClick) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = `btn ${className}`;
|
||||||
|
button.innerHTML = `<span>${text}</span>`;
|
||||||
|
button.addEventListener('click', onClick);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCaddyStatusView(status) {
|
||||||
|
const dot = DOMElements.caddyStatusIndicator.querySelector('.status-dot');
|
||||||
|
const text = DOMElements.caddyStatusIndicator.querySelector('.status-text');
|
||||||
|
const buttonContainer = DOMElements.caddyActionButtonContainer;
|
||||||
|
|
||||||
|
if(!dot || !text || !buttonContainer) return; // 如果元素不存在,则不执行
|
||||||
|
|
||||||
|
dot.className = 'status-dot';
|
||||||
|
buttonContainer.innerHTML = '';
|
||||||
|
let statusText, dotClass;
|
||||||
|
switch (status) {
|
||||||
|
case 'running':
|
||||||
|
statusText = '运行中'; dotClass = 'running';
|
||||||
|
buttonContainer.appendChild(createButton('重载配置', 'btn-warning', handleReloadCaddy));
|
||||||
|
buttonContainer.appendChild(createButton('停止 Caddy', 'btn-danger', handleStopCaddy));
|
||||||
|
break;
|
||||||
|
case 'stopped':
|
||||||
|
statusText = '已停止'; dotClass = 'stopped';
|
||||||
|
buttonContainer.appendChild(createButton('启动 Caddy', 'btn-success', handleStartCaddy));
|
||||||
|
break;
|
||||||
|
case 'checking': statusText = '检查中...'; dotClass = 'checking'; break;
|
||||||
|
default: statusText = '状态未知'; dotClass = 'error'; break;
|
||||||
|
}
|
||||||
|
text.textContent = statusText;
|
||||||
|
dot.classList.add(dotClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkCaddyStatus() {
|
||||||
|
try {
|
||||||
|
const response = await api.get('/caddy/status');
|
||||||
|
updateCaddyStatusView(response.message === 'Caddy is running' ? 'running' : 'stopped');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking Caddy status:', error);
|
||||||
|
updateCaddyStatusView('error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleStartCaddy() {
|
||||||
|
try {
|
||||||
|
const result = await api.post('/caddy/run');
|
||||||
|
notification.toast(result.message || '启动命令已发送。', 'success');
|
||||||
|
setTimeout(checkCaddyStatus, 500);
|
||||||
|
} catch (error) { notification.toast(`启动失败: ${error.message}`, 'error'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleStopCaddy() {
|
||||||
|
if (!await notification.confirm('您确定要停止 Caddy 实例吗?')) return;
|
||||||
|
try {
|
||||||
|
const result = await api.post('/caddy/stop');
|
||||||
|
notification.toast(result.message || '停止命令已发送。', 'info');
|
||||||
|
setTimeout(checkCaddyStatus, 500);
|
||||||
|
} catch(error) { notification.toast(`操作失败: ${error.message}`, 'error'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleReloadCaddy() {
|
||||||
|
if (!await notification.confirm('确定要重载 Caddy 配置吗?')) return;
|
||||||
|
try {
|
||||||
|
const result = await api.post('/caddy/restart');
|
||||||
|
notification.toast(result.message || '重载命令已发送。', 'success');
|
||||||
|
setTimeout(checkCaddyStatus, 500);
|
||||||
|
} catch(error) { notification.toast(`重载失败: ${error.message}`, 'error'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initCaddyStatus() {
|
||||||
|
// 确保通知模块已经初始化
|
||||||
|
const dialogContainer = document.getElementById('dialog-container');
|
||||||
|
const toastContainer = document.getElementById('toast-container');
|
||||||
|
if (dialogContainer && toastContainer) {
|
||||||
|
notification.init(toastContainer, dialogContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCaddyStatus();
|
||||||
|
if (caddyStatusInterval) {
|
||||||
|
clearInterval(caddyStatusInterval);
|
||||||
|
}
|
||||||
|
caddyStatusInterval = setInterval(checkCaddyStatus, POLLING_INTERVAL);
|
||||||
|
}
|
||||||
59
frontend/js/common.js
Normal file
59
frontend/js/common.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// js/common.js - 存放共享模块
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
init: (toggleElement) => {
|
||||||
|
const storedTheme = localStorage.getItem('theme');
|
||||||
|
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const currentTheme = storedTheme || (systemPrefersDark ? 'dark' : 'light');
|
||||||
|
theme.apply(currentTheme);
|
||||||
|
if (toggleElement) {
|
||||||
|
toggleElement.addEventListener('change', (e) => theme.apply(e.target.checked ? 'dark' : 'light'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
apply: (themeName) => {
|
||||||
|
document.documentElement.dataset.theme = themeName;
|
||||||
|
localStorage.setItem('theme', themeName);
|
||||||
|
const themeToggleInput = document.getElementById('theme-toggle-input');
|
||||||
|
if (themeToggleInput) {
|
||||||
|
themeToggleInput.checked = themeName === 'dark';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function hideToast(toastElement) {
|
||||||
|
if (!toastElement) return;
|
||||||
|
toastElement.classList.remove('show');
|
||||||
|
toastElement.addEventListener('transitionend', () => toastElement.remove(), { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = {
|
||||||
|
show: (message, type = 'info', duration = 3000) => {
|
||||||
|
const toastContainer = document.getElementById('toast-container');
|
||||||
|
if (!toastContainer) return;
|
||||||
|
const icons = { success: 'fa-check-circle', error: 'fa-times-circle', info: 'fa-info-circle' };
|
||||||
|
const iconClass = icons[type] || 'fa-info-circle';
|
||||||
|
const toastElement = document.createElement('div');
|
||||||
|
toastElement.className = `toast ${type}`;
|
||||||
|
toastElement.innerHTML = `<i class="toast-icon fa-solid ${iconClass}"></i><p class="toast-message">${message}</p><button class="toast-close" data-toast-close>×</button>`;
|
||||||
|
toastContainer.appendChild(toastElement);
|
||||||
|
requestAnimationFrame(() => toastElement.classList.add('show'));
|
||||||
|
const timeoutId = setTimeout(() => hideToast(toastElement), duration);
|
||||||
|
toastElement.querySelector('[data-toast-close]').addEventListener('click', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
hideToast(toastElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function activateNav(pageId) {
|
||||||
|
const navLinks = document.querySelectorAll('.sidebar-nav a');
|
||||||
|
navLinks.forEach(link => {
|
||||||
|
link.classList.remove('active');
|
||||||
|
if (link.dataset.navId === pageId) {
|
||||||
|
link.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出模块
|
||||||
|
export { theme, toast, activateNav };
|
||||||
100
frontend/js/settings.js
Normal file
100
frontend/js/settings.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// js/settings.js - 设置页面的逻辑
|
||||||
|
|
||||||
|
import { theme, toast, activateNav } from './common.js';
|
||||||
|
import { initCaddyStatus } from './caddy.js'; // 导入 Caddy 状态模块
|
||||||
|
import { notification } from './notifications.js'; // 导入通知模块
|
||||||
|
|
||||||
|
const RESET_PWD_API_URL = '/v0/api/auth/resetpwd';
|
||||||
|
const LOGOUT_API_URL = '/v0/api/auth/logout';
|
||||||
|
|
||||||
|
const DOMElements = {
|
||||||
|
resetForm: document.getElementById('reset-password-form'),
|
||||||
|
themeToggleInput: document.getElementById('theme-toggle-input'),
|
||||||
|
logoutBtn: document.getElementById('logout-btn'),
|
||||||
|
toastContainer: document.getElementById('toast-container'),
|
||||||
|
dialogContainer: document.getElementById('dialog-container'),
|
||||||
|
};
|
||||||
|
const resetButton = DOMElements.resetForm.querySelector('button[type="submit"]');
|
||||||
|
|
||||||
|
async function handleResetPassword(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const newPassword = DOMElements.resetForm.new_password.value;
|
||||||
|
const confirmPassword = DOMElements.resetForm.confirm_new_password.value;
|
||||||
|
|
||||||
|
//保证字段均不为空, 用户名 密码 新密码
|
||||||
|
const currentPassword = DOMElements.resetForm.old_password.value;
|
||||||
|
const username = DOMElements.resetForm.username.value;
|
||||||
|
|
||||||
|
if (username === '') {
|
||||||
|
toast.show('用户名不能为空', 'error');
|
||||||
|
DOMElements.resetForm.username.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPassword === '') {
|
||||||
|
toast.show('当前密码不能为空', 'error');
|
||||||
|
DOMElements.resetForm.old_password.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newPassword === '') {
|
||||||
|
notification.toast('新密码不能为空', 'error');
|
||||||
|
DOMElements.resetForm.new_password.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (confirmPassword === '') {
|
||||||
|
notification.toast('确认新密码不能为空', 'error');
|
||||||
|
DOMElements.resetForm.confirm_new_password.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword !== confirmPassword) {
|
||||||
|
notification.toast('新密码与确认密码不匹配', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newPassword.length < 8) {
|
||||||
|
notification.toast('新密码长度至少为8位', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetButton.disabled = true;
|
||||||
|
resetButton.querySelector('span').textContent = '重置中...';
|
||||||
|
|
||||||
|
const formData = new FormData(DOMElements.resetForm);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(RESET_PWD_API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: new URLSearchParams(formData),
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
notification.toast('密码重置成功!请重新登录。', 'success');
|
||||||
|
setTimeout(() => { window.location.href = LOGOUT_API_URL; }, 1500);
|
||||||
|
} else {
|
||||||
|
throw new Error(result.error || '重置密码失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notification.toast(error.message, 'error');
|
||||||
|
resetButton.disabled = false;
|
||||||
|
resetButton.querySelector('span').textContent = '重置密码';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogout() {
|
||||||
|
if (await notification.confirm('您确定要退出登录吗?')) {
|
||||||
|
notification.toast('正在退出...', 'info');
|
||||||
|
setTimeout(() => { window.location.href = LOGOUT_API_URL; }, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
theme.init(DOMElements.themeToggleInput);
|
||||||
|
notification.init(DOMElements.toastContainer, DOMElements.dialogContainer);
|
||||||
|
activateNav('settings');
|
||||||
|
initCaddyStatus(); // 初始化通用Caddy状态检查
|
||||||
|
|
||||||
|
DOMElements.resetForm.addEventListener('submit', handleResetPassword);
|
||||||
|
DOMElements.logoutBtn.addEventListener('click', handleLogout);
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
89
frontend/settings.html
Normal file
89
frontend/settings.html
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>面板设置 - CaddyDash</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 侧边栏 (与 index.html 完全相同) -->
|
||||||
|
<aside class="sidebar" id="sidebar">
|
||||||
|
<header class="sidebar-header">
|
||||||
|
<i class="fa-solid fa-rocket"></i>
|
||||||
|
<h1>CaddyDash</h1>
|
||||||
|
</header>
|
||||||
|
<nav class="sidebar-nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/" data-nav-id="configs"><i class="fa-solid fa-sitemap"></i> <span>配置管理</span></a></li>
|
||||||
|
<li><a href="/settings.html" data-nav-id="settings"><i class="fa-solid fa-gears"></i>
|
||||||
|
<span>面板设置</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="sidebar-bottom">
|
||||||
|
<div class="theme-switcher"><i class="fa-solid fa-sun"></i><label class="switch"><input type="checkbox"
|
||||||
|
id="theme-toggle-input"><span class="slider"></span></label><i class="fa-solid fa-moon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="caddy-control-panel">
|
||||||
|
<div id="caddy-status-indicator" class="caddy-status"><span class="status-dot checking"></span><span
|
||||||
|
class="status-text">检查中...</span></div>
|
||||||
|
<div id="caddy-action-button-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="logout-section"><button id="logout-btn" class="btn btn-secondary"><i
|
||||||
|
class="fa-solid fa-right-from-bracket"></i><span>退出登录</span></button></div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<header class="main-header">
|
||||||
|
<button class="btn-icon" id="menu-toggle-btn"><i class="fa-solid fa-bars"></i></button>
|
||||||
|
<h2>面板设置</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="view-container">
|
||||||
|
<section class="card-panel">
|
||||||
|
<h3>账户安全</h3>
|
||||||
|
<form id="reset-password-form" style="margin-top: 24px;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input type="text" id="username" name="username" autocomplete="username">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="old_password">当前密码</label>
|
||||||
|
<input type="password" id="old_password" name="old_password"
|
||||||
|
autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_password">新密码 (至少8位)</label>
|
||||||
|
<input type="password" id="new_password" name="new_password" autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_new_password">确认新密码</label>
|
||||||
|
<input type="password" id="confirm_new_password" name="confirm_new_password"
|
||||||
|
autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="fa-solid fa-key"></i>
|
||||||
|
<span>重置密码</span></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="toast-container" class="toast-container"></div>
|
||||||
|
<div id="dialog-container"></div>
|
||||||
|
|
||||||
|
<script type="module" src="js/common.js"></script>
|
||||||
|
<script type="module" src="js/settings.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -17,7 +17,7 @@ func InitAdminUser(username string, password string, cdb *db.ConfigDB) error {
|
||||||
userStatus.SetInitialized(true)
|
userStatus.SetInitialized(true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
hashedPassword, err := hashPassword(password)
|
hashedPassword, err := HashPassword(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to hash password: %w", err)
|
return fmt.Errorf("failed to hash password: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ func InitAdminUser(username string, password string, cdb *db.ConfigDB) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// bcrypt加密password串
|
// bcrypt加密password串
|
||||||
func hashPassword(password string) (string, error) {
|
func HashPassword(password string) (string, error) {
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 11)
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 11)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -69,7 +69,7 @@ func InitFormEnv(cdb *db.ConfigDB) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行初始化
|
// 执行初始化
|
||||||
hashedPassword, err := hashPassword(password)
|
hashedPassword, err := HashPassword(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to hash password from env: %w", err)
|
return fmt.Errorf("failed to hash password from env: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue