From 250e5b32bb9648c16aa9521423b5fb675afbf51a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:31:52 +0000 Subject: [PATCH 1/8] chore(deps): bump github.com/gin-contrib/cors from 1.4.0 to 1.6.0 Bumps [github.com/gin-contrib/cors](https://github.com/gin-contrib/cors) from 1.4.0 to 1.6.0. - [Release notes](https://github.com/gin-contrib/cors/releases) - [Changelog](https://github.com/gin-contrib/cors/blob/master/.goreleaser.yaml) - [Commits](https://github.com/gin-contrib/cors/compare/v1.4.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/gin-contrib/cors dependency-version: 1.6.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 14 ++++++++++---- go.sum | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 7128acd..0a77ad0 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,7 +69,7 @@ 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 @@ -76,6 +80,7 @@ require ( 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 +88,13 @@ 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/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..d09d917 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= @@ -142,6 +155,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 +217,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 +237,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 +247,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= @@ -270,6 +294,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 +338,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 +354,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= From 0f5ca767bdcd75b9f337b6d0b0e63e91bc84b1c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:32:10 +0000 Subject: [PATCH 2/8] chore(deps): bump github.com/jackc/pgx/v5 from 5.2.0 to 5.5.4 Bumps [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) from 5.2.0 to 5.5.4. - [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md) - [Commits](https://github.com/jackc/pgx/compare/v5.2.0...v5.5.4) --- updated-dependencies: - dependency-name: github.com/jackc/pgx/v5 dependency-version: 5.5.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 4 +++- go.sum | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 7128acd..25c0c46 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,8 @@ require ( 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 @@ -86,6 +87,7 @@ require ( github.com/ugorji/go/codec v1.3.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 diff --git a/go.sum b/go.sum index 5f27fc0..679ca92 100644 --- a/go.sum +++ b/go.sum @@ -129,9 +129,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= @@ -255,6 +258,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= From 8515a1d5a1ab269396436e208acaf18e0ff63366 Mon Sep 17 00:00:00 2001 From: murphyyi Date: Mon, 22 Sep 2025 02:44:42 +0800 Subject: [PATCH 3/8] release: v1.7.1 --- Makefile | 6 +++--- VERSION | 1 + docs/docs.go | 7 +++++-- docs/swagger-enhanced.yaml | 4 ++-- docs/swagger.json | 6 +++--- docs/swagger.yaml | 4 ++-- internal/handlers/api.go | 10 +++------- internal/mcp/filecodebox.go | 2 +- internal/mcp/manager.go | 3 ++- internal/models/service/system.go | 2 +- main.go | 2 +- scripts/build.sh | 8 ++++---- scripts/cross-build.sh | 6 +++--- scripts/release.sh | 17 ++++++++++++++++- 14 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 VERSION 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..943f9cb --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.7.1 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..f555c50 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.7.1" 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.7.1" uptime: type: "string" example: "2h30m15s" diff --git a/docs/swagger.json b/docs/swagger.json index 31f1abd..8e31f36 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.7.1" }, "host": "localhost:12345", "basePath": "/", @@ -553,7 +553,7 @@ }, "version": { "type": "string", - "example": "1.0.0" + "example": "1.7.1" } } }, @@ -608,4 +608,4 @@ "type": "basic" } } -} \ No newline at end of file +} diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 23f7f74..40e3092 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.7.1 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.7.1" paths: /api/config: get: diff --git a/internal/handlers/api.go b/internal/handlers/api.go index 41dd616..d1c57ae 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.7.1"` 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..4fcb9bc 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.7.1" ) // BuildInfo 构建信息结构体 diff --git a/main.go b/main.go index 4e7d5b0..e087245 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main // @title FileCodeBox API -// @version 1.0 +// @version 1.7.1 // @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 From f3950643d12e9436ab1ba24276a3a586f414ef9e Mon Sep 17 00:00:00 2001 From: murphyyi Date: Mon, 22 Sep 2025 02:47:28 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.yaml b/config.yaml index aaf5860..875d5aa 100644 --- a/config.yaml +++ b/config.yaml @@ -2,7 +2,7 @@ base: name: FileCodeBox description: 开箱即用的文件快传系统 keywords: "" - port: 12346 + port: 12345 host: 0.0.0.0 datapath: /Users/zhangyi/zy/FileCodeBox/data production: false From 9a7ee0cf2d27171dcf7e5ec3f552f64582b42b7d Mon Sep 17 00:00:00 2001 From: murphyyi Date: Mon, 22 Sep 2025 02:53:57 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/utils/{disk.go => disk_unix.go} | 2 + internal/utils/disk_windows.go | 56 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+) rename internal/utils/{disk.go => disk_unix.go} (96%) create mode 100644 internal/utils/disk_windows.go 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 +} From 7edcf03311879f2511f10d281deec096eef42ee5 Mon Sep 17 00:00:00 2001 From: murphyyi Date: Mon, 22 Sep 2025 14:01:29 +0800 Subject: [PATCH 6/8] release: v1.7.1 --- config.yaml | 16 +- internal/config/manager.go | 69 +- internal/config/manager_test.go | 15 + internal/config/source.go | 143 ++ internal/handlers/admin.go | 3 + internal/models/web/admin.go | 4 + internal/routes/admin.go | 127 +- internal/services/admin/config.go | 43 +- internal/static/assets.go | 247 +++- themes/2025/admin/css/admin-modern.css | 1665 ++++++++++++++++++++++++ themes/2025/admin/index.html | 407 +++--- themes/2025/admin/js/config-simple.js | 88 +- themes/2025/admin/js/main.js | 233 +++- 13 files changed, 2621 insertions(+), 439 deletions(-) create mode 100644 internal/config/source.go create mode 100644 themes/2025/admin/css/admin-modern.css diff --git a/config.yaml b/config.yaml index 875d5aa..508eae0 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,7 @@ base: name: FileCodeBox description: 开箱即用的文件快传系统 - keywords: "" + keywords: FileCodeBox, 文件快递柜, 口令传送箱, 匿名口令分享文本, 文件 port: 12345 host: 0.0.0.0 datapath: /Users/zhangyi/zy/FileCodeBox/data @@ -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/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/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/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 @@ + - - - - - + - -
-
- -
- - + + + + + + +
+ + + + +
+
+ - -
- -
+
+
@@ -224,36 +257,36 @@

系统状态

-
-
-
-
-
总文件数
+
+
+
总文件数
+
-
-
-
-
-
总存储大小
+
+
总存储大小
+
-
-
-
-
-
今日上传
+
+
今日上传
+
-
-
-
-
-
公开文件
+
+
公开文件
+
-
-
-
-
diff --git a/themes/2025/admin/js/config-simple.js b/themes/2025/admin/js/config-simple.js index 90e3283..5280260 100644 --- a/themes/2025/admin/js/config-simple.js +++ b/themes/2025/admin/js/config-simple.js @@ -23,9 +23,9 @@ async function loadConfig() { try { const result = await apiRequest('/admin/config'); - + if (result.code === 200) { - const config = result.data; + const config = normalizeConfigResponse(result.data); fillConfigForm(config); safeShowAlert('配置加载成功', 'success'); } else { @@ -40,6 +40,50 @@ async function loadConfig() { } } +function normalizeConfigResponse(config) { + if (!config || typeof config !== 'object') { + return config; + } + + const clone = typeof structuredClone === 'function' + ? structuredClone(config) + : JSON.parse(JSON.stringify(config)); + + if (clone.database) { + const db = clone.database; + clone.database = { + ...db, + type: db.type ?? db.database_type ?? '', + host: db.host ?? db.database_host ?? '', + port: db.port ?? db.database_port ?? 0, + name: db.name ?? db.database_name ?? '', + user: db.user ?? db.database_user ?? '', + pass: db.pass ?? db.database_pass ?? '', + ssl: db.ssl ?? db.database_ssl ?? '' + }; + } + + if (clone.storage) { + const storage = clone.storage; + clone.storage = { + ...storage, + type: storage.type ?? storage.storage_type ?? storage.file_storage ?? '', + storage_path: storage.storage_path ?? storage.path ?? '', + path: storage.path ?? storage.storage_path ?? '' + }; + } + + if (clone.ui) { + const opacity = clone.ui.opacity; + if (opacity !== undefined && opacity !== null) { + const parsed = typeof opacity === 'string' ? parseFloat(opacity) : opacity; + clone.ui.opacity = Number.isNaN(parsed) ? 0 : parsed; + } + } + + return clone; +} + /** * 填充配置表单 */ @@ -73,7 +117,13 @@ function fillConfigForm(config) { // 管理员令牌字段已移除,不回显任何敏感字段 setFieldValue('notify_title', config.notify_title); setFieldValue('notify_content', config.notify_content); - setFieldValue('page_explain', config.page_explain); + + const uiConfig = config.ui || {}; + const resolvedPageExplain = uiConfig.page_explain ?? ''; + const resolvedOpacity = uiConfig.opacity; + const resolvedTheme = uiConfig.themes_select ?? 'themes/2025'; + + setFieldValue('page_explain', resolvedPageExplain); // 上传限制设置 setFieldValue('upload_size_mb', bytesToMB(config.transfer?.upload?.upload_size || 0)); @@ -86,8 +136,12 @@ function fillConfigForm(config) { setCheckboxValue('enable_concurrent_download', config.transfer?.download?.enable_concurrent_download); setFieldValue('max_concurrent_downloads', config.transfer?.download?.max_concurrent_downloads); setFieldValue('download_timeout', config.transfer?.download?.download_timeout); - setFieldValue('opacity', config.opacity); - setFieldValue('themes_select', config.themes_select); + if (resolvedOpacity !== undefined && resolvedOpacity !== null) { + setFieldValue('opacity', resolvedOpacity); + } else { + setFieldValue('opacity', ''); + } + setFieldValue('themes_select', resolvedTheme); // 用户系统设置 (始终启用) // config.user.allow_user_registration 可能为 0/1,setCheckboxValue 接受布尔化 @@ -152,6 +206,13 @@ async function handleConfigSubmit(e) { try { safeShowAlert('正在保存配置...', 'info'); + const opacityValue = getFieldValue('opacity', 'float'); + const uiConfig = { + page_explain: getFieldValue('page_explain'), + themes_select: getFieldValue('themes_select'), + opacity: opacityValue ?? 0 + }; + // 构建配置对象 const config = { base: { @@ -183,9 +244,7 @@ async function handleConfigSubmit(e) { }, notify_title: getFieldValue('notify_title'), notify_content: getFieldValue('notify_content'), - page_explain: getFieldValue('page_explain'), - opacity: getFieldValue('opacity', 'number'), - themes_select: getFieldValue('themes_select') + ui: uiConfig }; // 管理员令牌字段已移除,不会写入到配置中。 @@ -214,12 +273,17 @@ async function handleConfigSubmit(e) { */ function getFieldValue(fieldId, type = 'string') { const field = document.getElementById(fieldId); - if (!field) return type === 'number' ? 0 : ''; - + if (!field) return type === 'number' ? 0 : type === 'float' ? null : ''; + const value = field.value.trim(); if (type === 'number') { - const num = parseInt(value) || 0; - return num; + const num = parseInt(value, 10); + return Number.isNaN(num) ? 0 : num; + } + if (type === 'float') { + if (value === '') return null; + const num = parseFloat(value); + return Number.isNaN(num) ? null : num; } return value; } diff --git a/themes/2025/admin/js/main.js b/themes/2025/admin/js/main.js index 02f818a..cf0955a 100644 --- a/themes/2025/admin/js/main.js +++ b/themes/2025/admin/js/main.js @@ -6,7 +6,7 @@ * 切换标签页 - 立即可用版本 * @param {string} tabName - 标签页名称 */ -function switchTab(tabName) { +function switchTab(tabName, trigger) { try { // 如果未认证,显示登录提示 const authToken = localStorage.getItem('user_token'); @@ -21,11 +21,19 @@ function switchTab(tabName) { }); // 找到被点击的按钮并激活 - const clickedBtn = event ? event.target : document.querySelector(`.tab-btn[onclick*="${tabName}"]`); + const fallbackBtn = document.querySelector(`.tab-btn[data-tab="${tabName}"]`); + const clickedBtn = trigger + || (typeof event !== 'undefined' && event + ? (event.currentTarget || (event.target && event.target.closest('.tab-btn'))) + : null) + || fallbackBtn; if (clickedBtn) { clickedBtn.classList.add('active'); + updateHeadlineFromNav(clickedBtn); + } else { + updateHeadlineFromNav(fallbackBtn); } - + // 隐藏登录提示 const loginPrompt = document.getElementById('login-prompt'); if (loginPrompt) { @@ -41,12 +49,18 @@ function switchTab(tabName) { if (targetTab) { targetTab.classList.add('active'); } - + + AppState.currentTab = tabName; + // 根据标签页加载相应数据 if (typeof loadTabData === 'function') { loadTabData(tabName); } - + + if (window.innerWidth <= 1024) { + closeMobileMenu(); + } + console.log(`Switched to tab: ${tabName}`); } catch (error) { console.error('Failed to switch tab:', error); @@ -77,6 +91,31 @@ function showLoginPrompt() { } } +function updateHeadlineFromNav(btn) { + if (!btn) { + return; + } + + const title = btn.dataset.title || btn.textContent.trim(); + const subtitle = btn.dataset.subtitle || ''; + const headline = document.querySelector('.admin-header .headline h1'); + const sub = document.querySelector('.admin-header .headline p'); + + if (headline && title) { + headline.textContent = title; + } + + if (sub) { + if (subtitle) { + sub.textContent = subtitle; + sub.style.display = ''; + } else { + sub.textContent = ''; + sub.style.display = 'none'; + } + } +} + // ========== 应用状态管理 ========== // 全局状态管理 @@ -95,6 +134,114 @@ let authToken = localStorage.getItem('user_token'); // 使用统一的user_token let currentStorageType = 'local'; let storageData = {}; +const ADMIN_THEME_KEY = 'filecodebox_admin_theme'; +const THEMES = { + LIGHT: 'light', + DARK: 'dark' +}; + +function applyTheme(theme) { + const body = document.body; + if (!body || !body.classList || !body.classList.contains('admin-modern-body')) { + return; + } + + const nextTheme = theme === THEMES.DARK ? THEMES.DARK : THEMES.LIGHT; + body.classList.remove('admin-theme-dark', 'admin-theme-light'); + body.classList.add(nextTheme === THEMES.DARK ? 'admin-theme-dark' : 'admin-theme-light'); + updateThemeToggleButton(nextTheme); +} + +function updateThemeToggleButton(theme) { + const btn = document.getElementById('theme-toggle-btn'); + if (!btn) { + return; + } + const icon = btn.querySelector('i'); + const label = btn.querySelector('span'); + if (theme === THEMES.DARK) { + btn.setAttribute('aria-label', '切换到浅色主题'); + btn.setAttribute('title', '切换到浅色主题'); + if (icon) { + icon.classList.remove('fa-moon'); + icon.classList.add('fa-sun'); + } + if (label) { + label.textContent = '浅色'; + } + } else { + btn.setAttribute('aria-label', '切换到深色主题'); + btn.setAttribute('title', '切换到深色主题'); + if (icon) { + icon.classList.remove('fa-sun'); + icon.classList.add('fa-moon'); + } + if (label) { + label.textContent = '深色'; + } + } +} + +function getStoredTheme() { + try { + return localStorage.getItem(ADMIN_THEME_KEY); + } catch (error) { + console.warn('无法读取主题偏好:', error); + return null; + } +} + +function storeTheme(theme) { + try { + localStorage.setItem(ADMIN_THEME_KEY, theme); + } catch (error) { + console.warn('无法保存主题偏好:', error); + } +} + +function initTheme() { + const stored = getStoredTheme(); + if (stored === THEMES.DARK || stored === THEMES.LIGHT) { + applyTheme(stored); + return stored; + } + const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + const initial = prefersDark ? THEMES.DARK : THEMES.LIGHT; + applyTheme(initial); + return initial; +} + +function handleSystemThemeChange(event) { + if (getStoredTheme()) { + return; + } + applyTheme(event.matches ? THEMES.DARK : THEMES.LIGHT); +} + +function setupSystemThemeSync() { + if (!window.matchMedia) { + return; + } + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = handleSystemThemeChange; + if (typeof mediaQuery.addEventListener === 'function') { + mediaQuery.addEventListener('change', handler); + } else if (typeof mediaQuery.addListener === 'function') { + mediaQuery.addListener(handler); + } +} + +function toggleTheme() { + const body = document.body; + if (!body) { + return; + } + const isDark = body.classList.contains('admin-theme-dark'); + const next = isDark ? THEMES.LIGHT : THEMES.DARK; + applyTheme(next); + storeTheme(next); +} + /** * 应用程序初始化 */ @@ -332,7 +479,8 @@ async function showAdminPage() { console.log('Showing admin page...'); // 默认显示dashboard标签 - switchTab('dashboard'); + const defaultNav = document.querySelector('.tab-btn[data-tab="dashboard"]'); + switchTab('dashboard', defaultNav); // 异步加载仪表板数据(不阻塞页面显示) try { @@ -452,11 +600,25 @@ async function loadStats() { const dashboardTodayUploadsEl = document.getElementById('dashboard-today-uploads'); const dashboardActiveUsersEl = document.getElementById('dashboard-active-users'); const dashboardTotalStorageEl = document.getElementById('dashboard-total-storage'); - + if (dashboardTotalFilesEl) dashboardTotalFilesEl.textContent = stats.total_files || 0; if (dashboardTodayUploadsEl) dashboardTodayUploadsEl.textContent = stats.today_uploads || 0; if (dashboardActiveUsersEl) dashboardActiveUsersEl.textContent = stats.active_files || 0; // 临时使用active_files作为活跃用户数 if (dashboardTotalStorageEl) dashboardTotalStorageEl.textContent = formatFileSize(stats.total_size || 0); + + const chipTodayUploadsEl = document.getElementById('chip-today-uploads'); + const chipTotalFilesEl = document.getElementById('chip-total-files'); + const chipTotalStorageEl = document.getElementById('chip-total-storage'); + + if (chipTodayUploadsEl) { + chipTodayUploadsEl.textContent = stats.today_uploads !== undefined ? stats.today_uploads : '-'; + } + if (chipTotalFilesEl) { + chipTotalFilesEl.textContent = stats.total_files !== undefined ? stats.total_files : '-'; + } + if (chipTotalStorageEl) { + chipTotalStorageEl.textContent = formatFileSize(stats.total_size || 0); + } // 更新趋势百分比(如果后端提供) const filesTrendEl = document.getElementById('files-trend'); @@ -713,51 +875,57 @@ function formatDateTimeLocal(dateString) { // 移动端菜单切换 function toggleMobileMenu() { - const tabHeader = document.querySelector('.tab-header'); - const overlay = document.querySelector('.mobile-menu-overlay'); - - if (tabHeader) { - tabHeader.classList.toggle('mobile-active'); - - // 如果没有遮罩层,创建一个 - if (!overlay && tabHeader.classList.contains('mobile-active')) { - const newOverlay = document.createElement('div'); - newOverlay.className = 'mobile-menu-overlay'; - newOverlay.onclick = closeMobileMenu; - document.body.appendChild(newOverlay); - } else if (overlay && !tabHeader.classList.contains('mobile-active')) { - overlay.remove(); - } + const sidebar = document.querySelector('.admin-sidebar'); + const overlay = document.getElementById('sidebarOverlay'); + + if (!sidebar) { + return; + } + + const isOpening = !sidebar.classList.contains('sidebar-open'); + sidebar.classList.toggle('sidebar-open', isOpening); + + if (overlay) { + overlay.classList.toggle('active', isOpening); + overlay.onclick = isOpening ? closeMobileMenu : null; } } // 关闭移动端菜单 function closeMobileMenu() { - const tabHeader = document.querySelector('.tab-header'); - const overlay = document.querySelector('.mobile-menu-overlay'); - - if (tabHeader) { - tabHeader.classList.remove('mobile-active'); + const sidebar = document.querySelector('.admin-sidebar'); + const overlay = document.getElementById('sidebarOverlay'); + + if (sidebar) { + sidebar.classList.remove('sidebar-open'); } - + if (overlay) { - overlay.remove(); + overlay.classList.remove('active'); + overlay.onclick = null; } } // DOM 加载完成后初始化应用程序 document.addEventListener('DOMContentLoaded', () => { console.log('DOM loaded, initializing app...'); + initTheme(); + setupSystemThemeSync(); initApp(); - + // 点击标签页项目时自动关闭移动端菜单 - document.querySelectorAll('.tab-item').forEach(item => { + document.querySelectorAll('.admin-sidebar .tab-btn').forEach(item => { item.addEventListener('click', () => { - if (window.innerWidth <= 768) { + if (window.innerWidth <= 1024) { closeMobileMenu(); } }); }); + + const activeBtn = document.querySelector('.admin-sidebar .tab-btn.active'); + if (activeBtn) { + updateHeadlineFromNav(activeBtn); + } }); // 将关键函数和变量暴露到全局作用域 @@ -771,4 +939,5 @@ window.toggleMobileMenu = toggleMobileMenu; window.closeMobileMenu = closeMobileMenu; window.redirectToUserLogin = redirectToUserLogin; window.showLoginPrompt = showLoginPrompt; +window.toggleTheme = toggleTheme; window.authToken = authToken; From 9b2eeea0f65c172cd02806f2801d7421a148b6f0 Mon Sep 17 00:00:00 2001 From: murphyyi Date: Mon, 22 Sep 2025 14:04:59 +0800 Subject: [PATCH 7/8] release: v1.8.2 --- VERSION | 2 +- docs/swagger-enhanced.yaml | 4 ++-- docs/swagger.json | 4 ++-- docs/swagger.yaml | 4 ++-- internal/handlers/api.go | 2 +- internal/models/service/system.go | 2 +- main.go | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/VERSION b/VERSION index 943f9cb..53adb84 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.1 +1.8.2 diff --git a/docs/swagger-enhanced.yaml b/docs/swagger-enhanced.yaml index f555c50..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.7.1" + 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.7.1" + example: "1.8.2" uptime: type: "string" example: "2h30m15s" diff --git a/docs/swagger.json b/docs/swagger.json index 8e31f36..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.7.1" + "version": "1.8.2" }, "host": "localhost:12345", "basePath": "/", @@ -553,7 +553,7 @@ }, "version": { "type": "string", - "example": "1.7.1" + "example": "1.8.2" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 40e3092..76a5289 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -12,7 +12,7 @@ definitions: example: 2h30m15s type: string version: - example: 1.7.1 + 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.7.1" + version: "1.8.2" paths: /api/config: get: diff --git a/internal/handlers/api.go b/internal/handlers/api.go index d1c57ae..c504726 100644 --- a/internal/handlers/api.go +++ b/internal/handlers/api.go @@ -29,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.7.1"` + Version string `json:"version" example:"1.8.2"` Uptime string `json:"uptime" example:"2h30m15s"` } diff --git a/internal/models/service/system.go b/internal/models/service/system.go index 4fcb9bc..c9155ba 100644 --- a/internal/models/service/system.go +++ b/internal/models/service/system.go @@ -19,7 +19,7 @@ var ( GitBranch = "unknown" // Version 应用版本号 - Version = "1.7.1" + Version = "1.8.2" ) // BuildInfo 构建信息结构体 diff --git a/main.go b/main.go index e087245..b0db491 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main // @title FileCodeBox API -// @version 1.7.1 +// @version 1.8.2 // @description FileCodeBox 是一个用于文件分享和代码片段管理的 Web 应用程序 // @termsOfService http://swagger.io/terms/ From def22f978966c718e1c0325cc98e9ef912f7dfc9 Mon Sep 17 00:00:00 2001 From: murphyyi Date: Mon, 22 Sep 2025 14:23:12 +0800 Subject: [PATCH 8/8] docs(release): add release notes for v1.8.2 --- docs/RELEASE_NOTES/v1.8.2.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/RELEASE_NOTES/v1.8.2.md 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`)未更改。