diff --git a/Makefile b/Makefile
index 2df5abd..8670bf1 100644
--- a/Makefile
+++ b/Makefile
@@ -24,9 +24,9 @@ GO_VERSION := $(shell go version | awk '{print $$3}')
# 构建标志
LDFLAGS := -ldflags "\
- -X 'github.com/zy84338719/filecodebox/internal/models.Version=$(VERSION)' \
- -X 'github.com/zy84338719/filecodebox/internal/models.GitCommit=$(COMMIT)' \
- -X 'github.com/zy84338719/filecodebox/internal/models.BuildTime=$(DATE)' \
+ -X 'github.com/zy84338719/filecodebox/internal/models/service.Version=$(VERSION)' \
+ -X 'github.com/zy84338719/filecodebox/internal/models/service.GitCommit=$(COMMIT)' \
+ -X 'github.com/zy84338719/filecodebox/internal/models/service.BuildTime=$(DATE)' \
-w -s"
# 默认目标
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..53adb84
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.8.2
diff --git a/config.yaml b/config.yaml
index aaf5860..508eae0 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,8 +1,8 @@
base:
name: FileCodeBox
description: 开箱即用的文件快传系统
- keywords: ""
- port: 12346
+ keywords: FileCodeBox, 文件快递柜, 口令传送箱, 匿名口令分享文本, 文件
+ port: 12345
host: 0.0.0.0
datapath: /Users/zhangyi/zy/FileCodeBox/data
production: false
@@ -44,8 +44,6 @@ mcp:
enablemcpserver: 0
mcpport: ""
mcphost: ""
-notifytitle: ""
-notifycontent: ""
ui:
themes_select: themes/2025
background: ""
@@ -54,15 +52,9 @@ ui:
User-agent: *
Disallow: /
show_admin_addr: 0
- opacity: 0
-themes_select: themes/2025
-robots_text: |-
- User-agent: *
- Disallow: /
-page_explain: 请勿上传或分享违法内容。根据《中华人民共和国网络安全法》、《中华人民共和国刑法》、《中华人民共和国治安管理处罚法》等相关规定。 传播或存储违法、违规内容,会受到相关处罚,严重者将承担刑事责任。本站坚决配合相关部门,确保网络内容的安全,和谐,打造绿色网络环境。
-show_admin_addr: 0
-opacity: 0
-background: ""
+ opacity: 1
+notify_title: ""
+notify_content: ""
sys_start: ""
upload_minute: 0
upload_count: 0
diff --git a/docs/RELEASE_NOTES/v1.8.2.md b/docs/RELEASE_NOTES/v1.8.2.md
new file mode 100644
index 0000000..f658122
--- /dev/null
+++ b/docs/RELEASE_NOTES/v1.8.2.md
@@ -0,0 +1,13 @@
+## v1.8.2 - 2025-09-22
+
+Release: v1.8.2
+
+Changes:
+
+- 更新版本标识与 API 文档到 `1.8.2`
+ - 修改文件:`VERSION`, `main.go`, `internal/models/service/system.go`, `internal/handlers/api.go`, `docs/swagger.yaml`, `docs/swagger.json`, `docs/swagger-enhanced.yaml`
+
+Notes:
+
+- 构建与单元测试已在本地通过(`go build ./...`, `go test ./...`)。
+- 依赖版本(`go.sum`)未更改。
diff --git a/docs/docs.go b/docs/docs.go
index b5c844c..4efe4cc 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -1,7 +1,10 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
-import "github.com/swaggo/swag"
+import (
+ "github.com/swaggo/swag"
+ "github.com/zy84338719/filecodebox/internal/models/service"
+)
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
@@ -618,7 +621,7 @@ const docTemplate = `{
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
- Version: "1.0",
+ Version: service.Version,
Host: "localhost:12345",
BasePath: "/",
Schemes: []string{},
diff --git a/docs/swagger-enhanced.yaml b/docs/swagger-enhanced.yaml
index df68ad2..d4d8c13 100644
--- a/docs/swagger-enhanced.yaml
+++ b/docs/swagger-enhanced.yaml
@@ -2,7 +2,7 @@ swagger: "2.0"
info:
title: "FileCodeBox API"
description: "FileCodeBox 是一个用于文件分享和代码片段管理的 Web 应用程序"
- version: "1.0"
+ version: "1.8.2"
termsOfService: "http://swagger.io/terms/"
contact:
name: "API Support"
@@ -69,7 +69,7 @@ paths:
example: "2025-09-11T10:00:00Z"
version:
type: "string"
- example: "1.0.0"
+ example: "1.8.2"
uptime:
type: "string"
example: "2h30m15s"
diff --git a/docs/swagger.json b/docs/swagger.json
index 31f1abd..0a5f130 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -13,7 +13,7 @@
"name": "MIT",
"url": "https://github.com/zy84338719/filecodebox/blob/main/LICENSE"
},
- "version": "1.0"
+ "version": "1.8.2"
},
"host": "localhost:12345",
"basePath": "/",
@@ -553,7 +553,7 @@
},
"version": {
"type": "string",
- "example": "1.0.0"
+ "example": "1.8.2"
}
}
},
@@ -608,4 +608,4 @@
"type": "basic"
}
}
-}
\ No newline at end of file
+}
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 23f7f74..76a5289 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -12,7 +12,7 @@ definitions:
example: 2h30m15s
type: string
version:
- example: 1.0.0
+ example: 1.8.2
type: string
type: object
handlers.SystemConfig:
@@ -57,7 +57,7 @@ info:
url: https://github.com/zy84338719/filecodebox/blob/main/LICENSE
termsOfService: http://swagger.io/terms/
title: FileCodeBox API
- version: "1.0"
+ version: "1.8.2"
paths:
/api/config:
get:
diff --git a/go.mod b/go.mod
index 7128acd..c1e9bc6 100644
--- a/go.mod
+++ b/go.mod
@@ -9,8 +9,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.27.27
github.com/aws/aws-sdk-go-v2/credentials v1.17.27
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3
- github.com/gin-contrib/cors v1.4.0
- github.com/gin-gonic/gin v1.8.2
+ github.com/gin-contrib/cors v1.6.0
+ github.com/gin-gonic/gin v1.9.1
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/mattn/go-sqlite3 v1.14.32
github.com/robfig/cron/v3 v3.0.1
@@ -47,6 +47,10 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
+ github.com/bytedance/sonic v1.11.2 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
+ github.com/chenzhuoyu/iasm v0.9.1 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-openapi/jsonpointer v0.22.0 // indirect
github.com/go-openapi/jsonreference v0.21.1 // indirect
@@ -65,17 +69,19 @@ require (
github.com/go-openapi/swag/yamlutils v0.24.0 // 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.11.2 // indirect
+ github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
- github.com/jackc/pgx/v5 v5.2.0 // indirect
+ github.com/jackc/pgx/v5 v5.5.4 // indirect
+ github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -83,12 +89,14 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/spf13/pflag v1.0.6 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
+ golang.org/x/arch v0.7.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.23.0 // indirect
+ golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
- gopkg.in/yaml.v2 v2.4.0 // indirect
)
diff --git a/go.sum b/go.sum
index 5f27fc0..399a32c 100644
--- a/go.sum
+++ b/go.sum
@@ -42,23 +42,36 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudr
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
+github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
+github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
+github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
+github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
-github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
+github.com/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg=
+github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
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.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
-github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
-github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM=
@@ -105,8 +118,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
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.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
-github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
-github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
+github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
@@ -129,9 +142,12 @@ github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8=
github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
+github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
+github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels=
+github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@@ -142,6 +158,10 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+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/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@@ -200,12 +220,14 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/studio-b12/gowebdav v0.10.0 h1:Yewz8FFiadcGEu4hxS/AAJQlHelndqln1bns3hcJIYc=
@@ -218,6 +240,8 @@ github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
+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.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
@@ -226,6 +250,9 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
+golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -255,6 +282,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -270,6 +299,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
@@ -313,7 +343,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -330,3 +359,5 @@ gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/config/manager.go b/internal/config/manager.go
index d749d2b..79b043a 100644
--- a/internal/config/manager.go
+++ b/internal/config/manager.go
@@ -3,7 +3,6 @@ package config
import (
"errors"
"os"
- "strconv"
"strings"
"gopkg.in/yaml.v3"
@@ -56,15 +55,17 @@ func NewConfigManager() *ConfigManager {
func InitManager() *ConfigManager {
cm := NewConfigManager()
- // 尝试加载 YAML 配置文件
+ var sources []ConfigSource
+
if configPath := os.Getenv("CONFIG_PATH"); configPath != "" {
- _ = cm.LoadFromYAML(configPath)
+ sources = append(sources, YAMLFileSource{Path: configPath})
} else if _, err := os.Stat("./config.yaml"); err == nil {
- _ = cm.LoadFromYAML("./config.yaml")
+ sources = append(sources, YAMLFileSource{Path: "./config.yaml"})
}
- // 应用环境变量覆盖
- cm.applyEnvironmentOverrides()
+ sources = append(sources, NewDefaultEnvSource())
+
+ _ = cm.ApplySources(sources...)
return cm
}
@@ -135,24 +136,24 @@ func (cm *ConfigManager) mergeSimpleFields(fileCfg *ConfigManager) {
}
}
-// LoadFromYAML 从 YAML 文件加载配置
-func (cm *ConfigManager) LoadFromYAML(path string) error {
- b, err := os.ReadFile(path)
- if err != nil {
- return err
- }
-
- var fileCfg ConfigManager
- if err := yaml.Unmarshal(b, &fileCfg); err != nil {
- return err
+// ApplySources processes a group of configuration sources and collects errors.
+func (cm *ConfigManager) ApplySources(sources ...ConfigSource) error {
+ var errs []error
+ for _, source := range sources {
+ if source == nil {
+ continue
+ }
+ if err := source.Apply(cm); err != nil {
+ errs = append(errs, err)
+ }
}
- // 按模块合并配置
- cm.mergeConfigModules(&fileCfg)
- cm.mergeUserConfig(fileCfg.User)
- cm.mergeSimpleFields(&fileCfg)
+ return errors.Join(errs...)
+}
- return nil
+// LoadFromYAML 从 YAML 文件加载配置
+func (cm *ConfigManager) LoadFromYAML(path string) error {
+ return cm.ApplySources(YAMLFileSource{Path: path})
}
// ReloadConfig 重新加载配置(仅支持环境变量,保持端口不变)
@@ -190,30 +191,8 @@ func (cm *ConfigManager) PersistYAML() error {
// applyEnvironmentOverrides 应用环境变量覆盖配置
func (cm *ConfigManager) applyEnvironmentOverrides() {
- // 基础配置环境变量
- if port := os.Getenv("PORT"); port != "" {
- if n, err := strconv.Atoi(port); err == nil {
- cm.Base.Port = n
- }
- }
- if dataPath := os.Getenv("DATA_PATH"); dataPath != "" {
- cm.Base.DataPath = dataPath
- }
-
- // MCP 配置环境变量
- if enableMCP := os.Getenv("ENABLE_MCP_SERVER"); enableMCP != "" {
- if enableMCP == "true" || enableMCP == "1" {
- cm.MCP.EnableMCPServer = 1
- } else {
- cm.MCP.EnableMCPServer = 0
- }
- }
- if mcpPort := os.Getenv("MCP_PORT"); mcpPort != "" {
- cm.MCP.MCPPort = mcpPort
- }
- if mcpHost := os.Getenv("MCP_HOST"); mcpHost != "" {
- cm.MCP.MCPHost = mcpHost
- }
+ // 收集错误以便在调用者中统一处理,保持现有签名
+ _ = NewDefaultEnvSource().Apply(cm)
}
// Save 保存配置(已废弃,请使用 config.yaml 和环境变量)
diff --git a/internal/config/manager_test.go b/internal/config/manager_test.go
index 9be60a8..5109ffc 100644
--- a/internal/config/manager_test.go
+++ b/internal/config/manager_test.go
@@ -66,3 +66,18 @@ func TestEnvOverride(t *testing.T) {
t.Fatalf("expected PORT env to override to 9090, got %d", cm.Base.Port)
}
}
+
+func TestApplySourcesAggregatesErrors(t *testing.T) {
+ cm := NewConfigManager()
+ src := NewDefaultEnvSource()
+ src.lookup = func(key string) string {
+ if key == "ENABLE_MCP_SERVER" {
+ return "definitely-not-bool"
+ }
+ return ""
+ }
+
+ if err := cm.ApplySources(src); err == nil {
+ t.Fatalf("expected aggregated error when environment value invalid")
+ }
+}
diff --git a/internal/config/source.go b/internal/config/source.go
new file mode 100644
index 0000000..4d8465b
--- /dev/null
+++ b/internal/config/source.go
@@ -0,0 +1,143 @@
+package config
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+)
+
+// ConfigSource represents a configuration input that can mutate the manager state.
+type ConfigSource interface {
+ Apply(*ConfigManager) error
+}
+
+// ConfigSourceFunc allows plain functions to be used as ConfigSource.
+type ConfigSourceFunc func(*ConfigManager) error
+
+// Apply executes the underlying function.
+func (f ConfigSourceFunc) Apply(cm *ConfigManager) error { return f(cm) }
+
+// YAMLFileSource loads configuration values from a YAML file.
+type YAMLFileSource struct {
+ Path string
+}
+
+// Apply reads and merges YAML content into the manager.
+func (s YAMLFileSource) Apply(cm *ConfigManager) error {
+ if strings.TrimSpace(s.Path) == "" {
+ return errors.New("config: YAML path is empty")
+ }
+
+ data, err := os.ReadFile(s.Path)
+ if err != nil {
+ return err
+ }
+
+ var fileCfg ConfigManager
+ if err := yaml.Unmarshal(data, &fileCfg); err != nil {
+ return fmt.Errorf("config: unmarshal %s: %w", s.Path, err)
+ }
+
+ cm.mergeConfigModules(&fileCfg)
+ cm.mergeUserConfig(fileCfg.User)
+ cm.mergeSimpleFields(&fileCfg)
+ return nil
+}
+
+type envOverride struct {
+ key string
+ apply func(string, *ConfigManager) error
+}
+
+// EnvSource mutates configuration using environment variables.
+type EnvSource struct {
+ overrides []envOverride
+ lookup func(string) string
+}
+
+// NewDefaultEnvSource returns the built-in environment overrides.
+func NewDefaultEnvSource() EnvSource {
+ return EnvSource{
+ overrides: []envOverride{
+ {key: "PORT", apply: applyPortOverride},
+ {key: "DATA_PATH", apply: applyDataPathOverride},
+ {key: "ENABLE_MCP_SERVER", apply: applyMCPEnabledOverride},
+ {key: "MCP_PORT", apply: applyMCPPortOverride},
+ {key: "MCP_HOST", apply: applyMCPHostOverride},
+ },
+ }
+}
+
+// Apply applies every configured override.
+func (s EnvSource) Apply(cm *ConfigManager) error {
+ lookup := s.lookup
+ if lookup == nil {
+ lookup = os.Getenv
+ }
+
+ var errs []error
+ for _, override := range s.overrides {
+ if value := lookup(override.key); value != "" {
+ if err := override.apply(value, cm); err != nil {
+ errs = append(errs, fmt.Errorf("%s: %w", override.key, err))
+ }
+ }
+ }
+
+ return errors.Join(errs...)
+}
+
+func applyPortOverride(val string, cm *ConfigManager) error {
+ port, err := strconv.Atoi(val)
+ if err != nil {
+ return fmt.Errorf("invalid port %q", val)
+ }
+ cm.Base.Port = port
+ return nil
+}
+
+func applyDataPathOverride(val string, cm *ConfigManager) error {
+ if strings.TrimSpace(val) == "" {
+ return errors.New("data path cannot be blank")
+ }
+ cm.Base.DataPath = val
+ return nil
+}
+
+func applyMCPEnabledOverride(val string, cm *ConfigManager) error {
+ enabled, err := parseBool(val)
+ if err != nil {
+ return err
+ }
+ if enabled {
+ cm.MCP.EnableMCPServer = 1
+ } else {
+ cm.MCP.EnableMCPServer = 0
+ }
+ return nil
+}
+
+func applyMCPPortOverride(val string, cm *ConfigManager) error {
+ cm.MCP.MCPPort = val
+ return nil
+}
+
+func applyMCPHostOverride(val string, cm *ConfigManager) error {
+ cm.MCP.MCPHost = val
+ return nil
+}
+
+func parseBool(val string) (bool, error) {
+ switch strings.ToLower(strings.TrimSpace(val)) {
+ case "1", "true", "t", "yes", "y":
+ return true, nil
+ case "0", "false", "f", "no", "n":
+ return false, nil
+ default:
+ return false, fmt.Errorf("invalid boolean value %q", val)
+ }
+}
diff --git a/internal/handlers/admin.go b/internal/handlers/admin.go
index 19e01bf..21ecb21 100644
--- a/internal/handlers/admin.go
+++ b/internal/handlers/admin.go
@@ -137,6 +137,9 @@ func (h *AdminHandler) GetConfig(c *gin.Context) {
SysStart: &cfg.SysStart,
},
}
+
+ resp.NotifyTitle = &cfg.NotifyTitle
+ resp.NotifyContent = &cfg.NotifyContent
common.SuccessResponse(c, resp)
}
diff --git a/internal/handlers/api.go b/internal/handlers/api.go
index 41dd616..c504726 100644
--- a/internal/handlers/api.go
+++ b/internal/handlers/api.go
@@ -8,11 +8,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/zy84338719/filecodebox/internal/common"
"github.com/zy84338719/filecodebox/internal/config"
-)
-
-// 应用版本号
-const (
- DefaultVersion = "1.0.0"
+ "github.com/zy84338719/filecodebox/internal/models"
)
// 应用启动时间
@@ -33,7 +29,7 @@ func NewAPIHandler(manager *config.ConfigManager) *APIHandler {
type HealthResponse struct {
Status string `json:"status" example:"ok"`
Timestamp string `json:"timestamp" example:"2025-09-11T10:00:00Z"`
- Version string `json:"version" example:"1.0.0"`
+ Version string `json:"version" example:"1.8.2"`
Uptime string `json:"uptime" example:"2h30m15s"`
}
@@ -49,7 +45,7 @@ func (h *APIHandler) GetHealth(c *gin.Context) {
// 从环境变量获取版本号,如果不存在则使用默认版本
version := os.Getenv("APP_VERSION")
if version == "" {
- version = DefaultVersion
+ version = models.Version
}
// 检查服务健康状态
diff --git a/internal/mcp/filecodebox.go b/internal/mcp/filecodebox.go
index bb4fbf3..ccbb5de 100644
--- a/internal/mcp/filecodebox.go
+++ b/internal/mcp/filecodebox.go
@@ -34,7 +34,7 @@ func NewFileCodeBoxMCPServer(
adminService *services.AdminService,
userService *services.UserService,
) *FileCodeBoxMCPServer {
- server := NewServer("FileCodeBox MCP Server", "1.0.0")
+ server := NewServer("FileCodeBox MCP Server", models.Version)
mcpServer := &FileCodeBoxMCPServer{
Server: server,
diff --git a/internal/mcp/manager.go b/internal/mcp/manager.go
index 99c9fb9..c40ef37 100644
--- a/internal/mcp/manager.go
+++ b/internal/mcp/manager.go
@@ -10,6 +10,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/zy84338719/filecodebox/internal/config"
+ "github.com/zy84338719/filecodebox/internal/models"
"github.com/zy84338719/filecodebox/internal/repository"
"github.com/zy84338719/filecodebox/internal/services"
"github.com/zy84338719/filecodebox/internal/storage"
@@ -180,7 +181,7 @@ func (m *MCPManager) GetStatus() MCPStatus {
if m.running && m.server != nil {
status.ServerInfo = ServerInfo{
Name: "FileCodeBox MCP Server",
- Version: "1.0.0",
+ Version: models.Version,
}
}
diff --git a/internal/models/service/system.go b/internal/models/service/system.go
index 5eed5ff..c9155ba 100644
--- a/internal/models/service/system.go
+++ b/internal/models/service/system.go
@@ -19,7 +19,7 @@ var (
GitBranch = "unknown"
// Version 应用版本号
- Version = "0.0.1"
+ Version = "1.8.2"
)
// BuildInfo 构建信息结构体
diff --git a/internal/models/web/admin.go b/internal/models/web/admin.go
index 4cab9b6..76a7f7d 100644
--- a/internal/models/web/admin.go
+++ b/internal/models/web/admin.go
@@ -160,6 +160,10 @@ type AdminConfigRequest struct {
// 系统运行时特有字段(不属于配置模块的字段)
SysStart *string `json:"sys_start,omitempty"`
+
+ // 顶层通知字段保留与历史配置结构兼容
+ NotifyTitle *string `json:"notify_title,omitempty"`
+ NotifyContent *string `json:"notify_content,omitempty"`
}
// CountResponse 通用计数响应
diff --git a/internal/routes/admin.go b/internal/routes/admin.go
index 724c54a..9bf90ee 100644
--- a/internal/routes/admin.go
+++ b/internal/routes/admin.go
@@ -1,9 +1,6 @@
package routes
import (
- "os"
- "path/filepath"
-
"github.com/zy84338719/filecodebox/internal/config"
"github.com/zy84338719/filecodebox/internal/handlers"
"github.com/zy84338719/filecodebox/internal/middleware"
@@ -56,116 +53,36 @@ func SetupAdminRoutes(
// 将管理后台静态资源与前端入口注册为公开路由,允许未认证用户加载登录页面和相关静态资源
// 注意:API 路由仍然放在受保护的 authGroup 中
- themeDir := "./" + cfg.UI.ThemesSelect
-
- // css
- adminGroup.GET("/css/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "admin", "css", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
+ serveFile := func(parts ...string) func(*gin.Context) {
+ return func(c *gin.Context) {
+ rel := c.Param("filepath")
+ joined := append(parts, rel)
+ path, err := static.ResolveThemeFile(cfg, joined...)
+ if err != nil {
+ c.Status(404)
+ return
+ }
+ c.File(path)
}
- c.File(p)
- })
+ }
- // HEAD for css
- adminGroup.HEAD("/css/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "admin", "css", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
+ // css
+ adminGroup.GET("/css/*filepath", serveFile("admin", "css"))
+ adminGroup.HEAD("/css/*filepath", serveFile("admin", "css"))
// js
- adminGroup.GET("/js/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "admin", "js", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
-
- // HEAD for js
- adminGroup.HEAD("/js/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "admin", "js", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
+ adminGroup.GET("/js/*filepath", serveFile("admin", "js"))
+ adminGroup.HEAD("/js/*filepath", serveFile("admin", "js"))
// templates
- adminGroup.GET("/templates/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "admin", "templates", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
-
- // HEAD for templates
- adminGroup.HEAD("/templates/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "admin", "templates", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
+ adminGroup.GET("/templates/*filepath", serveFile("admin", "templates"))
+ adminGroup.HEAD("/templates/*filepath", serveFile("admin", "templates"))
// assets and components
- adminGroup.GET("/assets/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "assets", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
-
- // HEAD for assets
- adminGroup.HEAD("/assets/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "assets", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
-
- adminGroup.GET("/components/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "components", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
-
- // HEAD for components
- adminGroup.HEAD("/components/*filepath", func(c *gin.Context) {
- fp := c.Param("filepath")
- p := filepath.Join(themeDir, "components", fp)
- if _, err := os.Stat(p); err != nil {
- c.Status(404)
- return
- }
- c.File(p)
- })
+ adminGroup.GET("/assets/*filepath", serveFile("assets"))
+ adminGroup.HEAD("/assets/*filepath", serveFile("assets"))
+ adminGroup.GET("/components/*filepath", serveFile("components"))
+ adminGroup.HEAD("/components/*filepath", serveFile("components"))
// 管理前端入口公开:允许未认证用户加载登录页面
adminGroup.GET("/", func(c *gin.Context) {
diff --git a/internal/services/admin/config.go b/internal/services/admin/config.go
index e4ccc80..a424c98 100644
--- a/internal/services/admin/config.go
+++ b/internal/services/admin/config.go
@@ -2,6 +2,7 @@ package admin
import (
"fmt"
+ "strings"
"github.com/zy84338719/filecodebox/internal/config"
"github.com/zy84338719/filecodebox/internal/models/web"
@@ -22,6 +23,12 @@ func (s *Service) UpdateConfig(configData map[string]interface{}) error {
// UpdateConfigFromRequest 从结构化请求更新配置
func (s *Service) UpdateConfigFromRequest(configRequest *web.AdminConfigRequest) error {
// 直接更新配置管理器的各个模块,不使用 map 转换
+ ensureUI := func() *config.UIConfig {
+ if s.manager.UI == nil {
+ s.manager.UI = &config.UIConfig{}
+ }
+ return s.manager.UI
+ }
// 处理基础配置
if configRequest.Base != nil {
@@ -152,24 +159,20 @@ func (s *Service) UpdateConfigFromRequest(configRequest *web.AdminConfigRequest)
// 处理 UI 配置
if configRequest.UI != nil {
uiConfig := configRequest.UI
- if uiConfig.ThemesSelect != "" {
- s.manager.UI.ThemesSelect = uiConfig.ThemesSelect
- }
- if uiConfig.Background != "" {
- s.manager.UI.Background = uiConfig.Background
- }
- if uiConfig.PageExplain != "" {
- s.manager.UI.PageExplain = uiConfig.PageExplain
- }
- if uiConfig.RobotsText != "" {
- s.manager.UI.RobotsText = uiConfig.RobotsText
- }
- if uiConfig.ShowAdminAddr != 0 {
- s.manager.UI.ShowAdminAddr = uiConfig.ShowAdminAddr
- }
- if uiConfig.Opacity != 0 {
- s.manager.UI.Opacity = uiConfig.Opacity
+ ui := ensureUI()
+ if strings.TrimSpace(uiConfig.ThemesSelect) != "" {
+ ui.ThemesSelect = uiConfig.ThemesSelect
}
+ ui.PageExplain = uiConfig.PageExplain
+ ui.Opacity = uiConfig.Opacity
+ }
+
+ // 顶层通知字段
+ if configRequest.NotifyTitle != nil {
+ s.manager.NotifyTitle = *configRequest.NotifyTitle
+ }
+ if configRequest.NotifyContent != nil {
+ s.manager.NotifyContent = *configRequest.NotifyContent
}
// 处理系统运行时字段
@@ -177,8 +180,10 @@ func (s *Service) UpdateConfigFromRequest(configRequest *web.AdminConfigRequest)
s.manager.SysStart = *configRequest.SysStart
}
- // 保存配置到数据库和文件
- return s.manager.Save()
+ if err := s.manager.PersistYAML(); err != nil {
+ return fmt.Errorf("persist config: %w", err)
+ }
+ return nil
}
// GetFullConfig 获取完整配置 - 返回配置管理器结构体
diff --git a/internal/static/assets.go b/internal/static/assets.go
index 32e53aa..0d78119 100644
--- a/internal/static/assets.go
+++ b/internal/static/assets.go
@@ -2,6 +2,7 @@ package static
import (
"fmt"
+ "html"
"net/http"
"os"
"path/filepath"
@@ -11,14 +12,102 @@ import (
"github.com/zy84338719/filecodebox/internal/config"
)
+const defaultThemeDir = "themes/2025"
+
+func themeCandidates(cfg *config.ConfigManager) []string {
+ var candidates []string
+ seen := make(map[string]struct{})
+ add := func(path string) {
+ path = strings.TrimSpace(path)
+ if path == "" {
+ return
+ }
+ if _, ok := seen[path]; ok {
+ return
+ }
+ seen[path] = struct{}{}
+ candidates = append(candidates, path)
+ }
+
+ if cfg != nil && cfg.UI != nil {
+ add(cfg.UI.ThemesSelect)
+ }
+ add(defaultThemeDir)
+ return candidates
+}
+
+func themeDirExists(dir string) bool {
+ info, err := os.Stat(dir)
+ return err == nil && info.IsDir()
+}
+
+func firstExistingThemeDir(cfg *config.ConfigManager) string {
+ for _, candidate := range themeCandidates(cfg) {
+ if filepath.IsAbs(candidate) {
+ if themeDirExists(candidate) {
+ return candidate
+ }
+ continue
+ }
+ if themeDirExists(candidate) {
+ return candidate
+ }
+ }
+ return defaultThemeDir
+}
+
+func resolveThemeFilePath(cfg *config.ConfigManager, parts ...string) (string, error) {
+ var firstErr error
+ for _, candidate := range themeCandidates(cfg) {
+ pathParts := append([]string{candidate}, parts...)
+ path := filepath.Join(pathParts...)
+ info, err := os.Stat(path)
+ if err == nil {
+ if !info.IsDir() {
+ return path, nil
+ }
+ continue
+ }
+ if firstErr == nil {
+ firstErr = err
+ }
+ }
+ if firstErr == nil {
+ firstErr = os.ErrNotExist
+ }
+ return "", firstErr
+}
+
+func loadThemeFile(cfg *config.ConfigManager, parts ...string) ([]byte, error) {
+ path, err := resolveThemeFilePath(cfg, parts...)
+ if err != nil {
+ return nil, err
+ }
+ return os.ReadFile(path)
+}
+
+// ResolveThemeFile returns the concrete filesystem path for a theme file, applying fallbacks.
+func ResolveThemeFile(cfg *config.ConfigManager, parts ...string) (string, error) {
+ return resolveThemeFilePath(cfg, parts...)
+}
+
+// ThemePath returns the resolved theme directory joined with optional relative parts.
+func ThemePath(cfg *config.ConfigManager, parts ...string) string {
+ root := firstExistingThemeDir(cfg)
+ if len(parts) == 0 {
+ return root
+ }
+ pathParts := append([]string{root}, parts...)
+ return filepath.Join(pathParts...)
+}
+
// RegisterStaticRoutes registers public-facing static routes (assets, css, js, components)
func RegisterStaticRoutes(router *gin.Engine, cfg *config.ConfigManager) {
- themeDir := fmt.Sprintf("./%s", cfg.UI.ThemesSelect)
-
- router.Static("/assets", fmt.Sprintf("%s/assets", themeDir))
- router.Static("/css", fmt.Sprintf("%s/css", themeDir))
- router.Static("/js", fmt.Sprintf("%s/js", themeDir))
- router.Static("/components", fmt.Sprintf("%s/components", themeDir))
+ themeDir := firstExistingThemeDir(cfg)
+ router.Static("/assets", filepath.Join(themeDir, "assets"))
+ router.Static("/css", filepath.Join(themeDir, "css"))
+ router.Static("/js", filepath.Join(themeDir, "js"))
+ router.Static("/components", filepath.Join(themeDir, "components"))
}
// Note: admin static routes are intentionally not registered here.
@@ -29,11 +118,12 @@ func RegisterStaticRoutes(router *gin.Engine, cfg *config.ConfigManager) {
// ServeIndex serves the main index page with basic template replacements.
func ServeIndex(c *gin.Context, cfg *config.ConfigManager) {
- indexPath := filepath.Join(".", cfg.UI.ThemesSelect, "index.html")
-
- content, err := os.ReadFile(indexPath)
+ content, err := loadThemeFile(cfg, "index.html")
if err != nil {
- c.String(http.StatusNotFound, "Index file not found")
+ html := fallbackIndexHTML(cfg)
+ c.Header("Cache-Control", "no-cache")
+ c.Header("Content-Type", "text/html; charset=utf-8")
+ c.String(http.StatusOK, html)
return
}
@@ -58,11 +148,12 @@ func ServeIndex(c *gin.Context, cfg *config.ConfigManager) {
// ServeSetup serves the setup page with template replacements.
func ServeSetup(c *gin.Context, cfg *config.ConfigManager) {
- setupPath := filepath.Join(".", cfg.UI.ThemesSelect, "setup.html")
-
- content, err := os.ReadFile(setupPath)
+ content, err := loadThemeFile(cfg, "setup.html")
if err != nil {
- c.String(http.StatusNotFound, "Setup page not found")
+ html := fallbackSetupHTML(cfg)
+ c.Header("Cache-Control", "no-cache")
+ c.Header("Content-Type", "text/html; charset=utf-8")
+ c.String(http.StatusOK, html)
return
}
@@ -81,11 +172,12 @@ func ServeSetup(c *gin.Context, cfg *config.ConfigManager) {
// ServeAdminPage serves the admin index page
func ServeAdminPage(c *gin.Context, cfg *config.ConfigManager) {
- adminPath := filepath.Join(".", cfg.UI.ThemesSelect, "admin", "index.html")
-
- content, err := os.ReadFile(adminPath)
+ content, err := loadThemeFile(cfg, "admin", "index.html")
if err != nil {
- c.String(http.StatusNotFound, "Admin page not found")
+ html := fallbackAdminHTML(cfg)
+ c.Header("Cache-Control", "no-cache")
+ c.Header("Content-Type", "text/html; charset=utf-8")
+ c.String(http.StatusOK, html)
return
}
@@ -96,9 +188,7 @@ func ServeAdminPage(c *gin.Context, cfg *config.ConfigManager) {
// ServeUserPage serves user-facing static pages (login/register/dashboard/etc.)
func ServeUserPage(c *gin.Context, cfg *config.ConfigManager, pageName string) {
- userPagePath := filepath.Join(".", cfg.UI.ThemesSelect, pageName)
-
- content, err := os.ReadFile(userPagePath)
+ content, err := loadThemeFile(cfg, pageName)
if err != nil {
c.String(http.StatusNotFound, "User page not found: "+pageName)
return
@@ -115,3 +205,118 @@ func ServeUserPage(c *gin.Context, cfg *config.ConfigManager, pageName string) {
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusOK, html)
}
+
+func fallbackBaseName(cfg *config.ConfigManager) string {
+ if cfg != nil && cfg.Base != nil {
+ if name := strings.TrimSpace(cfg.Base.Name); name != "" {
+ return name
+ }
+ }
+ return "FileCodeBox"
+}
+
+func fallbackBaseDescription(cfg *config.ConfigManager) string {
+ if cfg != nil && cfg.Base != nil {
+ if desc := strings.TrimSpace(cfg.Base.Description); desc != "" {
+ return desc
+ }
+ }
+ return "A lightweight file sharing service"
+}
+
+func fallbackPageExplain(cfg *config.ConfigManager) string {
+ if cfg != nil && cfg.UI != nil {
+ if explain := strings.TrimSpace(cfg.UI.PageExplain); explain != "" {
+ return explain
+ }
+ }
+ return "Service is running, but the selected theme assets were not found."
+}
+
+func fallbackIndexHTML(cfg *config.ConfigManager) string {
+ name := html.EscapeString(fallbackBaseName(cfg))
+ desc := html.EscapeString(fallbackBaseDescription(cfg))
+ explain := html.EscapeString(fallbackPageExplain(cfg))
+
+ return fmt.Sprintf(`
+
+
+
+
+%s
+
+
+
+
+
%s
+
%s
+
%s
+
The configured theme directory is missing; static assets will load once it is restored.
+
+
+`, name, name, desc, explain)
+}
+
+func fallbackSetupHTML(cfg *config.ConfigManager) string {
+ name := html.EscapeString(fallbackBaseName(cfg))
+ desc := html.EscapeString(fallbackBaseDescription(cfg))
+
+ return fmt.Sprintf(`
+
+
+
+
+%s - Setup
+
+
+
+
+
%s 初始化
+
%s
+
主题资源尚未就绪,请先完成配置文件中的 ui.themes_select 目录部署。
+
+- 确认主题目录已随构建产物一并分发
+- 或在配置中切换到有效的主题路径
+- 之后重新刷新本页面即可完成初始化流程
+
+
+
+`, name, name, desc)
+}
+
+func fallbackAdminHTML(cfg *config.ConfigManager) string {
+ name := html.EscapeString(fallbackBaseName(cfg))
+
+ return fmt.Sprintf(`
+
+
+
+
+%s Admin
+
+
+
+
+
Admin theme missing
+
Static assets for the admin console are unavailable. Restore the configured theme directory to load the full interface.
+
+
+`, name)
+}
diff --git a/internal/utils/disk.go b/internal/utils/disk_unix.go
similarity index 96%
rename from internal/utils/disk.go
rename to internal/utils/disk_unix.go
index d66692c..ce2ceaa 100644
--- a/internal/utils/disk.go
+++ b/internal/utils/disk_unix.go
@@ -1,3 +1,5 @@
+//go:build !windows
+
package utils
import (
diff --git a/internal/utils/disk_windows.go b/internal/utils/disk_windows.go
new file mode 100644
index 0000000..85c766a
--- /dev/null
+++ b/internal/utils/disk_windows.go
@@ -0,0 +1,56 @@
+//go:build windows
+
+package utils
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/sys/windows"
+)
+
+// GetUsagePercent returns disk usage percentage for the drive containing the given path on Windows.
+func GetUsagePercent(path string) (float64, error) {
+ volume, err := resolveVolume(path)
+ if err != nil {
+ return 0, err
+ }
+
+ var (
+ freeBytesAvailable uint64
+ totalNumberOfBytes uint64
+ totalNumberOfFree uint64
+ )
+
+ if err := windows.GetDiskFreeSpaceEx(windows.StringToUTF16Ptr(volume), &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFree); err != nil {
+ return 0, err
+ }
+
+ if totalNumberOfBytes == 0 {
+ return 0, fmt.Errorf("unable to compute total disk size for %s", volume)
+ }
+
+ used := totalNumberOfBytes - totalNumberOfFree
+ usage := (float64(used) / float64(totalNumberOfBytes)) * 100.0
+ return usage, nil
+}
+
+func resolveVolume(path string) (string, error) {
+ absPath, err := filepath.Abs(path)
+ if err != nil {
+ return "", err
+ }
+
+ volume := filepath.VolumeName(absPath)
+ if volume == "" {
+ return "", fmt.Errorf("unable to determine volume for path %s", absPath)
+ }
+
+ // Ensure the volume points to root (e.g., "C:\")
+ if !strings.HasSuffix(volume, "\\") {
+ volume += "\\"
+ }
+
+ return volume, nil
+}
diff --git a/main.go b/main.go
index 4e7d5b0..b0db491 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,7 @@
package main
// @title FileCodeBox API
-// @version 1.0
+// @version 1.8.2
// @description FileCodeBox 是一个用于文件分享和代码片段管理的 Web 应用程序
// @termsOfService http://swagger.io/terms/
diff --git a/scripts/build.sh b/scripts/build.sh
index dd57e51..9608ccb 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -77,9 +77,9 @@ print_info " BuildTime: $BUILD_TIME"
# 定义 ldflags
LDFLAGS="-s -w"
-LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models.Version=$VERSION'"
-LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models.GitCommit=$GIT_COMMIT'"
-LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models.BuildTime=$BUILD_TIME'"
+LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models/service.Version=$VERSION'"
+LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models/service.GitCommit=$GIT_COMMIT'"
+LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models/service.BuildTime=$BUILD_TIME'"
# 输出目录
OUTPUT_DIR="build"
@@ -120,4 +120,4 @@ if [ $? -eq 0 ]; then
else
print_error "构建失败"
exit 1
-fi
\ No newline at end of file
+fi
diff --git a/scripts/cross-build.sh b/scripts/cross-build.sh
index 192d8b3..463c14e 100755
--- a/scripts/cross-build.sh
+++ b/scripts/cross-build.sh
@@ -71,9 +71,9 @@ mkdir -p "$OUTPUT_DIR"
# 定义 ldflags
LDFLAGS="-s -w"
-LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models.Version=$VERSION'"
-LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models.GitCommit=$GIT_COMMIT'"
-LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models.BuildTime=$BUILD_TIME'"
+LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models/service.Version=$VERSION'"
+LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models/service.GitCommit=$GIT_COMMIT'"
+LDFLAGS="$LDFLAGS -X 'github.com/zy84338719/filecodebox/internal/models/service.BuildTime=$BUILD_TIME'"
# 构建每个平台
FAILED_BUILDS=()
diff --git a/scripts/release.sh b/scripts/release.sh
index 7e3d2be..7247d7d 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -170,7 +170,22 @@ build_project() {
# Go项目构建
if [[ -f "go.mod" ]]; then
- if ! go build -ldflags="-w -s" -o filecodebox .; then
+ local version_value="${VERSION#v}"
+ local git_commit="unknown"
+ if git rev-parse --short HEAD >/dev/null 2>&1; then
+ git_commit=$(git rev-parse --short HEAD)
+ if ! git diff-index --quiet HEAD --; then
+ git_commit="${git_commit}-dirty"
+ fi
+ fi
+ local build_time=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
+
+ local ldflags="-w -s"
+ ldflags="$ldflags -X 'github.com/zy84338719/filecodebox/internal/models/service.Version=$version_value'"
+ ldflags="$ldflags -X 'github.com/zy84338719/filecodebox/internal/models/service.GitCommit=$git_commit'"
+ ldflags="$ldflags -X 'github.com/zy84338719/filecodebox/internal/models/service.BuildTime=$build_time'"
+
+ if ! go build -ldflags="$ldflags" -o filecodebox .; then
log_error "构建失败"
exit 1
fi
diff --git a/themes/2025/admin/css/admin-modern.css b/themes/2025/admin/css/admin-modern.css
new file mode 100644
index 0000000..a17e8c4
--- /dev/null
+++ b/themes/2025/admin/css/admin-modern.css
@@ -0,0 +1,1665 @@
+:root {
+ --admin-bg-top: #eef2ff;
+ --admin-bg-bottom: #ffffff;
+ --admin-surface: #ffffff;
+ --admin-surface-subtle: #f7f8ff;
+ --admin-surface-muted: rgba(79, 70, 229, 0.08);
+ --admin-border: rgba(15, 23, 42, 0.08);
+ --admin-text: #0f172a;
+ --admin-muted: rgba(51, 65, 85, 0.88);
+ --admin-subtle: rgba(94, 106, 129, 0.7);
+ --admin-accent: #4f46e5;
+ --admin-accent-strong: #6366f1;
+ --admin-accent-soft: rgba(79, 70, 229, 0.12);
+ --admin-danger: #ef4444;
+ --admin-shadow-card: 0 18px 40px rgba(15, 23, 42, 0.08);
+ --admin-shadow-soft: 0 12px 24px rgba(79, 70, 229, 0.12);
+ --admin-placeholder-bg: #f8f9ff;
+ --admin-inverse-text: #f8fafc;
+ --admin-heading: #0f172a;
+ --admin-interactive-bg: rgba(79, 70, 229, 0.08);
+ --admin-interactive-bg-hover: rgba(79, 70, 229, 0.16);
+ --admin-icon-default: rgba(79, 70, 229, 0.7);
+ --admin-overlay: rgba(15, 23, 42, 0.55);
+ --admin-toggle-bg: rgba(148, 163, 184, 0.16);
+ --admin-ghost-bg: rgba(99, 102, 241, 0.12);
+ --admin-ghost-bg-hover: rgba(99, 102, 241, 0.2);
+ --admin-danger-soft: rgba(248, 113, 113, 0.12);
+ --admin-danger-soft-hover: rgba(248, 113, 113, 0.18);
+ --admin-danger-strong: #b91c1c;
+ --admin-nav-active-start: rgba(99, 102, 241, 0.78);
+ --admin-nav-active-end: rgba(129, 140, 248, 0.64);
+ --admin-nav-active-shadow: 0 16px 28px rgba(99, 102, 241, 0.26);
+ --admin-primary-btn-start: rgba(99, 102, 241, 0.82);
+ --admin-primary-btn-end: rgba(129, 140, 248, 0.68);
+ --admin-primary-btn-shadow: 0 12px 24px rgba(99, 102, 241, 0.22);
+ --admin-form-hint: rgba(71, 85, 105, 0.72);
+ --admin-stat-bg: rgba(255, 255, 255, 0.9);
+ --admin-stat-border: rgba(99, 102, 241, 0.16);
+ --admin-stat-highlight: rgba(99, 102, 241, 0.14);
+ --admin-stat-glow: 0 24px 45px rgba(79, 70, 229, 0.18);
+ --admin-stat-hover-glow: 0 28px 52px rgba(79, 70, 229, 0.24);
+ --admin-stat-icon-bg: rgba(79, 70, 229, 0.12);
+ --admin-stat-icon-color: #4338ca;
+ --admin-stat-trend-bg: rgba(79, 70, 229, 0.12);
+ --admin-stat-trend-text: #4338ca;
+ --admin-panel-bg: rgba(255, 255, 255, 0.95);
+ --admin-panel-border: rgba(99, 102, 241, 0.16);
+ --admin-panel-shadow: 0 24px 45px rgba(15, 23, 42, 0.08);
+ --admin-panel-highlight: rgba(99, 102, 241, 0.06);
+ --admin-action-bg: rgba(255, 255, 255, 0.92);
+ --admin-action-border: rgba(99, 102, 241, 0.12);
+ --admin-action-hover: rgba(79, 70, 229, 0.12);
+ --admin-action-glow: 0 18px 32px rgba(15, 23, 42, 0.08);
+ --admin-action-icon-bg: rgba(79, 70, 229, 0.12);
+ --admin-action-icon-color: #4338ca;
+ --admin-action-icon-shadow: 0 12px 26px rgba(79, 70, 229, 0.18);
+ --admin-status-bg: rgba(255, 255, 255, 0.92);
+ --admin-status-border: rgba(99, 102, 241, 0.14);
+ --admin-status-hover-border: rgba(79, 70, 229, 0.25);
+ --admin-status-dot-online: #34d399;
+ --admin-status-dot-warning: #facc15;
+ --admin-status-dot-offline: #f87171;
+}
+
+.admin-theme-dark {
+ --admin-bg-top: #0f172a;
+ --admin-bg-bottom: #111c44;
+ --admin-surface: rgba(17, 24, 39, 0.92);
+ --admin-surface-subtle: rgba(15, 23, 42, 0.72);
+ --admin-surface-muted: rgba(99, 102, 241, 0.22);
+ --admin-border: rgba(148, 163, 184, 0.2);
+ --admin-text: #e2e8f0;
+ --admin-muted: rgba(203, 213, 225, 0.88);
+ --admin-subtle: rgba(148, 163, 184, 0.68);
+ --admin-accent: #818cf8;
+ --admin-accent-strong: #6366f1;
+ --admin-accent-soft: rgba(99, 102, 241, 0.3);
+ --admin-danger: #f87171;
+ --admin-shadow-card: 0 24px 55px rgba(15, 23, 42, 0.45);
+ --admin-shadow-soft: 0 18px 36px rgba(99, 102, 241, 0.32);
+ --admin-placeholder-bg: rgba(15, 23, 42, 0.65);
+ --admin-inverse-text: #0f172a;
+ --admin-heading: #f8fafc;
+ --admin-interactive-bg: rgba(129, 140, 248, 0.18);
+ --admin-interactive-bg-hover: rgba(129, 140, 248, 0.32);
+ --admin-icon-default: rgba(226, 232, 240, 0.8);
+ --admin-overlay: rgba(15, 23, 42, 0.7);
+ --admin-toggle-bg: rgba(51, 65, 85, 0.6);
+ --admin-ghost-bg: rgba(99, 102, 241, 0.24);
+ --admin-ghost-bg-hover: rgba(129, 140, 248, 0.36);
+ --admin-danger-soft: rgba(248, 113, 113, 0.2);
+ --admin-danger-soft-hover: rgba(248, 113, 113, 0.28);
+ --admin-danger-strong: #fecaca;
+ --admin-nav-active-start: rgba(99, 102, 241, 0.92);
+ --admin-nav-active-end: rgba(129, 140, 248, 0.78);
+ --admin-nav-active-shadow: 0 18px 36px rgba(99, 102, 241, 0.38);
+ --admin-primary-btn-start: rgba(99, 102, 241, 0.94);
+ --admin-primary-btn-end: rgba(129, 140, 248, 0.82);
+ --admin-primary-btn-shadow: 0 14px 28px rgba(99, 102, 241, 0.42);
+ --admin-form-hint: rgba(148, 163, 184, 0.72);
+ --admin-stat-bg: rgba(17, 24, 39, 0.92);
+ --admin-stat-border: rgba(129, 140, 248, 0.32);
+ --admin-stat-highlight: rgba(99, 102, 241, 0.28);
+ --admin-stat-glow: 0 30px 56px rgba(2, 6, 23, 0.65);
+ --admin-stat-hover-glow: 0 36px 64px rgba(30, 41, 59, 0.72);
+ --admin-stat-icon-bg: rgba(99, 102, 241, 0.24);
+ --admin-stat-icon-color: #e0e7ff;
+ --admin-stat-trend-bg: rgba(99, 102, 241, 0.22);
+ --admin-stat-trend-text: #c7d2fe;
+ --admin-panel-bg: rgba(17, 24, 39, 0.9);
+ --admin-panel-border: rgba(99, 102, 241, 0.28);
+ --admin-panel-shadow: 0 26px 56px rgba(2, 6, 23, 0.7);
+ --admin-panel-highlight: rgba(79, 70, 229, 0.12);
+ --admin-action-bg: rgba(30, 41, 59, 0.85);
+ --admin-action-border: rgba(129, 140, 248, 0.22);
+ --admin-action-hover: rgba(99, 102, 241, 0.22);
+ --admin-action-glow: 0 24px 44px rgba(2, 6, 23, 0.6);
+ --admin-action-icon-bg: rgba(99, 102, 241, 0.24);
+ --admin-action-icon-color: #e0e7ff;
+ --admin-action-icon-shadow: 0 18px 38px rgba(2, 6, 23, 0.65);
+ --admin-status-bg: rgba(17, 24, 39, 0.88);
+ --admin-status-border: rgba(99, 102, 241, 0.28);
+ --admin-status-hover-border: rgba(129, 140, 248, 0.38);
+ --admin-status-dot-online: #34d399;
+ --admin-status-dot-warning: #facc15;
+ --admin-status-dot-offline: #f87171;
+}
+
+body.admin-modern-body {
+ margin: 0;
+ min-height: 100vh;
+ background: linear-gradient(220deg, var(--admin-bg-top) 0%, var(--admin-bg-bottom) 60%, var(--admin-surface) 100%);
+ color: var(--admin-text);
+ font-family: "Inter", "SF Pro Display", "PingFang SC", "Microsoft YaHei", sans-serif;
+ -webkit-font-smoothing: antialiased;
+}
+
+.admin-modern-body *,
+.admin-modern-body *::before,
+.admin-modern-body *::after {
+ box-sizing: border-box;
+}
+
+.admin-modern-body h1,
+.admin-modern-body h2,
+.admin-modern-body h3,
+.admin-modern-body h4,
+.admin-modern-body h5,
+.admin-modern-body h6 {
+ color: var(--admin-text);
+ font-weight: 600;
+ letter-spacing: 0.01em;
+ margin-top: 0;
+}
+
+.admin-modern-body p,
+.admin-modern-body span,
+.admin-modern-body label,
+.admin-modern-body small,
+.admin-modern-body li {
+ color: var(--admin-muted);
+ line-height: 1.6;
+}
+
+.admin-modern-body small {
+ font-size: 13px;
+}
+
+.admin-modern-body a {
+ color: var(--admin-accent);
+ text-decoration: none;
+ transition: color 0.2s ease;
+}
+
+.admin-modern-body a:hover {
+ color: #312e81;
+}
+
+.admin-shell {
+ display: flex;
+ min-height: 100vh;
+ position: relative;
+}
+
+.admin-sidebar {
+ width: 260px;
+ background: var(--admin-surface);
+ border-right: 1px solid var(--admin-border);
+ display: flex;
+ flex-direction: column;
+ padding: 32px 24px 28px;
+ gap: 32px;
+ z-index: 30;
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+ box-shadow: var(--admin-shadow-card);
+}
+
+.sidebar-brand {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.sidebar-brand .logo-icon {
+ width: 42px;
+ height: 42px;
+ border-radius: 14px;
+ background: var(--admin-accent-soft);
+ padding: 10px;
+}
+
+.brand-text {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.brand-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--admin-text);
+ letter-spacing: 0.02em;
+}
+
+.brand-subtitle {
+ font-size: 12px;
+ text-transform: uppercase;
+ letter-spacing: 0.18em;
+ color: var(--admin-subtle);
+}
+
+.sidebar-section,
+.sidebar-footer {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.sidebar-label {
+ font-size: 12px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--admin-subtle);
+ margin-bottom: 4px;
+}
+
+.tab-btn.nav-link {
+ width: 100%;
+ flex: none;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 16px;
+ border: none;
+ outline: none;
+ background: var(--admin-interactive-bg);
+ color: var(--admin-text);
+ border-radius: 14px;
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: transform 0.2s ease, background 0.2s ease, box-shadow 0.3s ease;
+ justify-content: flex-start;
+ text-transform: none;
+ letter-spacing: normal;
+ min-width: auto;
+ white-space: nowrap;
+}
+
+.tab-btn.nav-link i {
+ font-size: 16px;
+ color: var(--admin-icon-default);
+ transition: color 0.2s ease;
+}
+
+.tab-btn.nav-link:hover {
+ background: var(--admin-interactive-bg-hover);
+ transform: translateX(4px);
+}
+
+.tab-btn.nav-link.active {
+ background: linear-gradient(135deg, var(--admin-nav-active-start) 0%, var(--admin-nav-active-end) 100%);
+ color: #fff;
+ box-shadow: var(--admin-nav-active-shadow);
+}
+
+.tab-btn.nav-link.active i {
+ color: #fff;
+}
+
+.tab-btn.nav-link::before,
+.tab-btn.nav-link::after {
+ display: none !important;
+}
+
+.sidebar-action {
+ background: var(--admin-interactive-bg);
+ border: none;
+ color: var(--admin-text);
+ border-radius: 12px;
+ padding: 12px 16px;
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+ transition: background 0.2s ease, transform 0.2s ease;
+}
+
+.sidebar-action:hover {
+ background: var(--admin-interactive-bg-hover);
+ transform: translateX(4px);
+}
+
+.sidebar-action.logout {
+ background: var(--admin-danger-soft);
+ color: var(--admin-danger-strong);
+}
+
+.sidebar-action.logout:hover {
+ background: var(--admin-danger-soft-hover);
+}
+
+.sidebar-overlay {
+ position: fixed;
+ inset: 0;
+ background: var(--admin-overlay);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.3s ease;
+ z-index: 20;
+}
+
+.sidebar-overlay.active {
+ opacity: 0.85;
+ pointer-events: auto;
+}
+
+.admin-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ backdrop-filter: blur(18px);
+}
+
+.admin-header {
+ display: flex;
+ align-items: center;
+ gap: 24px;
+ padding: 32px 32px 8px;
+}
+
+.header-toggle {
+ display: none;
+ width: 44px;
+ height: 44px;
+ border-radius: 14px;
+ border: none;
+ background: var(--admin-toggle-bg);
+ color: var(--admin-text);
+ font-size: 18px;
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+}
+
+.headline {
+ flex: 1;
+}
+
+.headline h1 {
+ margin: 0;
+ font-size: 28px;
+ font-weight: 600;
+ color: var(--admin-heading);
+}
+
+.headline p {
+ margin: 8px 0 0;
+ max-width: 520px;
+ color: var(--admin-muted);
+ font-size: 14px;
+ line-height: 1.6;
+}
+
+.header-actions {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.header-btn-ghost {
+ background: var(--admin-ghost-bg);
+ color: var(--admin-accent);
+ box-shadow: none;
+}
+
+.header-btn-ghost:hover {
+ background: var(--admin-ghost-bg-hover);
+ box-shadow: none;
+}
+
+.header-metrics {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ max-width: 360px;
+}
+
+.metric-chip {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ border-radius: 12px;
+ padding: 10px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 95px;
+ box-shadow: 0 12px 22px rgba(99, 102, 241, 0.12);
+}
+
+.metric-chip .chip-label {
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ color: var(--admin-subtle);
+}
+
+.metric-chip .chip-value {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--admin-text);
+}
+
+.header-btn {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ border: none;
+ padding: 10px 18px;
+ border-radius: 12px;
+ background: linear-gradient(135deg, var(--admin-accent) 0%, var(--admin-accent-strong) 100%);
+ color: #fff;
+ font-weight: 600;
+ cursor: pointer;
+ box-shadow: 0 14px 28px rgba(99, 102, 241, 0.35);
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.header-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 16px 34px rgba(99, 102, 241, 0.42);
+}
+
+.admin-main {
+ flex: 1;
+ padding: 16px 32px 40px;
+ overflow-y: auto;
+}
+
+.tab-panels {
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+}
+
+.tab-panels .tab-content {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ border-radius: 26px;
+ padding: 32px;
+ box-shadow: 0 20px 45px rgba(15, 23, 42, 0.08);
+ display: none;
+}
+
+.tab-panels .tab-content.active {
+ display: block;
+}
+
+/* 让嵌套面板更舒展 */
+.tab-content > .dashboard-header,
+.tab-content > .maintenance-container,
+.tab-content > .files-container,
+.tab-content > .users-container,
+.tab-content > .storage-container,
+.tab-content > form {
+ margin-top: 8px;
+}
+
+/* 响应式 */
+@media (max-width: 1180px) {
+ .admin-header {
+ flex-wrap: wrap;
+ align-items: flex-start;
+ }
+
+ .header-actions {
+ width: 100%;
+ justify-content: flex-start;
+ order: 3;
+ }
+
+ .header-metrics {
+ order: 2;
+ max-width: none;
+ width: 100%;
+ justify-content: flex-start;
+ }
+
+ .headline {
+ order: 1;
+ }
+}
+
+@media (max-width: 1024px) {
+ .admin-shell {
+ flex-direction: column;
+ }
+
+ .admin-sidebar {
+ position: fixed;
+ inset: 0 auto 0 0;
+ transform: translateX(-105%);
+ max-width: 260px;
+ height: 100vh;
+ }
+
+ .admin-sidebar.sidebar-open {
+ transform: translateX(0);
+ }
+
+ .header-toggle {
+ display: flex;
+ order: -1;
+ }
+
+ .admin-main {
+ padding: 16px 20px 32px;
+ }
+}
+
+@media (max-width: 768px) {
+ .admin-header {
+ padding: 24px 20px 4px;
+ gap: 16px;
+ }
+
+ .header-metrics {
+ gap: 10px;
+ }
+
+ .headline h1 {
+ font-size: 22px;
+ }
+
+ .tab-panels .tab-content {
+ padding: 24px;
+ border-radius: 20px;
+ }
+}
+
+@media (max-width: 520px) {
+ .sidebar-action,
+ .tab-btn.nav-link {
+ font-size: 13px;
+ }
+
+ .admin-main {
+ padding: 16px;
+ }
+}
+
+@media (max-width: 640px) {
+ .admin-modern-body .stats-grid {
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 18px;
+ margin: 24px 0 28px;
+ }
+
+ .admin-modern-body .stat-card {
+ padding: 20px;
+ border-radius: 20px;
+ }
+
+ .admin-modern-body .stat-number {
+ font-size: 28px;
+ }
+
+ .admin-modern-body .dashboard-actions,
+ .admin-modern-body .dashboard-status {
+ padding: 24px;
+ border-radius: 20px;
+ }
+
+ .admin-modern-body .action-cards {
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 16px;
+ }
+
+ .admin-modern-body .action-card {
+ padding: 18px 20px;
+ border-radius: 18px;
+ }
+
+ .admin-modern-body .status-item {
+ padding: 16px 18px 16px 54px;
+ border-radius: 18px;
+ }
+}
+
+/* Dashboard + cards refinement */
+.admin-modern-body .dashboard-header {
+ position: relative;
+ z-index: 0;
+ background: var(--admin-panel-bg);
+ border: 1px solid var(--admin-panel-border);
+ border-radius: 22px;
+ box-shadow: var(--admin-panel-shadow);
+ padding: 28px 28px 24px;
+ overflow: hidden;
+}
+
+.admin-modern-body .dashboard-header::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at top right, var(--admin-panel-highlight) 0%, transparent 60%);
+ pointer-events: none;
+ z-index: 0;
+}
+
+.admin-modern-body .dashboard-header h3 {
+ position: relative;
+ z-index: 1;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ color: var(--admin-heading);
+}
+
+.admin-modern-body .dashboard-header h3 i {
+ color: var(--admin-accent);
+}
+
+.admin-modern-body .dashboard-header .dashboard-subtitle {
+ position: relative;
+ z-index: 1;
+ margin: 12px 0 0;
+ color: var(--admin-muted);
+}
+
+.admin-modern-body .stats-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 24px;
+ margin: 32px 0 36px;
+}
+
+.admin-modern-body .stat-card {
+ position: relative;
+ display: flex;
+ align-items: flex-start;
+ gap: 18px;
+ padding: 24px;
+ border-radius: 22px;
+ background: var(--admin-stat-bg);
+ border: 1px solid var(--admin-stat-border);
+ box-shadow: var(--admin-stat-glow);
+ overflow: hidden;
+ transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
+}
+
+.admin-modern-body .stat-card::before {
+ content: '';
+ position: absolute;
+ inset: -45% -35% 0 auto;
+ width: 70%;
+ height: 70%;
+ background: radial-gradient(circle at top right, var(--admin-stat-highlight) 0%, transparent 65%);
+ opacity: 0.65;
+ pointer-events: none;
+ transition: opacity 0.25s ease;
+}
+
+.admin-modern-body .stat-card:hover {
+ transform: translateY(-4px);
+ border-color: var(--admin-accent-soft);
+ box-shadow: var(--admin-stat-hover-glow);
+}
+
+.admin-modern-body .stat-card:hover::before {
+ opacity: 1;
+}
+
+.admin-modern-body .stat-icon {
+ position: relative;
+ z-index: 1;
+ width: 54px;
+ height: 54px;
+ border-radius: 18px;
+ display: grid;
+ place-items: center;
+ background: var(--admin-stat-icon-bg);
+ color: var(--admin-stat-icon-color);
+ font-size: 20px;
+}
+
+.admin-modern-body .stat-content {
+ position: relative;
+ z-index: 1;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.admin-modern-body .stat-number {
+ font-size: 32px;
+ font-weight: 700;
+ color: var(--admin-heading);
+ letter-spacing: -0.01em;
+ font-variant-numeric: tabular-nums;
+}
+
+.admin-modern-body .stat-label {
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ color: var(--admin-subtle);
+}
+
+.admin-modern-body .stat-trend {
+ margin-top: 8px;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ border-radius: 999px;
+ background: var(--admin-stat-trend-bg);
+ color: var(--admin-stat-trend-text);
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.admin-modern-body .stat-trend i {
+ font-size: 12px;
+}
+
+.admin-modern-body .maintenance-card,
+.admin-modern-body .files-container,
+.admin-modern-body .users-container,
+.admin-modern-body .storage-container,
+.admin-modern-body .maintenance-container {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ box-shadow: var(--admin-shadow-card);
+ border-radius: 18px;
+}
+
+.admin-modern-body .dashboard-actions {
+ padding: 28px;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ background: var(--admin-action-bg);
+ border: 1px solid var(--admin-action-border);
+ box-shadow: var(--admin-action-glow);
+ border-radius: 22px;
+}
+
+.admin-modern-body .dashboard-actions h4 {
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--admin-heading);
+}
+
+.admin-modern-body .dashboard-actions h4 i {
+ color: var(--admin-accent);
+ font-size: 18px;
+}
+
+.admin-modern-body .action-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 18px;
+}
+
+.admin-modern-body .action-card {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ padding: 20px 22px;
+ border-radius: 20px;
+ background: var(--admin-surface);
+ border: 1px solid transparent;
+ box-shadow: none;
+ color: var(--admin-text);
+ overflow: hidden;
+ transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
+}
+
+.admin-modern-body .action-card::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(140deg, var(--admin-panel-highlight) 0%, transparent 70%);
+ opacity: 0.14;
+ pointer-events: none;
+ transition: opacity 0.25s ease;
+}
+
+.admin-modern-body .action-card::after {
+ content: "\f061";
+ font-family: "Font Awesome 6 Free";
+ font-weight: 900;
+ font-size: 14px;
+ color: var(--admin-accent);
+ margin-left: auto;
+ transition: transform 0.25s ease, color 0.25s ease;
+}
+
+.admin-modern-body .action-card:hover {
+ transform: translateY(-3px);
+ border-color: var(--admin-action-border);
+ box-shadow: var(--admin-action-glow);
+ background: var(--admin-action-bg);
+}
+
+.admin-modern-body .action-card:hover::before {
+ opacity: 0.32;
+}
+
+.admin-modern-body .action-card:hover::after {
+ transform: translateX(4px);
+ color: var(--admin-accent-strong);
+}
+
+.admin-modern-body .action-icon {
+ width: 52px;
+ height: 52px;
+ border-radius: 16px;
+ display: grid;
+ place-items: center;
+ background: var(--admin-action-icon-bg);
+ color: var(--admin-action-icon-color);
+ box-shadow: var(--admin-action-icon-shadow);
+ font-size: 20px;
+}
+
+.admin-modern-body .action-content {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.admin-modern-body .action-content h5 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--admin-heading);
+}
+
+.admin-modern-body .action-content p {
+ margin: 0;
+ font-size: 13px;
+ color: var(--admin-muted);
+ line-height: 1.5;
+}
+
+.admin-modern-body .dashboard-status {
+ padding: 28px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ background: var(--admin-status-bg);
+ border: 1px solid var(--admin-status-border);
+ box-shadow: var(--admin-action-glow);
+ border-radius: 22px;
+}
+
+.admin-modern-body .dashboard-status h4 {
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--admin-heading);
+}
+
+.admin-modern-body .dashboard-status h4 i {
+ color: var(--admin-accent);
+ font-size: 18px;
+}
+
+.admin-modern-body .status-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 18px;
+}
+
+.admin-modern-body .status-item {
+ position: relative;
+ padding: 18px 20px 18px 58px;
+ border-radius: 18px;
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-status-border);
+ overflow: hidden;
+ transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
+}
+
+.admin-modern-body .status-item::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(130deg, var(--admin-panel-highlight) 0%, transparent 70%);
+ opacity: 0.18;
+ pointer-events: none;
+ transition: opacity 0.25s ease;
+}
+
+.admin-modern-body .status-item:hover {
+ transform: translateY(-2px);
+ border-color: var(--admin-status-hover-border);
+ box-shadow: var(--admin-action-glow);
+}
+
+.admin-modern-body .status-item:hover::before {
+ opacity: 0.28;
+}
+
+.admin-modern-body .status-indicator {
+ position: absolute;
+ left: 20px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.08);
+}
+
+.admin-modern-body .status-indicator.online {
+ background: var(--admin-status-dot-online);
+}
+
+.admin-modern-body .status-indicator.warning {
+ background: var(--admin-status-dot-warning);
+}
+
+.admin-modern-body .status-indicator.error {
+ background: var(--admin-status-dot-offline);
+}
+
+.admin-modern-body .status-label {
+ font-size: 13px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--admin-subtle);
+ margin-bottom: 6px;
+}
+
+.admin-modern-body .status-value {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--admin-heading);
+}
+
+/* Forms & tables */
+.admin-modern-body .form-control,
+.admin-modern-body input[type="text"],
+.admin-modern-body input[type="number"],
+.admin-modern-body input[type="password"],
+.admin-modern-body input[type="datetime-local"],
+.admin-modern-body textarea,
+.admin-modern-body select {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ color: var(--admin-text);
+ border-radius: 12px;
+ transition: border 0.2s ease, box-shadow 0.2s ease;
+}
+
+.admin-modern-body .form-control:focus,
+.admin-modern-body input:focus,
+.admin-modern-body textarea:focus,
+.admin-modern-body select:focus {
+ border-color: rgba(99, 102, 241, 0.45);
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.12);
+ outline: none;
+}
+
+.admin-modern-body .form-text {
+ color: var(--admin-form-hint);
+}
+
+.admin-modern-body .config-action-bar {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 16px;
+ padding: 20px;
+ margin-top: 32px;
+ background: rgba(15, 23, 42, 0.5);
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ border-radius: 18px;
+}
+
+.admin-modern-body .config-action-bar .btn {
+ min-width: 160px;
+}
+
+.admin-modern-body .config-action-bar .btn i {
+ margin-right: 6px;
+}
+
+.admin-modern-body .btn,
+.admin-modern-body button.btn,
+.admin-modern-body .logout-btn,
+.admin-modern-body .user-btn {
+ background: linear-gradient(135deg, var(--admin-primary-btn-start) 0%, var(--admin-primary-btn-end) 100%);
+ color: #fff;
+ border: none;
+ border-radius: 12px;
+ padding: 10px 18px;
+ font-weight: 600;
+ box-shadow: var(--admin-primary-btn-shadow);
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ min-height: 42px;
+ margin: 0;
+}
+
+.admin-modern-body .btn:hover,
+.admin-modern-body button.btn:hover,
+.admin-modern-body .logout-btn:hover,
+.admin-modern-body .user-btn:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 18px 36px rgba(129, 140, 248, 0.45);
+}
+
+.admin-modern-body .btn.btn-danger,
+.admin-modern-body button.btn.btn-danger {
+ background: linear-gradient(135deg, rgba(248, 113, 113, 0.75), rgba(239, 68, 68, 0.65));
+ box-shadow: 0 12px 22px rgba(239, 68, 68, 0.25);
+}
+
+.admin-modern-body .btn.btn-secondary,
+.admin-modern-body button.btn.btn-secondary {
+ background: rgba(148, 163, 184, 0.22);
+ color: #1f2937;
+ box-shadow: 0 14px 22px rgba(148, 163, 184, 0.2);
+}
+
+.admin-modern-body .btn.btn-secondary:hover,
+.admin-modern-body button.btn.btn-secondary:hover {
+ background: rgba(148, 163, 184, 0.3);
+}
+
+.admin-modern-body table {
+ background: #ffffff;
+ color: var(--admin-text);
+ width: 100%;
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+
+.admin-modern-body thead {
+ background: var(--admin-interactive-bg);
+}
+
+.admin-modern-body tbody tr:hover {
+ background: var(--admin-interactive-bg);
+}
+
+/* Modal glass */
+.admin-modern-body .modal .modal-content {
+ background: #ffffff;
+ border: 1px solid var(--admin-border);
+ box-shadow: 0 30px 60px rgba(148, 163, 184, 0.25);
+ border-radius: 18px;
+}
+
+.admin-modern-body .modal .modal-header {
+ border-bottom-color: rgba(99, 102, 241, 0.12);
+}
+
+.admin-modern-body .modal .modal-title {
+ color: var(--admin-text);
+}
+
+.admin-modern-body .modal .modal-form label {
+ color: rgba(15, 23, 42, 0.75);
+}
+
+/* Scrollbar */
+.admin-modern-body ::-webkit-scrollbar {
+ width: 8px;
+}
+
+.admin-modern-body ::-webkit-scrollbar-track {
+ background: #e5e7ff;
+}
+
+.admin-modern-body ::-webkit-scrollbar-thumb {
+ background: linear-gradient(180deg, rgba(99, 102, 241, 0.6), rgba(129, 140, 248, 0.5));
+ border-radius: 999px;
+}
+
+.admin-modern-body ::selection {
+ background: rgba(129, 140, 248, 0.45);
+}
+
+/* Section refinements */
+.admin-modern-body .section-header {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ margin-bottom: 16px;
+}
+
+.admin-modern-body .section-header h3 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--admin-heading);
+}
+
+.admin-modern-body .section-header p {
+ margin: 0;
+ color: rgba(71, 85, 105, 0.68);
+}
+
+.admin-modern-body .section-icon {
+ width: 44px;
+ height: 44px;
+ border-radius: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.12), rgba(129, 140, 248, 0.1));
+ color: var(--admin-accent);
+ box-shadow: 0 8px 18px rgba(79, 70, 229, 0.18);
+}
+
+
+.admin-modern-body .config-section,
+.admin-modern-body .maintenance-card,
+.admin-modern-body .dashboard-actions,
+.admin-modern-body .dashboard-status,
+.admin-modern-body .quick-actions,
+.admin-modern-body .storage-container .config-section,
+.admin-modern-body .mcp-container .config-section,
+.admin-modern-body .maintenance-container {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ border-radius: 18px;
+ padding: 24px;
+ box-shadow: var(--admin-shadow-card);
+ margin-bottom: 28px;
+}
+
+.admin-modern-body .maintenance-card,
+.admin-modern-body .config-section {
+ padding: 24px;
+}
+
+.admin-modern-body .maintenance-card p {
+ color: rgba(226, 232, 240, 0.65);
+ margin: 0 0 16px;
+}
+
+.admin-modern-body .maintenance-card .btn {
+ justify-content: flex-start;
+ width: 100%;
+}
+
+.admin-modern-body .quick-action-buttons {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+
+.admin-modern-body .row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+}
+
+.admin-modern-body .row .col-6 {
+ flex: 1 1 calc(50% - 20px);
+ min-width: 260px;
+}
+
+.admin-modern-body .form-group {
+ margin-bottom: 18px;
+}
+
+.admin-modern-body .form-group small,
+.admin-modern-body .form-group .form-text {
+ display: block;
+ margin-top: 6px;
+}
+
+.admin-modern-body form label {
+ color: rgba(226, 232, 240, 0.75);
+ font-weight: 500;
+}
+
+.admin-modern-body .checkbox-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.admin-modern-body .checkbox-label input[type="checkbox"] {
+ accent-color: rgba(129, 140, 248, 0.85);
+ transform: scale(1.1);
+}
+
+.admin-modern-body .form-text i {
+ color: var(--admin-accent);
+ margin-right: 6px;
+}
+
+.admin-modern-body table {
+ border-collapse: collapse;
+}
+
+.admin-modern-body tbody tr td,
+.admin-modern-body thead tr th {
+ border-bottom: 1px solid rgba(148, 163, 184, 0.12);
+ padding: 12px 16px;
+}
+
+.admin-modern-body table thead th {
+ font-weight: 600;
+ color: rgba(226, 232, 240, 0.85);
+}
+
+/* User modal refinement */
+.admin-modern-body #user-modal .modal-content {
+ background: rgba(15, 23, 42, 0.9);
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ border-radius: 22px;
+ box-shadow: 0 30px 65px rgba(15, 23, 42, 0.65);
+ overflow: hidden;
+}
+
+.admin-modern-body #user-modal .modal-header {
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.85) 0%, rgba(129, 140, 248, 0.72) 100%);
+ padding: 26px 32px;
+}
+
+.admin-modern-body #user-modal .modal-header h3 {
+ color: var(--admin-heading);
+ font-size: 22px;
+}
+
+.admin-modern-body #user-modal .close {
+ background: rgba(15, 23, 42, 0.15);
+ color: var(--admin-heading);
+}
+
+.admin-modern-body #user-modal .modal-body {
+ background: transparent;
+}
+
+.admin-modern-body #user-modal .form-group label {
+ color: rgba(226, 232, 240, 0.85);
+}
+
+.admin-modern-body #user-modal .form-control {
+ background: var(--admin-overlay);
+ border: 1px solid rgba(148, 163, 184, 0.25);
+ box-shadow: none;
+}
+
+.admin-modern-body #user-modal .form-control:focus {
+ background: rgba(15, 23, 42, 0.65);
+ border-color: rgba(99, 102, 241, 0.5);
+}
+
+.admin-modern-body #user-modal .form-text {
+ color: var(--admin-form-hint);
+}
+.admin-modern-body #current-storage-display,
+.admin-modern-body #storage-actions,
+.admin-modern-body #mcp-status-display,
+.admin-modern-body #mcp-config-options,
+.admin-modern-body .quick-actions,
+.admin-modern-body .dashboard-actions,
+.admin-modern-body .dashboard-status {
+ background: #ffffff !important;
+ border: 1px solid var(--admin-border) !important;
+ border-radius: 18px !important;
+ box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
+}
+
+.admin-modern-body #storage-actions {
+ padding: 20px;
+}
+
+.admin-modern-body #storage-actions p {
+ color: var(--admin-icon-default);
+}
+
+.admin-modern-body #mcp-config-options {
+ margin-top: 25px !important;
+ padding: 24px !important;
+ border-left: none !important;
+}
+
+.admin-modern-body #mcp-config-options .form-control {
+ border: 1px solid rgba(148, 163, 184, 0.3) !important;
+ border-radius: 12px !important;
+ padding: 12px !important;
+}
+
+.admin-modern-body #mcp-config-options .form-control:focus {
+ border-color: rgba(99, 102, 241, 0.55) !important;
+}
+.admin-modern-body .placeholder-block {
+ text-align: center;
+ padding: 40px;
+ color: var(--admin-muted);
+ background: var(--admin-placeholder-bg);
+ border-radius: 14px;
+}
+
+.admin-modern-body .muted-text {
+ color: rgba(71, 85, 105, 0.65) !important;
+ margin: 0;
+}
+
+.admin-modern-body .guide-panel {
+ background: var(--admin-surface-subtle);
+ border-radius: 16px;
+ border: 1px solid rgba(99, 102, 241, 0.12);
+ padding: 24px;
+ box-shadow: var(--admin-shadow-soft);
+}
+
+.admin-modern-body .guide-panel h4 {
+ color: var(--admin-text);
+ margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.admin-modern-body .guide-panel h4 i {
+ color: var(--admin-accent);
+}
+
+.admin-modern-body .guide-callout {
+ background: var(--admin-surface);
+ border-radius: 12px;
+ padding: 16px;
+ border-left: 4px solid var(--admin-accent-soft);
+ margin-bottom: 18px;
+}
+
+.admin-modern-body .guide-callout p {
+ margin: 0;
+}
+
+.admin-modern-body .guide-callout p + p {
+ margin-top: 8px;
+ font-size: 13px;
+}
+
+.admin-modern-body .guide-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 14px;
+}
+
+.admin-modern-body .guide-card,
+.admin-modern-body .guide-grid > div {
+ background: var(--admin-surface) !important;
+ border-radius: 14px;
+ padding: 16px;
+ border-left: 4px solid var(--admin-accent-soft) !important;
+ color: var(--admin-muted);
+}
+
+.admin-modern-body .guide-card strong,
+.admin-modern-body .guide-grid > div strong {
+ display: block;
+ margin-bottom: 6px;
+ color: var(--admin-text) !important;
+}
+
+.admin-modern-body .guide-grid p {
+ margin: 4px 0 0;
+ font-size: 13px;
+ color: var(--admin-muted);
+}
+
+/* Tab metrics */
+.admin-modern-body .tab-metrics {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 16px;
+ margin-bottom: 24px;
+}
+
+.admin-modern-body .metric-card {
+ background: rgba(15, 23, 42, 0.58);
+ border: 1px solid rgba(148, 163, 184, 0.16);
+ border-radius: 18px;
+ padding: 18px;
+ box-shadow: 0 18px 45px rgba(15, 23, 42, 0.45);
+}
+
+.admin-modern-body .metric-label {
+ color: rgba(226, 232, 240, 0.72);
+ font-size: 13px;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ margin-bottom: 8px;
+}
+
+.admin-modern-body .metric-value {
+ font-size: 24px;
+ font-weight: 600;
+ color: var(--admin-heading);
+}
+
+/* Toolbar shell */
+.admin-modern-body .toolbar-shell,
+.admin-modern-body .user-toolbar,
+.admin-modern-body .files-toolbar {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ border-radius: 16px;
+ padding: 18px 20px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+ align-items: center;
+ justify-content: space-between;
+ box-shadow: var(--admin-shadow-card);
+ margin-bottom: 20px;
+}
+
+.admin-modern-body .toolbar-left,
+.admin-modern-body .toolbar-right,
+.admin-modern-body .user-toolbar .search-section,
+.admin-modern-body .user-toolbar .action-section {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.admin-modern-body .search-box,
+.admin-modern-body .files-search-box {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.admin-modern-body .search-input,
+.admin-modern-body .files-search-input {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ color: var(--admin-text);
+ border-radius: 12px;
+ padding: 10px 14px;
+ min-width: 240px;
+}
+
+.admin-modern-body .filter-row,
+.admin-modern-body .user-filters {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+ margin-bottom: 18px;
+}
+
+.admin-modern-body .filter-label {
+ font-size: 13px;
+ color: var(--admin-icon-default);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.admin-modern-body .filter-tag {
+ padding: 6px 12px;
+ border-radius: 999px;
+ background: var(--admin-toggle-bg);
+ color: var(--admin-text);
+ cursor: pointer;
+ font-size: 13px;
+ transition: background 0.2s ease, color 0.2s ease;
+}
+
+.admin-modern-body .filter-tag.active {
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.75), rgba(129, 140, 248, 0.65));
+ box-shadow: 0 12px 24px rgba(99, 102, 241, 0.35);
+}
+
+.admin-modern-body .bulk-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ border-radius: 12px;
+ background: var(--admin-surface-muted);
+ border: 1px solid var(--admin-border);
+ margin-bottom: 18px;
+}
+
+.admin-modern-body .bulk-info {
+ color: rgba(71, 85, 105, 0.75);
+}
+
+.admin-modern-body .bulk-buttons {
+ display: flex;
+ gap: 8px;
+}
+
+.admin-modern-body .view-toggle {
+ display: inline-flex;
+ background: var(--admin-interactive-bg);
+ border-radius: 999px;
+ border: 1px solid rgba(99, 102, 241, 0.15);
+ overflow: hidden;
+}
+
+.admin-modern-body .view-toggle-btn {
+ background: transparent;
+ border: none;
+ color: rgba(71, 85, 105, 0.8);
+ padding: 8px 16px;
+ font-size: 13px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.admin-modern-body .view-toggle-btn.active {
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.16), rgba(129, 140, 248, 0.12));
+ color: var(--admin-text);
+}
+
+.admin-modern-body .table-shell,
+.admin-modern-body .users-table-container,
+.admin-modern-body .files-table-container {
+ background: var(--admin-surface);
+ border: 1px solid var(--admin-border);
+ border-radius: 16px;
+ box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
+ overflow: hidden;
+}
+
+.admin-modern-body .users-table,
+.admin-modern-body .files-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.admin-modern-body .users-table thead,
+.admin-modern-body .files-table thead {
+ background: var(--admin-interactive-bg);
+}
+
+.admin-modern-body .users-table th,
+.admin-modern-body .files-table th,
+.admin-modern-body .users-table td,
+.admin-modern-body .files-table td {
+ padding: 12px 16px;
+ border-bottom: 1px solid rgba(148, 163, 184, 0.12);
+}
+
+.admin-modern-body .pagination-container {
+ background: rgba(15, 23, 42, 0.45);
+ border: 1px solid rgba(148, 163, 184, 0.14);
+ border-radius: 14px;
+ padding: 12px 18px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.admin-modern-body .pagination-info {
+ color: var(--admin-icon-default);
+}
+.admin-modern-body .user-toolbar .dropdown {
+ position: relative;
+}
+
+.admin-modern-body .user-toolbar .dropdown-menu {
+ background: rgba(15, 23, 42, 0.9);
+ border: 1px solid rgba(148, 163, 184, 0.16);
+ border-radius: 12px;
+ padding: 10px 0;
+ box-shadow: 0 20px 35px rgba(15, 23, 42, 0.45);
+}
+
+.admin-modern-body .user-toolbar .dropdown-menu a {
+ color: rgba(226, 232, 240, 0.8);
+}
+
+.admin-modern-body .form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 18px;
+}
+
+.admin-modern-body .form-grid.two-cols {
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+}
+
+.admin-modern-body .form-actions {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.admin-modern-body .form-actions .btn + .btn {
+ margin-left: 12px;
+}
+
+.admin-modern-body .form-grid .span-2 {
+ grid-column: span 2;
+}
+
+@media (max-width: 768px) {
+ .admin-modern-body .form-grid .span-2 {
+ grid-column: span 1;
+ }
+}
+
+.admin-modern-body .storage-actions {
+ margin-top: 20px;
+ text-align: center;
+}
+
+.admin-modern-body .storage-actions p {
+ text-align: center;
+}
+
+.admin-modern-body .placeholder-block i {
+ font-size: 24px;
+ margin-bottom: 10px;
+ color: var(--admin-accent);
+}
diff --git a/themes/2025/admin/index.html b/themes/2025/admin/index.html
index 86cc098..603bf36 100644
--- a/themes/2025/admin/index.html
+++ b/themes/2025/admin/index.html
@@ -19,74 +19,107 @@
+
-
-
-
-
-
+
-
-