mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
161 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c77144f07 | |||
| 94fe6753da | |||
| fe67fccde3 | |||
| 6b733fc2af | |||
| 44f243e94f | |||
| 756834a59a | |||
| f024381660 | |||
| 3098d9fb64 | |||
| fa71b6f4ef | |||
| 39b5f10012 | |||
| 79fa002bd6 | |||
| 00a5acbc45 | |||
| 65e2750102 | |||
| 93e013ec56 | |||
| 07e608a3c0 | |||
| a255e9e1d8 | |||
| f5ad988576 | |||
| 2e18c25054 | |||
| a98142ea93 | |||
| 4d8f6fad9f | |||
| 67a7a41469 | |||
| afae33c69a | |||
| 62c5271c37 | |||
| ac116a7680 | |||
| c1092576b2 | |||
| fea51108d2 | |||
| 69dae9be95 | |||
| e7b2323c70 | |||
| 371f80451b | |||
| 6e3b35b3db | |||
| 2798d7fb5d | |||
| f3696332af | |||
| a510b81cf5 | |||
| 4b675c587f | |||
| 3efc9e2f66 | |||
| 3715e6db5e | |||
| 29247e6944 | |||
| 6303ac943a | |||
| 8a89a40bd3 | |||
| 3589bec397 | |||
| c2a8200817 | |||
| 4a22299111 | |||
| 54354c369a | |||
| 0181dadc05 | |||
| 30b593f90a | |||
| c22d6a430d | |||
| 08b09d58e8 | |||
| 3d3188630e | |||
| f5c23b8a2a | |||
| 78718db0a5 | |||
| 072cbb0132 | |||
| c3732477be | |||
| 03d617fea1 | |||
| 39f0d2564c | |||
| c66cdfbc1f | |||
| 97332f9650 | |||
| be7aea4a68 | |||
| a71b4009fb | |||
| ef58fcd6eb | |||
| e8d19a8171 | |||
| d2ba8190ca | |||
| 31bd29ae7a | |||
| 003feef87c | |||
| 0328215564 | |||
| 3e0c1dd574 | |||
| c06f7a3629 | |||
| 14790a1551 | |||
| 21593ca29d | |||
| 1a8bd4e400 | |||
| 865f12c75b | |||
| 8c28ac19ec | |||
| ee15bfa67c | |||
| c04fdfc7aa | |||
| ded7909fdb | |||
| b5083dd8fa | |||
| c7e078b7f8 | |||
| 3a937db506 | |||
| f92c661f33 | |||
| 8c29bba051 | |||
| 40e6f8e08f | |||
| 7d0a6ced8c | |||
| 104cbc00f1 | |||
| 75c4f44262 | |||
| ef44ab8f0d | |||
| 568ce6b270 | |||
| 20df3c5f81 | |||
| 8e3999ccbe | |||
| b4efbe823f | |||
| ca218b31ec | |||
| e1823854bd | |||
| 28bd1809cc | |||
| 120baf3bd7 | |||
| cbabd963df | |||
| 6a4f91e6cc | |||
| 8792d9b5cf | |||
| e667923c51 | |||
| c2d01c4371 | |||
| 1b713c8f32 | |||
| 561db6a861 | |||
| 9f3f0c8876 | |||
| a06af8cd12 | |||
| a2c2797942 | |||
| c39f3e29b8 | |||
| 19f3d80717 | |||
| b5bdcaded6 | |||
| 419cf50a37 | |||
| b8311af661 | |||
| c8805779ed | |||
| 5a156a84cb | |||
| 10415d3ac9 | |||
| 16025c080b | |||
| f7fcd0ca8a | |||
| 220d86348c | |||
| 215a8cf4d6 | |||
| 3df31d8e7f | |||
| 430974254e | |||
| 5884d1aeed | |||
| 61769323a6 | |||
| 3e5779bd1c | |||
| d70688503b | |||
| 85cd8ef2bb | |||
| 5fd4bf2270 | |||
| 0a162cbf57 | |||
| a19bf8f4bf | |||
| 03127e7f2a | |||
| 1a58daa007 | |||
| bac3dcc86d | |||
| 4739f6ea78 | |||
| 5077e087d5 | |||
| ef9789a6b2 | |||
| 24af0f21b1 | |||
| f115e6f93c | |||
| 4b8779bfb4 | |||
| 4407d4aec2 | |||
| 5ef6074870 | |||
| 2427e90591 | |||
| 292f22a1c2 | |||
| 7439cf489e | |||
| 471570db77 | |||
| bd24e6de20 | |||
| c81c13be1a | |||
| 0bb3aea1df | |||
| 8f144e3358 | |||
| fc74228e7e | |||
| d1eec7f7d6 | |||
| 05e6a10a4e | |||
| db7e342a23 | |||
| 5495a17748 | |||
| d152f4c59f | |||
| 823635c4d8 | |||
| aa6c1fb47e | |||
| bcff04bc36 | |||
| 8aaf4e5c78 | |||
| c47ba0eac9 | |||
| 9db21de85b | |||
| a0a98d7c4e | |||
| ae6a43a780 | |||
| 6e87b0f1b3 | |||
| e76b2ee7a9 | |||
| 37a883d0bc | |||
| ac48313948 |
@@ -11,11 +11,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: true
|
||||
run_install: |
|
||||
- cwd: web
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
@@ -44,7 +45,7 @@ jobs:
|
||||
fail-fast: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -21,7 +21,8 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: true
|
||||
run_install: |
|
||||
- cwd: web
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
@@ -12,11 +12,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: true
|
||||
run_install: |
|
||||
- cwd: web
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -42,11 +42,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: true
|
||||
run_install: |
|
||||
- cwd: web
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Run tests
|
||||
run: sudo go test -v -coverprofile="coverage.out" ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v6
|
||||
uses: codecov/codecov-action@v7
|
||||
with:
|
||||
disable_search: true
|
||||
files: coverage.out
|
||||
|
||||
@@ -12,7 +12,6 @@ func main() {
|
||||
}
|
||||
|
||||
debug.SetGCPercent(10)
|
||||
debug.SetMemoryLimit(256 << 20)
|
||||
|
||||
ace, err := initAce()
|
||||
if err != nil {
|
||||
|
||||
+6
-6
@@ -95,7 +95,7 @@ func initAce() (*app.Ace, error) {
|
||||
cronRepo := data.NewCronRepo(locale, db, logger)
|
||||
backupRepo := data.NewBackupRepo(locale, config, db, logger, settingRepo, websiteRepo)
|
||||
containerRepo := data.NewContainerRepo(settingRepo)
|
||||
homeService := service.NewHomeService(locale, config, taskRepo, websiteRepo, projectRepo, appRepo, environmentRepo, settingRepo, cronRepo, backupRepo, containerRepo)
|
||||
homeService := service.NewHomeService(locale, config, taskRepo, websiteRepo, projectRepo, appRepo, environmentRepo, settingRepo, databaseServerRepo, cronRepo, backupRepo, containerRepo)
|
||||
taskService := service.NewTaskService(taskRepo)
|
||||
websiteService := service.NewWebsiteService(websiteRepo, settingRepo)
|
||||
projectService := service.NewProjectService(projectRepo, settingRepo)
|
||||
@@ -106,7 +106,7 @@ func initAce() (*app.Ace, error) {
|
||||
databaseElasticsearchRepo := data.NewDatabaseElasticsearchRepo(locale, db, logger)
|
||||
databaseElasticsearchService := service.NewDatabaseElasticsearchService(databaseElasticsearchRepo)
|
||||
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
|
||||
backupService := service.NewBackupService(locale, backupRepo)
|
||||
backupService := service.NewBackupService(locale, backupRepo, taskRepo)
|
||||
backupAccountRepo := data.NewBackupAccountRepo(locale, db, logger, settingRepo)
|
||||
backupStorageService := service.NewBackupStorageService(locale, backupAccountRepo)
|
||||
certService := service.NewCertService(locale, certRepo)
|
||||
@@ -173,7 +173,7 @@ func initAce() (*app.Ace, error) {
|
||||
containerImageService := service.NewContainerImageService(containerImageRepo)
|
||||
containerVolumeRepo := data.NewContainerVolumeRepo(settingRepo)
|
||||
containerVolumeService := service.NewContainerVolumeService(containerVolumeRepo)
|
||||
fileService := service.NewFileService(locale, taskRepo)
|
||||
fileService := service.NewFileService(locale, taskRepo, containerRepo)
|
||||
logRepo := data.NewLogRepo(db)
|
||||
logService := service.NewLogService(locale, logRepo)
|
||||
monitorRepo := data.NewMonitorRepo(db, settingRepo)
|
||||
@@ -198,7 +198,7 @@ func initAce() (*app.Ace, error) {
|
||||
aggregator := websitestat.NewAggregator()
|
||||
websiteStatService := service.NewWebsiteStatService(settingRepo, websiteStatRepo, websiteRepo, aggregator)
|
||||
http := route.NewHttp(config, userService, userPasskeyService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseRedisService, databaseElasticsearchService, databaseUserService, backupService, backupStorageService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, environmentDotnetService, cronService, processService, safeService, firewallService, firewallScanService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxNetworkService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, toolboxMigrationService, webHookService, templateService, websiteStatService, loader)
|
||||
wsService := service.NewWsService(locale, config, logger, sshRepo, settingRepo, certRepo)
|
||||
wsService := service.NewWsService(locale, config, logger, sshRepo, settingRepo, certRepo, backupRepo, taskRepo)
|
||||
ws := route.NewWs(wsService, toolboxMigrationService)
|
||||
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)
|
||||
if err != nil {
|
||||
@@ -218,7 +218,7 @@ func initAce() (*app.Ace, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validation := bootstrap.NewValidator(config, db)
|
||||
ace := app.NewAce(config, mux, server, reloader, gormigrate, cron, taskRunner, validation)
|
||||
validator := service.NewValidator(config, db)
|
||||
ace := app.NewAce(config, mux, server, reloader, gormigrate, cron, taskRunner, validator)
|
||||
return ace, nil
|
||||
}
|
||||
|
||||
@@ -3,27 +3,26 @@ module github.com/acepanel/panel/v3
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/DeRuina/timberjack v1.4.2
|
||||
github.com/andybalholm/brotli v1.2.1
|
||||
github.com/bddjr/hlfhr v1.5.1
|
||||
github.com/DeRuina/timberjack v1.4.5
|
||||
github.com/andybalholm/brotli v1.2.2
|
||||
github.com/bddjr/hlfhr v1.6.1
|
||||
github.com/beevik/ntp v1.5.0
|
||||
github.com/cilium/ebpf v0.21.0
|
||||
github.com/coder/websocket v1.8.14
|
||||
github.com/cilium/ebpf v0.22.0
|
||||
github.com/coder/websocket v1.8.15
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/coreos/go-systemd/v22 v22.7.0
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/dchest/captcha v1.1.0
|
||||
github.com/expr-lang/expr v1.17.8
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/httplog/v3 v3.3.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5
|
||||
github.com/go-chi/chi/v5 v5.3.0
|
||||
github.com/go-chi/httplog/v3 v3.4.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.6
|
||||
github.com/go-sql-driver/mysql v1.10.0
|
||||
github.com/go-webauthn/webauthn v0.17.2
|
||||
github.com/go-webauthn/webauthn v0.17.4
|
||||
github.com/gomodule/redigo v1.9.3
|
||||
github.com/google/wire v0.7.0
|
||||
github.com/gookit/color v1.6.1
|
||||
github.com/gookit/validate v1.5.7
|
||||
github.com/hashicorp/go-version v1.9.0
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/lib/pq v1.12.3
|
||||
@@ -39,60 +38,63 @@ require (
|
||||
github.com/libdns/westcn v1.0.2
|
||||
github.com/libtnb/cache v1.2.0
|
||||
github.com/libtnb/chix v1.3.2
|
||||
github.com/libtnb/cron v0.2.0
|
||||
github.com/libtnb/gormstore v1.2.0
|
||||
github.com/libtnb/sessions v1.4.0
|
||||
github.com/libtnb/sqlite v1.1.1
|
||||
github.com/libtnb/utils v1.2.1
|
||||
github.com/libtnb/cron v0.2.2
|
||||
github.com/libtnb/gormstore v1.2.1
|
||||
github.com/libtnb/sessions v1.4.2
|
||||
github.com/libtnb/sqlite v1.1.2
|
||||
github.com/libtnb/utils v1.2.2
|
||||
github.com/libtnb/validator v0.1.1
|
||||
github.com/medama-io/go-useragent v1.2.3
|
||||
github.com/mholt/acmez/v3 v3.1.6
|
||||
github.com/moby/moby/api v1.54.2
|
||||
github.com/moby/moby/client v0.4.1
|
||||
github.com/moby/moby/api v1.55.0
|
||||
github.com/moby/moby/client v0.5.0
|
||||
github.com/orandin/slog-gorm v1.4.0
|
||||
github.com/pkg/sftp v1.13.10
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/rhnvrm/simples3 v0.11.1
|
||||
github.com/samber/lo v1.53.0
|
||||
github.com/sethvargo/go-limiter v1.1.0
|
||||
github.com/shirou/gopsutil/v4 v4.26.4
|
||||
github.com/shirou/gopsutil/v4 v4.26.6
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/studio-b12/gowebdav v0.12.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20260220081509-8e17ce617db3
|
||||
github.com/urfave/cli/v3 v3.8.0
|
||||
github.com/urfave/cli/v3 v3.10.1
|
||||
github.com/valyala/fastjson v1.6.10
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/net v0.53.0
|
||||
gorm.io/gorm v1.31.1
|
||||
modernc.org/sqlite v1.50.0
|
||||
resty.dev/v3 v3.0.0-beta.6
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.6
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/sync v0.21.0
|
||||
gorm.io/gorm v1.31.2
|
||||
modernc.org/sqlite v1.53.0
|
||||
resty.dev/v3 v3.0.0-rc.2
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.3.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/bddjr/shuttingdown v0.1.0 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/boyter/go-string v1.0.5 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.7.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.3 // indirect
|
||||
github.com/gofiber/schema v1.7.1 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.6 // indirect
|
||||
github.com/gofiber/schema v1.7.2 // indirect
|
||||
github.com/gofiber/utils/v2 v2.1.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gookit/filter v1.2.3 // indirect
|
||||
github.com/gookit/goutil v0.7.4 // indirect
|
||||
github.com/imega/luaformatter v0.0.0-20211025140405-86b0a68d6bef // indirect
|
||||
github.com/jaevor/go-nanoid v1.4.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
@@ -109,7 +111,6 @@ require (
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/timtadh/data-structures v0.6.2 // indirect
|
||||
github.com/timtadh/lexmachine v0.2.3 // indirect
|
||||
@@ -119,19 +120,21 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
modernc.org/libc v1.73.4 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/mholt/acmez/v3 => github.com/libtnb/acmez/v3 v3.0.0-20260406165834-a264acd02292
|
||||
github.com/moby/moby/client => github.com/libtnb/moby/client v0.0.0-20260127104057-d4b7902a560b
|
||||
github.com/rhnvrm/simples3 => github.com/devhaozi/simples3 v0.0.0-20260124160558-447c94ecedff
|
||||
github.com/stretchr/testify => github.com/libtnb/testify v0.0.0-20260406170114-25da2dad39e7
|
||||
)
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DeRuina/timberjack v1.4.2 h1:4bKlzhKdsR+2oNkgef9mqb4n11ICow8VK88RfzJPzN8=
|
||||
github.com/DeRuina/timberjack v1.4.2/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||
github.com/DeRuina/timberjack v1.4.5 h1:F/kms5MPNAXUeWdOILt5ALC6iDHWNRPevaeIVH7tqYU=
|
||||
github.com/DeRuina/timberjack v1.4.5/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.3.3 h1:McILJSbJ5nOcT0MI0aBYhEuufCF329YbqKwFIN0RjCI=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.3.3/go.mod h1:35t795gOfzfVanhzkFyUXEzaBuMXwETmJldPpP28MN4=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
@@ -26,13 +26,15 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM=
|
||||
github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/bddjr/hlfhr v1.5.1 h1:FxphatB9VSv8AV/50jwXX3QR12IJFD7fv2P99NymXqs=
|
||||
github.com/bddjr/hlfhr v1.5.1/go.mod h1:YmFjslYd0ulNO0CtykhkhRUXJpfdq9sPVHQ4G67D610=
|
||||
github.com/bddjr/hlfhr v1.6.1 h1:bzl0zsL2pl7Gvlbx6l8jZjjv9GbBTeOaX4/+RaXyNXc=
|
||||
github.com/bddjr/hlfhr v1.6.1/go.mod h1:cHRve//dit7d8ZCPUUFZAYW5WCm3LdMoiqX9i9GJdms=
|
||||
github.com/bddjr/shuttingdown v0.1.0 h1:1thUjnzTXNbzexEFkh638ebjln8F4KlqXfLIlBXfdbs=
|
||||
github.com/bddjr/shuttingdown v0.1.0/go.mod h1:+vG4Zp8uhFLT40DBABiA/XxJDHbzybe8RR72i29DFI4=
|
||||
github.com/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=
|
||||
github.com/beevik/ntp v1.5.0/go.mod h1:mJEhBrwT76w9D+IfOEGvuzyuudiW9E52U2BaTrMOYow=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -45,11 +47,11 @@ github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
|
||||
github.com/boyter/go-string v1.0.5 h1:/xcOlWdgelLYLVkUU0xBLfioGjZ9KIMUMI/RXG138YY=
|
||||
github.com/boyter/go-string v1.0.5/go.mod h1:Mww9cDld2S2cdJ0tQffBhsZFMQRA2OJdcjWYZXvZ4Ss=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cilium/ebpf v0.21.0 h1:4dpx1J/B/1apeTmWBH5BkVLayHTkFrMovVPnHEk+l3k=
|
||||
github.com/cilium/ebpf v0.21.0/go.mod h1:1kHKv6Kvh5a6TePP5vvvoMa1bclRyzUXELSs272fmIQ=
|
||||
github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY=
|
||||
github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
||||
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
@@ -67,14 +69,12 @@ github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfv
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v1.1.0 h1:2kt47EoYUUkaISobUdTbqwx55xvKOJxyScVfw25xzhQ=
|
||||
github.com/dchest/captcha v1.1.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
|
||||
github.com/devhaozi/simples3 v0.0.0-20260124160558-447c94ecedff h1:R8fAbRKWR+NVWIkjdK45CXS2cZzXQn8QuoM0Dij2Jso=
|
||||
github.com/devhaozi/simples3 v0.0.0-20260124160558-447c94ecedff/go.mod h1:c2xW30bukipkBlWNnXG1wDjq3gykQ6ww2AB/9NHMLMY=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
@@ -84,6 +84,8 @@ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLA
|
||||
github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM=
|
||||
github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
@@ -91,23 +93,28 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/httplog/v3 v3.3.0 h1:Gr6Y7nSzbpyCyRwKPOVKjDH3BH6TH5uvRNDsTZWDpvU=
|
||||
github.com/go-chi/httplog/v3 v3.3.0/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w=
|
||||
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
|
||||
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
github.com/go-chi/httplog/v3 v3.4.0 h1:gO4fvt8HEtFwHq926HoKe1aV2DymfPJuZy4+U4zwT3I=
|
||||
github.com/go-chi/httplog/v3 v3.4.0/go.mod h1:tDhJo9G+F4mioDgX4pKbyA0uVZwCtHejoSsDkvJkFkU=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5 h1:1OyorA5LtdQw12cyJDEHuTrEV3GiXiIhS4/QTTa/SM8=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v7fN3D9d3eEOAXRhi/+M=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.6 h1:VtX+l1Stj2v5RGubVQk0LS/8EPGXR+ldcOyCmlmKoyg=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.6/go.mod h1:PZpedQc4tWaxn6kvXicwhinh3L0seLpMc5ReKRX5id4=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
@@ -118,14 +125,14 @@ github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-webauthn/webauthn v0.17.2 h1:e9YtSZTVnxnMWFezXi6JvnqOSxmH4Er8QDHK2a/mM40=
|
||||
github.com/go-webauthn/webauthn v0.17.2/go.mod h1:mQC6L0lZ5Kiu35G70zeB2WnrW4+vbHjR8Koq4HdVaMg=
|
||||
github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA=
|
||||
github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk=
|
||||
github.com/gofiber/schema v1.7.1 h1:oSJBKdgP8JeIME4TQSAqlNKTU2iBB+2RNmKi8Nsc+TI=
|
||||
github.com/gofiber/schema v1.7.1/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
|
||||
github.com/gofiber/utils/v2 v2.0.0 h1:SCC3rpsEDWupFSHtc0RKxg/BKgV0s1qKfZg9Jv6D0sM=
|
||||
github.com/gofiber/utils/v2 v2.0.0/go.mod h1:xF9v89FfmbrYqI/bQUGN7gR8ZtXot2jxnZvmAUtiavE=
|
||||
github.com/go-webauthn/webauthn v0.17.4 h1:KFTSz3R2RYDiUn/0cDi3XTJgFenSG74eKTTHlqWhlxk=
|
||||
github.com/go-webauthn/webauthn v0.17.4/go.mod h1:pZk63EE/BdztlmyS4Yc+9H5g4a8blNlbtGmdHQHbZX8=
|
||||
github.com/go-webauthn/x v0.2.6 h1:TEyDuQAIiEgYpx60nKiBJIX/5nSUC8LxNbH+uf5U9uk=
|
||||
github.com/go-webauthn/x v0.2.6/go.mod h1:45bA7YEqyQhRcQJ/TiBb46Ww8yqHBGvgEhQ3WWF0aDo=
|
||||
github.com/gofiber/schema v1.7.2 h1:/0+siRIDYfMTh/edbdSNDXYZESBAFAu0CjKDUjo3g9Q=
|
||||
github.com/gofiber/schema v1.7.2/go.mod h1:lmbXPQ8hvzXSLkdS2DS7pb4kpunC2Roh7Sj3HMjGfzA=
|
||||
github.com/gofiber/utils/v2 v2.1.0 h1:WSu4COJhJw9moNfJu2nQvaM9AFvAQ/nZbigjhHqKgOQ=
|
||||
github.com/gofiber/utils/v2 v2.1.0/go.mod h1:DdOgEVwQTi8cou/AKWPqhXOR4fHGRVhA/rEWL3IXG7Q=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
@@ -168,12 +175,6 @@ github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
||||
github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
|
||||
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
|
||||
github.com/gookit/filter v1.2.3 h1:Zo7cBOtsVzAoa/jtf+Ury6zlsbJXqInFdUpbbnB2vMM=
|
||||
github.com/gookit/filter v1.2.3/go.mod h1:nFLJcOV8dRgS1iiX23gUQgmHUhpuS40qCvAGgIvA1pM=
|
||||
github.com/gookit/goutil v0.7.4 h1:OWgUngToNz+bPlX5aP+EMG31DraEU63uvKMwwT3vseM=
|
||||
github.com/gookit/goutil v0.7.4/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
|
||||
github.com/gookit/validate v1.5.7 h1:W0nqxAbgCJ4ND/oR+Tx8yyYn6l+PkFVvGHlG7LxpYsc=
|
||||
github.com/gookit/validate v1.5.7/go.mod h1:VeThM1saXWVJIG/Yn627PJL9yvap0s4bsjPUIV8I0UI=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
@@ -273,22 +274,22 @@ github.com/libtnb/cache v1.2.0 h1:6MHHRTzP2GgNs1gD9VJoTIeQ3ud20tkkgvbOtzIf0Zs=
|
||||
github.com/libtnb/cache v1.2.0/go.mod h1:QzCzSS5KBP2dcwMZtwKZO94eFMOViYMkUuxeLallZTE=
|
||||
github.com/libtnb/chix v1.3.2 h1:LkA/+OaHeMyTiQoWEcCQKAMSKDZ0LxBXSENQO5d+OKk=
|
||||
github.com/libtnb/chix v1.3.2/go.mod h1:ItssStBa/ov7v4qujyYzORqIf7Z6DO7Q4carOM7pyAQ=
|
||||
github.com/libtnb/cron v0.2.0 h1:Dgn8kgooA+bYGC16OHnufGLQlvtFDmnoM0NVjyLOfrE=
|
||||
github.com/libtnb/cron v0.2.0/go.mod h1:nrOHxS4Ernkn1E//vs64xzC58hGfoL0+lYSW2zsROGI=
|
||||
github.com/libtnb/gormstore v1.2.0 h1:VdNTiG8Ah+glDFNxtj+ZrBhcqiLFxMviqlz2gez6zWU=
|
||||
github.com/libtnb/gormstore v1.2.0/go.mod h1:8A5QzeZxi1MpSmjUVsHTDAL6KnU84feIXMutFLPawwA=
|
||||
github.com/libtnb/moby/client v0.0.0-20260127104057-d4b7902a560b h1:73OlJdl4Zk6Ipnm7KKeE6yCjfd7nk0BhTArujx6WJCU=
|
||||
github.com/libtnb/moby/client v0.0.0-20260127104057-d4b7902a560b/go.mod h1:bBQRIiaGR9EZBC+x5hnwunNgcDOts7E1Oa7AowJwMFU=
|
||||
github.com/libtnb/cron v0.2.2 h1:f7HM3qaoEqFU1Dw1S2UFva1pmxgYOHOPS3Cw2tsbNi0=
|
||||
github.com/libtnb/cron v0.2.2/go.mod h1:nrOHxS4Ernkn1E//vs64xzC58hGfoL0+lYSW2zsROGI=
|
||||
github.com/libtnb/gormstore v1.2.1 h1:oBRwSsVCa42leVT9GVFP5AzRxtyqj+FhRns9mQwSzEQ=
|
||||
github.com/libtnb/gormstore v1.2.1/go.mod h1:YLsGjjOiUIPhQ6f/ZDb/Vt1ZNI8TXhGZqSzxP1v7vGY=
|
||||
github.com/libtnb/securecookie v1.2.0 h1:2uc0PBDm0foeSTrcZ9QTX1IEjf6kFEwfgEYSIXQSKrA=
|
||||
github.com/libtnb/securecookie v1.2.0/go.mod h1:ja+wNGnQzYqcqXQnJWu6icsaWi5JEBwNEMJ2ReTVDxA=
|
||||
github.com/libtnb/sessions v1.4.0 h1:oQn4BckstpvqujSXejUcGAF4SQwTKFPIBe/Hv7s0aOs=
|
||||
github.com/libtnb/sessions v1.4.0/go.mod h1:PvxiaI3PCD+kzjPmIrpV3s9e4ACps6OUaXgmKCYlUFo=
|
||||
github.com/libtnb/sqlite v1.1.1 h1:p1Ud8vHTsPzgmChPUuJFkesrzl9ajD/pYrmoWf1cgig=
|
||||
github.com/libtnb/sqlite v1.1.1/go.mod h1:PNlcqMVWU+pMIUR499fHvRLUdxrNVVVXwEZkW1c9stE=
|
||||
github.com/libtnb/sessions v1.4.2 h1:KEYfuzEW7vtEedwvAZnDWWeW1yMSlwOUfp4hIUirFb8=
|
||||
github.com/libtnb/sessions v1.4.2/go.mod h1:PvxiaI3PCD+kzjPmIrpV3s9e4ACps6OUaXgmKCYlUFo=
|
||||
github.com/libtnb/sqlite v1.1.2 h1:3g8nez8MwdEhn+oqt3bfKWnYJxk0ZGTO0kVA1WT+hjQ=
|
||||
github.com/libtnb/sqlite v1.1.2/go.mod h1:2PGCWOR6HqqXuoIVMmwfJ655epvRO8sw0b9kHWuKNsQ=
|
||||
github.com/libtnb/testify v0.0.0-20260406170114-25da2dad39e7 h1:ZzONsNhw69uC/wJQlMj66n1zfsRgKAElfJ//E9G380U=
|
||||
github.com/libtnb/testify v0.0.0-20260406170114-25da2dad39e7/go.mod h1:HeQeTfKU6tj2Lx1z79UacwYeDioo6M4ZD7BDDI6+rrg=
|
||||
github.com/libtnb/utils v1.2.1 h1:LJmReRREnpqfHyy9PZtNgBh3ZaIGct81b8ZaAsolMkM=
|
||||
github.com/libtnb/utils v1.2.1/go.mod h1:o6LEDeC42PXI21uLWdWJWTVYvR9BtAZfzzTGJVQoQiU=
|
||||
github.com/libtnb/utils v1.2.2 h1:HUqgAIb+TIrZPXRytYB5XqzkEXG2U4a4/ZGpprNtL5A=
|
||||
github.com/libtnb/utils v1.2.2/go.mod h1:KUCx2+Phw3cLXOwuQDy6XwFOliaYuU1rpAuJQuHf+lQ=
|
||||
github.com/libtnb/validator v0.1.1 h1:8D3YkPGR9N2Ildb2PeApvgzCgIfKznEb6cTHWPskmNs=
|
||||
github.com/libtnb/validator v0.1.1/go.mod h1:j08Lydxwy7ModkoKeYIHxvZvAtnzzdE8xi2J+SGVrgc=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -296,6 +297,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
@@ -316,8 +319,10 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
||||
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc=
|
||||
github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@@ -367,10 +372,10 @@ github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRo
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sethvargo/go-limiter v1.1.0 h1:eLeZVQ2zqJOiEs03GguqmBVG6/T6lsZB+6PP1t7J6fA=
|
||||
github.com/sethvargo/go-limiter v1.1.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
github.com/shamaton/msgpack/v3 v3.0.0 h1:xl40uxWkSpwBCSTvS5wyXvJRsC6AcVcYeox9PspKiZg=
|
||||
github.com/shamaton/msgpack/v3 v3.0.0/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
|
||||
github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY=
|
||||
github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/shamaton/msgpack/v3 v3.1.2 h1:d5gWAIyMU4M0WgDjz6IFSCuXJUA2dFwRHBpDclE8CLw=
|
||||
github.com/shamaton/msgpack/v3 v3.1.2/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
|
||||
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
|
||||
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -410,8 +415,8 @@ github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZ
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20260220081509-8e17ce617db3 h1:ClWHRIz18WRVT3RD9y9uTwEWU9eEi+VYFEIumg05FLk=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20260220081509-8e17ce617db3/go.mod h1:ALbEe81QPWOZjDKCKNWodG2iqCMtregG8+ebQgjx2+4=
|
||||
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY=
|
||||
github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
|
||||
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
@@ -426,28 +431,42 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||
golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q=
|
||||
golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -461,8 +480,8 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -477,8 +496,8 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -487,8 +506,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -505,16 +524,16 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -534,8 +553,8 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@@ -569,44 +588,46 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
|
||||
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
||||
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
|
||||
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
|
||||
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
resty.dev/v3 v3.0.0-beta.6 h1:ghRdNpoE8/wBCv+kTKIOauW1aCrSIeTq7GxtfYgtevU=
|
||||
resty.dev/v3 v3.0.0-beta.6/go.mod h1:NTOerrC/4T7/FE6tXIZGIysXXBdgNqwMZuKtxpea9NM=
|
||||
resty.dev/v3 v3.0.0-rc.2 h1:NE6RqPkUd27eQ5dFSb5tfyT3s6XX9ZySp9qZ7Nw1bn0=
|
||||
resty.dev/v3 v3.0.0-rc.2/go.mod h1:NTOerrC/4T7/FE6tXIZGIysXXBdgNqwMZuKtxpea9NM=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/bddjr/hlfhr"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/gookit/validate"
|
||||
"github.com/libtnb/cron"
|
||||
"github.com/libtnb/validator"
|
||||
|
||||
"github.com/acepanel/panel/v3/pkg/config"
|
||||
"github.com/acepanel/panel/v3/pkg/tlscert"
|
||||
@@ -31,7 +31,7 @@ type Ace struct {
|
||||
runner types.TaskRunner
|
||||
}
|
||||
|
||||
func NewAce(conf *config.Config, router *chi.Mux, server *hlfhr.Server, reloader *tlscert.Reloader, migrator *gormigrate.Gormigrate, cron *cron.Cron, runner types.TaskRunner, _ *validate.Validation) *Ace {
|
||||
func NewAce(conf *config.Config, router *chi.Mux, server *hlfhr.Server, reloader *tlscert.Reloader, migrator *gormigrate.Gormigrate, cron *cron.Cron, runner types.TaskRunner, _ *validator.Validator) *Ace {
|
||||
return &Ace{
|
||||
conf: conf,
|
||||
router: router,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HealthLevelError 需要用户立即处理
|
||||
const HealthLevelError = "error"
|
||||
|
||||
// HealthLevelWarning 提示性问题,通常已被系统自动降级处理
|
||||
const HealthLevelWarning = "warning"
|
||||
|
||||
// HealthIssue 单条健康问题
|
||||
// Key 为稳定标识符(如 database:stat),前端据此选择翻译文案
|
||||
// Message 为原始错误详情,供诊断参考
|
||||
type HealthIssue struct {
|
||||
Key string `json:"key"`
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
Since time.Time `json:"since"`
|
||||
}
|
||||
|
||||
type healthRegistry struct {
|
||||
mu sync.RWMutex
|
||||
issues map[string]HealthIssue
|
||||
}
|
||||
|
||||
// Health 全局健康状态注册表,供各后台任务上报/清除故障
|
||||
var Health = &healthRegistry{issues: make(map[string]HealthIssue)}
|
||||
|
||||
// Report 上报或更新一条健康问题
|
||||
// 同 key 重复上报时保留首次上报时间,仅更新 message 和 level
|
||||
func (h *healthRegistry) Report(key, level, message string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if existing, ok := h.issues[key]; ok {
|
||||
existing.Level = level
|
||||
existing.Message = message
|
||||
h.issues[key] = existing
|
||||
return
|
||||
}
|
||||
h.issues[key] = HealthIssue{
|
||||
Key: key,
|
||||
Level: level,
|
||||
Message: message,
|
||||
Since: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Clear 清除指定 key 的健康问题
|
||||
func (h *healthRegistry) Clear(key string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
delete(h.issues, key)
|
||||
}
|
||||
|
||||
// Snapshot 返回当前所有健康问题,按 Since 升序(越早发生越靠前)
|
||||
func (h *healthRegistry) Snapshot() []HealthIssue {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
result := make([]HealthIssue, 0, len(h.issues))
|
||||
for _, issue := range h.issues {
|
||||
result = append(result, issue)
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Since.Before(result[j].Since)
|
||||
})
|
||||
return result
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 获取版本
|
||||
versionResp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/?query=SELECT+version()&user=default&password=%s", port, password))
|
||||
if err != nil || !versionResp.IsSuccess() {
|
||||
if err != nil || !versionResp.IsStatusSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,21 +7,21 @@ type UpdateConfig struct {
|
||||
|
||||
// SetDefaultPassword 设置 default 用户密码
|
||||
type SetDefaultPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
// ConfigTune ClickHouse 配置调整
|
||||
type ConfigTune struct {
|
||||
// 网络
|
||||
ListenHost string `form:"listen_host" json:"listen_host"`
|
||||
HTTPPort string `form:"http_port" json:"http_port"`
|
||||
TCPPort string `form:"tcp_port" json:"tcp_port"`
|
||||
HTTPPort string `form:"http_port" json:"http_port" validate:"number && min:1 && max:65535"`
|
||||
TCPPort string `form:"tcp_port" json:"tcp_port" validate:"number && min:1 && max:65535"`
|
||||
// 性能
|
||||
MaxMemoryUsage string `form:"max_memory_usage" json:"max_memory_usage"`
|
||||
MaxThreads string `form:"max_threads" json:"max_threads"`
|
||||
// 路径
|
||||
Path string `form:"path" json:"path"`
|
||||
TmpPath string `form:"tmp_path" json:"tmp_path"`
|
||||
Path string `form:"path" json:"path" validate:"unix_path"`
|
||||
TmpPath string `form:"tmp_path" json:"tmp_path" validate:"unix_path"`
|
||||
// 日志
|
||||
LogLevel string `form:"log_level" json:"log_level"`
|
||||
LogLevel string `form:"log_level" json:"log_level" validate:"in:trace,debug,information,warning,error"`
|
||||
}
|
||||
|
||||
@@ -12,22 +12,22 @@ type LogOpts struct {
|
||||
|
||||
// Settings Docker daemon 设置
|
||||
type Settings struct {
|
||||
RegistryMirrors []string `json:"registry-mirrors,omitempty"` // 注册表镜像
|
||||
InsecureRegistries []string `json:"insecure-registries,omitempty"` // 非安全镜像仓库
|
||||
LiveRestore bool `json:"live-restore,omitempty"` // Live restore
|
||||
LogDriver string `json:"log-driver,omitempty"` // 日志驱动
|
||||
LogOpts LogOpts `json:"log-opts"` // 日志配置选项
|
||||
CgroupDriver string `json:"cgroup-driver,omitempty"` // cgroup 驱动(从 exec-opts 中提取)
|
||||
Hosts []string `json:"hosts,omitempty"` // Socket 路径
|
||||
DataRoot string `json:"data-root,omitempty"` // 数据目录
|
||||
StorageDriver string `json:"storage-driver,omitempty"` // 存储驱动
|
||||
DNS []string `json:"dns,omitempty"` // DNS 配置
|
||||
FirewallBackend string `json:"firewall-backend,omitempty"` // 防火墙后端 (iptables/nftables)
|
||||
Iptables *bool `json:"iptables,omitempty"` // iptables 规则
|
||||
Ip6tables *bool `json:"ip6tables,omitempty"` // ip6tables 规则
|
||||
IpForward *bool `json:"ip-forward,omitempty"` // IP 转发
|
||||
IPv6 *bool `json:"ipv6,omitempty"` // IPv6 支持
|
||||
Bip string `json:"bip,omitempty"` // 默认 bridge 网络 IP 段
|
||||
RegistryMirrors []string `json:"registry-mirrors,omitempty"` // 注册表镜像
|
||||
InsecureRegistries []string `json:"insecure-registries,omitempty"` // 非安全镜像仓库
|
||||
LiveRestore bool `json:"live-restore,omitempty"` // Live restore
|
||||
LogDriver string `json:"log-driver,omitempty"` // 日志驱动
|
||||
LogOpts LogOpts `json:"log-opts"` // 日志配置选项
|
||||
CgroupDriver string `json:"cgroup-driver,omitempty" validate:"in:systemd,cgroupfs"` // cgroup 驱动(从 exec-opts 中提取)
|
||||
Hosts []string `json:"hosts,omitempty"` // Socket 路径
|
||||
DataRoot string `json:"data-root,omitempty"` // 数据目录
|
||||
StorageDriver string `json:"storage-driver,omitempty"` // 存储驱动
|
||||
DNS []string `json:"dns,omitempty"` // DNS 配置
|
||||
FirewallBackend string `json:"firewall-backend,omitempty" validate:"in:iptables,nftables"` // 防火墙后端 (iptables/nftables)
|
||||
Iptables *bool `json:"iptables,omitempty"` // iptables 规则
|
||||
Ip6tables *bool `json:"ip6tables,omitempty"` // ip6tables 规则
|
||||
IpForward *bool `json:"ip-forward,omitempty"` // IP 转发
|
||||
IPv6 *bool `json:"ipv6,omitempty"` // IPv6 支持
|
||||
Bip string `json:"bip,omitempty"` // 默认 bridge 网络 IP 段
|
||||
}
|
||||
|
||||
// UpdateSettings 更新设置请求
|
||||
|
||||
@@ -57,7 +57,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
defer func(client *resty.Client) { _ = client.Close() }(client)
|
||||
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/_cluster/health", port))
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
if err != nil || !resp.IsStatusSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ ignoreregex =
|
||||
switch jailName {
|
||||
case "ssh":
|
||||
filter = "sshd"
|
||||
port, err = shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
|
||||
port, err = shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}' | paste -sd ','")
|
||||
case "mysql":
|
||||
filter = "mysqld-auth"
|
||||
port, err = shell.Execf("cat %s/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'", app.Root)
|
||||
|
||||
@@ -2,12 +2,12 @@ package fail2ban
|
||||
|
||||
type Add struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
MaxRetry int `json:"maxretry" validate:"required"`
|
||||
FindTime int `json:"findtime" validate:"required"`
|
||||
BanTime int `json:"bantime" validate:"required"`
|
||||
WebsiteName string `json:"website_name"`
|
||||
WebsiteMode string `json:"website_mode"`
|
||||
Type string `json:"type" validate:"required && in:service,website"`
|
||||
MaxRetry int `json:"maxretry" validate:"required && min:1"`
|
||||
FindTime int `json:"findtime" validate:"required && min:1"`
|
||||
BanTime int `json:"bantime" validate:"required && min:1"`
|
||||
WebsiteName string `json:"website_name" validate:"required_if:Type,website"`
|
||||
WebsiteMode string `json:"website_mode" validate:"required_if:Type,website && in:cc,path"`
|
||||
WebsitePath string `json:"website_path"`
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type BanList struct {
|
||||
|
||||
type Unban struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
IP string `json:"ip" validate:"required"`
|
||||
IP string `json:"ip" validate:"required && ip"`
|
||||
}
|
||||
|
||||
type SetWhiteList struct {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package frp
|
||||
|
||||
type Name struct {
|
||||
Name string `form:"name" json:"name" validate:"required|in:frps,frpc"`
|
||||
Name string `form:"name" json:"name" validate:"required && in:frps,frpc"`
|
||||
}
|
||||
|
||||
type UpdateConfig struct {
|
||||
Name string `form:"name" json:"name" validate:"required|in:frps,frpc"`
|
||||
Name string `form:"name" json:"name" validate:"required && in:frps,frpc"`
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
type UpdateUser struct {
|
||||
Name string `form:"name" json:"name" validate:"required|in:frps,frpc"`
|
||||
User string `form:"user" json:"user" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Group string `form:"group" json:"group" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `form:"name" json:"name" validate:"required && in:frps,frpc"`
|
||||
User string `form:"user" json:"user" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Group string `form:"group" json:"group" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
defer func(client *resty.Client) { _ = client.Close() }(client)
|
||||
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/api/health", port))
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
if err != nil || !resp.IsStatusSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ type ConfigTune struct {
|
||||
// DataSource 数据源
|
||||
type DataSource struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:prometheus,mysql,postgres,influxdb,loki,elasticsearch,tempo,jaeger,zipkin,graphite,alertmanager,opentsdb,mssql,testdata,grafana-pyroscope-datasource,grafana-opensearch-datasource"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:prometheus,mysql,postgres,influxdb,loki,elasticsearch,tempo,jaeger,zipkin,graphite,alertmanager,opentsdb,mssql,testdata,grafana-pyroscope-datasource,grafana-opensearch-datasource"`
|
||||
URL string `form:"url" json:"url" validate:"required"`
|
||||
Access string `form:"access" json:"access"`
|
||||
Access string `form:"access" json:"access" validate:"in:proxy,direct"`
|
||||
IsDefault bool `form:"is_default" json:"is_default"`
|
||||
// 数据库类型专用
|
||||
Database string `form:"database" json:"database"`
|
||||
|
||||
@@ -6,10 +6,10 @@ type UpdateConfig struct {
|
||||
|
||||
// ConfigTune Memcached 配置调整
|
||||
type ConfigTune struct {
|
||||
Port string `form:"port" json:"port"`
|
||||
UDPPort string `form:"udp_port" json:"udp_port"`
|
||||
Port string `form:"port" json:"port" validate:"number && min:1 && max:65535"`
|
||||
UDPPort string `form:"udp_port" json:"udp_port" validate:"number && min:1 && max:65535"`
|
||||
ListenAddress string `form:"listen_address" json:"listen_address"`
|
||||
Memory string `form:"memory" json:"memory"`
|
||||
MaxConnections string `form:"max_connections" json:"max_connections"`
|
||||
Threads string `form:"threads" json:"threads"`
|
||||
Memory string `form:"memory" json:"memory" validate:"number && min:1"`
|
||||
MaxConnections string `form:"max_connections" json:"max_connections" validate:"number && min:1"`
|
||||
Threads string `form:"threads" json:"threads" validate:"number && min:1"`
|
||||
}
|
||||
|
||||
@@ -7,19 +7,19 @@ type UpdateConfig struct {
|
||||
|
||||
// SetAdminPassword 设置 admin 密码
|
||||
type SetAdminPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
// ConfigTune MongoDB 配置调整
|
||||
type ConfigTune struct {
|
||||
// 存储
|
||||
DbPath string `form:"db_path" json:"db_path"`
|
||||
DbPath string `form:"db_path" json:"db_path" validate:"unix_path"`
|
||||
CacheSizeGB string `form:"cache_size_gb" json:"cache_size_gb"`
|
||||
// 网络
|
||||
Port string `form:"port" json:"port"`
|
||||
Port string `form:"port" json:"port" validate:"number && min:1 && max:65535"`
|
||||
BindIp string `form:"bind_ip" json:"bind_ip"`
|
||||
// 日志
|
||||
SystemLogPath string `form:"system_log_path" json:"system_log_path"`
|
||||
SystemLogPath string `form:"system_log_path" json:"system_log_path" validate:"unix_path"`
|
||||
// 安全
|
||||
Authorization string `form:"authorization" json:"authorization"`
|
||||
Authorization string `form:"authorization" json:"authorization" validate:"in:enabled,disabled"`
|
||||
}
|
||||
|
||||
+24
-24
@@ -247,30 +247,6 @@ func (s *App) SetRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) getSock() string {
|
||||
if io.Exists("/tmp/mysql.sock") {
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
if io.Exists(app.Root + "/server/mysql/config/my.cnf") {
|
||||
config, _ := io.Read(app.Root + "/server/mysql/config/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
if io.Exists("/etc/my.cnf") {
|
||||
config, _ := io.Read("/etc/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
|
||||
// GetConfigTune 获取 MySQL 配置调整参数
|
||||
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(app.Root + "/server/mysql/conf/my.cnf")
|
||||
@@ -369,6 +345,30 @@ func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) getSock() string {
|
||||
if io.Exists("/tmp/mysql.sock") {
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
if io.Exists(app.Root + "/server/mysql/config/my.cnf") {
|
||||
config, _ := io.Read(app.Root + "/server/mysql/config/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
if io.Exists("/etc/my.cnf") {
|
||||
config, _ := io.Read("/etc/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
|
||||
// getINIValue 从 INI 格式内容中获取指定键的值
|
||||
func (s *App) getINIValue(content string, key string) string {
|
||||
lines := strings.SplitSeq(content, "\n")
|
||||
|
||||
@@ -5,13 +5,13 @@ type UpdateConfig struct {
|
||||
}
|
||||
|
||||
type SetRootPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
// ConfigTune MySQL 配置调整
|
||||
type ConfigTune struct {
|
||||
// 常规设置
|
||||
Port string `form:"port" json:"port"`
|
||||
Port string `form:"port" json:"port" validate:"number && min:1 && max:65535"`
|
||||
MaxConnections string `form:"max_connections" json:"max_connections"`
|
||||
MaxConnectErrors string `form:"max_connect_errors" json:"max_connect_errors"`
|
||||
DefaultStorageEngine string `form:"default_storage_engine" json:"default_storage_engine"`
|
||||
@@ -32,7 +32,7 @@ type ConfigTune struct {
|
||||
// InnoDB
|
||||
InnodbBufferPoolSize string `form:"innodb_buffer_pool_size" json:"innodb_buffer_pool_size"`
|
||||
InnodbLogBufferSize string `form:"innodb_log_buffer_size" json:"innodb_log_buffer_size"`
|
||||
InnodbFlushLogAtTrxCommit string `form:"innodb_flush_log_at_trx_commit" json:"innodb_flush_log_at_trx_commit"`
|
||||
InnodbFlushLogAtTrxCommit string `form:"innodb_flush_log_at_trx_commit" json:"innodb_flush_log_at_trx_commit" validate:"in:0,1,2"`
|
||||
InnodbLockWaitTimeout string `form:"innodb_lock_wait_timeout" json:"innodb_lock_wait_timeout"`
|
||||
InnodbMaxDirtyPagesPct string `form:"innodb_max_dirty_pages_pct" json:"innodb_max_dirty_pages_pct"`
|
||||
InnodbReadIoThreads string `form:"innodb_read_io_threads" json:"innodb_read_io_threads"`
|
||||
|
||||
@@ -103,7 +103,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
defer func(client *resty.Client) { _ = client.Close() }(client)
|
||||
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
if err != nil || !resp.IsStatusSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -39,22 +39,22 @@ type ConfigTune struct {
|
||||
}
|
||||
|
||||
type StreamServer struct {
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"` // 配置名称,用于文件命名
|
||||
Listen string `form:"listen" json:"listen" validate:"required"` // 监听地址,如: "12345", "0.0.0.0:12345", "[::]:12345"
|
||||
UDP bool `form:"udp" json:"udp"` // 是否 UDP 协议
|
||||
ProxyPass string `form:"proxy_pass" json:"proxy_pass" validate:"required"` // 代理地址,如: "127.0.0.1:3306", "upstream_name"
|
||||
ProxyProtocol bool `form:"proxy_protocol" json:"proxy_protocol"` // 是否启用 PROXY 协议
|
||||
ProxyTimeout time.Duration `form:"proxy_timeout" json:"proxy_timeout"` // 代理超时时间
|
||||
ProxyConnectTimeout time.Duration `form:"proxy_connect_timeout" json:"proxy_connect_timeout"` // 代理连接超时时间
|
||||
SSL bool `form:"ssl" json:"ssl"` // 是否启用 SSL
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate" validate:"requiredIf:SSL,true"` // SSL 证书路径
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key" validate:"requiredIf:SSL,true"` // SSL 私钥路径
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""` // 配置名称,用于文件命名
|
||||
Listen string `form:"listen" json:"listen" validate:"required"` // 监听地址,如: "12345", "0.0.0.0:12345", "[::]:12345"
|
||||
UDP bool `form:"udp" json:"udp"` // 是否 UDP 协议
|
||||
ProxyPass string `form:"proxy_pass" json:"proxy_pass" validate:"required"` // 代理地址,如: "127.0.0.1:3306", "upstream_name"
|
||||
ProxyProtocol bool `form:"proxy_protocol" json:"proxy_protocol"` // 是否启用 PROXY 协议
|
||||
ProxyTimeout time.Duration `form:"proxy_timeout" json:"proxy_timeout"` // 代理超时时间
|
||||
ProxyConnectTimeout time.Duration `form:"proxy_connect_timeout" json:"proxy_connect_timeout"` // 代理连接超时时间
|
||||
SSL bool `form:"ssl" json:"ssl"` // 是否启用 SSL
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate" validate:"required_if:SSL,true"` // SSL 证书路径
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key" validate:"required_if:SSL,true"` // SSL 私钥路径
|
||||
}
|
||||
|
||||
type StreamUpstream struct {
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"` // 上游名称
|
||||
Servers map[string]string `form:"servers" json:"servers" validate:"required"` // 上游服务器及配置,如: map["127.0.0.1:3306"] = "weight=5"
|
||||
Algo string `form:"algo" json:"algo"` // 负载均衡算法,如: "least_conn", "hash $remote_addr"
|
||||
Resolver []string `form:"resolver" json:"resolver"` // DNS 解析器,如: ["8.8.8.8", "ipv6=off"]
|
||||
ResolverTimeout time.Duration `form:"resolver_timeout" json:"resolver_timeout"` // DNS 解析超时时间
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""` // 上游名称
|
||||
Servers map[string]string `form:"servers" json:"servers" validate:"required"` // 上游服务器及配置,如: map["127.0.0.1:3306"] = "weight=5"
|
||||
Algo string `form:"algo" json:"algo"` // 负载均衡算法,如: "least_conn", "hash $remote_addr"
|
||||
Resolver []string `form:"resolver" json:"resolver"` // DNS 解析器,如: ["8.8.8.8", "ipv6=off"]
|
||||
ResolverTimeout time.Duration `form:"resolver_timeout" json:"resolver_timeout"` // DNS 解析超时时间
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@ func (s *App) Route(r chi.Router) {
|
||||
}
|
||||
|
||||
func (s *App) Status() string {
|
||||
ok, _ := systemctl.Status("openresty")
|
||||
ok, _ := systemctl.Status("nginx")
|
||||
return types.AggregateAppStatus(ok)
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
defer func(client *resty.Client) { _ = client.Close() }(client)
|
||||
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/_cluster/health", port))
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
if err != nil || !resp.IsStatusSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ type UpdateConfig struct {
|
||||
}
|
||||
|
||||
type UpdatePort struct {
|
||||
Port uint `form:"port" json:"port" validate:"required|number|min:1|max:65535"`
|
||||
Port uint `form:"port" json:"port" validate:"required && number && min:1 && max:65535"`
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/acepanel/panel/v3/internal/app"
|
||||
"github.com/acepanel/panel/v3/internal/biz"
|
||||
@@ -55,7 +56,7 @@ func (s *App) Status() string {
|
||||
// GetConfig 获取配置
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取配置
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root))
|
||||
config, err := io.Read(s.configPath())
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -72,13 +73,14 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
|
||||
oldPort := s.getPort()
|
||||
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err))
|
||||
if err = s.applyConfig(req.Config, oldPort); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to apply PostgreSQL config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -136,13 +138,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set PGPASSWORD env: %v", err))
|
||||
return
|
||||
}
|
||||
defer func() { _ = os.Unsetenv("PGPASSWORD") }()
|
||||
|
||||
start, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "select pg_postmaster_start_time();" | head -1 | cut -d'.' -f1`)
|
||||
port := s.getPort()
|
||||
start, err := shell.Execf(`psql -h 127.0.0.1 -p %d -U postgres -t -c "select pg_postmaster_start_time();" | head -1 | cut -d'.' -f1`, port)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL start time: %v", err))
|
||||
return
|
||||
}
|
||||
pid, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "select pg_backend_pid();"`)
|
||||
pid, err := shell.Execf(`psql -h 127.0.0.1 -p %d -U postgres -t -c "select pg_backend_pid();"`, port)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL backend pid: %v", err))
|
||||
return
|
||||
@@ -152,22 +156,17 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL process: %v", err))
|
||||
return
|
||||
}
|
||||
connections, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();"`)
|
||||
connections, err := shell.Execf(`psql -h 127.0.0.1 -p %d -U postgres -t -c "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();"`, port)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL connections: %v", err))
|
||||
return
|
||||
}
|
||||
storage, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "select pg_size_pretty(pg_database_size('postgres'));"`)
|
||||
storage, err := shell.Execf(`psql -h 127.0.0.1 -p %d -U postgres -t -c "select pg_size_pretty(pg_database_size('postgres'));"`, port)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL database size: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Unsetenv("PGPASSWORD"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to unset PGPASSWORD env: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
data := []types.NV{
|
||||
{Name: s.t.Get("Start Time"), Value: start},
|
||||
{Name: s.t.Get("Process PID"), Value: pid},
|
||||
@@ -210,10 +209,11 @@ func (s *App) SetPostgresPassword(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
oldPassword, _ := s.settingRepo.Get(biz.SettingKeyPostgresPassword)
|
||||
postgres, err := db.NewPostgres("postgres", oldPassword, "127.0.0.1", 5432)
|
||||
port := s.getPort()
|
||||
postgres, err := db.NewPostgres("postgres", oldPassword, "127.0.0.1", port)
|
||||
if err != nil {
|
||||
// 直接修改密码
|
||||
if _, err = shell.Execf(`su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD '%s';\""`, req.Password); err != nil {
|
||||
if _, err = shell.Execf(`su - postgres -c "psql -p %d -c \"ALTER USER postgres WITH PASSWORD '%s';\""`, port, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set postgres password: %v", err))
|
||||
return
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func (s *App) SetPostgresPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// GetConfigTune 获取 PostgreSQL 配置调整参数
|
||||
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root))
|
||||
config, err := io.Read(s.configPath())
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -284,7 +284,8 @@ func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
confPath := fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root)
|
||||
confPath := s.configPath()
|
||||
oldPort := s.getPort()
|
||||
config, err := io.Read(confPath)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
@@ -324,14 +325,48 @@ func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err))
|
||||
if err = s.applyConfig(config, oldPort); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to apply PostgreSQL config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) configPath() string {
|
||||
return fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root)
|
||||
}
|
||||
|
||||
// getPort 读取 PostgreSQL 端口
|
||||
func (s *App) getPort() uint {
|
||||
config, err := io.Read(s.configPath())
|
||||
if err != nil {
|
||||
return 5432
|
||||
}
|
||||
return s.parsePort(config)
|
||||
}
|
||||
|
||||
// parsePort 从 config 内容解析端口,未配置时返回默认值
|
||||
func (s *App) parsePort(config string) uint {
|
||||
port := cast.ToUint(s.getPGValue(config, "port"))
|
||||
if port == 0 {
|
||||
return 5432
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
// applyConfig 让 PostgreSQL 配置生效
|
||||
func (s *App) applyConfig(newConfig string, oldPort uint) error {
|
||||
newPort := s.parsePort(newConfig)
|
||||
if oldPort == newPort {
|
||||
return systemctl.Reload("postgresql")
|
||||
}
|
||||
if err := systemctl.Restart("postgresql"); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.databaseServerRepo.UpdatePort("local_postgresql", newPort)
|
||||
}
|
||||
|
||||
// getPGValue 从 PostgreSQL 配置内容中获取指定键的值
|
||||
func (s *App) getPGValue(content string, key string) string {
|
||||
lines := strings.SplitSeq(content, "\n")
|
||||
|
||||
@@ -5,14 +5,14 @@ type UpdateConfig struct {
|
||||
}
|
||||
|
||||
type SetPostgresPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
// ConfigTune PostgreSQL 配置调整
|
||||
type ConfigTune struct {
|
||||
// 连接设置
|
||||
ListenAddresses string `form:"listen_addresses" json:"listen_addresses"`
|
||||
Port string `form:"port" json:"port"`
|
||||
Port string `form:"port" json:"port" validate:"number && min:1 && max:65535"`
|
||||
MaxConnections string `form:"max_connections" json:"max_connections"`
|
||||
SuperuserReservedConnections string `form:"superuser_reserved_connections" json:"superuser_reserved_connections"`
|
||||
// 内存设置
|
||||
|
||||
@@ -73,7 +73,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
defer func(client *resty.Client) { _ = client.Close() }(client)
|
||||
resp, err := client.R().Get("http://127.0.0.1:9090/api/v1/status/runtimeinfo")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
if err != nil || !resp.IsStatusSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package pureftpd
|
||||
|
||||
type Create struct {
|
||||
Username string `form:"username" json:"username" validate:"required"`
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
Path string `form:"path" json:"path" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -12,21 +12,21 @@ type Delete struct {
|
||||
|
||||
type ChangePassword struct {
|
||||
Username string `form:"username" json:"username" validate:"required"`
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
type UpdatePort struct {
|
||||
Port uint `form:"port" json:"port" validate:"required|number|min:1|max:65535"`
|
||||
Port uint `form:"port" json:"port" validate:"required && number && min:1 && max:65535"`
|
||||
}
|
||||
|
||||
// ConfigTune Pure-FTPd 配置调整
|
||||
type ConfigTune struct {
|
||||
MaxClientsNumber string `form:"max_clients_number" json:"max_clients_number"`
|
||||
MaxClientsPerIP string `form:"max_clients_per_ip" json:"max_clients_per_ip"`
|
||||
MaxIdleTime string `form:"max_idle_time" json:"max_idle_time"`
|
||||
MaxClientsNumber string `form:"max_clients_number" json:"max_clients_number" validate:"number && min:1"`
|
||||
MaxClientsPerIP string `form:"max_clients_per_ip" json:"max_clients_per_ip" validate:"number && min:1"`
|
||||
MaxIdleTime string `form:"max_idle_time" json:"max_idle_time" validate:"number && min:0"`
|
||||
MaxLoad string `form:"max_load" json:"max_load"`
|
||||
PassivePortRange string `form:"passive_port_range" json:"passive_port_range"`
|
||||
AnonymousOnly string `form:"anonymous_only" json:"anonymous_only"`
|
||||
NoAnonymous string `form:"no_anonymous" json:"no_anonymous"`
|
||||
MaxDiskUsage string `form:"max_disk_usage" json:"max_disk_usage"`
|
||||
AnonymousOnly string `form:"anonymous_only" json:"anonymous_only" validate:"in:yes,no"`
|
||||
NoAnonymous string `form:"no_anonymous" json:"no_anonymous" validate:"in:yes,no"`
|
||||
MaxDiskUsage string `form:"max_disk_usage" json:"max_disk_usage" validate:"number && min:1 && max:100"`
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ type UpdateConfig struct {
|
||||
type ConfigTune struct {
|
||||
// 常规设置
|
||||
Bind string `form:"bind" json:"bind"`
|
||||
Port string `form:"port" json:"port"`
|
||||
Databases string `form:"databases" json:"databases"`
|
||||
Port string `form:"port" json:"port" validate:"number && min:1 && max:65535"`
|
||||
Databases string `form:"databases" json:"databases" validate:"number && min:1"`
|
||||
Requirepass string `form:"requirepass" json:"requirepass"`
|
||||
Timeout string `form:"timeout" json:"timeout"`
|
||||
TCPKeepalive string `form:"tcp_keepalive" json:"tcp_keepalive"`
|
||||
Timeout string `form:"timeout" json:"timeout" validate:"number"`
|
||||
TCPKeepalive string `form:"tcp_keepalive" json:"tcp_keepalive" validate:"number"`
|
||||
// 内存
|
||||
Maxmemory string `form:"maxmemory" json:"maxmemory"`
|
||||
MaxmemoryPolicy string `form:"maxmemory_policy" json:"maxmemory_policy"`
|
||||
MaxmemoryPolicy string `form:"maxmemory_policy" json:"maxmemory_policy" validate:"in:noeviction,allkeys-lru,allkeys-lfu,allkeys-random,volatile-lru,volatile-lfu,volatile-random,volatile-ttl"`
|
||||
// 持久化
|
||||
Appendonly string `form:"appendonly" json:"appendonly"`
|
||||
Appendfsync string `form:"appendfsync" json:"appendfsync"`
|
||||
Appendonly string `form:"appendonly" json:"appendonly" validate:"in:yes,no"`
|
||||
Appendfsync string `form:"appendfsync" json:"appendfsync" validate:"in:always,everysec,no"`
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ type UpdateConfig struct {
|
||||
// ConfigTune RocketMQ 配置调整
|
||||
type ConfigTune struct {
|
||||
// Broker 基础
|
||||
BrokerName string `form:"broker_name" json:"broker_name"` // brokerName
|
||||
ListenPort string `form:"listen_port" json:"listen_port"` // listenPort
|
||||
NamesrvAddr string `form:"namesrv_addr" json:"namesrv_addr"` // namesrvAddr
|
||||
BrokerRole string `form:"broker_role" json:"broker_role"` // brokerRole
|
||||
FlushDiskType string `form:"flush_disk_type" json:"flush_disk_type"` // flushDiskType
|
||||
BrokerName string `form:"broker_name" json:"broker_name"` // brokerName
|
||||
ListenPort string `form:"listen_port" json:"listen_port" validate:"number && min:1 && max:65535"` // listenPort
|
||||
NamesrvAddr string `form:"namesrv_addr" json:"namesrv_addr"` // namesrvAddr
|
||||
BrokerRole string `form:"broker_role" json:"broker_role" validate:"in:ASYNC_MASTER,SYNC_MASTER,SLAVE"` // brokerRole
|
||||
FlushDiskType string `form:"flush_disk_type" json:"flush_disk_type" validate:"in:ASYNC_FLUSH,SYNC_FLUSH"` // flushDiskType
|
||||
// 存储
|
||||
StorePathRootDir string `form:"store_path_root_dir" json:"store_path_root_dir"` // storePathRootDir
|
||||
StorePathCommitLog string `form:"store_path_commit_log" json:"store_path_commit_log"` // storePathCommitLog
|
||||
MaxMessageSize string `form:"max_message_size" json:"max_message_size"` // maxMessageSize
|
||||
StorePathRootDir string `form:"store_path_root_dir" json:"store_path_root_dir" validate:"unix_path"` // storePathRootDir
|
||||
StorePathCommitLog string `form:"store_path_commit_log" json:"store_path_commit_log" validate:"unix_path"` // storePathCommitLog
|
||||
MaxMessageSize string `form:"max_message_size" json:"max_message_size" validate:"number"` // maxMessageSize
|
||||
// JVM - NameServer
|
||||
NamesrvHeapInitSize string `form:"namesrv_heap_init_size" json:"namesrv_heap_init_size"` // -Xms (namesrv)
|
||||
NamesrvHeapMaxSize string `form:"namesrv_heap_max_size" json:"namesrv_heap_max_size"` // -Xmx (namesrv)
|
||||
|
||||
@@ -4,8 +4,8 @@ type Create struct {
|
||||
Ak string `form:"ak" json:"ak" validate:"required"`
|
||||
Sk string `form:"sk" json:"sk" validate:"required"`
|
||||
Bucket string `form:"bucket" json:"bucket" validate:"required"`
|
||||
URL string `form:"url" json:"url" validate:"required"`
|
||||
Path string `form:"path" json:"path" validate:"required"`
|
||||
URL string `form:"url" json:"url" validate:"required && url"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
|
||||
@@ -16,7 +16,7 @@ type ProcessName struct {
|
||||
type CreateProcess struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
User string `form:"user" json:"user" validate:"required"`
|
||||
Path string `form:"path" json:"path" validate:"required"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
Command string `form:"command" json:"command" validate:"required"`
|
||||
Num int `form:"num" json:"num" validate:"required|min:1"`
|
||||
Num int `form:"num" json:"num" validate:"required && min:1"`
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ type UpdateConfig struct {
|
||||
type ConfigTune struct {
|
||||
// 常规设置
|
||||
Bind string `form:"bind" json:"bind"`
|
||||
Port string `form:"port" json:"port"`
|
||||
Databases string `form:"databases" json:"databases"`
|
||||
Port string `form:"port" json:"port" validate:"number && min:1 && max:65535"`
|
||||
Databases string `form:"databases" json:"databases" validate:"number && min:1"`
|
||||
Requirepass string `form:"requirepass" json:"requirepass"`
|
||||
Timeout string `form:"timeout" json:"timeout"`
|
||||
TCPKeepalive string `form:"tcp_keepalive" json:"tcp_keepalive"`
|
||||
Timeout string `form:"timeout" json:"timeout" validate:"number"`
|
||||
TCPKeepalive string `form:"tcp_keepalive" json:"tcp_keepalive" validate:"number"`
|
||||
// 内存
|
||||
Maxmemory string `form:"maxmemory" json:"maxmemory"`
|
||||
MaxmemoryPolicy string `form:"maxmemory_policy" json:"maxmemory_policy"`
|
||||
MaxmemoryPolicy string `form:"maxmemory_policy" json:"maxmemory_policy" validate:"in:noeviction,allkeys-lru,allkeys-lfu,volatile-lru,volatile-lfu,allkeys-random,volatile-random,volatile-ttl"`
|
||||
// 持久化
|
||||
Appendonly string `form:"appendonly" json:"appendonly"`
|
||||
Appendfsync string `form:"appendfsync" json:"appendfsync"`
|
||||
Appendonly string `form:"appendonly" json:"appendonly" validate:"in:yes,no"`
|
||||
Appendfsync string `form:"appendfsync" json:"appendfsync" validate:"in:always,everysec,no"`
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ import (
|
||||
type BackupType string
|
||||
|
||||
const (
|
||||
BackupTypePath BackupType = "path"
|
||||
BackupTypeWebsite BackupType = "website"
|
||||
BackupTypeMySQL BackupType = "mysql"
|
||||
BackupTypePostgres BackupType = "postgresql"
|
||||
BackupTypeRedis BackupType = "redis"
|
||||
BackupTypePanel BackupType = "panel"
|
||||
BackupTypePath BackupType = "path"
|
||||
BackupTypeWebsite BackupType = "website"
|
||||
BackupTypeMySQL BackupType = "mysql"
|
||||
BackupTypePostgres BackupType = "postgresql"
|
||||
BackupTypeClickHouse BackupType = "clickhouse"
|
||||
BackupTypeRedis BackupType = "redis"
|
||||
BackupTypeValkey BackupType = "valkey"
|
||||
BackupTypePanel BackupType = "panel"
|
||||
)
|
||||
|
||||
type BackupRepo interface {
|
||||
@@ -29,5 +31,5 @@ type BackupRepo interface {
|
||||
CutoffUpload(account uint, typ BackupType, name string, files []string) error
|
||||
GetDefaultPath(typ BackupType) string
|
||||
FixPanel() error
|
||||
UpdatePanel(version, url, checksum string) error
|
||||
UpdatePanel(version, url, checksum string, progress func(string)) error
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ type ContainerRepo interface {
|
||||
Unpause(id string) error
|
||||
Kill(id string) error
|
||||
Rename(id string, newName string) error
|
||||
Logs(id string) (string, error)
|
||||
Logs(id string, tail int) (string, error)
|
||||
Prune() error
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ type DatabaseServerRepo interface {
|
||||
Update(req *request.DatabaseServerUpdate) error
|
||||
UpdateRemark(req *request.DatabaseServerUpdateRemark) error
|
||||
UpdatePassword(name string, password string) error
|
||||
UpdatePort(name string, port uint) error
|
||||
Delete(id uint) error
|
||||
ClearUsers(id uint) error
|
||||
Sync(id uint) error
|
||||
|
||||
@@ -17,7 +17,7 @@ const (
|
||||
SettingKeyMonitorDays SettingKey = "monitor_days"
|
||||
SettingKeyMonitorInterval SettingKey = "monitor_interval"
|
||||
SettingKeyBackupPath SettingKey = "backup_path"
|
||||
SettingKeyBackupFormat SettingKey = "backup_format" // tar.xz / tar.gz / zip / 7z
|
||||
SettingKeyBackupFormat SettingKey = "backup_format" // tar.xz / tar.gz / tar.zst / zip / 7z
|
||||
SettingKeyWebsitePath SettingKey = "website_path"
|
||||
SettingKeyProjectPath SettingKey = "project_path"
|
||||
SettingKeyContainerSock SettingKey = "container_sock"
|
||||
@@ -50,6 +50,7 @@ const (
|
||||
SettingKeyIPDBType SettingKey = "ipdb_type" // "" / "custom" / "subscribe"
|
||||
SettingKeyIPDBURL SettingKey = "ipdb_url" // 订阅链接
|
||||
SettingKeyIPDBPath SettingKey = "ipdb_path"
|
||||
SettingKeyInfoRan SettingKey = "info_ran" // info 命令是否已运行过
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
|
||||
@@ -7,4 +7,4 @@ import (
|
||||
)
|
||||
|
||||
// ProviderSet is bootstrap providers.
|
||||
var ProviderSet = wire.NewSet(NewConf, NewT, NewLog, NewCli, NewValidator, NewRouter, NewTLSReloader, NewHttp, NewDB, NewMigrate, NewLoader, NewSession, NewCron, NewRunner, websitestat.NewAggregator)
|
||||
var ProviderSet = wire.NewSet(NewConf, NewT, NewLog, NewCli, NewRouter, NewTLSReloader, NewHttp, NewDB, NewMigrate, NewLoader, NewSession, NewCron, NewRunner, websitestat.NewAggregator)
|
||||
|
||||
@@ -32,7 +32,7 @@ func NewDB(conf *config.Config) (*gorm.DB, error) {
|
||||
options = append(options, sloggorm.WithTraceAll())
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("file:"+filepath.Join(app.Root, "panel/storage/panel.db")+"?_txlock=immediate&_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)"),
|
||||
db, err := gorm.Open(sqlite.Open("file:"+filepath.Join(app.Root, "panel/storage/panel.db")+"?_txlock=immediate&_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=synchronous(FULL)"),
|
||||
&gorm.Config{
|
||||
Logger: sloggorm.New(options...),
|
||||
SkipDefaultTransaction: true,
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/gookit/validate"
|
||||
"github.com/gookit/validate/locales/ruru"
|
||||
"github.com/gookit/validate/locales/zhcn"
|
||||
"github.com/gookit/validate/locales/zhtw"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/acepanel/panel/v3/internal/http/rule"
|
||||
"github.com/acepanel/panel/v3/pkg/config"
|
||||
)
|
||||
|
||||
// NewValidator just for register global rules
|
||||
func NewValidator(conf *config.Config, db *gorm.DB) *validate.Validation {
|
||||
switch conf.App.Locale {
|
||||
case "zh_CN":
|
||||
zhcn.RegisterGlobal()
|
||||
case "zh_TW":
|
||||
zhtw.RegisterGlobal()
|
||||
case "ru_RU":
|
||||
ruru.RegisterGlobal()
|
||||
}
|
||||
|
||||
validate.Config(func(opt *validate.GlobalOption) {
|
||||
opt.StopOnError = false
|
||||
opt.SkipOnEmpty = true
|
||||
})
|
||||
|
||||
// register global rules
|
||||
rule.GlobalRules(db)
|
||||
|
||||
return validate.NewEmpty()
|
||||
}
|
||||
+586
-163
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
@@ -22,18 +23,20 @@ import (
|
||||
"github.com/acepanel/panel/v3/pkg/io"
|
||||
"github.com/acepanel/panel/v3/pkg/shell"
|
||||
"github.com/acepanel/panel/v3/pkg/storage"
|
||||
"github.com/acepanel/panel/v3/pkg/systemctl"
|
||||
"github.com/acepanel/panel/v3/pkg/tools"
|
||||
"github.com/acepanel/panel/v3/pkg/types"
|
||||
)
|
||||
|
||||
type backupRepo struct {
|
||||
hr string
|
||||
t *gotext.Locale
|
||||
conf *config.Config
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
setting biz.SettingRepo
|
||||
website biz.WebsiteRepo
|
||||
hr string
|
||||
t *gotext.Locale
|
||||
conf *config.Config
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
setting biz.SettingRepo
|
||||
website biz.WebsiteRepo
|
||||
updating atomic.Bool // 面板升级进行中标志,防止并发触发
|
||||
}
|
||||
|
||||
func NewBackupRepo(t *gotext.Locale, conf *config.Config, db *gorm.DB, log *slog.Logger, setting biz.SettingRepo, website biz.WebsiteRepo) biz.BackupRepo {
|
||||
@@ -127,6 +130,12 @@ func (r *backupRepo) Create(ctx context.Context, typ biz.BackupType, target stri
|
||||
err = r.createMySQL(name, client, target)
|
||||
case biz.BackupTypePostgres:
|
||||
err = r.createPostgres(name, client, target)
|
||||
case biz.BackupTypeClickHouse:
|
||||
err = r.createClickHouse(name, client, target)
|
||||
case biz.BackupTypeRedis:
|
||||
err = r.createRedisLike(name, client, "redis")
|
||||
case biz.BackupTypeValkey:
|
||||
err = r.createRedisLike(name, client, "valkey")
|
||||
case biz.BackupTypePath:
|
||||
err = r.createPath(name, client, target)
|
||||
default:
|
||||
@@ -171,26 +180,34 @@ func (r *backupRepo) Create(ctx context.Context, typ biz.BackupType, target stri
|
||||
func (r *backupRepo) CreatePanel() error {
|
||||
start := time.Now()
|
||||
|
||||
backup := filepath.Join(r.GetDefaultPath(biz.BackupTypePanel), fmt.Sprintf("panel_%s%s", time.Now().Format("20060102150405"), r.backupExt()))
|
||||
backup := filepath.Join(r.GetDefaultPath(biz.BackupTypePanel), fmt.Sprintf("panel_%s.tar.xz", time.Now().Format("20060102150405")))
|
||||
|
||||
temp, err := os.MkdirTemp("", "ace-backup-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(path string) { _ = os.RemoveAll(path) }(temp)
|
||||
// 备份前 checkpoint 主库,尽量减少 -wal 中未落盘数据(热备份一致性)
|
||||
_ = r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error
|
||||
|
||||
if err = io.Cp(filepath.Join(app.Root, "panel"), temp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Cp("/usr/local/sbin/acepanel", temp); err != nil {
|
||||
return err
|
||||
// 只备份恢复面板运行所必需的核心文件;checkpoint 后 -wal 已并入 panel.db、
|
||||
// -shm 为纯临时共享内存,均无需备份
|
||||
var files []string
|
||||
for _, f := range []string{
|
||||
"panel/ace",
|
||||
"panel/storage/panel.db",
|
||||
"panel/storage/config.yml",
|
||||
"panel/storage/cert.pem",
|
||||
"panel/storage/cert.key",
|
||||
} {
|
||||
if io.Exists(filepath.Join(app.Root, f)) {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
|
||||
_ = io.Chmod(temp, 0600)
|
||||
if err = io.Compress(temp, nil, backup); err != nil {
|
||||
// 两个 -C 把 panel 内的核心文件与 panel 外的 cli 二进制收进同一个包
|
||||
if _, err := shell.Execf(
|
||||
"tar -cJf '%s' -C '%s' %s -C /usr/local/sbin acepanel",
|
||||
backup, app.Root, strings.Join(files, " "),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Chmod(backup, 0600); err != nil {
|
||||
if err := io.Chmod(backup, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -237,6 +254,12 @@ func (r *backupRepo) Restore(ctx context.Context, typ biz.BackupType, backup, ta
|
||||
err = r.restoreMySQL(backup, target)
|
||||
case biz.BackupTypePostgres:
|
||||
err = r.restorePostgres(backup, target)
|
||||
case biz.BackupTypeClickHouse:
|
||||
err = r.restoreClickHouse(backup, target)
|
||||
case biz.BackupTypeRedis:
|
||||
err = r.restoreRedisLike(backup, "redis")
|
||||
case biz.BackupTypeValkey:
|
||||
err = r.restoreRedisLike(backup, "valkey")
|
||||
default:
|
||||
return errors.New(r.t.Get("unknown backup type"))
|
||||
}
|
||||
@@ -260,9 +283,11 @@ func (r *backupRepo) Restore(ctx context.Context, typ biz.BackupType, backup, ta
|
||||
func (r *backupRepo) GetDefaultPath(typ biz.BackupType) string {
|
||||
backupPath, err := r.setting.Get(biz.SettingKeyBackupPath)
|
||||
if err != nil {
|
||||
return filepath.Join(app.Root, "backup", string(typ))
|
||||
backupPath = filepath.Join(app.Root, "backup")
|
||||
}
|
||||
return filepath.Join(backupPath, string(typ))
|
||||
path := filepath.Join(backupPath, string(typ))
|
||||
_ = os.MkdirAll(path, 0755)
|
||||
return path
|
||||
}
|
||||
|
||||
// CutoffLog 切割日志
|
||||
@@ -627,6 +652,119 @@ func (r *backupRepo) createPostgres(name string, storage storage.Storage, target
|
||||
return nil
|
||||
}
|
||||
|
||||
// createClickHouse 创建 ClickHouse 备份
|
||||
func (r *backupRepo) createClickHouse(name string, storage storage.Storage, target string) error {
|
||||
password, err := r.setting.Get(biz.SettingKeyClickHouseDefaultPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// clickhouse-client 走 native 9000 端口,本地 default 用户
|
||||
conn := fmt.Sprintf("--host 127.0.0.1 --port 9000 --user default --password '%s'", password)
|
||||
|
||||
// 校验数据库是否存在
|
||||
exist, err := shell.Execf("clickhouse-client %s --query \"SELECT count() FROM system.databases WHERE name = '%s'\"", conn, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(exist) == "0" {
|
||||
return errors.New(r.t.Get("database does not exist: %s", target))
|
||||
}
|
||||
|
||||
// 创建用于压缩的临时目录
|
||||
tmpDir, err := os.MkdirTemp("", "ace-backup-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(path string) { _ = os.RemoveAll(path) }(tmpDir)
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Temporary directory: %s", tmpDir))
|
||||
}
|
||||
|
||||
// 数据表(含数据)在前,视图(仅结构)在后
|
||||
dataTables, err := r.clickHouseTables(conn, target, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
views, err := r.clickHouseTables(conn, target, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 导出结构到 schema.sql,去掉库名限定(恢复时由 --database 指定目标库)
|
||||
objects := make([]string, 0, len(dataTables)+len(views))
|
||||
objects = append(objects, dataTables...)
|
||||
objects = append(objects, views...)
|
||||
var schema strings.Builder
|
||||
for _, tbl := range objects {
|
||||
create, err := shell.Execf("clickhouse-client %s --query \"SELECT create_table_query FROM system.tables WHERE database = '%s' AND name = '%s' FORMAT TabSeparatedRaw\"", conn, target, tbl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmt := strings.TrimSpace(create)
|
||||
stmt = strings.ReplaceAll(stmt, fmt.Sprintf("`%s`.", target), "")
|
||||
stmt = strings.ReplaceAll(stmt, fmt.Sprintf("%s.", target), "")
|
||||
schema.WriteString(stmt)
|
||||
schema.WriteString(";\n")
|
||||
}
|
||||
if err = io.Write(filepath.Join(tmpDir, "schema.sql"), schema.String(), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 导出数据(仅数据表,Native 格式)
|
||||
files := []string{"schema.sql"}
|
||||
for _, tbl := range dataTables {
|
||||
dataFile := tbl + ".native"
|
||||
if _, err = shell.Execf("clickhouse-client %s --query 'SELECT * FROM `%s`.`%s` FORMAT Native' > '%s'", conn, target, tbl, filepath.Join(tmpDir, dataFile)); err != nil {
|
||||
return err
|
||||
}
|
||||
files = append(files, dataFile)
|
||||
}
|
||||
|
||||
// 压缩备份文件
|
||||
name = name + r.backupExt()
|
||||
if err = io.Compress(tmpDir, files, filepath.Join(tmpDir, name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 上传备份文件到存储器
|
||||
file, err := os.Open(filepath.Join(tmpDir, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(file *os.File) { _ = file.Close() }(file)
|
||||
|
||||
if err = storage.Put(filepath.Join(string(biz.BackupTypeClickHouse), name), file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Backup file: %s", name))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clickHouseTables 列出库中对象名,onlyView 为 true 时仅返回视图,否则返回非视图数据表
|
||||
func (r *backupRepo) clickHouseTables(conn, database string, onlyView bool) ([]string, error) {
|
||||
op := "NOT LIKE"
|
||||
if onlyView {
|
||||
op = "LIKE"
|
||||
}
|
||||
out, err := shell.Execf("clickhouse-client %s --query \"SELECT name FROM system.tables WHERE database = '%s' AND NOT is_temporary AND engine %s '%%View%%' ORDER BY name FORMAT TabSeparated\"", conn, database, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tables []string
|
||||
for line := range strings.SplitSeq(out, "\n") {
|
||||
if l := strings.TrimSpace(line); l != "" {
|
||||
tables = append(tables, l)
|
||||
}
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
// createPath 创建目录备份
|
||||
func (r *backupRepo) createPath(name string, storage storage.Storage, target string) error {
|
||||
if !io.Exists(target) {
|
||||
@@ -766,6 +904,276 @@ func (r *backupRepo) restorePostgres(backup, target string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreClickHouse 恢复 ClickHouse 备份
|
||||
func (r *backupRepo) restoreClickHouse(backup, target string) error {
|
||||
password, err := r.setting.Get(biz.SettingKeyClickHouseDefaultPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := fmt.Sprintf("--host 127.0.0.1 --port 9000 --user default --password '%s'", password)
|
||||
|
||||
// 校验目标数据库是否存在
|
||||
exist, err := shell.Execf("clickhouse-client %s --query \"SELECT count() FROM system.databases WHERE name = '%s'\"", conn, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(exist) == "0" {
|
||||
return errors.New(r.t.Get("database does not exist: %s", target))
|
||||
}
|
||||
|
||||
// 纯 SQL 文件直接执行
|
||||
if strings.HasSuffix(backup, ".sql") {
|
||||
_, err = shell.Execf("clickhouse-client %s --database '%s' --multiquery < '%s'", conn, target, backup)
|
||||
return err
|
||||
}
|
||||
|
||||
// 解压到临时目录
|
||||
tmpDir, err := os.MkdirTemp("", "acepanel-ch-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(path string) { _ = os.RemoveAll(path) }(tmpDir)
|
||||
|
||||
if err = io.UnCompress(backup, tmpDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 先恢复结构(视图已排在末尾,源表先建好,规避物化视图依赖顺序问题)
|
||||
schemaPath := filepath.Join(tmpDir, "schema.sql")
|
||||
if io.Exists(schemaPath) {
|
||||
if _, err = shell.Execf("clickhouse-client %s --database '%s' --multiquery < '%s'", conn, target, schemaPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 再导入数据
|
||||
entries, err := os.ReadDir(tmpDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".native") {
|
||||
continue
|
||||
}
|
||||
tbl := strings.TrimSuffix(entry.Name(), ".native")
|
||||
if _, err = shell.Execf("clickhouse-client %s --database '%s' --query 'INSERT INTO `%s` FORMAT Native' < '%s'", conn, target, tbl, filepath.Join(tmpDir, entry.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// redisLikeConf redis/valkey 的连接与持久化参数
|
||||
type redisLikeConf struct {
|
||||
kind string // redis / valkey
|
||||
cli string // redis-cli / valkey-cli
|
||||
authEnv string // REDISCLI_AUTH / VALKEYCLI_AUTH
|
||||
dataDir string // {app.Root}/server/{kind}
|
||||
confPath string
|
||||
port string
|
||||
password string
|
||||
appendonly bool
|
||||
}
|
||||
|
||||
// loadRedisLikeConf 从 {kind}.conf 读取连接与持久化参数
|
||||
func (r *backupRepo) loadRedisLikeConf(kind string) (*redisLikeConf, error) {
|
||||
dataDir := fmt.Sprintf("%s/server/%s", app.Root, kind)
|
||||
confPath := fmt.Sprintf("%s/%s.conf", dataDir, kind)
|
||||
content, err := io.Read(confPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
port := redisLikeValue(content, "port")
|
||||
if port == "" {
|
||||
port = "6379"
|
||||
}
|
||||
authEnv := "REDISCLI_AUTH"
|
||||
if kind == "valkey" {
|
||||
authEnv = "VALKEYCLI_AUTH"
|
||||
}
|
||||
|
||||
return &redisLikeConf{
|
||||
kind: kind,
|
||||
cli: kind + "-cli",
|
||||
authEnv: authEnv,
|
||||
dataDir: dataDir,
|
||||
confPath: confPath,
|
||||
port: port,
|
||||
password: redisLikeValue(content, "requirepass"),
|
||||
appendonly: redisLikeValue(content, "appendonly") == "yes",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// redisLikeValue 从 redis/valkey 配置内容中读取指定键的有效值(忽略注释行)
|
||||
func redisLikeValue(content, key string) string {
|
||||
for line := range strings.SplitSeq(content, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(trimmed)
|
||||
if len(fields) >= 2 && fields[0] == key {
|
||||
return strings.Join(fields[1:], " ")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// createRedisLike 创建 Redis/Valkey 整实例备份({kind}-cli --rdb 导出快照)
|
||||
// redis 与 valkey 共用本实现,kind 为 "redis" 或 "valkey"
|
||||
func (r *backupRepo) createRedisLike(name string, storage storage.Storage, kind string) error {
|
||||
conf, err := r.loadRedisLikeConf(kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 用环境变量传密码,避免密码出现在命令行/进程列表
|
||||
if conf.password != "" {
|
||||
_ = os.Setenv(conf.authEnv, conf.password)
|
||||
defer func() { _ = os.Unsetenv(conf.authEnv) }()
|
||||
}
|
||||
|
||||
// 创建用于压缩的临时目录
|
||||
tmpDir, err := os.MkdirTemp("", "ace-backup-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(path string) { _ = os.RemoveAll(path) }(tmpDir)
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Temporary directory: %s", tmpDir))
|
||||
}
|
||||
|
||||
// 通过复制协议拉取整实例 RDB 快照到本地文件
|
||||
rdb := filepath.Join(tmpDir, "dump.rdb")
|
||||
if _, err = shell.Execf("%s -h 127.0.0.1 -p %s --rdb '%s'", conf.cli, conf.port, rdb); err != nil {
|
||||
return err
|
||||
}
|
||||
if !io.Exists(rdb) {
|
||||
return errors.New(r.t.Get("failed to export RDB snapshot"))
|
||||
}
|
||||
|
||||
// 压缩备份文件
|
||||
name = name + r.backupExt()
|
||||
if err = io.Compress(tmpDir, []string{"dump.rdb"}, filepath.Join(tmpDir, name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 上传备份文件到存储器
|
||||
file, err := os.Open(filepath.Join(tmpDir, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(file *os.File) { _ = file.Close() }(file)
|
||||
|
||||
if err = storage.Put(filepath.Join(kind, name), file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Backup file: %s", name))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreRedisLike 恢复 Redis/Valkey 整实例备份
|
||||
// 停服务 → 替换 dump.rdb → 启动;妥善处理 AOF 优先级陷阱(appendonly=yes 时 AOF 会盖过 RDB)
|
||||
func (r *backupRepo) restoreRedisLike(backup, kind string) error {
|
||||
conf, err := r.loadRedisLikeConf(kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 准备 dump.rdb:裸 .rdb 直接用,否则解压取包内的 dump.rdb
|
||||
rdb := backup
|
||||
if !strings.HasSuffix(backup, ".rdb") {
|
||||
tmpDir, err := os.MkdirTemp("", "acepanel-rdb-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(path string) { _ = os.RemoveAll(path) }(tmpDir)
|
||||
if err = io.UnCompress(backup, tmpDir); err != nil {
|
||||
return err
|
||||
}
|
||||
rdb = filepath.Join(tmpDir, "dump.rdb")
|
||||
if !io.Exists(rdb) {
|
||||
return errors.New(r.t.Get("dump.rdb not found in backup file"))
|
||||
}
|
||||
}
|
||||
|
||||
// 停止服务
|
||||
if err = systemctl.Stop(conf.kind); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清理旧 AOF(多部件目录与旧式单文件),避免 AOF 优先于 RDB 被加载
|
||||
_ = io.Remove(filepath.Join(conf.dataDir, "appendonlydir"))
|
||||
_ = io.Remove(filepath.Join(conf.dataDir, "appendonly.aof"))
|
||||
|
||||
// 覆盖 dump.rdb
|
||||
target := filepath.Join(conf.dataDir, "dump.rdb")
|
||||
if err = io.Cp(rdb, target); err != nil {
|
||||
_ = systemctl.Start(conf.kind) // 尽力恢复服务
|
||||
return err
|
||||
}
|
||||
_ = io.Chown(target, kind, kind)
|
||||
_ = io.Chmod(target, 0640)
|
||||
|
||||
// 若原本开启 AOF,必须先以 appendonly no 启动加载 RDB,否则会建空 AOF 以空库覆盖
|
||||
if conf.appendonly {
|
||||
if err = r.disableAppendonly(conf.confPath); err != nil {
|
||||
_ = systemctl.Start(conf.kind)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 启动服务(Type=notify,返回即已加载 RDB)
|
||||
if err = systemctl.Start(conf.kind); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 原本开启 AOF 的,在线转回并持久化配置
|
||||
if conf.appendonly {
|
||||
if conf.password != "" {
|
||||
_ = os.Setenv(conf.authEnv, conf.password)
|
||||
defer func() { _ = os.Unsetenv(conf.authEnv) }()
|
||||
}
|
||||
_, _ = shell.Execf("%s -h 127.0.0.1 -p %s config set appendonly yes", conf.cli, conf.port)
|
||||
_, _ = shell.Execf("%s -h 127.0.0.1 -p %s config rewrite", conf.cli, conf.port)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// disableAppendonly 将 redis/valkey 配置中的 appendonly 临时改为 no
|
||||
func (r *backupRepo) disableAppendonly(confPath string) error {
|
||||
content, err := io.Read(confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
found := false
|
||||
for i, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
if fields := strings.Fields(trimmed); len(fields) >= 1 && fields[0] == "appendonly" {
|
||||
lines[i] = "appendonly no"
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
lines = append(lines, "appendonly no")
|
||||
}
|
||||
|
||||
return io.Write(confPath, strings.Join(lines, "\n"), 0644)
|
||||
}
|
||||
|
||||
// autoUnCompressSQL 自动处理压缩文件
|
||||
func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) {
|
||||
temp, err := os.MkdirTemp("", "acepanel-sql-*")
|
||||
@@ -802,10 +1210,9 @@ func (r *backupRepo) FixPanel() error {
|
||||
// 检查关键文件是否正常
|
||||
panelBroken := !io.Exists(filepath.Join(app.Root, "panel", "ace")) ||
|
||||
!io.Exists(filepath.Join(app.Root, "panel", "storage", "config.yml")) ||
|
||||
!io.Exists(filepath.Join(app.Root, "panel", "storage", "panel.db")) ||
|
||||
io.Exists("/tmp/panel-storage.zip")
|
||||
// 检查主数据库连接
|
||||
if err := r.db.Exec("VACUUM").Error; err != nil {
|
||||
!io.Exists(filepath.Join(app.Root, "panel", "storage", "panel.db"))
|
||||
// 检查主数据库完整性
|
||||
if !quickCheck(r.db) {
|
||||
panelBroken = true
|
||||
}
|
||||
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
||||
@@ -816,13 +1223,17 @@ func (r *backupRepo) FixPanel() error {
|
||||
var brokenAuxDBs []string
|
||||
for _, name := range []string{"stat", "scan"} {
|
||||
auxDB, err := openDB(name)
|
||||
if err == nil {
|
||||
if sqlDB, dbErr := auxDB.DB(); dbErr == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
if err != nil {
|
||||
brokenAuxDBs = append(brokenAuxDBs, name)
|
||||
continue
|
||||
}
|
||||
brokenAuxDBs = append(brokenAuxDBs, name)
|
||||
ok := quickCheck(auxDB)
|
||||
if sqlDB, dbErr := auxDB.DB(); dbErr == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
if !ok {
|
||||
brokenAuxDBs = append(brokenAuxDBs, name)
|
||||
}
|
||||
}
|
||||
|
||||
// 一切正常,无需修复
|
||||
@@ -853,22 +1264,6 @@ func (r *backupRepo) FixPanel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 再次确认是否需要修复
|
||||
if io.Exists("/tmp/panel-storage.zip") {
|
||||
// 文件齐全情况下只移除临时文件
|
||||
if io.Exists(filepath.Join(app.Root, "panel", "ace")) &&
|
||||
io.Exists(filepath.Join(app.Root, "panel", "storage", "config.yml")) &&
|
||||
io.Exists(filepath.Join(app.Root, "panel", "storage", "panel.db")) {
|
||||
if err := io.Remove("/tmp/panel-storage.zip"); err != nil {
|
||||
return errors.New(r.t.Get("failed to clean temporary files: %v", err))
|
||||
}
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Cleaned up temporary files, please run acepanel update to update the panel"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 从备份目录中找最新的备份文件
|
||||
files, err := os.ReadDir(r.GetDefaultPath(biz.BackupTypePanel))
|
||||
if err != nil {
|
||||
@@ -876,6 +1271,9 @@ func (r *backupRepo) FixPanel() error {
|
||||
}
|
||||
var list []os.FileInfo
|
||||
for _, file := range files {
|
||||
if !r.isBackupArchive(file.Name()) {
|
||||
continue
|
||||
}
|
||||
info, infoErr := file.Info()
|
||||
if infoErr != nil {
|
||||
continue
|
||||
@@ -923,19 +1321,6 @@ func (r *backupRepo) FixPanel() error {
|
||||
}
|
||||
}
|
||||
|
||||
// tmp 目录下如果有 storage 备份,则解压回去
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Restore panel data..."))
|
||||
}
|
||||
if io.Exists("/tmp/panel-storage.zip") {
|
||||
if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel")); err != nil {
|
||||
return errors.New(r.t.Get("Unzip panel data failed: %v", err))
|
||||
}
|
||||
if err = io.Remove("/tmp/panel-storage.zip"); err != nil {
|
||||
return errors.New(r.t.Get("Cleaning temporary file failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 下载服务文件
|
||||
if !io.Exists("/etc/systemd/system/acepanel.service") {
|
||||
if _, err = shell.Execf(`wget -O /etc/systemd/system/acepanel.service https://%s/acepanel.service && sed -i "s|/opt/ace|%s|g" /etc/systemd/system/acepanel.service`, r.conf.App.DownloadEndpoint, app.Root); err != nil {
|
||||
@@ -975,132 +1360,170 @@ func (r *backupRepo) FixPanel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *backupRepo) UpdatePanel(version, url, checksum string) error {
|
||||
// 预先优化数据库
|
||||
if err := r.db.Exec("VACUUM").Error; err != nil {
|
||||
// UpdatePanel 升级面板
|
||||
func (r *backupRepo) UpdatePanel(version, url, checksum string, progress func(string)) error {
|
||||
if progress == nil {
|
||||
progress = func(string) {}
|
||||
}
|
||||
|
||||
// 进程级升级锁
|
||||
if !r.updating.CompareAndSwap(false, true) {
|
||||
return errors.New(r.t.Get("panel is already updating, please try again later"))
|
||||
}
|
||||
defer r.updating.Store(false)
|
||||
|
||||
panelDir := filepath.Join(app.Root, "panel")
|
||||
workDir := filepath.Join(panelDir, ".update-work") // staging 目录固定在 panel 内,绝不用 /tmp(可能跨分区)
|
||||
newDir := filepath.Join(workDir, "new")
|
||||
name := filepath.Base(url)
|
||||
|
||||
// 失败回滚
|
||||
rollback := func(err error) error {
|
||||
_ = io.Remove(workDir)
|
||||
app.Status = app.StatusNormal
|
||||
return err
|
||||
}
|
||||
|
||||
app.Status = app.StatusUpgrade
|
||||
|
||||
progress(r.t.Get("Preparing to update to %s...", version))
|
||||
if err := io.Remove(workDir); err != nil {
|
||||
return rollback(errors.New(r.t.Get("Failed to clean up temporary directory: %v", err)))
|
||||
}
|
||||
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
||||
return err
|
||||
return rollback(errors.New(r.t.Get("Failed to optimize database: %v", err)))
|
||||
}
|
||||
|
||||
name := filepath.Base(url)
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Target version: %s", version))
|
||||
fmt.Println(r.t.Get("|-Download link: %s", url))
|
||||
fmt.Println(r.t.Get("|-File name: %s", name))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Downloading..."))
|
||||
}
|
||||
if _, err := shell.Execf("aria2c -c --file-allocation=falloc --allow-overwrite=true --auto-file-renaming=false --retry-wait=5 --max-tries=5 -x 16 -s 16 -k 1M -d /tmp -o %s %s", name, url); err != nil {
|
||||
return errors.New(r.t.Get("Download failed: %v", err))
|
||||
}
|
||||
if _, err := shell.Execf("aria2c -c --file-allocation=falloc --allow-overwrite=true --auto-file-renaming=false --retry-wait=5 --max-tries=5 -x 1 -s 1 -k 1M -d /tmp -o %s %s", name+".sha256", checksum); err != nil {
|
||||
return errors.New(r.t.Get("Download failed: %v", err))
|
||||
}
|
||||
if !io.Exists(filepath.Join("/tmp", name)) || !io.Exists(filepath.Join("/tmp", name+".sha256")) {
|
||||
return errors.New(r.t.Get("Download file check failed"))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Verify download file..."))
|
||||
}
|
||||
if check, err := shell.Execf("cd /tmp && sha256sum -c %s --ignore-missing", name+".sha256"); check != name+": OK" || err != nil {
|
||||
return errors.New(r.t.Get("Verify download file failed: %v", err))
|
||||
}
|
||||
if err := io.Remove(filepath.Join("/tmp", name+".sha256")); err != nil {
|
||||
return errors.New(r.t.Get("|-Clean up verification file failed: %v", err))
|
||||
}
|
||||
|
||||
if io.Exists("/tmp/panel-storage.zip") {
|
||||
return errors.New(r.t.Get("Temporary file detected in /tmp, this may be caused by the last update failure, please run acepanel fix to repair and try again"))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Backup panel data..."))
|
||||
}
|
||||
// 备份面板
|
||||
// 备份留档
|
||||
progress(r.t.Get("Backing up panel data..."))
|
||||
if err := r.CreatePanel(); err != nil {
|
||||
return errors.New(r.t.Get("|-Backup panel data failed: %v", err))
|
||||
}
|
||||
if err := io.Compress(filepath.Join(app.Root, "panel/storage"), nil, "/tmp/panel-storage.zip"); err != nil {
|
||||
return errors.New(r.t.Get("|-Backup panel data failed: %v", err))
|
||||
}
|
||||
if !io.Exists("/tmp/panel-storage.zip") {
|
||||
return errors.New(r.t.Get("|-Backup panel data failed, missing file"))
|
||||
r.log.Warn("failed to backup panel before update", slog.Any("err", err))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Cleaning old version..."))
|
||||
// 下载新版本
|
||||
if err := os.MkdirAll(workDir, 0700); err != nil {
|
||||
return rollback(errors.New(r.t.Get("Failed to create temporary directory: %v", err)))
|
||||
}
|
||||
if _, err := shell.Execf("rm -rf %s/panel/*", app.Root); err != nil {
|
||||
return errors.New(r.t.Get("|-Cleaning old version failed: %v", err))
|
||||
progress(r.t.Get("Downloading new version..."))
|
||||
if _, err := shell.Execf("aria2c -c --file-allocation=falloc --allow-overwrite=true --auto-file-renaming=false --retry-wait=5 --max-tries=5 -x 16 -s 16 -k 1M -d %s -o %s %s", workDir, name, url); err != nil {
|
||||
return rollback(errors.New(r.t.Get("Download failed: %v", err)))
|
||||
}
|
||||
if _, err := shell.Execf("aria2c -c --file-allocation=falloc --allow-overwrite=true --auto-file-renaming=false --retry-wait=5 --max-tries=5 -x 1 -s 1 -k 1M -d %s -o %s %s", workDir, name+".sha256", checksum); err != nil {
|
||||
return rollback(errors.New(r.t.Get("Download failed: %v", err)))
|
||||
}
|
||||
if !io.Exists(filepath.Join(workDir, name)) || !io.Exists(filepath.Join(workDir, name+".sha256")) {
|
||||
return rollback(errors.New(r.t.Get("Download file check failed")))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Unzip new version..."))
|
||||
}
|
||||
if err := io.UnCompress(filepath.Join("/tmp", name), filepath.Join(app.Root, "panel")); err != nil {
|
||||
return errors.New(r.t.Get("|-Unzip new version failed: %v", err))
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel", "ace")) {
|
||||
return errors.New(r.t.Get("|-Unzip new version failed, missing file"))
|
||||
}
|
||||
if err := io.Remove(filepath.Join("/tmp", name)); err != nil {
|
||||
return errors.New(r.t.Get("|-Clean up temporary file failed: %v", err))
|
||||
// 校验 sha256
|
||||
progress(r.t.Get("Verifying download file..."))
|
||||
if check, err := shell.ExecfWithDir(workDir, "sha256sum -c %s --ignore-missing", name+".sha256"); check != name+": OK" || err != nil {
|
||||
return rollback(errors.New(r.t.Get("Verify download file failed: %v", err)))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Restore panel data..."))
|
||||
// 解压
|
||||
progress(r.t.Get("Extracting new version..."))
|
||||
if err := io.UnCompress(filepath.Join(workDir, name), newDir); err != nil {
|
||||
return rollback(errors.New(r.t.Get("Unzip new version failed: %v", err)))
|
||||
}
|
||||
if err := io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel", "storage")); err != nil {
|
||||
return errors.New(r.t.Get("|-Restore panel data failed: %v", err))
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel/storage/panel.db")) {
|
||||
return errors.New(r.t.Get("|-Restore panel data failed, missing file"))
|
||||
if !io.Exists(filepath.Join(newDir, "ace")) {
|
||||
return rollback(errors.New(r.t.Get("Unzip new version failed, missing file")))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Run post-update script..."))
|
||||
}
|
||||
if _, err := shell.Execf("curl -sSLm 10 https://%s/auto_update.sh | bash", r.conf.App.DownloadEndpoint); err != nil {
|
||||
return errors.New(r.t.Get("|-Run post-update script failed: %v", err))
|
||||
}
|
||||
if _, err := shell.Execf(
|
||||
`wget -O /etc/systemd/system/acepanel.service https://%s/acepanel.service && sed -i "s|/www|%s|g" /etc/systemd/system/acepanel.service`,
|
||||
r.conf.App.DownloadEndpoint, app.Root,
|
||||
); err != nil {
|
||||
return errors.New(r.t.Get("|-Download panel service file failed: %v", err))
|
||||
}
|
||||
if _, err := shell.Execf("acepanel setting write version %s", version); err != nil {
|
||||
return errors.New(r.t.Get("|-Write new panel version failed: %v", err))
|
||||
}
|
||||
if err := io.Mv(filepath.Join(app.Root, "panel/cli"), "/usr/local/sbin/acepanel"); err != nil {
|
||||
return errors.New(r.t.Get("|-Move acepanel tool failed: %v", err))
|
||||
// 应用
|
||||
progress(r.t.Get("Applying update..."))
|
||||
if err := r.applyUpdate(newDir); err != nil {
|
||||
return rollback(errors.New(r.t.Get("Applying update failed: %v", err)))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Set key file permissions..."))
|
||||
}
|
||||
_ = io.Chmod("/usr/local/sbin/acepanel", 0700)
|
||||
_ = io.Chmod("/etc/systemd/system/acepanel.service", 0644)
|
||||
_ = io.Chmod(filepath.Join(app.Root, "panel"), 0700)
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Update completed"))
|
||||
// 收尾
|
||||
progress(r.t.Get("Finishing up..."))
|
||||
if err := r.finishUpdate(version); err != nil {
|
||||
return rollback(errors.New(r.t.Get("Finishing update failed: %v", err)))
|
||||
}
|
||||
|
||||
_ = io.Remove(workDir)
|
||||
r.log.Info("panel updated", slog.String("version", version))
|
||||
progress(r.t.Get("Update completed"))
|
||||
|
||||
_, _ = shell.Execf("systemctl daemon-reload")
|
||||
_ = io.Remove("/tmp/panel-storage.zip")
|
||||
_ = io.Remove(filepath.Join(app.Root, "panel/config.example.yml"))
|
||||
// 由调用方重启面板
|
||||
if sqlDB, err := r.db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
tools.RestartPanel()
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyUpdate 用 newDir 下的新版本文件替换 panel/ 中的程序文件
|
||||
func (r *backupRepo) applyUpdate(newDir string) error {
|
||||
entries, err := os.ReadDir(newDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
panelDir := filepath.Join(app.Root, "panel")
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if name == "storage" {
|
||||
continue
|
||||
}
|
||||
src := filepath.Join(newDir, name)
|
||||
if name == "cli" {
|
||||
// 先 cp 保证原子替换
|
||||
tmp := "/usr/local/sbin/.acepanel.new"
|
||||
if err = io.Cp(src, tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Mv(tmp, "/usr/local/sbin/acepanel"); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 其余程序文件
|
||||
if err = io.Mv(src, filepath.Join(panelDir, name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// finishUpdate 升级收尾
|
||||
func (r *backupRepo) finishUpdate(version string) error {
|
||||
panelDir := filepath.Join(app.Root, "panel")
|
||||
serviceFile := "/etc/systemd/system/acepanel.service"
|
||||
|
||||
// 更新 service 文件
|
||||
tmpService := serviceFile + ".new"
|
||||
if _, err := shell.Execf(`wget -O %s https://%s/acepanel.service`, tmpService, r.conf.App.DownloadEndpoint); err == nil {
|
||||
_, _ = shell.Execf(`sed -i "s|/opt/ace|%s|g" %s`, app.Root, tmpService)
|
||||
if out, _ := shell.Execf("grep -c ExecStart %s", tmpService); strings.TrimSpace(out) != "0" {
|
||||
_ = io.Mv(tmpService, serviceFile) // 同在 /etc/systemd/system → 同分区 rename
|
||||
}
|
||||
}
|
||||
_ = io.Remove(tmpService)
|
||||
if !io.Exists(serviceFile) {
|
||||
return errors.New(r.t.Get("panel service file is missing"))
|
||||
}
|
||||
// 校验 unit 指向的主程序确实存在,避免 daemon-reload 后起不来
|
||||
if !io.Exists(filepath.Join(panelDir, "ace")) {
|
||||
return errors.New(r.t.Get("panel binary is missing after update"))
|
||||
}
|
||||
_, _ = shell.Execf("systemctl daemon-reload")
|
||||
|
||||
// 执行后置脚本
|
||||
_, _ = shell.Execf("curl -sSLm 10 --fail --retry 3 https://%s/auto_update.sh | bash", r.conf.App.DownloadEndpoint)
|
||||
|
||||
// 后置脚本之后再写版本号
|
||||
if err := r.setting.Set(biz.SettingKeyVersion, version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置权限
|
||||
_ = io.Chmod(filepath.Join(panelDir, "ace"), 0700)
|
||||
_ = io.Chmod("/usr/local/sbin/acepanel", 0700)
|
||||
_ = io.Chmod(serviceFile, 0644)
|
||||
_ = io.Remove(filepath.Join(panelDir, "config.example.yml"))
|
||||
|
||||
// 修正可能从 staging 继承的错误 SELinux 上下文
|
||||
_, _ = shell.Execf("restorecon %s /usr/local/sbin/acepanel %s", filepath.Join(panelDir, "ace"), serviceFile)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1113,7 +1536,7 @@ func (r *backupRepo) backupExt() string {
|
||||
|
||||
// isBackupArchive 判断文件名是否是已知的备份压缩包后缀
|
||||
func (r *backupRepo) isBackupArchive(name string) bool {
|
||||
for _, ext := range []string{".tar.xz", ".tar.gz", ".zip", ".7z"} {
|
||||
for _, ext := range []string{".tar.xz", ".tar.gz", ".tar.zst", ".zip", ".7z"} {
|
||||
if strings.HasSuffix(name, ext) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func (r certAccountRepo) getGoogleEAB() (*acme.EAB, error) {
|
||||
client.SetRetryCount(3)
|
||||
|
||||
resp, err := client.R().SetResult(&data{}).Get("https://gts.rat.dev/eab")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
if err != nil || !resp.IsStatusSuccess() {
|
||||
return &acme.EAB{}, errors.New(r.t.Get("failed to get Google EAB: %v", err))
|
||||
}
|
||||
eab := resp.Result().(*data)
|
||||
@@ -238,7 +238,7 @@ func (r certAccountRepo) getZeroSSLEAB(email string) (*acme.EAB, error) {
|
||||
resp, err := client.R().SetFormData(map[string]string{
|
||||
"email": email,
|
||||
}).SetResult(&data{}).Post("https://api.zerossl.com/acme/eab-credentials-email")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
if err != nil || !resp.IsStatusSuccess() {
|
||||
return &acme.EAB{}, errors.New(r.t.Get("failed to get ZeroSSL EAB: %v", err))
|
||||
}
|
||||
eab := resp.Result().(*data)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/acepanel/panel/v3/internal/biz"
|
||||
"github.com/acepanel/panel/v3/internal/http/request"
|
||||
"github.com/acepanel/panel/v3/pkg/docker"
|
||||
"github.com/acepanel/panel/v3/pkg/types"
|
||||
)
|
||||
|
||||
@@ -311,30 +312,37 @@ func (r *containerRepo) Rename(id string, newName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Logs 查看容器日志
|
||||
func (r *containerRepo) Logs(id string) (string, error) {
|
||||
// Logs 查看容器末尾 tail 行日志
|
||||
func (r *containerRepo) Logs(id string, tail int) (string, error) {
|
||||
apiClient, err := getDockerClient(getContainerSock(r.settingRepo))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient)
|
||||
|
||||
// 非 TTY 容器日志为多路复用流,需按 TTY 设置决定是否解复用
|
||||
inspect, err := apiClient.ContainerInspect(context.Background(), id, client.ContainerInspectOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tty := inspect.Container.Config != nil && inspect.Container.Config.Tty
|
||||
|
||||
reader, err := apiClient.ContainerLogs(context.Background(), id, client.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Tail: "100",
|
||||
Tail: strconv.Itoa(tail),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func(reader io.ReadCloser) { _ = reader.Close() }(reader)
|
||||
defer func(reader client.ContainerLogsResult) { _ = reader.Close() }(reader)
|
||||
|
||||
logs, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
var buf bytes.Buffer
|
||||
if err = docker.CopyLogs(&buf, reader, tty); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(logs), nil
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// Prune 清理未使用的容器
|
||||
|
||||
@@ -2,6 +2,7 @@ package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
@@ -151,6 +152,15 @@ func (r *containerNetworkRepo) Remove(id string) error {
|
||||
}
|
||||
defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient)
|
||||
|
||||
// 拦截受保护网络的删除
|
||||
info, err := apiClient.NetworkInspect(context.Background(), id, client.NetworkInspectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Network.Name == "acepanel-network" {
|
||||
return errors.New("cannot delete acepanel-network")
|
||||
}
|
||||
|
||||
_, err = apiClient.NetworkRemove(context.Background(), id, client.NetworkRemoveOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ func (r *cronRepo) generateScript(typ string, config types.CronConfig, rawScript
|
||||
switch config.Type {
|
||||
case "website":
|
||||
_, _ = fmt.Fprintf(&sb, "acepanel backup website -n '%s' -s '%d'\n", target, config.Storage)
|
||||
case "mysql", "postgresql":
|
||||
case "mysql", "postgresql", "clickhouse", "redis", "valkey":
|
||||
_, _ = fmt.Fprintf(&sb, "acepanel backup database -t '%s' -n '%s' -s '%d'\n", config.Type, target, config.Storage)
|
||||
case "path":
|
||||
_, _ = fmt.Fprintf(&sb, "acepanel backup path -p '%s' -s '%d'\n", target, config.Storage)
|
||||
@@ -299,7 +299,7 @@ func (r *cronRepo) generateScript(typ string, config types.CronConfig, rawScript
|
||||
switch config.Type {
|
||||
case "website":
|
||||
_, _ = fmt.Fprintf(&sb, "acepanel backup clear -t website -f '%s' -k '%d' -s '%d'\n", target, config.Keep, config.Storage)
|
||||
case "mysql", "postgresql":
|
||||
case "mysql", "postgresql", "clickhouse", "redis", "valkey":
|
||||
_, _ = fmt.Fprintf(&sb, "acepanel backup clear -t '%s' -f '%s' -k '%d' -s '%d'\n", config.Type, target, config.Keep, config.Storage)
|
||||
case "path":
|
||||
_, _ = fmt.Fprintf(&sb, "acepanel backup clear -t path -f '%s' -k '%d' -s '%d'\n", filepath.Base(target), config.Keep, config.Storage)
|
||||
|
||||
@@ -121,6 +121,10 @@ func (r *databaseServerRepo) UpdatePassword(name string, password string) error
|
||||
return r.db.Model(&biz.DatabaseServer{}).Where("name = ?", name).Update("password", password).Error
|
||||
}
|
||||
|
||||
func (r *databaseServerRepo) UpdatePort(name string, port uint) error {
|
||||
return r.db.Model(&biz.DatabaseServer{}).Where("name = ?", name).Update("port", port).Error
|
||||
}
|
||||
|
||||
func (r *databaseServerRepo) Delete(id uint) error {
|
||||
if err := r.ClearUsers(id); err != nil {
|
||||
return err
|
||||
|
||||
+10
-1
@@ -55,7 +55,7 @@ func getOperatorID(ctx context.Context) uint64 {
|
||||
// openDB 打开数据库
|
||||
func openDB(name string) (*gorm.DB, error) {
|
||||
dsn := "file:" + filepath.Join(app.Root, fmt.Sprintf("panel/storage/%s.db", name)) +
|
||||
"?_txlock=immediate&_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)"
|
||||
"?_txlock=immediate&_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=synchronous(FULL)"
|
||||
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
|
||||
SkipDefaultTransaction: true,
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
@@ -69,6 +69,15 @@ func openDB(name string) (*gorm.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// quickCheck 用 PRAGMA quick_check 检测 SQLite 数据库是否损坏
|
||||
func quickCheck(db *gorm.DB) bool {
|
||||
var result string
|
||||
if err := db.Raw("PRAGMA quick_check").Row().Scan(&result); err != nil {
|
||||
return false
|
||||
}
|
||||
return result == "ok"
|
||||
}
|
||||
|
||||
// upsert 分批大小,避免超出 SQLite 变量数限制
|
||||
const upsertBatchSize = 100
|
||||
|
||||
|
||||
+42
-16
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/acepanel/panel/v3/pkg/punycode"
|
||||
"github.com/acepanel/panel/v3/pkg/shell"
|
||||
"github.com/acepanel/panel/v3/pkg/systemctl"
|
||||
"github.com/acepanel/panel/v3/pkg/tools"
|
||||
"github.com/acepanel/panel/v3/pkg/types"
|
||||
"github.com/acepanel/panel/v3/pkg/webserver"
|
||||
webservertypes "github.com/acepanel/panel/v3/pkg/webserver/types"
|
||||
@@ -147,6 +148,7 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
setting.Listens = vhost.Listen()
|
||||
// 域名
|
||||
domains := vhost.ServerName()
|
||||
domains = lo.Map(domains, func(d string, _ int) string { return tools.UnwrapIPv6(d) })
|
||||
domains, err = punycode.DecodeDomains(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -201,7 +203,7 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
if phpVhost, ok := vhost.(webservertypes.PHPVhost); ok {
|
||||
setting.PHP = phpVhost.PHP()
|
||||
// 伪静态
|
||||
setting.Rewrite = phpVhost.Config("010-rewrite.conf", "site")
|
||||
setting.Rewrite = phpVhost.Config("010-rewrite.conf", webservertypes.ScopeSite)
|
||||
}
|
||||
|
||||
// 反向代理网站特有
|
||||
@@ -226,7 +228,7 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
setting.CustomConfigs = r.getCustomConfigs(configDir)
|
||||
|
||||
// 访问统计
|
||||
setting.StatEnabled = vhost.Config("021-stats-log.conf", "site") != ""
|
||||
setting.StatEnabled = vhost.Config("021-stats-log.conf", webservertypes.ScopeSite) != ""
|
||||
|
||||
return setting, err
|
||||
}
|
||||
@@ -322,6 +324,7 @@ func (r *websiteRepo) Create(ctx context.Context, req *request.WebsiteCreate) (*
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domains = lo.Map(domains, func(d string, _ int) string { return tools.WrapIPv6(d) })
|
||||
if err = vhost.SetServerName(domains); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -344,7 +347,7 @@ func (r *websiteRepo) Create(ctx context.Context, req *request.WebsiteCreate) (*
|
||||
case "apache":
|
||||
errorPageConfig = `ErrorDocument 404 /404.html`
|
||||
}
|
||||
if err = vhost.SetConfig("010-error-404.conf", "site", errorPageConfig); err != nil {
|
||||
if err = vhost.SetConfig("010-error-404.conf", webservertypes.ScopeSite, errorPageConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -368,7 +371,7 @@ func (r *websiteRepo) Create(ctx context.Context, req *request.WebsiteCreate) (*
|
||||
if err = phpVhost.SetIndex([]string{"index.php", "index.html"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = phpVhost.SetConfig("010-rewrite.conf", "site", "", true); err != nil {
|
||||
if err = phpVhost.SetRawConfig("010-rewrite.conf", webservertypes.ScopeSite, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cacheConfig string
|
||||
@@ -420,11 +423,33 @@ location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
|
||||
</FilesMatch>
|
||||
`
|
||||
}
|
||||
if err = phpVhost.SetConfig("010-cache.conf", "site", cacheConfig); err != nil {
|
||||
if err = phpVhost.SetConfig("010-cache.conf", webservertypes.ScopeSite, cacheConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 纯静态网站默认写入单页应用(SPA)前端路由回退配置
|
||||
if w.Type == biz.WebsiteTypeStatic {
|
||||
var spaConfig string
|
||||
switch webServer {
|
||||
case "nginx":
|
||||
spaConfig = `# single-page application route fallback, remove if not needed
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
`
|
||||
case "apache":
|
||||
spaConfig = `# single-page application route fallback, remove if not needed
|
||||
FallbackResource /index.html
|
||||
`
|
||||
}
|
||||
if spaConfig != "" {
|
||||
if err = vhost.SetRawConfig("800-spa.conf", webservertypes.ScopeSite, spaConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化网站目录
|
||||
if err = os.MkdirAll(req.Path, 0755); err != nil {
|
||||
return nil, err
|
||||
@@ -472,7 +497,7 @@ location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
|
||||
}
|
||||
|
||||
// 写配置
|
||||
if err = vhost.SetConfig("001-acme.conf", "site", ""); err != nil {
|
||||
if err = vhost.SetConfig("001-acme.conf", webservertypes.ScopeSite, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -582,6 +607,7 @@ func (r *websiteRepo) Update(ctx context.Context, req *request.WebsiteUpdate) er
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domains = lo.Map(domains, func(d string, _ int) string { return tools.WrapIPv6(d) })
|
||||
if err = vhost.SetServerName(domains); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -665,7 +691,7 @@ func (r *websiteRepo) Update(ctx context.Context, req *request.WebsiteUpdate) er
|
||||
return err
|
||||
}
|
||||
// 伪静态
|
||||
if err = phpVhost.SetConfig("010-rewrite.conf", "site", req.Rewrite, true); err != nil {
|
||||
if err = phpVhost.SetRawConfig("010-rewrite.conf", webservertypes.ScopeSite, req.Rewrite); err != nil {
|
||||
return err
|
||||
}
|
||||
// 防跨站
|
||||
@@ -765,8 +791,8 @@ func (r *websiteRepo) Update(ctx context.Context, req *request.WebsiteUpdate) er
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_ = vhost.RemoveConfig("010-stat-format.conf", "shared")
|
||||
_ = vhost.RemoveConfig("021-stats-log.conf", "site")
|
||||
_ = vhost.RemoveConfig("010-stat-format.conf", webservertypes.ScopeShared)
|
||||
_ = vhost.RemoveConfig("021-stats-log.conf", webservertypes.ScopeSite)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,7 +896,7 @@ func (r *websiteRepo) ResetConfig(id uint) error {
|
||||
return err
|
||||
}
|
||||
// 保存配置
|
||||
if err = vhost.SetConfig("001-acme.conf", "site", ""); err != nil {
|
||||
if err = vhost.SetConfig("001-acme.conf", webservertypes.ScopeSite, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = vhost.Save(); err != nil {
|
||||
@@ -1202,13 +1228,13 @@ func (r *websiteRepo) reloadWebServer() error {
|
||||
switch webServer {
|
||||
case "nginx":
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return err
|
||||
out, _ := shell.Execf("nginx -t")
|
||||
return fmt.Errorf("failed to reload nginx: %w; config test: %s", err, out)
|
||||
}
|
||||
case "apache":
|
||||
if err = systemctl.Reload("apache"); err != nil {
|
||||
_, err = shell.Execf("apachectl configtest")
|
||||
return err
|
||||
out, _ := shell.Execf("apachectl configtest")
|
||||
return fmt.Errorf("failed to reload apache: %w; config test: %s", err, out)
|
||||
}
|
||||
default:
|
||||
return errors.New(r.t.Get("unsupported web server: %s", webServer))
|
||||
@@ -1300,9 +1326,9 @@ func (r *websiteRepo) enableStat(vhost webservertypes.Vhost, name string) error
|
||||
'"https":"$https",'
|
||||
'"upstream_time":"$upstream_response_time",'
|
||||
'"upstream_status":"$upstream_status"}';`, safeName, name)
|
||||
if err := vhost.SetConfig("010-stat-format.conf", "shared", formatConf); err != nil {
|
||||
if err := vhost.SetConfig("010-stat-format.conf", webservertypes.ScopeShared, formatConf); err != nil {
|
||||
return err
|
||||
}
|
||||
logConf := fmt.Sprintf("client_body_in_single_buffer on;\naccess_log syslog:server=unix:/tmp/ace_stats.sock,nohostname,tag=%s ace_stat_%s;", safeName, safeName)
|
||||
return vhost.SetConfig("021-stats-log.conf", "site", logConf)
|
||||
return vhost.SetConfig("021-stats-log.conf", webservertypes.ScopeSite, logConf)
|
||||
}
|
||||
|
||||
@@ -100,15 +100,13 @@ func Entrance(t *gotext.Locale, conf *config.Config, session *sessions.Manager)
|
||||
}
|
||||
}
|
||||
|
||||
// 情况三:通过APIKey+入口路径访问,重写请求路径并跳过验证
|
||||
if strings.HasPrefix(r.URL.Path, entrance) && r.Header.Get("Authorization") != "" {
|
||||
// 只在设置了入口路径的情况下,才进行重写
|
||||
if entrance != "/" {
|
||||
if rctx := chi.RouteContext(r.Context()); rctx != nil {
|
||||
rctx.RoutePath = strings.TrimPrefix(rctx.RoutePath, entrance)
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, entrance)
|
||||
}
|
||||
// 情况三:通过 APIKey+入口路径访问 API,重写请求路径并跳过入口验证
|
||||
apiPath, ok := trimEntranceAPIPath(r.URL.Path, entrance)
|
||||
if ok && r.Header.Get("Authorization") != "" {
|
||||
if rctx := chi.RouteContext(r.Context()); rctx != nil {
|
||||
rctx.RoutePath = apiPath
|
||||
}
|
||||
r.URL.Path = apiPath
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
@@ -132,6 +130,29 @@ func Entrance(t *gotext.Locale, conf *config.Config, session *sessions.Manager)
|
||||
}
|
||||
}
|
||||
|
||||
func trimEntranceAPIPath(path string, entrance string) (string, bool) {
|
||||
if entrance == "/" {
|
||||
if path == "/api" || strings.HasPrefix(path, "/api/") {
|
||||
return path, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
if path != entrance && !strings.HasPrefix(path, entrance+"/") {
|
||||
return "", false
|
||||
}
|
||||
|
||||
apiPath := strings.TrimPrefix(path, entrance)
|
||||
if apiPath == "" {
|
||||
return "", false
|
||||
}
|
||||
if apiPath != "/api" && !strings.HasPrefix(apiPath, "/api/") {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return apiPath, true
|
||||
}
|
||||
|
||||
func abortEntrance(w http.ResponseWriter, r *http.Request, conf *config.Config, locale string) {
|
||||
errorType := conf.HTTP.EntranceError
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package middleware
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTrimEntranceAPIPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
entrance string
|
||||
wantPath string
|
||||
wantOK bool
|
||||
}{
|
||||
{name: "root api", path: "/api/user/info", entrance: "/", wantPath: "/api/user/info", wantOK: true},
|
||||
{name: "root api exact", path: "/api", entrance: "/", wantPath: "/api", wantOK: true},
|
||||
{name: "root api prefix confusion", path: "/apiary", entrance: "/", wantOK: false},
|
||||
{name: "entrance api", path: "/secret/api/user/info", entrance: "/secret", wantPath: "/api/user/info", wantOK: true},
|
||||
{name: "entrance api exact", path: "/secret/api", entrance: "/secret", wantPath: "/api", wantOK: true},
|
||||
{name: "entrance page", path: "/secret/login", entrance: "/secret", wantOK: false},
|
||||
{name: "entrance prefix confusion", path: "/secretx/api/user/info", entrance: "/secret", wantOK: false},
|
||||
{name: "api prefix confusion", path: "/secret/apiary", entrance: "/secret", wantOK: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotPath, gotOK := trimEntranceAPIPath(tt.path, tt.entrance)
|
||||
if gotOK != tt.wantOK {
|
||||
t.Fatalf("ok = %v, want %v", gotOK, tt.wantOK)
|
||||
}
|
||||
if gotPath != tt.wantPath {
|
||||
t.Fatalf("path = %q, want %q", gotPath, tt.wantPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,10 @@ func (r *Middlewares) Globals(t *gotext.Locale, mux *chi.Mux) []func(http.Handle
|
||||
return respStatus == 404 || respStatus == 405
|
||||
},
|
||||
LogRequestBody: func(req *http.Request) bool {
|
||||
return req.Header.Get("X-Debug-Request") == "1"
|
||||
return r.conf.App.Debug && req.Header.Get("X-Debug-Request") == "1"
|
||||
},
|
||||
LogResponseBody: func(req *http.Request) bool {
|
||||
return req.Header.Get("X-Debug-Response") == "1"
|
||||
return r.conf.App.Debug && req.Header.Get("X-Debug-Response") == "1"
|
||||
},
|
||||
}),
|
||||
compressor.Handler,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package request
|
||||
|
||||
type App struct {
|
||||
Slug string `json:"slug" form:"slug" validate:"required|notExists:apps,slug"`
|
||||
Slug string `json:"slug" form:"slug" validate:"required && not_exists:apps,slug"`
|
||||
Channel string `json:"channel" form:"channel" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ type AppSlugs struct {
|
||||
}
|
||||
|
||||
type AppUpdateShow struct {
|
||||
Slug string `json:"slug" form:"slug" validate:"required|exists:apps,slug"`
|
||||
Slug string `json:"slug" form:"slug" validate:"required && exists:apps,slug"`
|
||||
Show bool `json:"show" form:"show"`
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ package request
|
||||
import "mime/multipart"
|
||||
|
||||
type BackupList struct {
|
||||
Type string `uri:"type" form:"type" validate:"required|in:path,website,mysql,postgresql,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required && in:path,website,mysql,postgresql,clickhouse,redis,valkey,panel"`
|
||||
}
|
||||
|
||||
type BackupCreate struct {
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgresql,redis,panel"`
|
||||
Target string `json:"target" form:"target" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Type string `uri:"type" form:"type" validate:"required && in:website,mysql,postgresql,clickhouse,redis,valkey,panel"`
|
||||
Target string `json:"target" form:"target" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Storage uint `form:"storage" json:"storage"`
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ type BackupUpload struct {
|
||||
}
|
||||
|
||||
type BackupFile struct {
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgresql,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required && in:website,mysql,postgresql,clickhouse,redis,valkey,panel"`
|
||||
File string `json:"file" form:"file" validate:"required"`
|
||||
}
|
||||
|
||||
type BackupRestore struct {
|
||||
BackupFile
|
||||
Target string `json:"target" form:"target" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Target string `json:"target" form:"target" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ package request
|
||||
import "github.com/acepanel/panel/v3/pkg/types"
|
||||
|
||||
type BackupStorageCreate struct {
|
||||
Type string `form:"type" json:"type" validate:"required|in:s3,sftp,webdav"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:s3,sftp,webdav"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Info types.BackupStorageInfo `form:"info" json:"info"`
|
||||
}
|
||||
|
||||
type BackupStorageUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:backup_storages,id"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:s3,sftp,webdav"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:backup_storages,id"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:s3,sftp,webdav"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Info types.BackupStorageInfo `form:"info" json:"info"`
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package request
|
||||
|
||||
import "net/http"
|
||||
|
||||
type CertUpload struct {
|
||||
Cert string `form:"cert" json:"cert" validate:"required"`
|
||||
Key string `form:"key" json:"key" validate:"required"`
|
||||
}
|
||||
|
||||
type CertCreate struct {
|
||||
Type string `form:"type" json:"type" validate:"required|in:P256,P384,2048,3072,4096"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required|isSlice"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:P256,P384,2048,3072,4096"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required && dive && required"`
|
||||
Alias map[string]string `form:"alias" json:"alias"`
|
||||
AutoRenewal bool `form:"auto_renewal" json:"auto_renewal"`
|
||||
AccountID uint `form:"account_id" json:"account_id"`
|
||||
@@ -17,16 +15,10 @@ type CertCreate struct {
|
||||
WebsiteID uint `form:"website_id" json:"website_id"`
|
||||
}
|
||||
|
||||
func (r *CertCreate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"Domains.*": "required",
|
||||
}
|
||||
}
|
||||
|
||||
type CertUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:certs,id"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:P256,P384,2048,3072,4096,upload"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required|isSlice"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:certs,id"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:P256,P384,2048,3072,4096,upload"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required && dive && required"`
|
||||
Alias map[string]string `form:"alias" json:"alias"`
|
||||
Cert string `form:"cert" json:"cert"`
|
||||
Key string `form:"key" json:"key"`
|
||||
@@ -37,14 +29,8 @@ type CertUpdate struct {
|
||||
WebsiteID uint `form:"website_id" json:"website_id"`
|
||||
}
|
||||
|
||||
func (r *CertUpdate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"Domains.*": "required",
|
||||
}
|
||||
}
|
||||
|
||||
type CertDeploy struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:certs,id"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" validate:"required|exists:websites,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:certs,id"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" validate:"required && exists:websites,id"`
|
||||
EnableHTTPS bool `form:"enable_https" json:"enable_https"`
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package request
|
||||
|
||||
type CertAccountCreate struct {
|
||||
CA string `form:"ca" json:"ca" validate:"required|in:googlecn,google,letsencrypt,litessl,zerossl,sslcom"`
|
||||
Email string `form:"email" json:"email" validate:"required"`
|
||||
CA string `form:"ca" json:"ca" validate:"required && in:googlecn,google,letsencrypt,litessl,zerossl,sslcom"`
|
||||
Email string `form:"email" json:"email" validate:"required && email"`
|
||||
Kid string `form:"kid" json:"kid"`
|
||||
HmacEncoded string `form:"hmac_encoded" json:"hmac_encoded"`
|
||||
KeyType string `form:"key_type" json:"key_type" validate:"required|in:P256,P384,2048,3072,4096"`
|
||||
KeyType string `form:"key_type" json:"key_type" validate:"required && in:P256,P384,2048,3072,4096"`
|
||||
}
|
||||
|
||||
type CertAccountUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:cert_accounts,id"`
|
||||
CA string `form:"ca" json:"ca" validate:"required|in:googlecn,google,letsencrypt,litessl,zerossl,sslcom"`
|
||||
Email string `form:"email" json:"email" validate:"required"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:cert_accounts,id"`
|
||||
CA string `form:"ca" json:"ca" validate:"required && in:googlecn,google,letsencrypt,litessl,zerossl,sslcom"`
|
||||
Email string `form:"email" json:"email" validate:"required && email"`
|
||||
Kid string `form:"kid" json:"kid"`
|
||||
HmacEncoded string `form:"hmac_encoded" json:"hmac_encoded"`
|
||||
KeyType string `form:"key_type" json:"key_type" validate:"required|in:P256,P384,2048,3072,4096"`
|
||||
KeyType string `form:"key_type" json:"key_type" validate:"required && in:P256,P384,2048,3072,4096"`
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ package request
|
||||
import "github.com/acepanel/panel/v3/pkg/acme"
|
||||
|
||||
type CertDNSCreate struct {
|
||||
Type acme.DnsType `form:"type" json:"type" validate:"required|in:aliyun,tencent,huawei,westcn,cloudflare,gcore,porkbun,namesilo,cloudns"`
|
||||
Type acme.DnsType `form:"type" json:"type" validate:"required && in:aliyun,tencent,huawei,westcn,cloudflare,gcore,porkbun,namesilo,cloudns"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Data acme.DNSParam `form:"data" json:"data" validate:"required"`
|
||||
}
|
||||
|
||||
type CertDNSUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:cert_dns,id"`
|
||||
Type acme.DnsType `form:"type" json:"type" validate:"required|in:aliyun,tencent,huawei,westcn,cloudflare,gcore,porkbun,namesilo,cloudns"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:cert_dns,id"`
|
||||
Type acme.DnsType `form:"type" json:"type" validate:"required && in:aliyun,tencent,huawei,westcn,cloudflare,gcore,porkbun,namesilo,cloudns"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Data acme.DNSParam `form:"data" json:"data" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package request
|
||||
|
||||
type ID struct {
|
||||
ID uint `json:"id" form:"id" query:"id" uri:"id" validate:"required|min:1"`
|
||||
ID uint `json:"id" form:"id" query:"id" uri:"id" validate:"required && min:1"`
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ type ContainerID struct {
|
||||
|
||||
type ContainerRename struct {
|
||||
ID string `form:"id" json:"id" validate:"required"`
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
}
|
||||
|
||||
type ContainerCreate struct {
|
||||
@@ -21,7 +21,7 @@ type ContainerCreate struct {
|
||||
Env []types.KV `form:"env" json:"env"`
|
||||
Entrypoint []string `form:"entrypoint" json:"entrypoint"`
|
||||
Command []string `form:"command" json:"command"`
|
||||
RestartPolicy string `form:"restart_policy" json:"restart_policy"`
|
||||
RestartPolicy string `form:"restart_policy" json:"restart_policy" validate:"in:no,always,on-failure,unless-stopped"`
|
||||
AutoRemove bool `form:"auto_remove" json:"auto_remove"`
|
||||
Privileged bool `form:"privileged" json:"privileged"`
|
||||
OpenStdin bool `form:"openStdin" json:"open_stdin"`
|
||||
|
||||
@@ -3,30 +3,30 @@ package request
|
||||
import "github.com/acepanel/panel/v3/pkg/types"
|
||||
|
||||
type ContainerComposeGet struct {
|
||||
Name string `uri:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `uri:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
}
|
||||
|
||||
type ContainerComposeCreate struct {
|
||||
Name string `json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Compose string `json:"compose" validate:"required"`
|
||||
Envs []types.KV `json:"envs"`
|
||||
}
|
||||
|
||||
type ContainerComposeUpdate struct {
|
||||
Name string `uri:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `uri:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Compose string `json:"compose" validate:"required"`
|
||||
Envs []types.KV `json:"envs"`
|
||||
}
|
||||
|
||||
type ContainerComposeUp struct {
|
||||
Name string `uri:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `uri:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
|
||||
type ContainerComposeDown struct {
|
||||
Name string `uri:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `uri:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
}
|
||||
|
||||
type ContainerComposeRemove struct {
|
||||
Name string `uri:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `uri:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ type ContainerImageID struct {
|
||||
type ContainerImagePull struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Auth bool `form:"auth" json:"auth"`
|
||||
Username string `form:"username" json:"username" validate:"requiredIf:Auth,true"`
|
||||
Password string `form:"password" json:"password" validate:"requiredIf:Auth,true"`
|
||||
Username string `form:"username" json:"username" validate:"required_if:Auth,true"`
|
||||
Password string `form:"password" json:"password" validate:"required_if:Auth,true"`
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ type ContainerNetworkID struct {
|
||||
}
|
||||
|
||||
type ContainerNetworkCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Driver string `form:"driver" json:"driver" validate:"required|in:bridge,host,overlay,macvlan,ipvlan,none"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Driver string `form:"driver" json:"driver" validate:"required && in:bridge,host,overlay,macvlan,ipvlan,none"`
|
||||
Ipv4 types.ContainerContainerNetwork `form:"ipv4" json:"ipv4"`
|
||||
Ipv6 types.ContainerContainerNetwork `form:"ipv6" json:"ipv6"`
|
||||
Labels []types.KV `form:"labels" json:"labels"`
|
||||
|
||||
@@ -7,8 +7,8 @@ type ContainerVolumeID struct {
|
||||
}
|
||||
|
||||
type ContainerVolumeCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Driver string `form:"driver" json:"driver" validate:"required|in:local"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Driver string `form:"driver" json:"driver" validate:"required && in:local"`
|
||||
Labels []types.KV `form:"labels" json:"labels"`
|
||||
Options []types.KV `form:"options" json:"options"`
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package request
|
||||
|
||||
type CronCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required|notExists:crons,name"`
|
||||
Type string `form:"type" json:"type" validate:"required"`
|
||||
Time string `form:"time" json:"time" validate:"required|cron"`
|
||||
Name string `form:"name" json:"name" validate:"required && not_exists:crons,name"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:shell,backup,cutoff,url,synctime"`
|
||||
Time string `form:"time" json:"time" validate:"required && cron"`
|
||||
Script string `form:"script" json:"script"`
|
||||
SubType string `form:"sub_type" json:"sub_type" validate:"requiredIf:Type,backup,cutoff"`
|
||||
SubType string `form:"sub_type" json:"sub_type" validate:"required_if:Type,backup,cutoff"`
|
||||
Flock bool `form:"flock" json:"flock"`
|
||||
Storage uint `form:"storage" json:"storage"`
|
||||
Targets []string `form:"targets" json:"targets" validate:"requiredIf:Type,backup,cutoff"`
|
||||
Targets []string `form:"targets" json:"targets" validate:"required_if:Type,backup,cutoff"`
|
||||
Keep uint `form:"keep" json:"keep" validate:"required"`
|
||||
URL string `form:"url" json:"url"`
|
||||
Method string `form:"method" json:"method"`
|
||||
@@ -20,10 +20,10 @@ type CronCreate struct {
|
||||
}
|
||||
|
||||
type CronUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:crons,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:crons,id"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Type string `form:"type" json:"type" validate:"required"`
|
||||
Time string `form:"time" json:"time" validate:"required|cron"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:shell,backup,cutoff,url,synctime"`
|
||||
Time string `form:"time" json:"time" validate:"required && cron"`
|
||||
Script string `form:"script" json:"script"`
|
||||
SubType string `form:"sub_type" json:"sub_type"`
|
||||
Flock bool `form:"flock" json:"flock"`
|
||||
@@ -40,6 +40,6 @@ type CronUpdate struct {
|
||||
}
|
||||
|
||||
type CronStatus struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:crons,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:crons,id"`
|
||||
Status bool `form:"status" json:"status"`
|
||||
}
|
||||
|
||||
@@ -6,22 +6,22 @@ type DatabaseList struct {
|
||||
}
|
||||
|
||||
type DatabaseCreate struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z_-][a-zA-Z0-9_-]*$"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z_-][a-zA-Z0-9_-]*$\""`
|
||||
CreateUser bool `form:"create_user" json:"create_user"`
|
||||
Username string `form:"username" json:"username" validate:"requiredIf:CreateUser,true|not_in:root,admin|regex:^[a-zA-Z_-][a-zA-Z0-9_-]*$"`
|
||||
Password string `form:"password" json:"password" validate:"requiredIf:CreateUser,true"`
|
||||
Username string `form:"username" json:"username" validate:"required_if:CreateUser,true && not_in:root,admin && regex:\"^[a-zA-Z_-][a-zA-Z0-9_-]*$\""`
|
||||
Password string `form:"password" json:"password" validate:"required_if:CreateUser,true"`
|
||||
Host string `form:"host" json:"host"`
|
||||
Comment string `form:"comment" json:"comment"`
|
||||
}
|
||||
|
||||
type DatabaseDelete struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z_-][a-zA-Z0-9_-]*$"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z_-][a-zA-Z0-9_-]*$\""`
|
||||
}
|
||||
|
||||
type DatabaseComment struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z_-][a-zA-Z0-9_-]*$"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z_-][a-zA-Z0-9_-]*$\""`
|
||||
Comment string `form:"comment" json:"comment"`
|
||||
}
|
||||
|
||||
@@ -2,39 +2,39 @@ package request
|
||||
|
||||
// DatabaseESIndices 获取索引列表
|
||||
type DatabaseESIndices struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required && exists:database_servers,id"`
|
||||
}
|
||||
|
||||
// DatabaseESIndexCreate 创建索引
|
||||
type DatabaseESIndexCreate struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
// DatabaseESIndexDelete 删除索引
|
||||
type DatabaseESIndexDelete struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
// DatabaseESData 搜索文档列表
|
||||
type DatabaseESData struct {
|
||||
Paginate
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Index string `form:"index" json:"index" query:"index" validate:"required"`
|
||||
Search string `form:"search" json:"search" query:"search"`
|
||||
}
|
||||
|
||||
// DatabaseESDocumentGet 获取文档
|
||||
type DatabaseESDocumentGet struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Index string `form:"index" json:"index" query:"index" validate:"required"`
|
||||
ID string `form:"id" json:"id" query:"id" validate:"required"`
|
||||
}
|
||||
|
||||
// DatabaseESDocumentSet 创建/更新文档
|
||||
type DatabaseESDocumentSet struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Index string `form:"index" json:"index" validate:"required"`
|
||||
ID string `form:"id" json:"id"`
|
||||
Body string `form:"body" json:"body" validate:"required"`
|
||||
@@ -42,7 +42,7 @@ type DatabaseESDocumentSet struct {
|
||||
|
||||
// DatabaseESDocumentDelete 删除文档
|
||||
type DatabaseESDocumentDelete struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Index string `form:"index" json:"index" validate:"required"`
|
||||
ID string `form:"id" json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
package request
|
||||
|
||||
type DatabaseRedisDatabases struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required && exists:database_servers,id"`
|
||||
}
|
||||
|
||||
type DatabaseRedisData struct {
|
||||
Paginate
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required && exists:database_servers,id"`
|
||||
DB int `form:"db" json:"db" query:"db"`
|
||||
Search string `form:"search" json:"search" query:"search"`
|
||||
}
|
||||
|
||||
type DatabaseRedisKeyGet struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" query:"server_id" validate:"required && exists:database_servers,id"`
|
||||
DB int `form:"db" json:"db" query:"db"`
|
||||
Key string `form:"key" json:"key" query:"key" validate:"required"`
|
||||
}
|
||||
|
||||
type DatabaseRedisKeySet struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
DB int `form:"db" json:"db"`
|
||||
Key string `form:"key" json:"key" validate:"required"`
|
||||
Value string `form:"value" json:"value" validate:"required"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:string,list,set,zset,hash"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:string,list,set,zset,hash"`
|
||||
TTL int64 `form:"ttl" json:"ttl"`
|
||||
}
|
||||
|
||||
type DatabaseRedisKeyDelete struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
DB int `form:"db" json:"db"`
|
||||
Key string `form:"key" json:"key" validate:"required"`
|
||||
}
|
||||
|
||||
type DatabaseRedisKeyTTL struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
DB int `form:"db" json:"db"`
|
||||
Key string `form:"key" json:"key" validate:"required"`
|
||||
TTL int64 `form:"ttl" json:"ttl"`
|
||||
}
|
||||
|
||||
type DatabaseRedisKeyRename struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
DB int `form:"db" json:"db"`
|
||||
OldKey string `form:"old_key" json:"old_key" validate:"required"`
|
||||
NewKey string `form:"new_key" json:"new_key" validate:"required"`
|
||||
}
|
||||
|
||||
type DatabaseRedisClear struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
DB int `form:"db" json:"db"`
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
package request
|
||||
|
||||
type DatabaseServerCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required|notExists:database_servers,name|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:mysql,postgresql,redis,clickhouse,mongodb,sqlite,elasticsearch"`
|
||||
Name string `form:"name" json:"name" validate:"required && not_exists:database_servers,name && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Type string `form:"type" json:"type" validate:"required && in:mysql,postgresql,redis,clickhouse,mongodb,sqlite,elasticsearch"`
|
||||
Host string `form:"host" json:"host" validate:"required"`
|
||||
Port uint `form:"port" json:"port" validate:"max:65535"`
|
||||
Port uint `form:"port" json:"port" validate:"min:1 && max:65535"`
|
||||
Username string `form:"username" json:"username"`
|
||||
Password string `form:"password" json:"password"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type DatabaseServerUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:database_servers,id"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Host string `form:"host" json:"host" validate:"required"`
|
||||
Port uint `form:"port" json:"port" validate:"max:65535"`
|
||||
Port uint `form:"port" json:"port" validate:"min:1 && max:65535"`
|
||||
Username string `form:"username" json:"username"`
|
||||
Password string `form:"password" json:"password"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type DatabaseServerUpdateRemark struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:database_servers,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:database_servers,id"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package request
|
||||
|
||||
type DatabaseUserCreate struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required|exists:database_servers,id"`
|
||||
Username string `form:"username" json:"username" validate:"required|not_in:root,admin|regex:^[a-zA-Z_-][a-zA-Z0-9_-]*$"`
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required && exists:database_servers,id"`
|
||||
Username string `form:"username" json:"username" validate:"required && not_in:root,admin && regex:\"^[a-zA-Z_-][a-zA-Z0-9_-]*$\""`
|
||||
Password string `form:"password" json:"password" validate:"required"`
|
||||
Host string `form:"host" json:"host"`
|
||||
Privileges []string `form:"privileges" json:"privileges"`
|
||||
@@ -10,13 +10,13 @@ type DatabaseUserCreate struct {
|
||||
}
|
||||
|
||||
type DatabaseUserUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:database_users,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:database_users,id"`
|
||||
Password string `form:"password" json:"password"`
|
||||
Privileges []string `form:"privileges" json:"privileges"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type DatabaseUserUpdateRemark struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:database_users,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:database_users,id"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
@@ -27,21 +27,21 @@ type EnvironmentPHPConfigTune struct {
|
||||
// php.ini 上传限制
|
||||
UploadMaxFilesize string `form:"upload_max_filesize" json:"upload_max_filesize"`
|
||||
PostMaxSize string `form:"post_max_size" json:"post_max_size"`
|
||||
MaxFileUploads string `form:"max_file_uploads" json:"max_file_uploads"`
|
||||
MaxFileUploads string `form:"max_file_uploads" json:"max_file_uploads" validate:"number"`
|
||||
MemoryLimit string `form:"memory_limit" json:"memory_limit"`
|
||||
// php.ini 超时限制
|
||||
MaxExecutionTime string `form:"max_execution_time" json:"max_execution_time"`
|
||||
MaxInputTime string `form:"max_input_time" json:"max_input_time"`
|
||||
MaxInputVars string `form:"max_input_vars" json:"max_input_vars"`
|
||||
MaxExecutionTime string `form:"max_execution_time" json:"max_execution_time" validate:"number"`
|
||||
MaxInputTime string `form:"max_input_time" json:"max_input_time" validate:"number"`
|
||||
MaxInputVars string `form:"max_input_vars" json:"max_input_vars" validate:"number"`
|
||||
// php.ini Session 相关
|
||||
SessionSaveHandler string `form:"session_save_handler" json:"session_save_handler"`
|
||||
SessionSavePath string `form:"session_save_path" json:"session_save_path"`
|
||||
SessionGcMaxlifetime string `form:"session_gc_maxlifetime" json:"session_gc_maxlifetime"`
|
||||
SessionCookieLifetime string `form:"session_cookie_lifetime" json:"session_cookie_lifetime"`
|
||||
SessionGcMaxlifetime string `form:"session_gc_maxlifetime" json:"session_gc_maxlifetime" validate:"number"`
|
||||
SessionCookieLifetime string `form:"session_cookie_lifetime" json:"session_cookie_lifetime" validate:"number"`
|
||||
// php-fpm.conf 相关
|
||||
Pm string `form:"pm" json:"pm"`
|
||||
PmMaxChildren string `form:"pm_max_children" json:"pm_max_children"`
|
||||
PmStartServers string `form:"pm_start_servers" json:"pm_start_servers"`
|
||||
PmMinSpareServers string `form:"pm_min_spare_servers" json:"pm_min_spare_servers"`
|
||||
PmMaxSpareServers string `form:"pm_max_spare_servers" json:"pm_max_spare_servers"`
|
||||
Pm string `form:"pm" json:"pm" validate:"in:static,dynamic,ondemand"`
|
||||
PmMaxChildren string `form:"pm_max_children" json:"pm_max_children" validate:"number"`
|
||||
PmStartServers string `form:"pm_start_servers" json:"pm_start_servers" validate:"number"`
|
||||
PmMinSpareServers string `form:"pm_min_spare_servers" json:"pm_min_spare_servers" validate:"number"`
|
||||
PmMaxSpareServers string `form:"pm_max_spare_servers" json:"pm_max_spare_servers" validate:"number"`
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type FileList struct {
|
||||
Path string `json:"path" form:"path" validate:"required|isUnixPath"`
|
||||
Path string `json:"path" form:"path" validate:"required && unix_path"`
|
||||
Sort string `json:"sort" form:"sort"`
|
||||
Keyword string `form:"keyword" json:"keyword"`
|
||||
Sub bool `form:"sub" json:"sub"`
|
||||
@@ -19,80 +19,76 @@ func (r *FileList) Prepare(req *http.Request) error {
|
||||
}
|
||||
|
||||
type FilePath struct {
|
||||
Path string `json:"path" form:"path" validate:"required|isUnixPath"`
|
||||
Path string `json:"path" form:"path" validate:"required && unix_path"`
|
||||
}
|
||||
|
||||
type FileTail struct {
|
||||
Path string `json:"path" form:"path"`
|
||||
Service string `json:"service" form:"service"`
|
||||
Offset int `json:"offset" form:"offset"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Path string `json:"path" form:"path"`
|
||||
Service string `json:"service" form:"service"`
|
||||
Container string `json:"container" form:"container"`
|
||||
Offset int `json:"offset" form:"offset"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
}
|
||||
|
||||
type FileFollow struct {
|
||||
Path string `json:"path" form:"path"`
|
||||
Service string `json:"service" form:"service"`
|
||||
Path string `json:"path" form:"path"`
|
||||
Service string `json:"service" form:"service"`
|
||||
Container string `json:"container" form:"container"`
|
||||
}
|
||||
|
||||
type FileCreate struct {
|
||||
Dir bool `json:"dir" form:"dir"`
|
||||
Path string `json:"path" form:"path" validate:"required|isUnixPath"`
|
||||
Path string `json:"path" form:"path" validate:"required && unix_path"`
|
||||
}
|
||||
|
||||
type FileSave struct {
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
Content string `form:"content" json:"content"`
|
||||
}
|
||||
|
||||
type FileControl struct {
|
||||
Source string `form:"source" json:"source" validate:"required|isUnixPath"`
|
||||
Target string `form:"target" json:"target" validate:"required|isUnixPath"`
|
||||
Source string `form:"source" json:"source" validate:"required && unix_path"`
|
||||
Target string `form:"target" json:"target" validate:"required && unix_path"`
|
||||
Force bool `form:"force" json:"force"`
|
||||
}
|
||||
|
||||
type FileRemoteDownload struct {
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
URL string `form:"url" json:"url" validate:"required|isFullURL"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
URL string `form:"url" json:"url" validate:"required && url"`
|
||||
}
|
||||
|
||||
type FilePermission struct {
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
Mode string `form:"mode" json:"mode" validate:"required"`
|
||||
Owner string `form:"owner" json:"owner" validate:"required"`
|
||||
Group string `form:"group" json:"group" validate:"required"`
|
||||
}
|
||||
|
||||
type FileCompress struct {
|
||||
Dir string `form:"dir" json:"dir" validate:"required|isUnixPath"`
|
||||
Paths []string `form:"paths" json:"paths" validate:"required|isSlice"`
|
||||
File string `form:"file" json:"file" validate:"required|isUnixPath"`
|
||||
}
|
||||
|
||||
func (r *FileCompress) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"Paths.*": "required",
|
||||
}
|
||||
Dir string `form:"dir" json:"dir" validate:"required && unix_path"`
|
||||
Paths []string `form:"paths" json:"paths" validate:"required && dive && required"`
|
||||
File string `form:"file" json:"file" validate:"required && unix_path"`
|
||||
}
|
||||
|
||||
type FileUnCompress struct {
|
||||
File string `form:"file" json:"file" validate:"required|isUnixPath"`
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
File string `form:"file" json:"file" validate:"required && unix_path"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
}
|
||||
|
||||
// ChunkUploadStart 分块上传开始请求
|
||||
type ChunkUploadStart struct {
|
||||
Path string `json:"path" validate:"required|isUnixPath"` // 目标目录
|
||||
FileName string `json:"file_name" validate:"required"` // 文件名
|
||||
FileHash string `json:"file_hash" validate:"required|len:64"` // 文件SHA256
|
||||
ChunkCount int `json:"chunk_count" validate:"required|min:1"` // 分块总数
|
||||
Force bool `json:"force"` // 是否覆盖已存在文件
|
||||
Path string `json:"path" validate:"required && unix_path"` // 目标目录
|
||||
FileName string `json:"file_name" validate:"required"` // 文件名
|
||||
FileHash string `json:"file_hash" validate:"required && len:64"` // 文件SHA256
|
||||
ChunkCount int `json:"chunk_count" validate:"required && min:1"` // 分块总数
|
||||
Force bool `json:"force"` // 是否覆盖已存在文件
|
||||
}
|
||||
|
||||
// ChunkUploadFinish 分块上传完成请求
|
||||
type ChunkUploadFinish struct {
|
||||
Path string `json:"path" validate:"required|isUnixPath"` // 目标目录
|
||||
FileName string `json:"file_name" validate:"required"` // 文件名
|
||||
FileHash string `json:"file_hash" validate:"required|len:64"` // 文件SHA256
|
||||
ChunkCount int `json:"chunk_count" validate:"required|min:1"` // 分块总数
|
||||
Force bool `json:"force"` // 是否覆盖已存在文件
|
||||
Path string `json:"path" validate:"required && unix_path"` // 目标目录
|
||||
FileName string `json:"file_name" validate:"required"` // 文件名
|
||||
FileHash string `json:"file_hash" validate:"required && len:64"` // 文件SHA256
|
||||
ChunkCount int `json:"chunk_count" validate:"required && min:1"` // 分块总数
|
||||
Force bool `json:"force"` // 是否覆盖已存在文件
|
||||
}
|
||||
|
||||
@@ -5,27 +5,27 @@ type FirewallStatus struct {
|
||||
}
|
||||
|
||||
type FirewallRule struct {
|
||||
Type string `json:"type"`
|
||||
Family string `json:"family" validate:"required|in:ipv4,ipv6"`
|
||||
PortStart uint `json:"port_start" validate:"required|min:1|max:65535"`
|
||||
PortEnd uint `json:"port_end" validate:"required|min:1|max:65535"`
|
||||
Protocol string `json:"protocol" validate:"required|in:tcp,udp,tcp/udp"`
|
||||
Address string `json:"address"`
|
||||
Strategy string `json:"strategy" validate:"required|in:accept,drop,reject"`
|
||||
Direction string `json:"direction" validate:"required|in:in,out"`
|
||||
Type string `json:"type" validate:"in:rich,normal"`
|
||||
Family string `json:"family" validate:"required && in:ipv4,ipv6"`
|
||||
PortStart uint `json:"port_start" validate:"required && min:1 && max:65535"`
|
||||
PortEnd uint `json:"port_end" validate:"required && min:1 && max:65535 && gtefield:PortStart"`
|
||||
Protocol string `json:"protocol" validate:"required && in:tcp,udp,tcp/udp"`
|
||||
Address string `json:"address" validate:"ipcidr"`
|
||||
Strategy string `json:"strategy" validate:"required && in:accept,drop,reject"`
|
||||
Direction string `json:"direction" validate:"required && in:in,out"`
|
||||
}
|
||||
|
||||
type FirewallIPRule struct {
|
||||
Family string `json:"family" validate:"required|in:ipv4,ipv6"`
|
||||
Protocol string `json:"protocol" validate:"required|in:tcp,udp,tcp/udp"`
|
||||
Address string `json:"address"`
|
||||
Strategy string `json:"strategy" validate:"required|in:accept,drop,reject"`
|
||||
Direction string `json:"direction" validate:"required|in:in,out"`
|
||||
Family string `json:"family" validate:"required && in:ipv4,ipv6"`
|
||||
Protocol string `json:"protocol" validate:"required && in:tcp,udp,tcp/udp"`
|
||||
Address string `json:"address" validate:"ipcidr"`
|
||||
Strategy string `json:"strategy" validate:"required && in:accept,drop,reject"`
|
||||
Direction string `json:"direction" validate:"required && in:in,out"`
|
||||
}
|
||||
|
||||
type FirewallForward struct {
|
||||
Protocol string `json:"protocol" validate:"required|in:tcp,udp,tcp/udp"`
|
||||
Port uint `json:"port" validate:"required|min:1|max:65535"`
|
||||
TargetIP string `json:"target_ip" validate:"required"`
|
||||
TargetPort uint `json:"target_port" validate:"required|min:1|max:65535"`
|
||||
Protocol string `json:"protocol" validate:"required && in:tcp,udp,tcp/udp"`
|
||||
Port uint `json:"port" validate:"required && min:1 && max:65535"`
|
||||
TargetIP string `json:"target_ip" validate:"required && ip"`
|
||||
TargetPort uint `json:"target_port" validate:"required && min:1 && max:65535"`
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package request
|
||||
// FirewallScanSetting 扫描感知设置
|
||||
type FirewallScanSetting struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Days uint `json:"days" validate:"min:1|max:365"`
|
||||
Days uint `json:"days" validate:"min:1 && max:365"`
|
||||
Interfaces []string `json:"interfaces"`
|
||||
AutoBlock bool `json:"auto_block"`
|
||||
BlockThreshold uint `json:"block_threshold" validate:"min:1|max:100000"`
|
||||
BlockWindow uint `json:"block_window" validate:"min:1|max:1440"`
|
||||
BlockThreshold uint `json:"block_threshold" validate:"min:1 && max:100000"`
|
||||
BlockWindow uint `json:"block_window" validate:"min:1 && max:1440"`
|
||||
BlockDuration uint `json:"block_duration" validate:"max:87600"`
|
||||
Whitelist []string `json:"whitelist"`
|
||||
Whitelist []string `json:"whitelist" validate:"dive && ipcidr"`
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package request
|
||||
|
||||
// LogList 日志列表请求
|
||||
type LogList struct {
|
||||
Type string `json:"type" form:"type" query:"type" validate:"required|in:app,db,http"`
|
||||
Limit int `json:"limit" form:"limit" query:"limit" validate:"min:1|max:1000"`
|
||||
Date string `json:"date" form:"date" query:"date"` // 日期,格式为 YYYY-MM-DD,空表示当天
|
||||
Type string `json:"type" form:"type" query:"type" validate:"required && in:app,db,http"`
|
||||
Limit int `json:"limit" form:"limit" query:"limit" validate:"min:1 && max:1000"`
|
||||
Date string `json:"date" form:"date" query:"date" validate:"datetime:2006-01-02"` // 日期,格式为 YYYY-MM-DD,空表示当天
|
||||
}
|
||||
|
||||
// LogDates 日志日期列表请求
|
||||
type LogDates struct {
|
||||
Type string `json:"type" form:"type" query:"type" validate:"required|in:app,db,http"`
|
||||
Type string `json:"type" form:"type" query:"type" validate:"required && in:app,db,http"`
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package request
|
||||
type MonitorSetting struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Days uint `json:"days"`
|
||||
Interval uint `json:"interval" validate:"required|min:1|max:120"` // 采集间隔(分钟),最小 1
|
||||
Interval uint `json:"interval" validate:"required && min:1 && max:120"` // 采集间隔(分钟),最小 1
|
||||
}
|
||||
|
||||
type MonitorList struct {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
)
|
||||
|
||||
type Paginate struct {
|
||||
Page uint `json:"page" form:"page" query:"page" validate:"required|min:1"`
|
||||
Limit uint `json:"limit" form:"limit" query:"limit" validate:"required|min:1|max:10000"`
|
||||
Page uint `json:"page" form:"page" query:"page" validate:"required && min:1"`
|
||||
Limit uint `json:"limit" form:"limit" query:"limit" validate:"required && min:1 && max:10000"`
|
||||
}
|
||||
|
||||
func (r *Paginate) Messages(_ *http.Request) map[string]string {
|
||||
|
||||
@@ -14,7 +14,7 @@ type ProcessDetail struct {
|
||||
// 支持的信号: SIGHUP(1), SIGINT(2), SIGKILL(9), SIGUSR1(10), SIGUSR2(12), SIGTERM(15), SIGCONT(18), SIGSTOP(19)
|
||||
type ProcessSignal struct {
|
||||
PID int32 `json:"pid" validate:"required"`
|
||||
Signal int `json:"signal" validate:"required|in:1,2,9,10,12,15,18,19"`
|
||||
Signal int `json:"signal" validate:"required && in:1,2,9,10,12,15,18,19"`
|
||||
}
|
||||
|
||||
// ProcessList 进程列表请求
|
||||
|
||||
@@ -3,8 +3,8 @@ package request
|
||||
import "github.com/acepanel/panel/v3/pkg/types"
|
||||
|
||||
type ProjectCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Type types.ProjectType `form:"type" json:"type" validate:"required|in:general,php,java,go,python,nodejs,dotnet"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Type types.ProjectType `form:"type" json:"type" validate:"required && in:general,php,java,go,python,nodejs,dotnet"`
|
||||
Description string `form:"description" json:"description"`
|
||||
RootDir string `form:"root_dir" json:"root_dir"`
|
||||
WorkingDir string `form:"working_dir" json:"working_dir"`
|
||||
@@ -15,8 +15,8 @@ type ProjectCreate struct {
|
||||
}
|
||||
|
||||
type ProjectUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:projects,id"`
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:projects,id"`
|
||||
Name string `form:"name" json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Description string `form:"description" json:"description"`
|
||||
RootDir string `form:"root_dir" json:"root_dir" validate:"required"`
|
||||
WorkingDir string `form:"working_dir" json:"working_dir"`
|
||||
@@ -28,9 +28,9 @@ type ProjectUpdate struct {
|
||||
User string `form:"user" json:"user"`
|
||||
Restart string `json:"restart"`
|
||||
RestartSec string `json:"restart_sec"`
|
||||
RestartMax int `json:"restart_max"`
|
||||
TimeoutStartSec int `json:"timeout_start_sec"`
|
||||
TimeoutStopSec int `json:"timeout_stop_sec"`
|
||||
RestartMax int `json:"restart_max" validate:"min:0"`
|
||||
TimeoutStartSec int `json:"timeout_start_sec" validate:"min:0"`
|
||||
TimeoutStopSec int `json:"timeout_stop_sec" validate:"min:0"`
|
||||
Environments []types.KV `form:"environments" json:"environments"`
|
||||
StandardOutput string `form:"standard_output" json:"standard_output"`
|
||||
StandardError string `form:"standard_error" json:"standard_error"`
|
||||
@@ -39,7 +39,7 @@ type ProjectUpdate struct {
|
||||
After []string `form:"after" json:"after"`
|
||||
Before []string `form:"before" json:"before"`
|
||||
|
||||
MemoryLimit float64 `form:"memory_limit" json:"memory_limit"`
|
||||
MemoryLimit float64 `form:"memory_limit" json:"memory_limit" validate:"min:0"`
|
||||
CPUQuota string `form:"cpu_quota" json:"cpu_quota"`
|
||||
|
||||
NoNewPrivileges bool `form:"no_new_privileges" json:"no_new_privileges"`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package request
|
||||
|
||||
type SafeUpdateSSH struct {
|
||||
Port uint `json:"port" form:"port" validate:"required|min:1|max:65535"`
|
||||
Port uint `json:"port" form:"port" validate:"required && min:1 && max:65535"`
|
||||
Status bool `json:"status" form:"status"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package request
|
||||
|
||||
import "net/http"
|
||||
|
||||
type SettingPanel struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Channel string `json:"channel" validate:"required|in:stable,beta"`
|
||||
Channel string `json:"channel" validate:"required && in:stable,beta"`
|
||||
Locale string `json:"locale" validate:"required"`
|
||||
Entrance string `json:"entrance" validate:"required"`
|
||||
EntranceError string `json:"entrance_error" validate:"in:418,nginx,close"` // 安全入口错误页伪装类型
|
||||
@@ -12,34 +10,28 @@ type SettingPanel struct {
|
||||
OfflineMode bool `json:"offline_mode"`
|
||||
AutoUpdate bool `json:"auto_update"`
|
||||
TwoFA bool `json:"two_fa"`
|
||||
Lifetime uint `json:"lifetime" validate:"required|min:10|max:43200"` // 登录超时,单位:分
|
||||
Lifetime uint `json:"lifetime" validate:"required && min:10 && max:43200"` // 登录超时,单位:分
|
||||
IPHeader string `json:"ip_header"`
|
||||
BindDomain []string `json:"bind_domain"`
|
||||
BindIP []string `json:"bind_ip"`
|
||||
BindIP []string `json:"bind_ip" validate:"dive && ipcidr"`
|
||||
BindUA []string `json:"bind_ua"`
|
||||
WebsitePath string `json:"website_path" validate:"required"`
|
||||
BackupPath string `json:"backup_path" validate:"required"`
|
||||
BackupFormat string `json:"backup_format" validate:"in:tar.xz,tar.gz,zip,7z"` // 备份压缩格式
|
||||
BackupFormat string `json:"backup_format" validate:"in:tar.xz,tar.gz,tar.zst,zip,7z"` // 备份压缩格式
|
||||
ProjectPath string `json:"project_path" validate:"required"`
|
||||
ContainerSock string `json:"container_sock"`
|
||||
HiddenMenu []string `json:"hidden_menu"` // 隐藏的菜单项
|
||||
CustomLogo string `json:"custom_logo" validate:"isFullURL"` // 自定义 Logo URL
|
||||
IPDBType string `json:"ipdb_type"` // IPDB 来源类型: "" / "custom" / "subscribe"
|
||||
IPDBURL string `json:"ipdb_url"` // IPDB 订阅链接
|
||||
IPDBPath string `json:"ipdb_path"` // IPDB 地理位置库路径
|
||||
Port uint `json:"port" validate:"required|min:1|max:65535"` // 面板端口
|
||||
TLS string `json:"tls"` // 面板 TLS: off, acme, self-signed, custom
|
||||
HiddenMenu []string `json:"hidden_menu"` // 隐藏的菜单项
|
||||
CustomLogo string `json:"custom_logo" validate:"url"` // 自定义 Logo URL
|
||||
IPDBType string `json:"ipdb_type" validate:"in:custom,subscribe"` // IPDB 来源类型: "" / "custom" / "subscribe"
|
||||
IPDBURL string `json:"ipdb_url"` // IPDB 订阅链接
|
||||
IPDBPath string `json:"ipdb_path"` // IPDB 地理位置库路径
|
||||
Port uint `json:"port" validate:"required && min:1 && max:65535"` // 面板端口
|
||||
TLS string `json:"tls" validate:"in:off,acme,self-signed,custom"` // 面板 TLS: off, acme, self-signed, custom
|
||||
PublicIP []string `json:"public_ip"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func (r *SettingPanel) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"BindIP.*": "ipcidr",
|
||||
}
|
||||
}
|
||||
|
||||
type SettingCert struct {
|
||||
Cert string `json:"cert" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
|
||||
@@ -3,24 +3,24 @@ package request
|
||||
type SSHCreate struct {
|
||||
Name string `json:"name" form:"name"`
|
||||
Host string `json:"host" form:"host" validate:"required"`
|
||||
Port uint `json:"port" form:"port" validate:"required|min:1|max:65535"`
|
||||
AuthMethod string `json:"auth_method" form:"auth_method" validate:"required|in:password,publickey"`
|
||||
Port uint `json:"port" form:"port" validate:"required && min:1 && max:65535"`
|
||||
AuthMethod string `json:"auth_method" form:"auth_method" validate:"required && in:password,publickey"`
|
||||
User string `json:"user" form:"user" validate:"required"`
|
||||
Password string `json:"password" form:"password" validate:"requiredIf:AuthMethod,password"`
|
||||
Key string `json:"key" form:"key" validate:"requiredIf:AuthMethod,publickey"`
|
||||
Password string `json:"password" form:"password" validate:"required_if:AuthMethod,password"`
|
||||
Key string `json:"key" form:"key" validate:"required_if:AuthMethod,publickey"`
|
||||
Passphrase string `json:"passphrase" form:"passphrase"`
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
}
|
||||
|
||||
type SSHUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:sshes,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:sshes,id"`
|
||||
Name string `json:"name" form:"name"`
|
||||
Host string `json:"host" form:"host" validate:"required"`
|
||||
Port uint `json:"port" form:"port" validate:"required|min:1|max:65535"`
|
||||
AuthMethod string `json:"auth_method" form:"auth_method" validate:"required|in:password,publickey"`
|
||||
Port uint `json:"port" form:"port" validate:"required && min:1 && max:65535"`
|
||||
AuthMethod string `json:"auth_method" form:"auth_method" validate:"required && in:password,publickey"`
|
||||
User string `json:"user" form:"user" validate:"required"`
|
||||
Password string `json:"password" form:"password" validate:"requiredIf:AuthMethod,password"`
|
||||
Key string `json:"key" form:"key" validate:"requiredIf:AuthMethod,publickey"`
|
||||
Password string `json:"password" form:"password" validate:"required_if:AuthMethod,password"`
|
||||
Key string `json:"key" form:"key" validate:"required_if:AuthMethod,publickey"`
|
||||
Passphrase string `json:"passphrase" form:"passphrase"`
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ type TemplateSlug struct {
|
||||
|
||||
type TemplateCreate struct {
|
||||
Slug string `json:"slug" validate:"required"`
|
||||
Name string `json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Compose string `json:"compose"`
|
||||
Envs []types.KV `json:"envs"`
|
||||
AutoFirewall bool `json:"auto_firewall"`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package request
|
||||
|
||||
type ToolboxBenchmarkTest struct {
|
||||
Name string `json:"name" validate:"required|in:image,machine,compile,encryption,compression,physics,json,memory,disk"`
|
||||
Name string `json:"name" validate:"required && in:image,machine,compile,encryption,compression,physics,json,memory,disk"`
|
||||
}
|
||||
|
||||
@@ -8,20 +8,20 @@ type ToolboxDiskDevice struct {
|
||||
// ToolboxDiskMount 挂载请求
|
||||
type ToolboxDiskMount struct {
|
||||
Device string `form:"device" json:"device" validate:"required"`
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
WriteFstab bool `form:"write_fstab" json:"write_fstab"`
|
||||
MountOption string `form:"mount_option" json:"mount_option"`
|
||||
}
|
||||
|
||||
// ToolboxDiskUmount 卸载请求
|
||||
type ToolboxDiskUmount struct {
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
}
|
||||
|
||||
// ToolboxDiskFormat 格式化请求
|
||||
type ToolboxDiskFormat struct {
|
||||
Device string `form:"device" json:"device" validate:"required"`
|
||||
FsType string `form:"fs_type" json:"fs_type" validate:"required|in:ext4,ext3,xfs,btrfs"`
|
||||
FsType string `form:"fs_type" json:"fs_type" validate:"required && in:ext4,ext3,xfs,btrfs"`
|
||||
}
|
||||
|
||||
// ToolboxDiskVG 卷组请求
|
||||
@@ -34,7 +34,7 @@ type ToolboxDiskVG struct {
|
||||
type ToolboxDiskLV struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
VGName string `form:"vg_name" json:"vg_name" validate:"required"`
|
||||
Size int `form:"size" json:"size" validate:"required|min:1"`
|
||||
Size int `form:"size" json:"size" validate:"required && min:1"`
|
||||
}
|
||||
|
||||
// ToolboxDiskVGName 卷组名称请求
|
||||
@@ -44,20 +44,20 @@ type ToolboxDiskVGName struct {
|
||||
|
||||
// ToolboxDiskLVPath 逻辑卷路径请求
|
||||
type ToolboxDiskLVPath struct {
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
}
|
||||
|
||||
// ToolboxDiskExtendLV 扩容逻辑卷请求
|
||||
type ToolboxDiskExtendLV struct {
|
||||
Path string `form:"path" json:"path" validate:"required|isUnixPath"`
|
||||
Size int `form:"size" json:"size" validate:"required|min:1"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"`
|
||||
Size int `form:"size" json:"size" validate:"required && min:1"`
|
||||
Resize bool `form:"resize" json:"resize"`
|
||||
}
|
||||
|
||||
// ToolboxDiskInit 初始化磁盘请求
|
||||
type ToolboxDiskInit struct {
|
||||
Device string `form:"device" json:"device" validate:"required"`
|
||||
FsType string `form:"fs_type" json:"fs_type" validate:"required|in:ext4,ext3,xfs,btrfs"`
|
||||
FsType string `form:"fs_type" json:"fs_type" validate:"required && in:ext4,ext3,xfs,btrfs"`
|
||||
}
|
||||
|
||||
// ToolboxDiskFstabEntry fstab 条目结构
|
||||
|
||||
@@ -2,5 +2,5 @@ package request
|
||||
|
||||
// ToolboxLogClean 日志清理请求
|
||||
type ToolboxLogClean struct {
|
||||
Type string `form:"type" json:"type" validate:"required|in:panel,website,mysql,postgresql,docker,system"`
|
||||
Type string `form:"type" json:"type" validate:"required && in:panel,website,mysql,postgresql,docker,system"`
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type ToolboxMigrationWebsite struct {
|
||||
|
||||
// ToolboxMigrationDatabase 迁移数据库项
|
||||
type ToolboxMigrationDatabase struct {
|
||||
Type string `json:"type"` // mysql / postgresql
|
||||
Type string `json:"type" validate:"in:mysql,postgresql"` // mysql / postgresql
|
||||
Name string `json:"name"`
|
||||
ServerID uint `json:"server_id"`
|
||||
Server string `json:"server"` // 服务器名称
|
||||
@@ -38,8 +38,8 @@ type ToolboxMigrationDatabaseUser struct {
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"` // 仅 mysql
|
||||
ServerID uint `json:"server_id"`
|
||||
Server string `json:"server"` // 服务器名称
|
||||
Type string `json:"type"` // mysql / postgresql
|
||||
Server string `json:"server"` // 服务器名称
|
||||
Type string `json:"type" validate:"in:mysql,postgresql"` // mysql / postgresql
|
||||
}
|
||||
|
||||
// ToolboxMigrationProject 迁移项目项
|
||||
|
||||
@@ -2,7 +2,7 @@ package request
|
||||
|
||||
// ToolboxSSHPort SSH 端口设置
|
||||
type ToolboxSSHPort struct {
|
||||
Port uint `form:"port" json:"port" validate:"required|min:1|max:65535"`
|
||||
Port uint `form:"port" json:"port" validate:"required && min:1 && max:65535"`
|
||||
}
|
||||
|
||||
// ToolboxSSHPasswordAuth SSH 密码认证设置
|
||||
@@ -17,10 +17,10 @@ type ToolboxSSHPubKeyAuth struct {
|
||||
|
||||
// ToolboxSSHRootLogin Root 登录设置
|
||||
type ToolboxSSHRootLogin struct {
|
||||
Mode string `form:"mode" json:"mode" validate:"required|in:yes,no,prohibit-password,forced-commands-only"`
|
||||
Mode string `form:"mode" json:"mode" validate:"required && in:yes,no,prohibit-password,forced-commands-only"`
|
||||
}
|
||||
|
||||
// ToolboxSSHRootPassword Root 密码设置
|
||||
type ToolboxSSHRootPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package request
|
||||
import "time"
|
||||
|
||||
type ToolboxSystemDNS struct {
|
||||
DNS1 string `form:"dns1" json:"dns1" validate:"required"`
|
||||
DNS2 string `form:"dns2" json:"dns2" validate:"required"`
|
||||
DNS1 string `form:"dns1" json:"dns1" validate:"required && ip"`
|
||||
DNS2 string `form:"dns2" json:"dns2" validate:"required && ip"`
|
||||
}
|
||||
|
||||
type ToolboxSystemSWAP struct {
|
||||
@@ -20,7 +20,7 @@ type ToolboxSystemTime struct {
|
||||
}
|
||||
|
||||
type ToolboxSystemHostname struct {
|
||||
Hostname string `form:"hostname" json:"hostname" validate:"required|regex:^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$"`
|
||||
Hostname string `form:"hostname" json:"hostname" validate:"required && regex:\"^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$\""`
|
||||
}
|
||||
|
||||
type ToolboxSystemHosts struct {
|
||||
@@ -28,7 +28,7 @@ type ToolboxSystemHosts struct {
|
||||
}
|
||||
|
||||
type ToolboxSystemPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
Password string `form:"password" json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
type ToolboxSystemSyncTime struct {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package request
|
||||
|
||||
type UserID struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
ID uint `json:"id" validate:"required && exists:users,id"`
|
||||
}
|
||||
|
||||
type UserLogin struct {
|
||||
@@ -17,28 +17,28 @@ type UserIsTwoFA struct {
|
||||
}
|
||||
|
||||
type UserCreate struct {
|
||||
Username string `json:"username" validate:"required|notExists:users,username|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Password string `json:"password" validate:"required|password"`
|
||||
Email string `json:"email" validate:"required|email"`
|
||||
Username string `json:"username" validate:"required && not_exists:users,username && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Password string `json:"password" validate:"required && password"`
|
||||
Email string `json:"email" validate:"required && email"`
|
||||
}
|
||||
|
||||
type UserUpdateUsername struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
Username string `json:"username" validate:"required|notExists:users,username|regex:^[a-zA-Z0-9_-]+$"`
|
||||
ID uint `json:"id" validate:"required && exists:users,id"`
|
||||
Username string `json:"username" validate:"required && not_exists:users,username && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
}
|
||||
|
||||
type UserUpdatePassword struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
Password string `json:"password" validate:"required|password"`
|
||||
ID uint `json:"id" validate:"required && exists:users,id"`
|
||||
Password string `json:"password" validate:"required && password"`
|
||||
}
|
||||
|
||||
type UserUpdateEmail struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
Email string `json:"email" validate:"required|email"`
|
||||
ID uint `json:"id" validate:"required && exists:users,id"`
|
||||
Email string `json:"email" validate:"required && email"`
|
||||
}
|
||||
|
||||
type UserUpdateTwoFA struct {
|
||||
ID uint `uri:"id" validate:"required|exists:users,id"`
|
||||
ID uint `uri:"id" validate:"required && exists:users,id"`
|
||||
Secret string `json:"secret"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ type UserPasskeyList struct {
|
||||
}
|
||||
|
||||
type UserPasskeyDelete struct {
|
||||
ID uint `uri:"id" validate:"required|min:1"`
|
||||
ID uint `uri:"id" validate:"required && min:1"`
|
||||
UserID uint `query:"user_id"`
|
||||
}
|
||||
|
||||
@@ -1,32 +1,18 @@
|
||||
package request
|
||||
|
||||
import "net/http"
|
||||
|
||||
type UserTokenList struct {
|
||||
UserID uint `query:"user_id"`
|
||||
Paginate
|
||||
}
|
||||
|
||||
type UserTokenCreate struct {
|
||||
UserID uint `json:"user_id" validate:"required|exists:users,id"`
|
||||
IPs []string `json:"ips"`
|
||||
UserID uint `json:"user_id" validate:"required && exists:users,id"`
|
||||
IPs []string `json:"ips" validate:"dive && ipcidr"`
|
||||
ExpiredAt int64 `json:"expired_at" validate:"required"`
|
||||
}
|
||||
|
||||
func (r *UserTokenCreate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"IPs.*": "ipcidr",
|
||||
}
|
||||
}
|
||||
|
||||
type UserTokenUpdate struct {
|
||||
ID uint `uri:"id"`
|
||||
IPs []string `json:"ips"`
|
||||
IPs []string `json:"ips" validate:"dive && ipcidr"`
|
||||
ExpiredAt int64 `json:"expired_at" validate:"required"`
|
||||
}
|
||||
|
||||
func (r *UserTokenUpdate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"IPs.*": "ipcidr",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ type WebHookCreate struct {
|
||||
}
|
||||
|
||||
type WebHookUpdate struct {
|
||||
ID uint `json:"id" form:"id" uri:"id" validate:"required|exists:web_hooks,id"`
|
||||
ID uint `json:"id" form:"id" uri:"id" validate:"required && exists:web_hooks,id"`
|
||||
Name string `json:"name" form:"name" validate:"required"`
|
||||
Script string `json:"script" form:"script" validate:"required"`
|
||||
Raw bool `json:"raw" form:"raw"`
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/acepanel/panel/v3/pkg/webserver/types"
|
||||
)
|
||||
|
||||
@@ -10,57 +8,44 @@ type WebsiteDefaultConfig struct {
|
||||
Index string `json:"index" form:"index" validate:"required"`
|
||||
Stop string `json:"stop" form:"stop" validate:"required"`
|
||||
NotFound string `json:"not_found" form:"not_found"`
|
||||
TLSVersions []string `json:"tls_versions" form:"tls_versions" validate:"required|isSlice"`
|
||||
}
|
||||
|
||||
func (r *WebsiteDefaultConfig) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"TLSVersions.*": "required",
|
||||
}
|
||||
TLSVersions []string `json:"tls_versions" form:"tls_versions" validate:"required && dive && required"`
|
||||
}
|
||||
|
||||
type WebsiteList struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:all,proxy,static,php"`
|
||||
Type string `json:"type" form:"type" validate:"required && in:all,proxy,static,php"`
|
||||
Paginate
|
||||
}
|
||||
|
||||
type WebsiteCreate struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:proxy,static,php"`
|
||||
Name string `form:"name" json:"name" validate:"required|notExists:websites,name|not_in:phpmyadmin,default|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Listens []string `form:"listens" json:"listens" validate:"required|isSlice"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required|isSlice"`
|
||||
Type string `json:"type" form:"type" validate:"required && in:proxy,static,php"`
|
||||
Name string `form:"name" json:"name" validate:"required && not_exists:websites,name && not_in:phpmyadmin,default && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Listens []string `form:"listens" json:"listens" validate:"required && dive && required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required && dive && required"`
|
||||
Path string `form:"path" json:"path"`
|
||||
DB bool `form:"db" json:"db"`
|
||||
DBType string `form:"db_type" json:"db_type" validate:"requiredIf:DB,true"`
|
||||
DBName string `form:"db_name" json:"db_name" validate:"requiredIf:DB,true|regex:^[a-zA-Z_-][a-zA-Z0-9_-]*$"`
|
||||
DBUser string `form:"db_user" json:"db_user" validate:"requiredIf:DB,true|regex:^[a-zA-Z_-][a-zA-Z0-9_-]*$"`
|
||||
DBPassword string `form:"db_password" json:"db_password" validate:"requiredIf:DB,true"`
|
||||
DBType string `form:"db_type" json:"db_type" validate:"required_if:DB,true"`
|
||||
DBName string `form:"db_name" json:"db_name" validate:"required_if:DB,true && regex:\"^[a-zA-Z_-][a-zA-Z0-9_-]*$\""`
|
||||
DBUser string `form:"db_user" json:"db_user" validate:"required_if:DB,true && regex:\"^[a-zA-Z_-][a-zA-Z0-9_-]*$\""`
|
||||
DBPassword string `form:"db_password" json:"db_password" validate:"required_if:DB,true"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
|
||||
PHP uint `form:"php" json:"php" validate:"requiredIf:Type,php"` // 仅 PHP 网站需要
|
||||
Proxy string `form:"proxy" json:"proxy" validate:"requiredIf:Type,proxy"` // 仅反向代理网站需要
|
||||
}
|
||||
|
||||
func (r *WebsiteCreate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"Listens.*": "required",
|
||||
"Domains.*": "required",
|
||||
}
|
||||
PHP uint `form:"php" json:"php" validate:"required_if:Type,php"` // 仅 PHP 网站需要
|
||||
Proxy string `form:"proxy" json:"proxy" validate:"required_if:Type,proxy"` // 仅反向代理网站需要
|
||||
}
|
||||
|
||||
type WebsiteDelete struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:websites,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:websites,id"`
|
||||
Path bool `form:"path" json:"path"`
|
||||
DB bool `form:"db" json:"db"`
|
||||
}
|
||||
|
||||
type WebsiteUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:websites,id"`
|
||||
Listens []types.Listen `form:"listens" json:"listens" validate:"required|isSlice"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required|isSlice"`
|
||||
Path string `form:"path" json:"path" validate:"required"` // 网站目录
|
||||
Root string `form:"root" json:"root" validate:"required"` // 运行目录
|
||||
Index []string `form:"index" json:"index" validate:"required|isSlice"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:websites,id"`
|
||||
Listens []types.Listen `form:"listens" json:"listens" validate:"required && dive && required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required && dive && required"`
|
||||
Path string `form:"path" json:"path" validate:"required && unix_path"` // 网站目录
|
||||
Root string `form:"root" json:"root" validate:"required && unix_path"` // 运行目录
|
||||
Index []string `form:"index" json:"index" validate:"required && dive && required"`
|
||||
|
||||
// SSL 相关
|
||||
SSL bool `form:"ssl" json:"ssl"`
|
||||
@@ -95,43 +80,35 @@ type WebsiteUpdate struct {
|
||||
CustomConfigs []WebsiteCustomConfig `json:"custom_configs"`
|
||||
}
|
||||
|
||||
func (r *WebsiteUpdate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"Listens.*": "required",
|
||||
"Domains.*": "required",
|
||||
"Index.*": "required",
|
||||
}
|
||||
}
|
||||
|
||||
// WebsiteCustomConfig 网站自定义配置请求
|
||||
type WebsiteCustomConfig struct {
|
||||
Name string `json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"` // 配置名称
|
||||
Scope string `json:"scope" validate:"required|in:site,shared"` // 作用域: site(此网站), shared(全局)
|
||||
Content string `json:"content"` // 配置内容
|
||||
Name string `json:"name" validate:"required && regex:\"^[a-zA-Z0-9_-]+$\""` // 配置名称
|
||||
Scope string `json:"scope" validate:"required && in:site,shared"` // 作用域: site(此网站), shared(全局)
|
||||
Content string `json:"content"` // 配置内容
|
||||
}
|
||||
|
||||
type WebsiteUpdateRemark struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:websites,id"`
|
||||
ID uint `form:"id" json:"id" validate:"required && exists:websites,id"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type WebsiteUpdateStatus struct {
|
||||
ID uint `json:"id" form:"id" validate:"required|exists:websites,id"`
|
||||
ID uint `json:"id" form:"id" validate:"required && exists:websites,id"`
|
||||
Status bool `json:"status" form:"status"`
|
||||
}
|
||||
|
||||
type WebsiteUpdateExpireAt struct {
|
||||
ID uint `json:"id" form:"id" validate:"required|exists:websites,id"`
|
||||
ID uint `json:"id" form:"id" validate:"required && exists:websites,id"`
|
||||
ExpireAt string `json:"expire_at" form:"expire_at"` // 为空表示清除到期时间(不限时)
|
||||
}
|
||||
|
||||
type WebsiteUpdateCert struct {
|
||||
Name string `json:"name" validate:"required|exists:websites,name|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Name string `json:"name" validate:"required && exists:websites,name && regex:\"^[a-zA-Z0-9_-]+$\""`
|
||||
Cert string `json:"cert" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteObtainCert struct {
|
||||
ID uint `json:"id" form:"id" uri:"id" validate:"required|exists:websites,id"`
|
||||
ID uint `json:"id" form:"id" uri:"id" validate:"required && exists:websites,id"`
|
||||
DNSID uint `json:"dns_id" form:"dns_id"`
|
||||
}
|
||||
|
||||
@@ -10,18 +10,18 @@ import (
|
||||
|
||||
// WebsiteStatSetting 网站统计设置
|
||||
type WebsiteStatSetting struct {
|
||||
Days uint `json:"days" validate:"required|min:1|max:365"`
|
||||
ErrBufMax int `json:"err_buf_max" validate:"min:0|max:1000000"`
|
||||
UVMaxKeys int `json:"uv_max_keys" validate:"min:0|max:100000000"`
|
||||
IPMaxKeys int `json:"ip_max_keys" validate:"min:0|max:100000000"`
|
||||
DetailMaxKeys int `json:"detail_max_keys" validate:"min:0|max:100000000"`
|
||||
Days uint `json:"days" validate:"required && min:1 && max:365"`
|
||||
ErrBufMax int `json:"err_buf_max" validate:"min:0 && max:1000000"`
|
||||
UVMaxKeys int `json:"uv_max_keys" validate:"min:0 && max:100000000"`
|
||||
IPMaxKeys int `json:"ip_max_keys" validate:"min:0 && max:100000000"`
|
||||
DetailMaxKeys int `json:"detail_max_keys" validate:"min:0 && max:100000000"`
|
||||
BodyEnabled bool `json:"body_enabled"`
|
||||
}
|
||||
|
||||
// WebsiteStatDateRange 统计日期范围查询参数
|
||||
type WebsiteStatDateRange struct {
|
||||
Start string `json:"start" form:"start" query:"start"`
|
||||
End string `json:"end" form:"end" query:"end"`
|
||||
Start string `json:"start" form:"start" query:"start" validate:"datetime:2006-01-02"`
|
||||
End string `json:"end" form:"end" query:"end" validate:"datetime:2006-01-02"`
|
||||
Sites string `json:"sites" form:"sites" query:"sites"`
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user