mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 20:05:34 +08:00
Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4e31c4902 | |||
| b2d382c2af | |||
| 5d0ad333c9 | |||
| ccf05b8c42 | |||
| 684c1b7140 | |||
| 15e12d083d | |||
| f6937bb4cb | |||
| 8683a85640 | |||
| 7ee6dd4d0e | |||
| 1c7c6074a1 | |||
| af865b62a5 | |||
| 69b430d7cb | |||
| 0956385284 | |||
| 46cf1fa7da | |||
| ac9b42ca4d | |||
| 264335429a | |||
| 323e2bf95a | |||
| 6b413d2962 | |||
| 83e0e33207 | |||
| 827df91241 | |||
| f78f414c77 | |||
| dfb73d1730 | |||
| 92c1d136c2 | |||
| 45428f3e0b | |||
| 139679ed0f | |||
| eed834bd5f | |||
| 422c92cca5 | |||
| 28d699c886 | |||
| f4533c9662 | |||
| ad71a8075b | |||
| 7d39015d97 | |||
| c3498aa5c5 | |||
| 2c3bb5bf91 | |||
| 3c2b6e6488 | |||
| dff0a61182 | |||
| d081f1e312 | |||
| 46809c8d5b | |||
| f1246ee779 | |||
| 03168a5652 | |||
| 0e43d25430 | |||
| 46a9446007 | |||
| 3761223230 | |||
| 836b34e59d | |||
| 3dc1106012 | |||
| 3eb8bf793f | |||
| cd823c4506 | |||
| 36d45ee66f | |||
| 068d36de3d | |||
| e3607b9bc7 | |||
| 76a2ff8e43 | |||
| b3e3da6833 | |||
| fa6c574743 | |||
| e7625a77a8 | |||
| e1b3c80771 | |||
| 3e9228cb65 | |||
| e8b5bb266f | |||
| a6d27f261b | |||
| 1a85571fca | |||
| 9b4f308367 | |||
| aabf49d041 | |||
| 0d22b53b36 | |||
| 8d9c25c195 | |||
| bfd4fe41d3 | |||
| 8400705053 | |||
| b3e72acad8 | |||
| 33884c4c67 | |||
| 45a939571d | |||
| ac4f2a8531 | |||
| 7c09b017c9 | |||
| 02c207d0b8 | |||
| 6d23f59d6a | |||
| 4c55552dec | |||
| 5ba24d11f6 | |||
| bcf557c47e | |||
| cac55a4f79 | |||
| 0d26edf6f0 | |||
| 7a39ef706c | |||
| f5c37c0402 | |||
| 16d571e4ea | |||
| b4b75aa9c5 | |||
| 6ed7784a89 | |||
| d2c7c1109d | |||
| 079a7f7c07 | |||
| 923c6a478c | |||
| 9e0f65cf52 | |||
| eab8184b6b | |||
| 3972a17bd1 | |||
| 34b7b8a10a | |||
| a4da49d8ae | |||
| f8a989fbfc | |||
| 4497b8fa82 | |||
| 0bb7ccd121 | |||
| b90227017c | |||
| 75d941060f | |||
| b37be8aae9 | |||
| f5a471768b | |||
| f6ad6b23cb | |||
| f5b9d3e912 | |||
| 9619c3f55b | |||
| 8c08ebb041 | |||
| 0567319671 | |||
| 760c3be9c7 | |||
| 1084968e99 | |||
| 188c2ffce0 | |||
| f6296e7704 | |||
| bd149f6524 | |||
| 989beb3642 | |||
| 5d4d94a5d8 | |||
| 41eca93f12 | |||
| 946def76c5 | |||
| 24768a4078 | |||
| 7751117dc6 | |||
| f9f286335d | |||
| 1d50da79bb | |||
| 7e78b6eaa3 | |||
| a098c988a7 | |||
| 0b04f32aed | |||
| e14c1db060 | |||
| e109e16610 | |||
| cd3f2b5951 | |||
| fe28e84bf7 | |||
| fe4e3850c9 | |||
| 2a070e14c0 | |||
| 3984f85d57 | |||
| dcd4eb1e49 | |||
| bc62fbadbb | |||
| d17ef0bfda | |||
| 622d08f6f4 | |||
| 2a2fbbf287 | |||
| 77fa6ec58f | |||
| fae4894cea | |||
| b4195459c5 | |||
| 5a40cd3910 | |||
| c6971004f5 | |||
| e6ff46ccac | |||
| a91d0b79a7 | |||
| f0a9aceb13 | |||
| c251f73895 | |||
| 79b8e58412 | |||
| 73be25dcf7 | |||
| fff4d3eae2 | |||
| e614e97312 | |||
| 7ac448d2c4 | |||
| 08f5c88cee | |||
| 779d734dad | |||
| c3ca61a665 | |||
| 832367eb2a | |||
| 929a799f68 | |||
| f1cdb19a59 | |||
| 6f588efea7 | |||
| 333e8ae39f | |||
| 12dc49968a | |||
| 960fc78518 | |||
| 447c393b46 |
@@ -6,14 +6,13 @@ on:
|
||||
pull_request:
|
||||
jobs:
|
||||
frontend:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
@@ -35,7 +34,7 @@ jobs:
|
||||
path: web/dist/
|
||||
backend:
|
||||
needs: frontend
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goarch: [ amd64, arm64 ]
|
||||
|
||||
@@ -7,7 +7,7 @@ permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -21,7 +21,6 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ☢️ Bug
|
||||
if: github.event.label.name == '☢️ Bug'
|
||||
|
||||
@@ -9,7 +9,7 @@ permissions:
|
||||
jobs:
|
||||
golangci:
|
||||
name: golanci-lint
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
version: latest
|
||||
args: --timeout=30m ./...
|
||||
govulncheck:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -39,14 +39,13 @@ jobs:
|
||||
- name: Run Govulncheck
|
||||
run: govulncheck ./...
|
||||
frontend:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
|
||||
@@ -10,6 +10,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install Mockery
|
||||
run: |
|
||||
go install github.com/vektra/mockery/v2@latest
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
pull_request:
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
企业级全能服务器运维管理面板。简单轻量,高效运维。
|
||||
新一代全能服务器运维管理面板。简单轻量,高效运维。
|
||||
|
||||
官网:[panel.haozi.net](https://panel.haozi.net) | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
Enterprise-level all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
|
||||
A new generation of all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
|
||||
|
||||
Website: [panel.haozi.net](https://panel.haozi.net) | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
|
||||
|
||||
+3
-1
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
@@ -76,6 +77,7 @@ func initCli() (*app.Cli, error) {
|
||||
fail2banApp := fail2ban.NewApp(websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
minioApp := minio.NewApp()
|
||||
memcachedApp := memcached.NewApp()
|
||||
mysqlApp := mysql.NewApp(settingRepo)
|
||||
nginxApp := nginx.NewApp()
|
||||
@@ -94,7 +96,7 @@ func initCli() (*app.Cli, error) {
|
||||
s3fsApp := s3fs.NewApp(settingRepo)
|
||||
supervisorApp := supervisor.NewApp()
|
||||
toolboxApp := toolbox.NewApp()
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, minioApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
appCli := app.NewCli(command, gormigrate, loader)
|
||||
return appCli, nil
|
||||
}
|
||||
|
||||
+6
-2
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
@@ -97,6 +98,8 @@ func initWeb() (*app.Web, error) {
|
||||
sshService := service.NewSSHService(sshRepo)
|
||||
containerRepo := data.NewContainerRepo()
|
||||
containerService := service.NewContainerService(containerRepo)
|
||||
containerComposeRepo := data.NewContainerComposeRepo()
|
||||
containerComposeService := service.NewContainerComposeService(containerComposeRepo)
|
||||
containerNetworkRepo := data.NewContainerNetworkRepo()
|
||||
containerNetworkService := service.NewContainerNetworkService(containerNetworkRepo)
|
||||
containerImageRepo := data.NewContainerImageRepo()
|
||||
@@ -113,6 +116,7 @@ func initWeb() (*app.Web, error) {
|
||||
fail2banApp := fail2ban.NewApp(websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
minioApp := minio.NewApp()
|
||||
memcachedApp := memcached.NewApp()
|
||||
mysqlApp := mysql.NewApp(settingRepo)
|
||||
nginxApp := nginx.NewApp()
|
||||
@@ -131,8 +135,8 @@ func initWeb() (*app.Web, error) {
|
||||
s3fsApp := s3fs.NewApp(settingRepo)
|
||||
supervisorApp := supervisor.NewApp()
|
||||
toolboxApp := toolbox.NewApp()
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, minioApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
|
||||
wsService := service.NewWsService(koanf, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
mux, err := bootstrap.NewRouter(middlewares, http, ws)
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
module github.com/tnb-labs/panel
|
||||
|
||||
go 1.23
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/bddjr/hlfhr v1.3.8
|
||||
github.com/beevik/ntp v1.4.3
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/expr-lang/expr v1.16.9
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/expr-lang/expr v1.17.2
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3
|
||||
github.com/go-rat/chix v1.1.4
|
||||
github.com/go-rat/chix v1.1.5
|
||||
github.com/go-rat/gormstore v1.0.6
|
||||
github.com/go-rat/sessions v1.0.11
|
||||
github.com/go-rat/utils v1.0.9
|
||||
github.com/go-rat/sessions v1.1.0
|
||||
github.com/go-rat/utils v1.1.4
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/go-sql-driver/mysql v1.9.1
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
|
||||
github.com/gomodule/redigo v1.9.2
|
||||
github.com/google/wire v0.6.0
|
||||
@@ -27,22 +26,24 @@ require (
|
||||
github.com/knadh/koanf/v2 v2.1.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/libdns/cloudflare v0.1.3
|
||||
github.com/libdns/huaweicloud v0.3.1
|
||||
github.com/libdns/libdns v0.2.2
|
||||
github.com/libdns/libdns v0.2.3
|
||||
github.com/libdns/tencentcloud v1.2.0
|
||||
github.com/mholt/acmez/v3 v3.0.1
|
||||
github.com/mholt/acmez/v3 v3.1.1
|
||||
github.com/ncruces/go-sqlite3 v0.24.1
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0
|
||||
github.com/orandin/slog-gorm v1.4.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/sethvargo/go-limiter v1.0.0
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cast v1.7.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1
|
||||
golang.org/x/crypto v0.33.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/net v0.38.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.12
|
||||
@@ -51,42 +52,31 @@ require (
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-rat/securecookie v1.0.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-rat/securecookie v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gofiber/schema v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gofiber/schema v1.3.0 // indirect
|
||||
github.com/gookit/filter v1.2.2 // indirect
|
||||
github.com/gookit/goutil v0.6.18 // indirect
|
||||
github.com/jaevor/go-nanoid v1.4.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
modernc.org/libc v1.60.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/libdns/tencentcloud => github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504
|
||||
github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1
|
||||
)
|
||||
replace github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250328123917-f204dcee9d09
|
||||
|
||||
@@ -8,46 +8,39 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504 h1:Wk7RQyamGDfAz+5hZIT/GIm6ay5cORztgYVXOJTobKY=
|
||||
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504/go.mod h1:o0+WCxQ7LGLtyjnjYU4HbGW9uVjN44SdUDhxdUYLGPw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
|
||||
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/expr-lang/expr v1.17.2 h1:o0A99O/Px+/DTjEnQiodAgOIK9PPxL8DtXhBRKC+Iso=
|
||||
github.com/expr-lang/expr v1.17.2/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3 h1:ei3Vq/rpPI/jCJY9mRHJAKg5vU+EhZyWhBAkaAomQuw=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3/go.mod h1:VJ9FIOBAur+NmQ8c4tDVwOuiJcgupTG105FexPFrXzA=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-rat/chix v1.1.4 h1:YVDwF4vrlkm5hmlDGWKoiWPgP98pJooBvhsto8UWq+o=
|
||||
github.com/go-rat/chix v1.1.4/go.mod h1:eKUTG7GXqMS278PH0QZi8yMWZddvMfxqWu7mverM6kQ=
|
||||
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=
|
||||
github.com/go-rat/chix v1.1.5 h1:nv+NHODd9o3AwK9JhEaUsuMCAc7cvw4bh39yTrVuo1M=
|
||||
github.com/go-rat/chix v1.1.5/go.mod h1:YQP0YT0x/U3Xx5uQe2jfyMiS/bA732Nunnii6MG0wos=
|
||||
github.com/go-rat/gormstore v1.0.6 h1:YxmaY6n3JoTk6o9RXoZ1+cQ6HQL+ayf/6d8D76ydCAc=
|
||||
github.com/go-rat/gormstore v1.0.6/go.mod h1:xG/n+Du8baWf7ptiafiHLWBZWVXrYIPC42CsoEbfr4M=
|
||||
github.com/go-rat/securecookie v1.0.1 h1:HW0fpKmB+FjJzXTw8ABOwBJ+XrPmRBSZqHhmrv86lBo=
|
||||
github.com/go-rat/securecookie v1.0.1/go.mod h1:tP/ObWYyjmcpabQ7WTon/i2lBSip/Aolliw2llXuPDU=
|
||||
github.com/go-rat/sessions v1.0.11 h1:2eTUvhEBtymrAtwv71LlGHNylz+Ad2SgY3jg/sSUboA=
|
||||
github.com/go-rat/sessions v1.0.11/go.mod h1:Ray/GCbuhm4U9xpjFFSCfOTCEn91puEhAXX5creHE9g=
|
||||
github.com/go-rat/utils v1.0.9 h1:16zq6CpeULJgoW+TaJZ+gS4EPl0tQYAcuAdx3/GNCNs=
|
||||
github.com/go-rat/utils v1.0.9/go.mod h1:LhzbrdsTHIo6Rjlz27Nh8edkugmoxbZjN87iV+ch+5M=
|
||||
github.com/go-rat/securecookie v1.1.0 h1:bvwE9n6X3a4eazOIevEvZghkvZ3OpY4BX1cnH/1/x0s=
|
||||
github.com/go-rat/securecookie v1.1.0/go.mod h1:Z870TcL7ktAjRuhPeQXYuQcA6/DdzKwwqSwDQuPmufI=
|
||||
github.com/go-rat/sessions v1.1.0 h1:5zCySVVxNE6xLymf5F2u4TQPKIM7kz1MuD4TwIb/7Do=
|
||||
github.com/go-rat/sessions v1.1.0/go.mod h1:ofnsWh7JoAApzCfIGd60xEyT5HlJY+Nx8zpWcwUXSHc=
|
||||
github.com/go-rat/utils v1.1.4 h1:tjlX6IWHhA/VvEBVbLGjzA96dcFSdMU5quetUHY2uqs=
|
||||
github.com/go-rat/utils v1.1.4/go.mod h1:SUy4Xw44VkZvo2FaURgiIiJlL6Kukll85fVKMp/50+8=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
|
||||
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
|
||||
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||
github.com/gofiber/schema v1.3.0 h1:K3F3wYzAY+aivfCCEHPufCthu5/13r/lzp1nuk6mr3Q=
|
||||
github.com/gofiber/schema v1.3.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a h1:BAyyIK6rc6Tq9vT2KVlPJXD8EVv/LpaFwn4fgfLwtHg=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a/go.mod h1:bgk4Ij/0OQ89UeoFFAQrSNhbbr4rKJ0fwWfo7wc+TCc=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
@@ -57,11 +50,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
@@ -98,29 +87,31 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||
github.com/libdns/cloudflare v0.1.3 h1:XPFa2f3Mm/3FDNwl9Ki2bfAQJ0Cm5GQB0e8PQVy25Us=
|
||||
github.com/libdns/cloudflare v0.1.3/go.mod h1:XbvSCSMcxspwpSialM3bq0LsS3/Houy9WYxW8Ok8b6M=
|
||||
github.com/libdns/huaweicloud v0.3.1 h1:wro0zpG86JKL3QVEaV/xfHW/59rmqwMYhcbl16w9EYg=
|
||||
github.com/libdns/huaweicloud v0.3.1/go.mod h1:6s5ZIwLUr2qKsQz2SRNo7uh8X5uJohxoBR95rtSpNcI=
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
|
||||
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/tencentcloud v1.2.0 h1:SBbZ9gUZ+ba/p2d8NpiOcj+WZTdYqB5Fz7zypdbK3YU=
|
||||
github.com/libdns/tencentcloud v1.2.0/go.mod h1:o0+WCxQ7LGLtyjnjYU4HbGW9uVjN44SdUDhxdUYLGPw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-sqlite3 v0.24.1 h1:qHlIz+dlH3Y0wCUErFZXon5hCvw1Kc9eEkZVYYi8p14=
|
||||
github.com/ncruces/go-sqlite3 v0.24.1/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0 h1:81sHeq3CCdhjoqAB650n5wEdRlLO9VBvosArskcN3+c=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0/go.mod h1:vXfVWdBfg7qOgqQqHpzUWl9LLswD0h+8mK4oouaV2oc=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsTUU=
|
||||
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
@@ -129,24 +120,26 @@ github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4=
|
||||
github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible h1:lOGOyCG67a5dv2hq5Z1BLDUqqKp3HkbjPcz5j6XMS0U=
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051 h1:3mg0L9vv9eO8UN4Oa7vNawe6yUIuXf9D0Q79rUmnblo=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 h1:DMokC7T0UF8wMfT1kD+mX3M+hc2C06gmFvQ9gsfRPmI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1 h1:7IhZYEiBWy3KfKi9wZkv4Um8QYYF2fYzQ+xNrOx/tHI=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a h1:bFMBmB409YhHd+B4yfM7JPYTCgxnQjqzIfFOiN4Tpsc=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a/go.mod h1:hdMWBc1+TyB6G5ZZBBgPWQ8cjRZ6zpYdhal0uu6E9QM=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250328123917-f204dcee9d09 h1:Rz/1C7lc0qCuyZY20oh6grqy/hjm9gcjIJB3yEZ4i30=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250328123917-f204dcee9d09/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6 h1:HmtcQ7w07RI2SdTKkPf+NM8R33B1oR9MjIZYzlBizwA=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6/go.mod h1:hdMWBc1+TyB6G5ZZBBgPWQ8cjRZ6zpYdhal0uu6E9QM=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
@@ -158,16 +151,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -175,36 +166,36 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -212,8 +203,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -222,8 +213,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
@@ -236,27 +225,3 @@ gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s=
|
||||
modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
|
||||
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
@@ -34,6 +35,7 @@ var ProviderSet = wire.NewSet(
|
||||
fail2ban.NewApp,
|
||||
frp.NewApp,
|
||||
gitea.NewApp,
|
||||
minio.NewApp,
|
||||
memcached.NewApp,
|
||||
mysql.NewApp,
|
||||
nginx.NewApp,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/env", s.GetEnv)
|
||||
r.Post("/env", s.UpdateEnv)
|
||||
}
|
||||
|
||||
func (s *App) GetEnv(w http.ResponseWriter, r *http.Request) {
|
||||
env, _ := io.Read("/etc/default/minio")
|
||||
service.Success(w, env)
|
||||
}
|
||||
|
||||
func (s *App) UpdateEnv(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateEnv](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/default/minio", req.Env, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("minio"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package minio
|
||||
|
||||
type UpdateEnv struct {
|
||||
Env string `form:"env" json:"env" validate:"required"`
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
re := regexp.MustCompile(`requirepass\s+(.+)`)
|
||||
re := regexp.MustCompile(`^requirepass\s+(.+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) == 2 {
|
||||
withPassword = " -a " + matches[1]
|
||||
|
||||
@@ -19,6 +19,7 @@ type Cert struct {
|
||||
CertURL string `gorm:"not null" json:"cert_url"` // 证书 URL (续签时使用)
|
||||
Cert string `gorm:"not null" json:"cert"` // 证书内容
|
||||
Key string `gorm:"not null" json:"key"` // 私钥内容
|
||||
Script string `gorm:"not null" json:"script"` // 部署脚本
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package biz
|
||||
|
||||
import "github.com/tnb-labs/panel/pkg/types"
|
||||
|
||||
type ContainerComposeRepo interface {
|
||||
List() ([]types.ContainerCompose, error)
|
||||
Get(name string) (string, []types.KV, error)
|
||||
Create(name, compose string, envs []types.KV) error
|
||||
Update(name, compose string, envs []types.KV) error
|
||||
Up(name string, force bool) error
|
||||
Down(name string) error
|
||||
Remove(name string) error
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
@@ -33,6 +34,7 @@ func NewLoader(
|
||||
fail2ban *fail2ban.App,
|
||||
frp *frp.App,
|
||||
gitea *gitea.App,
|
||||
minio *minio.App,
|
||||
memcached *memcached.App,
|
||||
mysql *mysql.App,
|
||||
nginx *nginx.App,
|
||||
@@ -53,6 +55,6 @@ func NewLoader(
|
||||
toolbox *toolbox.App,
|
||||
) *apploader.Loader {
|
||||
loader := new(apploader.Loader)
|
||||
loader.Add(benchmark, docker, fail2ban, frp, gitea, memcached, mysql, nginx, php74, php80, php81, php82, php83, php84, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor, toolbox)
|
||||
loader.Add(benchmark, docker, fail2ban, frp, gitea, minio, memcached, mysql, nginx, php74, php80, php81, php82, php83, php84, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor, toolbox)
|
||||
return loader
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/knadh/koanf/v2"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/gormlite"
|
||||
sloggorm "github.com/orandin/slog-gorm"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
func NewDB(conf *koanf.Koanf, log *slog.Logger) (*gorm.DB, error) {
|
||||
// You can use any other database, like MySQL or PostgreSQL.
|
||||
return gorm.Open(sqlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
|
||||
return gorm.Open(gormlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
|
||||
Logger: sloggorm.New(sloggorm.WithHandler(log.Handler())),
|
||||
SkipDefaultTransaction: true,
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewHttp(conf *koanf.Koanf, r *chi.Mux) (*hlfhr.Server, error) {
|
||||
|
||||
if conf.Bool("http.tls") {
|
||||
srv.TLSConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ func NewSession(conf *koanf.Koanf, db *gorm.DB) (*sessions.Manager, error) {
|
||||
manager, err := sessions.NewManager(&sessions.ManagerOptions{
|
||||
Key: conf.MustString("app.key"),
|
||||
Lifetime: 120,
|
||||
GcInterval: 30,
|
||||
GcInterval: 5,
|
||||
DisableDefaultDriver: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -833,6 +833,9 @@ func (r *backupRepo) UpdatePanel(version, url, checksum string) error {
|
||||
_, _ = 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
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -49,6 +50,7 @@ func (r *certRepo) List(page, limit uint) ([]*types.CertList, int64, error) {
|
||||
AutoRenew: cert.AutoRenew,
|
||||
Cert: cert.Cert,
|
||||
Key: cert.Key,
|
||||
Script: cert.Script,
|
||||
CreatedAt: cert.CreatedAt,
|
||||
UpdatedAt: cert.UpdatedAt,
|
||||
}
|
||||
@@ -131,6 +133,7 @@ func (r *certRepo) Update(req *request.CertUpdate) error {
|
||||
Type: req.Type,
|
||||
Cert: req.Cert,
|
||||
Key: req.Key,
|
||||
Script: req.Script,
|
||||
Domains: req.Domains,
|
||||
AutoRenew: req.AutoRenew,
|
||||
}).Error
|
||||
@@ -183,6 +186,10 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
|
||||
return &ssl, r.Deploy(cert.ID, cert.WebsiteID)
|
||||
}
|
||||
|
||||
if err = r.runScript(cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ssl, nil
|
||||
}
|
||||
|
||||
@@ -212,6 +219,10 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
|
||||
return &ssl, r.Deploy(cert.ID, cert.WebsiteID)
|
||||
}
|
||||
|
||||
if err = r.runScript(cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ssl, nil
|
||||
}
|
||||
|
||||
@@ -236,6 +247,10 @@ func (r *certRepo) ObtainSelfSigned(id uint) error {
|
||||
return r.Deploy(cert.ID, cert.WebsiteID)
|
||||
}
|
||||
|
||||
if err = r.runScript(cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -344,6 +359,35 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *certRepo) runScript(cert *biz.Cert) error {
|
||||
if cert.Script == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "cert-deploy-*.sh")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 替换变量
|
||||
cert.Script = strings.ReplaceAll(cert.Script, "{cert}", cert.Cert)
|
||||
cert.Script = strings.ReplaceAll(cert.Script, "{key}", cert.Key)
|
||||
|
||||
if _, err = f.WriteString(cert.Script); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Chmod(0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
_, err = shell.Execf("bash " + f.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
|
||||
if cert.Account == nil {
|
||||
return nil, errors.New("this certificate is not associated with an ACME account and cannot be signed")
|
||||
|
||||
@@ -39,7 +39,7 @@ func (r *containerRepo) ListAll() ([]types.Container, error) {
|
||||
for _, port := range item.Ports {
|
||||
ports = append(ports, types.ContainerPort{
|
||||
ContainerStart: uint(port.PrivatePort),
|
||||
ContainerEnd: uint(port.PublicPort),
|
||||
ContainerEnd: uint(port.PrivatePort),
|
||||
HostStart: uint(port.PublicPort),
|
||||
HostEnd: uint(port.PublicPort),
|
||||
Protocol: port.Type,
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type containerComposeRepo struct{}
|
||||
|
||||
func NewContainerComposeRepo() biz.ContainerComposeRepo {
|
||||
return &containerComposeRepo{}
|
||||
}
|
||||
|
||||
// List 列出所有编排文件名
|
||||
func (r *containerComposeRepo) List() ([]types.ContainerCompose, error) {
|
||||
raw, err := shell.Execf("docker compose ls -a --format json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var composeRaws []types.ContainerComposeRaw
|
||||
if err = json.Unmarshal([]byte(raw), &composeRaws); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
composeDir := filepath.Join(app.Root, "server", "compose")
|
||||
entries, err := os.ReadDir(composeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var composes []types.ContainerCompose
|
||||
index := make(map[string]int)
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(composeDir, entry.Name())
|
||||
var createdAt time.Time
|
||||
if info, err := entry.Info(); err == nil {
|
||||
createdAt = info.ModTime()
|
||||
}
|
||||
|
||||
composes = append(composes, types.ContainerCompose{
|
||||
Name: entry.Name(),
|
||||
Path: path,
|
||||
Status: "unknown",
|
||||
CreatedAt: createdAt,
|
||||
})
|
||||
index[entry.Name()] = len(composes) - 1
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
for _, item := range composeRaws {
|
||||
if i, ok := index[item.Name]; ok {
|
||||
composes[i].Status = item.Status
|
||||
}
|
||||
}
|
||||
|
||||
return composes, nil
|
||||
}
|
||||
|
||||
// Get 获取编排文件和环境变量内容
|
||||
func (r *containerComposeRepo) Get(name string) (string, []types.KV, error) {
|
||||
content, _ := os.ReadFile(filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml"))
|
||||
env, _ := os.ReadFile(filepath.Join(app.Root, "server", "compose", name, ".env"))
|
||||
|
||||
var envs []types.KV
|
||||
for _, line := range strings.Split(string(env), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
kv := strings.SplitN(line, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
envs = append(envs, types.KV{Key: kv[0], Value: kv[1]})
|
||||
}
|
||||
|
||||
return string(content), envs, nil // 有意忽略错误,这样可以允许新建文件
|
||||
}
|
||||
|
||||
// Create 创建编排文件
|
||||
func (r *containerComposeRepo) Create(name, compose string, envs []types.KV) error {
|
||||
dir := filepath.Join(app.Root, "server", "compose", name)
|
||||
if err := os.MkdirAll(dir, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "docker-compose.yml"), []byte(compose), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, kv := range envs {
|
||||
sb.WriteString(kv.Key)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(kv.Value)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(sb.String()), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 更新编排文件
|
||||
func (r *containerComposeRepo) Update(name, compose string, envs []types.KV) error {
|
||||
dir := filepath.Join(app.Root, "server", "compose", name)
|
||||
if err := os.WriteFile(filepath.Join(dir, "docker-compose.yml"), []byte(compose), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, kv := range envs {
|
||||
sb.WriteString(kv.Key)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(kv.Value)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(sb.String()), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Up 启动编排
|
||||
func (r *containerComposeRepo) Up(name string, force bool) error {
|
||||
file := filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml")
|
||||
cmd := "docker compose -f %s up -d"
|
||||
if force {
|
||||
cmd += " --pull always" // 强制拉取镜像
|
||||
}
|
||||
_, err := shell.Execf(cmd, file)
|
||||
return err
|
||||
}
|
||||
|
||||
// Down 停止编排
|
||||
func (r *containerComposeRepo) Down(name string) error {
|
||||
file := filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml")
|
||||
_, err := shell.Execf("docker compose -f %s down", file)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove 删除编排
|
||||
func (r *containerComposeRepo) Remove(name string) error {
|
||||
dir := filepath.Join(app.Root, "server", "compose", name)
|
||||
return os.RemoveAll(dir)
|
||||
}
|
||||
@@ -65,6 +65,7 @@ export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
panel-cli backup website -n '%s' -p '%s'
|
||||
panel-cli backup clear -t website -f '%s' -s '%d' -p '%s'
|
||||
systemctl reload nginx
|
||||
`, req.Target, req.BackupPath, req.Target, req.Save, req.BackupPath)
|
||||
}
|
||||
if req.BackupType == "mysql" || req.BackupType == "postgres" {
|
||||
|
||||
@@ -11,6 +11,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewCertAccountRepo,
|
||||
NewCertDNSRepo,
|
||||
NewContainerRepo,
|
||||
NewContainerComposeRepo,
|
||||
NewContainerImageRepo,
|
||||
NewContainerNetworkRepo,
|
||||
NewContainerVolumeRepo,
|
||||
|
||||
@@ -2,6 +2,8 @@ package embed
|
||||
|
||||
import "embed"
|
||||
|
||||
// TODO 移到pkg目录下
|
||||
|
||||
//go:embed all:frontend/*
|
||||
var PublicFS embed.FS
|
||||
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
package request
|
||||
|
||||
import "mime/multipart"
|
||||
|
||||
type BackupList struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:path,website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:path,website,mysql,postgres,redis,panel"`
|
||||
}
|
||||
|
||||
type BackupCreate struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Target string `json:"target" form:"target" validate:"required"`
|
||||
Path string `json:"path" form:"path"`
|
||||
}
|
||||
|
||||
type BackupUpload struct {
|
||||
Type string `uri:"type" form:"type"` // 校验没有必要,因为根本没经过验证器
|
||||
File *multipart.FileHeader `form:"file"`
|
||||
}
|
||||
|
||||
type BackupFile struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
File string `json:"file" form:"file" validate:"required"`
|
||||
}
|
||||
|
||||
type BackupRestore struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
File string `json:"file" form:"file" validate:"required"`
|
||||
Target string `json:"target" form:"target" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type CertUpdate struct {
|
||||
Domains []string `form:"domains" json:"domains" validate:"required|isSlice"`
|
||||
Cert string `form:"cert" json:"cert"`
|
||||
Key string `form:"key" json:"key"`
|
||||
Script string `form:"script" json:"script"`
|
||||
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
|
||||
AccountID uint `form:"account_id" json:"account_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package request
|
||||
|
||||
import "github.com/tnb-labs/panel/pkg/types"
|
||||
|
||||
type ContainerComposeGet struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type ContainerComposeCreate struct {
|
||||
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_-]+$"`
|
||||
Compose string `json:"compose" validate:"required"`
|
||||
Envs []types.KV `json:"envs"`
|
||||
}
|
||||
|
||||
type ContainerComposeUp struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
|
||||
type ContainerComposeDown struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type ContainerComposeRemove struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
}
|
||||
@@ -7,7 +7,7 @@ type CronCreate struct {
|
||||
Script string `form:"script" json:"script"`
|
||||
BackupType string `form:"backup_type" json:"backup_type" validate:"requiredIf:Type,backup"`
|
||||
BackupPath string `form:"backup_path" json:"backup_path"`
|
||||
Target string `form:"target" json:"target" validate:"required"`
|
||||
Target string `form:"target" json:"target" validate:"requiredIf:Type,backup,cutoff"`
|
||||
Save int `form:"save" json:"save" validate:"required"`
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,11 @@ func (r *PanelTask) Run() {
|
||||
r.log.Warn("[Panel Task] failed to vacuum database", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
if err := r.db.Exec("PRAGMA journal_mode=WAL;").Error; err != nil {
|
||||
app.Status = app.StatusFailed
|
||||
r.log.Warn("[Panel Task] failed to set database journal_mode to WAL", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
||||
app.Status = app.StatusFailed
|
||||
r.log.Warn("[Panel Task] failed to wal checkpoint database", slog.Any("err", err))
|
||||
|
||||
@@ -69,4 +69,13 @@ func init() {
|
||||
)
|
||||
},
|
||||
})
|
||||
Migrations = append(Migrations, &gormigrate.Migration{
|
||||
ID: "20250318-cert-script",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&biz.Cert{})
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return tx.Migrator().DropColumn(&biz.Cert{}, "script")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type Http struct {
|
||||
firewall *service.FirewallService
|
||||
ssh *service.SSHService
|
||||
container *service.ContainerService
|
||||
containerCompose *service.ContainerComposeService
|
||||
containerNetwork *service.ContainerNetworkService
|
||||
containerImage *service.ContainerImageService
|
||||
containerVolume *service.ContainerVolumeService
|
||||
@@ -62,6 +63,7 @@ func NewHttp(
|
||||
firewall *service.FirewallService,
|
||||
ssh *service.SSHService,
|
||||
container *service.ContainerService,
|
||||
containerCompose *service.ContainerComposeService,
|
||||
containerNetwork *service.ContainerNetworkService,
|
||||
containerImage *service.ContainerImageService,
|
||||
containerVolume *service.ContainerVolumeService,
|
||||
@@ -90,6 +92,7 @@ func NewHttp(
|
||||
firewall: firewall,
|
||||
ssh: ssh,
|
||||
container: container,
|
||||
containerCompose: containerCompose,
|
||||
containerNetwork: containerNetwork,
|
||||
containerImage: containerImage,
|
||||
containerVolume: containerVolume,
|
||||
@@ -284,6 +287,15 @@ func (route *Http) Register(r *chi.Mux) {
|
||||
r.Get("/{id}/logs", route.container.Logs)
|
||||
r.Post("/prune", route.container.Prune)
|
||||
})
|
||||
r.Route("/compose", func(r chi.Router) {
|
||||
r.Get("/", route.containerCompose.List)
|
||||
r.Get("/{name}", route.containerCompose.Get)
|
||||
r.Post("/", route.containerCompose.Create)
|
||||
r.Put("/{name}", route.containerCompose.Update)
|
||||
r.Post("/{name}/up", route.containerCompose.Up)
|
||||
r.Post("/{name}/down", route.containerCompose.Down)
|
||||
r.Delete("/{name}", route.containerCompose.Remove)
|
||||
})
|
||||
r.Route("/network", func(r chi.Router) {
|
||||
r.Get("/", route.containerNetwork.List)
|
||||
r.Post("/", route.containerNetwork.Create)
|
||||
|
||||
+22
-15
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
@@ -55,38 +56,44 @@ func (s *BackupService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *BackupService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(2 << 30); err != nil {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
req := new(request.BackupUpload)
|
||||
if err := binder.MultipartForm(req, 2<<30); err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if err := binder.URI(req); err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, handler, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "上传文件失败:%v", err)
|
||||
// 只允许上传 .sql .zip .tar .gz .tgz .bz2 .xz .7z
|
||||
if !slices.Contains([]string{".sql", ".zip", ".tar", ".gz", ".tgz", ".bz2", ".xz", ".7z"}, filepath.Ext(req.File.Filename)) {
|
||||
Error(w, http.StatusForbidden, "unsupported file type")
|
||||
return
|
||||
}
|
||||
path, err := s.backupRepo.GetPath(biz.BackupType(r.FormValue("type")))
|
||||
|
||||
path, err := s.backupRepo.GetPath(biz.BackupType(req.Type))
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !io.Exists(filepath.Dir(path)) {
|
||||
if err = os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err)
|
||||
return
|
||||
}
|
||||
if io.Exists(filepath.Join(path, req.File.Filename)) {
|
||||
Error(w, http.StatusForbidden, "target backup %s already exists", path)
|
||||
return
|
||||
}
|
||||
|
||||
src, _ := handler.Open()
|
||||
out, err := os.OpenFile(filepath.Join(path, handler.Filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
|
||||
src, _ := req.File.Open()
|
||||
out, err := os.OpenFile(filepath.Join(path, req.File.Filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "打开文件失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "open file error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = stdio.Copy(out, src); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "写入文件失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "write file error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -535,7 +535,7 @@ func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-备份类型:网站")
|
||||
fmt.Println("|-备份类型:website")
|
||||
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
|
||||
if err := s.backupRepo.Create(biz.BackupTypeWebsite, cmd.String("name"), cmd.String("path")); err != nil {
|
||||
return fmt.Errorf("备份失败:%v", err)
|
||||
@@ -550,7 +550,7 @@ func (s *CliService) BackupDatabase(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-备份类型:数据库")
|
||||
fmt.Println("|-备份类型:database")
|
||||
fmt.Printf("|-数据库:%s\n", cmd.String("type"))
|
||||
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
|
||||
if err := s.backupRepo.Create(biz.BackupType(cmd.String("type")), cmd.String("name"), cmd.String("path")); err != nil {
|
||||
@@ -566,7 +566,7 @@ func (s *CliService) BackupPanel(ctx context.Context, cmd *cli.Command) error {
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-备份类型:面板")
|
||||
fmt.Println("|-备份类型:panel")
|
||||
if err := s.backupRepo.Create(biz.BackupTypePanel, "", cmd.String("path")); err != nil {
|
||||
return fmt.Errorf("备份失败:%v", err)
|
||||
}
|
||||
@@ -613,7 +613,7 @@ func (s *CliService) CutoffWebsite(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始切割日志 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-切割类型:网站")
|
||||
fmt.Println("|-切割类型:website")
|
||||
fmt.Printf("|-切割目标:%s\n", website.Name)
|
||||
if err = s.backupRepo.CutoffLog(path, filepath.Join(app.Root, "wwwlogs", website.Name+".log")); err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type ContainerComposeService struct {
|
||||
containerComposeRepo biz.ContainerComposeRepo
|
||||
}
|
||||
|
||||
func NewContainerComposeService(containerCompose biz.ContainerComposeRepo) *ContainerComposeService {
|
||||
return &ContainerComposeService{
|
||||
containerComposeRepo: containerCompose,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) List(w http.ResponseWriter, r *http.Request) {
|
||||
composes, err := s.containerComposeRepo.List()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
paged, total := Paginate(r, composes)
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Get(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeGet](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
compose, envs, err := s.containerComposeRepo.Get(req.Name)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"compose": compose,
|
||||
"envs": envs,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeCreate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Create(req.Name, req.Compose, req.Envs); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeUpdate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Update(req.Name, req.Compose, req.Envs); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Up(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeUp](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Up(req.Name, req.Force); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Down(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeDown](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Down(req.Name); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Remove(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeRemove](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Remove(req.Name); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
@@ -291,7 +291,7 @@ func (s *FileService) RemoteDownload(w http.ResponseWriter, r *http.Request) {
|
||||
task := new(biz.Task)
|
||||
task.Name = "下载远程文件"
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = fmt.Sprintf(`wget -o /tmp/remote-download-%s.log -O '%s' '%s'`, timestamp, req.Path, req.URL)
|
||||
task.Shell = fmt.Sprintf(`wget -o /tmp/remote-download-%s.log -O '%s' '%s' && chmod 0755 '%s' && chown www:www '%s'`, timestamp, req.Path, req.URL, req.Path, req.Path)
|
||||
task.Log = fmt.Sprintf("/tmp/remote-download-%s.log", timestamp)
|
||||
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewCertDNSService,
|
||||
NewCliService,
|
||||
NewContainerService,
|
||||
NewContainerComposeService,
|
||||
NewContainerImageService,
|
||||
NewContainerNetworkService,
|
||||
NewContainerVolumeService,
|
||||
|
||||
@@ -93,7 +93,7 @@ func (s *WsService) Exec(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
out, err := shell.ExecfWithPipe(ctx, string(cmd)) // nolint: govet
|
||||
out, err := shell.ExecfWithPipe(ctx, string(cmd))
|
||||
if err != nil {
|
||||
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "failed to run command"))
|
||||
cancel()
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package biz
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
types "github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
// ContainerComposeRepo is an autogenerated mock type for the ContainerComposeRepo type
|
||||
type ContainerComposeRepo struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type ContainerComposeRepo_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *ContainerComposeRepo) EXPECT() *ContainerComposeRepo_Expecter {
|
||||
return &ContainerComposeRepo_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: name, compose, envs
|
||||
func (_m *ContainerComposeRepo) Create(name string, compose string, envs []types.KV) error {
|
||||
ret := _m.Called(name, compose, envs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, []types.KV) error); ok {
|
||||
r0 = rf(name, compose, envs)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||
type ContainerComposeRepo_Create_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Create is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - compose string
|
||||
// - envs []types.KV
|
||||
func (_e *ContainerComposeRepo_Expecter) Create(name interface{}, compose interface{}, envs interface{}) *ContainerComposeRepo_Create_Call {
|
||||
return &ContainerComposeRepo_Create_Call{Call: _e.mock.On("Create", name, compose, envs)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Create_Call) Run(run func(name string, compose string, envs []types.KV)) *ContainerComposeRepo_Create_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].([]types.KV))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Create_Call) Return(_a0 error) *ContainerComposeRepo_Create_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Create_Call) RunAndReturn(run func(string, string, []types.KV) error) *ContainerComposeRepo_Create_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Down provides a mock function with given fields: name
|
||||
func (_m *ContainerComposeRepo) Down(name string) error {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Down")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Down_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Down'
|
||||
type ContainerComposeRepo_Down_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Down is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *ContainerComposeRepo_Expecter) Down(name interface{}) *ContainerComposeRepo_Down_Call {
|
||||
return &ContainerComposeRepo_Down_Call{Call: _e.mock.On("Down", name)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Down_Call) Run(run func(name string)) *ContainerComposeRepo_Down_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Down_Call) Return(_a0 error) *ContainerComposeRepo_Down_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Down_Call) RunAndReturn(run func(string) error) *ContainerComposeRepo_Down_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: name
|
||||
func (_m *ContainerComposeRepo) Get(name string) (string, []types.KV, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 []types.KV
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, []types.KV, error)); ok {
|
||||
return rf(name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) []types.KV); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).([]types.KV)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(string) error); ok {
|
||||
r2 = rf(name)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
|
||||
type ContainerComposeRepo_Get_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Get is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *ContainerComposeRepo_Expecter) Get(name interface{}) *ContainerComposeRepo_Get_Call {
|
||||
return &ContainerComposeRepo_Get_Call{Call: _e.mock.On("Get", name)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Get_Call) Run(run func(name string)) *ContainerComposeRepo_Get_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Get_Call) Return(_a0 string, _a1 []types.KV, _a2 error) *ContainerComposeRepo_Get_Call {
|
||||
_c.Call.Return(_a0, _a1, _a2)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Get_Call) RunAndReturn(run func(string) (string, []types.KV, error)) *ContainerComposeRepo_Get_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// List provides a mock function with no fields
|
||||
func (_m *ContainerComposeRepo) List() ([]types.ContainerCompose, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for List")
|
||||
}
|
||||
|
||||
var r0 []types.ContainerCompose
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]types.ContainerCompose, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []types.ContainerCompose); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]types.ContainerCompose)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
|
||||
type ContainerComposeRepo_List_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// List is a helper method to define mock.On call
|
||||
func (_e *ContainerComposeRepo_Expecter) List() *ContainerComposeRepo_List_Call {
|
||||
return &ContainerComposeRepo_List_Call{Call: _e.mock.On("List")}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_List_Call) Run(run func()) *ContainerComposeRepo_List_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_List_Call) Return(_a0 []types.ContainerCompose, _a1 error) *ContainerComposeRepo_List_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_List_Call) RunAndReturn(run func() ([]types.ContainerCompose, error)) *ContainerComposeRepo_List_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Remove provides a mock function with given fields: name
|
||||
func (_m *ContainerComposeRepo) Remove(name string) error {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Remove")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
||||
type ContainerComposeRepo_Remove_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Remove is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *ContainerComposeRepo_Expecter) Remove(name interface{}) *ContainerComposeRepo_Remove_Call {
|
||||
return &ContainerComposeRepo_Remove_Call{Call: _e.mock.On("Remove", name)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Remove_Call) Run(run func(name string)) *ContainerComposeRepo_Remove_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Remove_Call) Return(_a0 error) *ContainerComposeRepo_Remove_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Remove_Call) RunAndReturn(run func(string) error) *ContainerComposeRepo_Remove_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Up provides a mock function with given fields: name, force
|
||||
func (_m *ContainerComposeRepo) Up(name string, force bool) error {
|
||||
ret := _m.Called(name, force)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Up")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(name, force)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Up_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Up'
|
||||
type ContainerComposeRepo_Up_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Up is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - force bool
|
||||
func (_e *ContainerComposeRepo_Expecter) Up(name interface{}, force interface{}) *ContainerComposeRepo_Up_Call {
|
||||
return &ContainerComposeRepo_Up_Call{Call: _e.mock.On("Up", name, force)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Up_Call) Run(run func(name string, force bool)) *ContainerComposeRepo_Up_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Up_Call) Return(_a0 error) *ContainerComposeRepo_Up_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Up_Call) RunAndReturn(run func(string, bool) error) *ContainerComposeRepo_Up_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: name, compose, envs
|
||||
func (_m *ContainerComposeRepo) Update(name string, compose string, envs []types.KV) error {
|
||||
ret := _m.Called(name, compose, envs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, []types.KV) error); ok {
|
||||
r0 = rf(name, compose, envs)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||
type ContainerComposeRepo_Update_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Update is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - compose string
|
||||
// - envs []types.KV
|
||||
func (_e *ContainerComposeRepo_Expecter) Update(name interface{}, compose interface{}, envs interface{}) *ContainerComposeRepo_Update_Call {
|
||||
return &ContainerComposeRepo_Update_Call{Call: _e.mock.On("Update", name, compose, envs)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Update_Call) Run(run func(name string, compose string, envs []types.KV)) *ContainerComposeRepo_Update_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].([]types.KV))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Update_Call) Return(_a0 error) *ContainerComposeRepo_Update_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Update_Call) RunAndReturn(run func(string, string, []types.KV) error) *ContainerComposeRepo_Update_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewContainerComposeRepo creates a new instance of ContainerComposeRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewContainerComposeRepo(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *ContainerComposeRepo {
|
||||
mock := &ContainerComposeRepo{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -120,8 +120,3 @@ func GetSymlink(path string) string {
|
||||
}
|
||||
return linkPath
|
||||
}
|
||||
|
||||
// TempFile 创建临时文件
|
||||
func TempFile(dir, prefix string) (*os.File, error) {
|
||||
return os.CreateTemp(dir, prefix)
|
||||
}
|
||||
|
||||
+37
-16
@@ -37,16 +37,19 @@ func Execf(shell string, args ...any) (string, error) {
|
||||
if !preCheckArg(args) {
|
||||
return "", errors.New("command contains illegal characters")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
shell = fmt.Sprintf(shell, args...)
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
cmd := exec.Command("bash", "-c", shell)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
@@ -57,9 +60,12 @@ func ExecfAsync(shell string, args ...any) error {
|
||||
if !preCheckArg(args) {
|
||||
return errors.New("command contains illegal characters")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
shell = fmt.Sprintf(shell, args...)
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
cmd := exec.Command("bash", "-c", shell)
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
@@ -68,7 +74,7 @@ func ExecfAsync(shell string, args ...any) error {
|
||||
|
||||
go func() {
|
||||
if err = cmd.Wait(); err != nil {
|
||||
fmt.Println(fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(err.Error())))
|
||||
fmt.Println(fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(err.Error())))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -80,9 +86,12 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
|
||||
if !preCheckArg(args) {
|
||||
return "", errors.New("command contains illegal characters")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
shell = fmt.Sprintf(shell, args...)
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
cmd := exec.Command("bash", "-c", shell)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
@@ -90,7 +99,7 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
done := make(chan error)
|
||||
@@ -101,10 +110,10 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
_ = cmd.Process.Kill()
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), "timeout")
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, "timeout")
|
||||
case err = <-done:
|
||||
if err != nil {
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,9 +125,12 @@ func ExecfWithOutput(shell string, args ...any) error {
|
||||
if !preCheckArg(args) {
|
||||
return errors.New("command contains illegal characters")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
shell = fmt.Sprintf(shell, args...)
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
cmd := exec.Command("bash", "-c", shell)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
@@ -130,9 +142,12 @@ func ExecfWithPipe(ctx context.Context, shell string, args ...any) (out io.ReadC
|
||||
if !preCheckArg(args) {
|
||||
return nil, errors.New("command contains illegal characters")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
shell = fmt.Sprintf(shell, args...)
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.CommandContext(ctx, "bash", "-c", fmt.Sprintf(shell, args...))
|
||||
cmd := exec.CommandContext(ctx, "bash", "-c", shell)
|
||||
|
||||
out, err = cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -149,9 +164,12 @@ func ExecfWithDir(dir, shell string, args ...any) (string, error) {
|
||||
if !preCheckArg(args) {
|
||||
return "", errors.New("command contains illegal characters")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
shell = fmt.Sprintf(shell, args...)
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
cmd := exec.Command("bash", "-c", shell)
|
||||
cmd.Dir = dir
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
@@ -159,7 +177,7 @@ func ExecfWithDir(dir, shell string, args ...any) (string, error) {
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
@@ -170,9 +188,12 @@ func ExecfWithTTY(shell string, args ...any) (string, error) {
|
||||
if !preCheckArg(args) {
|
||||
return "", errors.New("command contains illegal characters")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
shell = fmt.Sprintf(shell, args...)
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.Command("bash", "-i", "-c", fmt.Sprintf(shell, args...))
|
||||
cmd := exec.Command("bash", "-i", "-c", shell)
|
||||
|
||||
var out bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
@@ -180,15 +201,15 @@ func ExecfWithTTY(shell string, args ...any) (string, error) {
|
||||
|
||||
f, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("run %s failed", fmt.Sprintf(shell, args...))
|
||||
return "", fmt.Errorf("run %s failed", shell)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err = io.Copy(&out, f); ptyError(err) != nil {
|
||||
return "", fmt.Errorf("run %s failed, out: %s, err: %w", fmt.Sprintf(shell, args...), strings.TrimSpace(out.String()), err)
|
||||
return "", fmt.Errorf("run %s failed, out: %s, err: %w", shell, strings.TrimSpace(out.String()), err)
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
return "", fmt.Errorf("run %s failed, out: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
return "", fmt.Errorf("run %s failed, out: %s", shell, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(out.String()), nil
|
||||
|
||||
@@ -12,6 +12,7 @@ type CertList struct {
|
||||
AutoRenew bool `json:"auto_renew"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
Script string `json:"script"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
// ContainerComposeRaw docker compose ls 命令原始输出
|
||||
type ContainerComposeRaw struct {
|
||||
Name string `json:"Name"`
|
||||
Status string `json:"Status"`
|
||||
ConfigFiles string `json:"ConfigFiles"`
|
||||
}
|
||||
|
||||
type ContainerCompose struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
+9
-7
@@ -27,8 +27,9 @@
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.1.1",
|
||||
"@guolao/vue-monaco-editor": "^1.5.4",
|
||||
"@vavt/copy2clipboard": "^1.0.1",
|
||||
"@vue-js-cron/naive-ui": "^2.0.9",
|
||||
"@vueuse/core": "^12.2.0",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"@xterm/addon-attach": "^0.11.0",
|
||||
"@xterm/addon-clipboard": "^0.1.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
@@ -45,7 +46,7 @@
|
||||
"marked": "^15.0.4",
|
||||
"mitt": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"pinia": "^2.3.0",
|
||||
"pinia": "^3.0.0",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
"remove": "^0.1.5",
|
||||
"vue": "^3.5.13",
|
||||
@@ -62,7 +63,7 @@
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@unocss/eslint-config": "^65.0.0",
|
||||
"@unocss/eslint-config": "^66.0.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/eslint-config-prettier": "^10.1.0",
|
||||
"@vue/eslint-config-typescript": "^14.2.0",
|
||||
@@ -70,7 +71,7 @@
|
||||
"colord": "^2.9.3",
|
||||
"cpx2": "^8.0.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"eslint-plugin-vue": "^10.0.0",
|
||||
"md-editor-v3": "^5.1.1",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"naive-ui": "^2.40.4",
|
||||
@@ -79,8 +80,8 @@
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"sass": "^1.83.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "5.7.3",
|
||||
"unocss": "^65.0.0",
|
||||
"typescript": "5.8.2",
|
||||
"unocss": "^66.0.0",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
"unplugin-icons": "^22.0.0",
|
||||
"unplugin-vue-components": "^28.0.0",
|
||||
@@ -90,5 +91,6 @@
|
||||
"vite-plugin-static-copy": "^2.2.0",
|
||||
"vite-plugin-vue-devtools": "^7.6.8",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||
}
|
||||
|
||||
Generated
+1421
-1828
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@parcel/watcher'
|
||||
- esbuild
|
||||
- vue-demi
|
||||
@@ -0,0 +1,8 @@
|
||||
import { http } from '@/utils'
|
||||
|
||||
export default {
|
||||
// 获取环境变量
|
||||
env: (): any => http.Get('/apps/minio/env'),
|
||||
// 保存环境变量
|
||||
saveEnv: (env: string): any => http.Post('/apps/minio/env', { env })
|
||||
}
|
||||
@@ -8,11 +8,7 @@ export default {
|
||||
create: (type: string, target: string, path: string): any =>
|
||||
http.Post(`/backup/${type}`, { target, path }),
|
||||
// 上传备份
|
||||
upload: (type: string, formData: FormData): any => {
|
||||
return http.Post(`/backup/${type}/upload`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
upload: (type: string, formData: FormData): any => http.Post(`/backup/${type}/upload`, formData),
|
||||
// 删除备份
|
||||
delete: (type: string, file: string): any => http.Delete(`/backup/${type}/delete`, { file }),
|
||||
// 恢复备份
|
||||
|
||||
@@ -27,6 +27,22 @@ export default {
|
||||
containerLogs: (id: string): any => http.Get(`/container/container/${id}/logs`),
|
||||
// 清理容器
|
||||
containerPrune: (): any => http.Post(`/container/container/prune`),
|
||||
// 获取编排列表
|
||||
composeList: (page: number, limit: number): any =>
|
||||
http.Get('/container/compose', { params: { page, limit } }),
|
||||
// 获取编排
|
||||
composeGet: (name: string): any => http.Get(`/container/compose/${name}`),
|
||||
// 创建编排
|
||||
composeCreate: (config: any): any => http.Post('/container/compose', config),
|
||||
// 更新编排
|
||||
composeUpdate: (name: string, config: any): any => http.Put(`/container/compose/${name}`, config),
|
||||
// 删除编排
|
||||
composeRemove: (name: string): any => http.Delete(`/container/compose/${name}`),
|
||||
// 启动编排
|
||||
composeUp: (name: string, force: boolean): any =>
|
||||
http.Post(`/container/compose/${name}/up`, { force }),
|
||||
// 停止编排
|
||||
composeDown: (name: string): any => http.Post(`/container/compose/${name}/down`),
|
||||
// 获取网络列表
|
||||
networkList: (page: number, limit: number): any =>
|
||||
http.Get(`/container/network`, { params: { page, limit } }),
|
||||
|
||||
@@ -16,12 +16,12 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const disabled = ref(false) // 在出现错误的情况下禁用保存
|
||||
const data = ref('')
|
||||
const content = ref('')
|
||||
|
||||
const get = () => {
|
||||
useRequest(file.content(props.path))
|
||||
.onSuccess(({ data }) => {
|
||||
data.value = decodeBase64(data.content)
|
||||
content.value = decodeBase64(data.content)
|
||||
window.$message.success('获取成功')
|
||||
})
|
||||
.onError(() => {
|
||||
@@ -34,7 +34,7 @@ const save = () => {
|
||||
window.$message.error('当前状态下不可保存')
|
||||
return
|
||||
}
|
||||
useRequest(file.save(props.path, data.value)).onSuccess(() => {
|
||||
useRequest(file.save(props.path, content.value)).onSuccess(() => {
|
||||
window.$message.success('保存成功')
|
||||
})
|
||||
}
|
||||
@@ -51,7 +51,7 @@ defineExpose({
|
||||
|
||||
<template>
|
||||
<Editor
|
||||
v-model:value="data"
|
||||
v-model:value="content"
|
||||
:language="languageByPath(props.path)"
|
||||
theme="vs-dark"
|
||||
height="60vh"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ async function setupApp() {
|
||||
const app = createApp(App)
|
||||
app.use(VueMonacoEditorPlugin, {
|
||||
paths: {
|
||||
vs: '/assets/vs'
|
||||
vs: window.location.origin + '/assets/vs'
|
||||
},
|
||||
'vs/nls': {
|
||||
availableLanguages: { '*': 'zh-cn' }
|
||||
|
||||
@@ -8,7 +8,8 @@ body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f2f2f2;
|
||||
font-family: -apple-system, 'Noto Sans', 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial,
|
||||
font-family:
|
||||
-apple-system, 'Noto Sans', 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial,
|
||||
'Liberation Sans', 'PingFang SC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Noto Sans SC',
|
||||
'Source Han Sans SC', 'Source Han Sans CN', 'Microsoft YaHei', 'Wenquanyi Micro Hei',
|
||||
'WenQuanYi Zen Hei', 'ST Heiti', SimHei, 'WenQuanYi Zen Hei Sharp', sans-serif;
|
||||
|
||||
@@ -144,6 +144,9 @@ onMounted(() => {
|
||||
/>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="docker" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
</template>
|
||||
|
||||
@@ -376,6 +376,9 @@ onMounted(() => {
|
||||
/>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="fail2ban" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
<n-modal v-model:show="addJailModal" title="添加规则">
|
||||
|
||||
@@ -139,6 +139,9 @@ onMounted(() => {
|
||||
/>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="gitea" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
</template>
|
||||
|
||||
@@ -171,6 +171,9 @@ onMounted(() => {
|
||||
:data="load"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="memcached" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'apps-minio-index'
|
||||
})
|
||||
|
||||
import Editor from '@guolao/vue-monaco-editor'
|
||||
import { NButton, NPopconfirm } from 'naive-ui'
|
||||
|
||||
import minio from '@/api/apps/minio'
|
||||
import systemctl from '@/api/panel/systemctl'
|
||||
|
||||
const currentTab = ref('status')
|
||||
const status = ref(false)
|
||||
const isEnabled = ref(false)
|
||||
const env = ref('')
|
||||
|
||||
const statusStr = computed(() => {
|
||||
return status.value ? '正常运行中' : '已停止运行'
|
||||
})
|
||||
|
||||
const getStatus = async () => {
|
||||
status.value = await systemctl.status('minio')
|
||||
}
|
||||
|
||||
const getIsEnabled = async () => {
|
||||
isEnabled.value = await systemctl.isEnabled('minio')
|
||||
}
|
||||
|
||||
const getEnv = async () => {
|
||||
env.value = await minio.env()
|
||||
}
|
||||
|
||||
const handleSaveEnv = () => {
|
||||
useRequest(minio.saveEnv(env.value)).onSuccess(() => {
|
||||
window.$message.success('保存成功')
|
||||
})
|
||||
}
|
||||
|
||||
const handleStart = async () => {
|
||||
await systemctl.start('minio')
|
||||
window.$message.success('启动成功')
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleStop = async () => {
|
||||
await systemctl.stop('minio')
|
||||
window.$message.success('停止成功')
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleRestart = async () => {
|
||||
await systemctl.restart('minio')
|
||||
window.$message.success('重启成功')
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleIsEnabled = async () => {
|
||||
if (isEnabled.value) {
|
||||
await systemctl.enable('minio')
|
||||
window.$message.success('开启自启动成功')
|
||||
} else {
|
||||
await systemctl.disable('minio')
|
||||
window.$message.success('禁用自启动成功')
|
||||
}
|
||||
await getIsEnabled()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getStatus()
|
||||
getIsEnabled()
|
||||
getEnv()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button v-if="currentTab == 'env'" class="ml-16" type="primary" @click="handleSaveEnv">
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
<n-tabs v-model:value="currentTab" type="line" animated>
|
||||
<n-tab-pane name="status" tab="运行状态">
|
||||
<n-card title="运行状态">
|
||||
<template #header-extra>
|
||||
<n-switch v-model:value="isEnabled" @update:value="handleIsEnabled">
|
||||
<template #checked> 自启动开 </template>
|
||||
<template #unchecked> 自启动关 </template>
|
||||
</n-switch>
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<n-alert :type="status ? 'success' : 'error'">
|
||||
{{ statusStr }}
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
确定要停止 Minio 吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="env" tab="环境变量">
|
||||
<n-space vertical>
|
||||
<n-alert type="warning">
|
||||
此处修改的是 Minio 环境变量文件
|
||||
/etc/default/minio,如果您不了解各参数的含义,请不要随意修改!
|
||||
</n-alert>
|
||||
<Editor
|
||||
v-model:value="env"
|
||||
language="ini"
|
||||
theme="vs-dark"
|
||||
height="60vh"
|
||||
mt-8
|
||||
:options="{
|
||||
automaticLayout: true,
|
||||
formatOnType: true,
|
||||
formatOnPaste: true
|
||||
}"
|
||||
/>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="minio" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
</template>
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { RouteType } from '~/types/router'
|
||||
|
||||
const Layout = () => import('@/layout/IndexView.vue')
|
||||
|
||||
export default {
|
||||
name: 'minio',
|
||||
path: '/apps/minio',
|
||||
component: Layout,
|
||||
isHidden: true,
|
||||
children: [
|
||||
{
|
||||
name: 'apps-minio-index',
|
||||
path: '',
|
||||
component: () => import('./IndexView.vue'),
|
||||
meta: {
|
||||
title: 'Minio',
|
||||
icon: 'simple-icons:minio',
|
||||
role: ['admin'],
|
||||
requireAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
} as RouteType
|
||||
@@ -104,12 +104,6 @@ const handleRestart = async () => {
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleReload = async () => {
|
||||
await systemctl.reload('mysqld')
|
||||
window.$message.success('重载成功')
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleSetRootPassword = async () => {
|
||||
await mysql.setRootPassword(rootPassword.value)
|
||||
window.$message.success('修改成功')
|
||||
@@ -184,10 +178,6 @@ onMounted(() => {
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleReload">
|
||||
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
|
||||
重载
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
@@ -232,7 +222,7 @@ onMounted(() => {
|
||||
:data="load"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="error-log" tab="错误日志">
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="mysqld" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="slow-log" tab="慢查询日志">
|
||||
|
||||
@@ -198,6 +198,9 @@ onMounted(() => {
|
||||
:data="load"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="nginx" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="error-log" tab="错误日志">
|
||||
<realtime-log :path="errorLog" />
|
||||
</n-tab-pane>
|
||||
|
||||
@@ -377,6 +377,9 @@ onMounted(() => {
|
||||
:data="load"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log :service="'php-fpm-' + version" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="error-log" tab="错误日志">
|
||||
<realtime-log :path="errorLog" />
|
||||
</n-tab-pane>
|
||||
|
||||
@@ -180,6 +180,9 @@ onMounted(() => {
|
||||
/>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="podman" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
</template>
|
||||
|
||||
@@ -228,7 +228,10 @@ onMounted(() => {
|
||||
:data="load"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="log" tab="日志">
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="postgresql" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="slow-log" tab="慢日志">
|
||||
<realtime-log :path="log" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
@@ -281,6 +281,9 @@ onMounted(() => {
|
||||
/>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="pure-ftpd" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
<n-modal v-model:show="addUserModal" title="创建用户">
|
||||
|
||||
@@ -171,6 +171,9 @@ onMounted(() => {
|
||||
:data="load"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="redis" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
</template>
|
||||
|
||||
@@ -335,6 +335,9 @@ onMounted(() => {
|
||||
/>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="rsyncd" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
<n-modal
|
||||
|
||||
@@ -453,7 +453,10 @@ onUnmounted(() => {
|
||||
/>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="log" tab="日志">
|
||||
<n-tab-pane name="run-log" tab="运行日志">
|
||||
<realtime-log service="supervisor" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="log" tab="守护日志">
|
||||
<realtime-log path="/var/log/supervisor/supervisord.log" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
@@ -3,79 +3,45 @@ defineOptions({
|
||||
name: 'backup-index'
|
||||
})
|
||||
|
||||
import backup from '@/api/panel/backup'
|
||||
import dashboard from '@/api/panel/dashboard'
|
||||
import ListView from '@/views/backup/ListView.vue'
|
||||
import { NButton, NInput } from 'naive-ui'
|
||||
|
||||
const currentTab = ref('website')
|
||||
const createModal = ref(false)
|
||||
const createModel = ref({
|
||||
target: '',
|
||||
path: ''
|
||||
|
||||
const { data: installedDbAndPhp } = useRequest(dashboard.installedDbAndPhp, {
|
||||
initialData: {
|
||||
db: [
|
||||
{
|
||||
label: '',
|
||||
value: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const handleCreate = () => {
|
||||
useRequest(
|
||||
backup.create(currentTab.value, createModel.value.target, createModel.value.path)
|
||||
).onSuccess(() => {
|
||||
createModal.value = false
|
||||
window.$bus.emit('backup:refresh')
|
||||
window.$message.success('创建成功')
|
||||
})
|
||||
}
|
||||
const mySQLInstalled = computed(() => {
|
||||
return installedDbAndPhp.value.db.find((item: any) => item.value === 'mysql')
|
||||
})
|
||||
|
||||
const postgreSQLInstalled = computed(() => {
|
||||
return installedDbAndPhp.value.db.find((item: any) => item.value === 'postgresql')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button type="primary" @click="createModal = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建备份
|
||||
</n-button>
|
||||
</template>
|
||||
<n-flex vertical>
|
||||
<n-alert type="info">该页面预计下版本移除!</n-alert>
|
||||
<n-tabs v-model:value="currentTab" type="line" animated>
|
||||
<n-tab-pane name="website" tab="网站">
|
||||
<list-view v-model:type="currentTab" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="mysql" tab="MySQL">
|
||||
<n-tab-pane v-if="mySQLInstalled" name="mysql" tab="MySQL">
|
||||
<list-view v-model:type="currentTab" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="postgres" tab="PostgreSQL">
|
||||
<n-tab-pane v-if="postgreSQLInstalled" name="postgres" tab="PostgreSQL">
|
||||
<list-view v-model:type="currentTab" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-flex>
|
||||
</common-page>
|
||||
<n-modal
|
||||
v-model:show="createModal"
|
||||
preset="card"
|
||||
title="创建备份"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="createModal = false"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="name" label="名称">
|
||||
<n-input
|
||||
v-model:value="createModel.target"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
placeholder="输入网站/数据库名称"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="path" label="目录">
|
||||
<n-input
|
||||
v-model:value="createModel.path"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
placeholder="留空使用默认路径"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleCreate">提交</n-button>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import backup from '@/api/panel/backup'
|
||||
import { renderIcon } from '@/utils'
|
||||
import { MessageReactive, NButton, NDataTable, NInput, NPopconfirm } from 'naive-ui'
|
||||
import type { MessageReactive } from 'naive-ui'
|
||||
import { NButton, NDataTable, NFlex, NInput, NPopconfirm } from 'naive-ui'
|
||||
|
||||
import app from '@/api/panel/app'
|
||||
import website from '@/api/panel/website'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import UploadModal from '@/views/backup/UploadModal.vue'
|
||||
|
||||
const type = defineModel<string>('type', { type: String, required: true })
|
||||
|
||||
let messageReactive: MessageReactive | null = null
|
||||
|
||||
const uploadModal = ref(false)
|
||||
|
||||
const createModal = ref(false)
|
||||
const createModel = ref({
|
||||
target: '',
|
||||
path: ''
|
||||
})
|
||||
|
||||
const restoreModal = ref(false)
|
||||
const restoreModel = ref({
|
||||
file: '',
|
||||
target: ''
|
||||
})
|
||||
|
||||
const websites = ref<any>([])
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: '文件名',
|
||||
@@ -102,6 +116,16 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
|
||||
}
|
||||
)
|
||||
|
||||
const handleCreate = () => {
|
||||
useRequest(backup.create(type.value, createModel.value.target, createModel.value.path)).onSuccess(
|
||||
() => {
|
||||
createModal.value = false
|
||||
window.$bus.emit('backup:refresh')
|
||||
window.$message.success('创建成功')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const handleRestore = () => {
|
||||
messageReactive = window.$message.loading('恢复中...', {
|
||||
duration: 0
|
||||
@@ -125,10 +149,24 @@ const handleDelete = async (file: string) => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
window.$bus.on('backup:refresh', () => {
|
||||
refresh()
|
||||
useRequest(app.isInstalled('nginx')).onSuccess(({ data }) => {
|
||||
if (data.installed) {
|
||||
useRequest(website.list(1, 10000)).onSuccess(({ data }: { data: any }) => {
|
||||
for (const item of data.items) {
|
||||
websites.value.push({
|
||||
label: item.name,
|
||||
value: item.name
|
||||
})
|
||||
}
|
||||
if (type.value === 'website') {
|
||||
createModel.value.target = websites.value[0]?.value
|
||||
restoreModel.value.target = websites.value[0]?.value
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
refresh()
|
||||
window.$bus.on('backup:refresh', refresh)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -137,26 +175,65 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-data-table
|
||||
striped
|
||||
remote
|
||||
:scroll-x="1000"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-key="(row: any) => row.name"
|
||||
v-model:page="page"
|
||||
v-model:pageSize="pageSize"
|
||||
:pagination="{
|
||||
page: page,
|
||||
pageCount: pageCount,
|
||||
pageSize: pageSize,
|
||||
itemCount: total,
|
||||
showQuickJumper: true,
|
||||
showSizePicker: true,
|
||||
pageSizes: [20, 50, 100, 200]
|
||||
}"
|
||||
/>
|
||||
<n-flex vertical :size="20">
|
||||
<n-flex>
|
||||
<n-button type="primary" @click="createModal = true"> 创建备份 </n-button>
|
||||
<n-button type="primary" @click="uploadModal = true" ghost> 上传备份 </n-button>
|
||||
</n-flex>
|
||||
<n-data-table
|
||||
striped
|
||||
remote
|
||||
:scroll-x="1000"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-key="(row: any) => row.name"
|
||||
v-model:page="page"
|
||||
v-model:pageSize="pageSize"
|
||||
:pagination="{
|
||||
page: page,
|
||||
pageCount: pageCount,
|
||||
pageSize: pageSize,
|
||||
itemCount: total,
|
||||
showQuickJumper: true,
|
||||
showSizePicker: true,
|
||||
pageSizes: [20, 50, 100, 200]
|
||||
}"
|
||||
/>
|
||||
</n-flex>
|
||||
<n-modal
|
||||
v-model:show="createModal"
|
||||
preset="card"
|
||||
title="创建备份"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="createModal = false"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item v-if="type == 'website'" path="name" label="网站">
|
||||
<n-select v-model:value="createModel.target" :options="websites" placeholder="选择网站" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="type != 'website'" path="name" label="数据库名">
|
||||
<n-input
|
||||
v-model:value="createModel.target"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
placeholder="输入数据库名称"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="path" label="保存目录">
|
||||
<n-input
|
||||
v-model:value="createModel.path"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
placeholder="留空使用默认路径"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleCreate">提交</n-button>
|
||||
</n-modal>
|
||||
<n-modal
|
||||
v-model:show="restoreModal"
|
||||
preset="card"
|
||||
@@ -168,12 +245,16 @@ onUnmounted(() => {
|
||||
@close="restoreModal = false"
|
||||
>
|
||||
<n-form :model="restoreModel">
|
||||
<n-form-item path="name" label="恢复目标">
|
||||
<n-form-item v-if="type == 'website'" path="name" label="网站">
|
||||
<n-select v-model:value="restoreModel.target" :options="websites" placeholder="选择网站" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="type != 'website'" path="name" label="数据库">
|
||||
<n-input v-model:value="restoreModel.target" type="text" @keydown.enter.prevent />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleRestore">提交</n-button>
|
||||
</n-modal>
|
||||
<upload-modal v-model:show="uploadModal" v-model:type="type" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import type { UploadCustomRequestOptions } from 'naive-ui'
|
||||
|
||||
import api from '@/api/panel/backup'
|
||||
|
||||
const show = defineModel<boolean>('show', { type: Boolean, required: true })
|
||||
const type = defineModel<string>('type', { type: String, required: true })
|
||||
const upload = ref<any>(null)
|
||||
|
||||
const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequestOptions) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file.file as File)
|
||||
const { uploading } = useRequest(api.upload(type.value, formData))
|
||||
.onSuccess(() => {
|
||||
onFinish()
|
||||
window.$bus.emit('backup:refresh')
|
||||
window.$message.success(`上传 ${file.name} 成功`)
|
||||
})
|
||||
.onError(() => {
|
||||
onError()
|
||||
})
|
||||
.onComplete(() => {
|
||||
stopWatch()
|
||||
})
|
||||
const stopWatch = watch(uploading, (progress) => {
|
||||
onProgress({ percent: Math.ceil((progress.loaded / progress.total) * 100) })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="show"
|
||||
preset="card"
|
||||
title="上传备份"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<n-flex vertical>
|
||||
<n-upload ref="upload" multiple directory-dnd :custom-request="uploadRequest">
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<the-icon :size="48" icon="bi:arrow-up-square" />
|
||||
</div>
|
||||
<NText text-18> 点击或者拖动文件到该区域来上传</NText>
|
||||
<NP depth="3" m-10> 大文件建议使用 SFTP 等方式上传 </NP>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-flex>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -38,7 +38,8 @@ const updateModel = ref<any>({
|
||||
website_id: null,
|
||||
auto_renew: true,
|
||||
cert: '',
|
||||
key: ''
|
||||
key: '',
|
||||
script: ''
|
||||
})
|
||||
const updateModal = ref(false)
|
||||
const updateCert = ref<any>()
|
||||
@@ -278,6 +279,7 @@ const columns: any = [
|
||||
updateModel.value.auto_renew = row.auto_renew
|
||||
updateModel.value.cert = row.cert
|
||||
updateModel.value.key = row.key
|
||||
updateModel.value.script = row.script
|
||||
updateModal.value = true
|
||||
}
|
||||
},
|
||||
@@ -341,6 +343,7 @@ const handleUpdateCert = () => {
|
||||
updateModel.value.auto_renew = true
|
||||
updateModel.value.cert = ''
|
||||
updateModel.value.key = ''
|
||||
updateModel.value.script = ''
|
||||
window.$message.success('更新成功')
|
||||
})
|
||||
}
|
||||
@@ -354,6 +357,7 @@ const handleAutoRenewUpdate = (row: any) => {
|
||||
updateModel.value.auto_renew = !row.auto_renew
|
||||
updateModel.value.cert = row.cert
|
||||
updateModel.value.key = row.key
|
||||
updateModel.value.script = row.script
|
||||
useRequest(cert.certUpdate(row.id, updateModel.value))
|
||||
.onSuccess(() => {
|
||||
refresh()
|
||||
@@ -368,21 +372,20 @@ const handleAutoRenewUpdate = (row: any) => {
|
||||
updateModel.value.auto_renew = true
|
||||
updateModel.value.cert = ''
|
||||
updateModel.value.key = ''
|
||||
updateModel.value.script = ''
|
||||
})
|
||||
}
|
||||
|
||||
const handleDeployCert = async () => {
|
||||
const promises = deployModel.value.websites.map((website: any) =>
|
||||
useRequest(cert.deploy(deployModel.value.id, website)).onSuccess(() => {
|
||||
window.$message.success(`部署网站 ${website.name} 成功`)
|
||||
})
|
||||
cert.deploy(deployModel.value.id, website)
|
||||
)
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
deployModal.value = false
|
||||
deployModel.value.id = null
|
||||
deployModel.value.websites = []
|
||||
window.$message.success('部署成功')
|
||||
}
|
||||
|
||||
const handleShowModalClose = () => {
|
||||
@@ -437,7 +440,7 @@ onUnmounted(() => {
|
||||
<n-space vertical>
|
||||
<n-alert v-if="updateModel.type != 'upload'" type="info">
|
||||
可以通过选择网站 / DNS 中的任意一项来自动签发和部署证书,也可以手动输入域名并设置 DNS
|
||||
解析来签发证书
|
||||
解析来签发证书,还可以填写部署脚本来自动部署证书。
|
||||
</n-alert>
|
||||
<n-form :model="updateModel">
|
||||
<n-form-item v-if="updateModel.type != 'upload'" path="domains" label="域名">
|
||||
@@ -496,6 +499,14 @@ onUnmounted(() => {
|
||||
:autosize="{ minRows: 10, maxRows: 15 }"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="updateModel.type != 'upload'" path="key" label="部署脚本">
|
||||
<n-input
|
||||
v-model:value="updateModel.script"
|
||||
type="textarea"
|
||||
placeholder="脚本中的 {cert} 和 {key} 会被替换为证书和私钥内容"
|
||||
:autosize="{ minRows: 5, maxRows: 10 }"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleUpdateCert">提交</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
|
||||
|
||||
import container from '@/api/panel/container'
|
||||
import { useFileStore } from '@/store'
|
||||
import { formatDateTime } from '@/utils'
|
||||
|
||||
const fileStore = useFileStore()
|
||||
const router = useRouter()
|
||||
|
||||
const forcePush = ref(false)
|
||||
|
||||
const createModel = ref({
|
||||
name: '',
|
||||
compose: '',
|
||||
envs: []
|
||||
})
|
||||
const createModal = ref(false)
|
||||
|
||||
const updateModel = ref({
|
||||
name: '',
|
||||
compose: '',
|
||||
envs: []
|
||||
})
|
||||
const updateModal = ref(false)
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
minWidth: 150,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '目录',
|
||||
key: 'path',
|
||||
minWidth: 150,
|
||||
resizable: true,
|
||||
render(row: any) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
class: 'cursor-pointer hover:opacity-60',
|
||||
type: 'info',
|
||||
onClick: () => {
|
||||
fileStore.path = row.path
|
||||
router.push({ name: 'file-index' })
|
||||
}
|
||||
},
|
||||
{ default: () => row.path }
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'created_at',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
render(row: any) {
|
||||
return formatDateTime(row.created_at)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 280,
|
||||
align: 'center',
|
||||
hideInExcel: true,
|
||||
render(row: any) {
|
||||
return [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
useRequest(container.composeGet(row.name)).onSuccess(({ data }: { data: any }) => {
|
||||
updateModel.value = {
|
||||
name: row.name,
|
||||
compose: data.compose,
|
||||
envs: data.envs
|
||||
}
|
||||
updateModal.value = true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => '编辑'
|
||||
}
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
showIcon: false,
|
||||
onPositiveClick: () => {
|
||||
const messageReactive = window.$message.loading('启动中...', {
|
||||
duration: 0
|
||||
})
|
||||
useRequest(container.composeUp(row.name, forcePush.value))
|
||||
.onSuccess(() => {
|
||||
refresh()
|
||||
forcePush.value = false
|
||||
window.$message.success('启动成功')
|
||||
})
|
||||
.onComplete(() => {
|
||||
messageReactive?.destroy()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return h(
|
||||
NFlex,
|
||||
{
|
||||
vertical: true
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
h('strong', {}, { default: () => `确定启动编排 ${row.name} 吗?` }),
|
||||
h(
|
||||
NCheckbox,
|
||||
{
|
||||
checked: forcePush.value,
|
||||
onUpdateChecked: (v) => (forcePush.value = v)
|
||||
},
|
||||
{ default: () => '强制拉取镜像' }
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
style: 'margin-left: 15px;',
|
||||
size: 'small',
|
||||
type: 'success'
|
||||
},
|
||||
{
|
||||
default: () => '启动'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => {
|
||||
useRequest(container.composeDown(row.name)).onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success('停止成功')
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return `确定停止编排 ${row.name} 吗?`
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
style: 'margin-left: 15px;',
|
||||
size: 'small',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
default: () => '停止'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => {
|
||||
useRequest(container.composeRemove(row.name)).onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success('删除成功')
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return `确定删除编排 ${row.name} 吗?`
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
style: 'margin-left: 15px;',
|
||||
size: 'small',
|
||||
type: 'error'
|
||||
},
|
||||
{
|
||||
default: () => '删除'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
|
||||
(page, pageSize) => container.composeList(page, pageSize),
|
||||
{
|
||||
initialData: { total: 0, list: [] },
|
||||
initialPageSize: 20,
|
||||
total: (res: any) => res.total,
|
||||
data: (res: any) => res.items
|
||||
}
|
||||
)
|
||||
|
||||
const handleCreate = () => {
|
||||
loading.value = true
|
||||
useRequest(container.composeCreate(createModel.value))
|
||||
.onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success('创建成功')
|
||||
})
|
||||
.onComplete(() => {
|
||||
loading.value = false
|
||||
createModal.value = false
|
||||
createModel.value = {
|
||||
name: '',
|
||||
compose: '',
|
||||
envs: []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleUpdate = () => {
|
||||
loading.value = true
|
||||
useRequest(container.composeUpdate(updateModel.value.name, updateModel.value))
|
||||
.onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success('更新成功')
|
||||
})
|
||||
.onComplete(() => {
|
||||
loading.value = false
|
||||
updateModal.value = false
|
||||
updateModel.value = {
|
||||
name: '',
|
||||
compose: '',
|
||||
envs: []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex vertical :size="20">
|
||||
<n-flex>
|
||||
<n-button type="primary" @click="createModal = true">创建编排</n-button>
|
||||
</n-flex>
|
||||
<n-data-table
|
||||
striped
|
||||
remote
|
||||
:loading="loading"
|
||||
:scroll-x="1000"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:row-key="(row: any) => row.id"
|
||||
v-model:page="page"
|
||||
v-model:pageSize="pageSize"
|
||||
:pagination="{
|
||||
page: page,
|
||||
pageCount: pageCount,
|
||||
pageSize: pageSize,
|
||||
itemCount: total,
|
||||
showQuickJumper: true,
|
||||
showSizePicker: true,
|
||||
pageSizes: [20, 50, 100, 200]
|
||||
}"
|
||||
/>
|
||||
</n-flex>
|
||||
<n-modal
|
||||
v-model:show="createModal"
|
||||
preset="card"
|
||||
title="创建编排"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="name" label="编排名">
|
||||
<n-input v-model:value="createModel.name" type="text" />
|
||||
</n-form-item>
|
||||
<n-form-item path="compose" label="编排">
|
||||
<n-input
|
||||
v-model:value="createModel.compose"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 10, maxRows: 20 }"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="envs" label="环境变量">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.envs"
|
||||
preset="pair"
|
||||
key-placeholder="变量名"
|
||||
value-placeholder="变量值"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleCreate">
|
||||
提交
|
||||
</n-button>
|
||||
</n-modal>
|
||||
<n-modal
|
||||
v-model:show="updateModal"
|
||||
preset="card"
|
||||
title="编辑编排"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<n-form :model="updateModel">
|
||||
<n-form-item path="compose" label="编排">
|
||||
<n-input
|
||||
v-model:value="updateModel.compose"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 10, maxRows: 20 }"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="envs" label="环境变量">
|
||||
<n-dynamic-input
|
||||
v-model:value="updateModel.envs"
|
||||
preset="pair"
|
||||
key-placeholder="变量名"
|
||||
value-placeholder="变量值"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleUpdate">
|
||||
提交
|
||||
</n-button>
|
||||
</n-modal>
|
||||
</template>
|
||||
@@ -335,11 +335,11 @@ onMounted(() => {
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.env"
|
||||
preset="pair"
|
||||
key-placeholder="环境变量名"
|
||||
value-placeholder="环境变量值"
|
||||
key-placeholder="变量名"
|
||||
value-placeholder="变量值"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="env" label="标签">
|
||||
<n-form-item path="labels" label="标签">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.labels"
|
||||
preset="pair"
|
||||
|
||||
@@ -291,17 +291,12 @@ const bulkStart = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) =>
|
||||
useRequest(container.containerStart(id)).onSuccess(() => {
|
||||
const container = data.value.find((item: any) => item.id === id)
|
||||
window.$message.success(`${container?.name} 启动成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => container.containerStart(id))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('启动成功')
|
||||
}
|
||||
|
||||
const bulkStop = async () => {
|
||||
@@ -310,17 +305,12 @@ const bulkStop = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) =>
|
||||
useRequest(container.containerStop(id)).onSuccess(() => {
|
||||
const container = data.value.find((item: any) => item.id === id)
|
||||
window.$message.success(`${container?.name} 停止成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => container.containerStop(id))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('停止成功')
|
||||
}
|
||||
|
||||
const bulkRestart = async () => {
|
||||
@@ -329,17 +319,12 @@ const bulkRestart = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) =>
|
||||
useRequest(container.containerRestart(id)).onSuccess(() => {
|
||||
const container = data.value.find((item: any) => item.id === id)
|
||||
window.$message.success(`${container?.name} 重启成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => container.containerRestart(id))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('重启成功')
|
||||
}
|
||||
|
||||
const bulkForceStop = async () => {
|
||||
@@ -348,17 +333,12 @@ const bulkForceStop = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) =>
|
||||
useRequest(container.containerKill(id)).onSuccess(() => {
|
||||
const container = data.value.find((item: any) => item.id === id)
|
||||
window.$message.success(`${container?.name} 强制停止成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => container.containerKill(id))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('强制停止成功')
|
||||
}
|
||||
|
||||
const bulkDelete = async () => {
|
||||
@@ -367,17 +347,12 @@ const bulkDelete = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) =>
|
||||
useRequest(container.containerRemove(id)).onSuccess(() => {
|
||||
const container = data.value.find((item: any) => item.id === id)
|
||||
window.$message.success(`${container?.name} 删除成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => container.containerRemove(id))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('删除成功')
|
||||
}
|
||||
|
||||
const bulkPause = async () => {
|
||||
@@ -386,17 +361,12 @@ const bulkPause = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) =>
|
||||
useRequest(container.containerPause(id)).onSuccess(() => {
|
||||
const container = data.value.find((item: any) => item.id === id)
|
||||
window.$message.success(`${container?.name} 暂停成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => container.containerPause(id))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('暂停成功')
|
||||
}
|
||||
|
||||
const bulkUnpause = async () => {
|
||||
@@ -405,17 +375,12 @@ const bulkUnpause = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) =>
|
||||
useRequest(container.containerUnpause(id)).onSuccess(() => {
|
||||
const container = data.value.find((item: any) => item.id === id)
|
||||
window.$message.success(`${container?.name} 恢复成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => container.containerUnpause(id))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('恢复成功')
|
||||
}
|
||||
|
||||
const closeContainerCreateModal = () => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import ComposeView from '@/views/container/ComposeView.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'container-index'
|
||||
})
|
||||
@@ -17,6 +19,9 @@ const current = ref('container')
|
||||
<n-tab-pane name="container" tab="容器">
|
||||
<container-view />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="compose" tab="编排">
|
||||
<compose-view />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="image" tab="镜像">
|
||||
<image-view />
|
||||
</n-tab-pane>
|
||||
|
||||
@@ -152,6 +152,7 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
|
||||
|
||||
const handleDelete = (row: any) => {
|
||||
useRequest(container.networkRemove(row.id)).onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success('删除成功')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ if (import.meta.hot) {
|
||||
|
||||
<n-card :segmented="true" size="small" title="资源总览">
|
||||
<n-flex v-if="realtime" size="large">
|
||||
<n-popover trigger="hover">
|
||||
<n-popover placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-flex vertical flex items-center p-20 pl-40 pr-40>
|
||||
<p>负载状态</p>
|
||||
@@ -475,7 +475,7 @@ if (import.meta.hot) {
|
||||
</tr>
|
||||
</n-table>
|
||||
</n-popover>
|
||||
<n-popover trigger="hover">
|
||||
<n-popover placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-flex vertical flex items-center p-20 pl-40 pr-40>
|
||||
<p>CPU</p>
|
||||
@@ -508,7 +508,7 @@ if (import.meta.hot) {
|
||||
</tr>
|
||||
</n-table>
|
||||
</n-popover>
|
||||
<n-popover trigger="hover">
|
||||
<n-popover placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-flex vertical flex items-center p-20 pl-40 pr-40>
|
||||
<p>内存</p>
|
||||
@@ -602,7 +602,12 @@ if (import.meta.hot) {
|
||||
</tr>
|
||||
</n-table>
|
||||
</n-popover>
|
||||
<n-popover v-for="item in realtime.disk_usage" :key="item.path" trigger="hover">
|
||||
<n-popover
|
||||
v-for="item in realtime.disk_usage"
|
||||
:key="item.path"
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #trigger>
|
||||
<n-flex vertical flex items-center p-20 pl-40 pr-40>
|
||||
<p>{{ item.path }}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { renderIcon } from '@/utils'
|
||||
import copy2clipboard from '@vavt/copy2clipboard'
|
||||
import { NButton, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
|
||||
|
||||
import database from '@/api/panel/database'
|
||||
@@ -69,8 +70,9 @@ const columns: any = [
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
onClick: () => {
|
||||
navigator.clipboard.writeText(row.password)
|
||||
window.$message.success('复制成功')
|
||||
copy2clipboard(row.password).then(() => {
|
||||
window.$message.success('复制成功')
|
||||
})
|
||||
}
|
||||
},
|
||||
{ default: () => '复制' }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { renderIcon } from '@/utils'
|
||||
import copy2clipboard from '@vavt/copy2clipboard'
|
||||
import { NButton, NFlex, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
|
||||
|
||||
import database from '@/api/panel/database'
|
||||
@@ -63,8 +64,9 @@ const columns: any = [
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
onClick: () => {
|
||||
navigator.clipboard.writeText(row.password)
|
||||
window.$message.success('复制成功')
|
||||
copy2clipboard(row.password).then(() => {
|
||||
window.$message.success('复制成功')
|
||||
})
|
||||
}
|
||||
},
|
||||
{ default: () => '复制' }
|
||||
|
||||
@@ -17,16 +17,14 @@ const checkbox = ref({
|
||||
|
||||
const handlePermission = async () => {
|
||||
const promises = selected.value.map((path) =>
|
||||
useRequest(file.permission(path, `0${mode.value}`, owner.value, group.value)).onSuccess(() => {
|
||||
window.$message.success(`修改 ${path} 成功`)
|
||||
})
|
||||
file.permission(path, `0${mode.value}`, owner.value, group.value)
|
||||
)
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
show.value = false
|
||||
selected.value = []
|
||||
window.$bus.emit('file:refresh')
|
||||
window.$message.success('修改成功')
|
||||
}
|
||||
|
||||
const calculateOctal = (permissions: string[]) => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import file from '@/api/panel/file'
|
||||
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||
|
||||
import copy2clipboard from '@vavt/copy2clipboard'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import type { RowData } from 'naive-ui/es/data-table/src/interface'
|
||||
|
||||
@@ -58,8 +59,9 @@ const columns: DataTableColumns<RowData> = [
|
||||
type: 'success',
|
||||
tertiary: true,
|
||||
onClick: () => {
|
||||
navigator.clipboard.writeText(row.full)
|
||||
window.$message.success('复制成功')
|
||||
copy2clipboard(row.full).then(() => {
|
||||
window.$message.success('复制成功')
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -170,16 +170,12 @@ const bulkDelete = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selected.value.map((path) =>
|
||||
useRequest(file.delete(path)).onSuccess(() => {
|
||||
window.$message.success(`删除 ${path} 成功`)
|
||||
})
|
||||
)
|
||||
|
||||
const promises = selected.value.map((path) => file.delete(path))
|
||||
await Promise.all(promises)
|
||||
|
||||
selected.value = []
|
||||
window.$bus.emit('file:refresh')
|
||||
window.$message.success('删除成功')
|
||||
}
|
||||
|
||||
// 自动填充下载文件名
|
||||
|
||||
@@ -40,13 +40,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
|
||||
:segmented="false"
|
||||
>
|
||||
<n-flex vertical>
|
||||
<n-upload
|
||||
ref="upload"
|
||||
multiple
|
||||
directory-dnd
|
||||
action="/api/panel/file/upload"
|
||||
:custom-request="uploadRequest"
|
||||
>
|
||||
<n-upload ref="upload" multiple directory-dnd :custom-request="uploadRequest">
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<the-icon :size="48" icon="bi:arrow-up-square" />
|
||||
|
||||
@@ -139,15 +139,13 @@ const batchDelete = async () => {
|
||||
|
||||
const promises = selectedRowKeys.value.map((key: any) => {
|
||||
const rule = JSON.parse(key)
|
||||
return useRequest(firewall.deleteForward(rule)).onSuccess(() => {
|
||||
window.$message.success(`${rule.protocol} ${rule.target_ip}:${rule.target_port} 删除成功`)
|
||||
})
|
||||
return firewall.deleteForward(rule)
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('删除成功')
|
||||
}
|
||||
|
||||
watch(createModalShow, () => {
|
||||
|
||||
@@ -181,15 +181,13 @@ const batchDelete = async () => {
|
||||
|
||||
const promises = selectedRowKeys.value.map((key: any) => {
|
||||
const rule = JSON.parse(key)
|
||||
return useRequest(firewall.deleteIpRule(rule)).onSuccess(() => {
|
||||
window.$message.success(`${rule.address} 删除成功`)
|
||||
})
|
||||
return firewall.deleteIpRule(rule)
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('删除成功')
|
||||
}
|
||||
|
||||
watch(createModalShow, () => {
|
||||
|
||||
@@ -218,17 +218,13 @@ const batchDelete = async () => {
|
||||
|
||||
const promises = selectedRowKeys.value.map((key: any) => {
|
||||
const rule = JSON.parse(key)
|
||||
return useRequest(firewall.deleteRule(rule)).onSuccess(() => {
|
||||
const port =
|
||||
rule.port_start == rule.port_end ? rule.port_start : `${rule.port_start}-${rule.port_end}`
|
||||
window.$message.success(`${rule.family} 规则 ${port}/${rule.protocol} 删除成功`)
|
||||
})
|
||||
return firewall.deleteRule(rule)
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('删除成功')
|
||||
}
|
||||
|
||||
watch(createModalShow, () => {
|
||||
|
||||
@@ -151,7 +151,7 @@ const openSession = async (id: number) => {
|
||||
})
|
||||
term.value.open(terminal.value!)
|
||||
|
||||
fitAddon.fit()
|
||||
onResize()
|
||||
term.value.focus()
|
||||
window.addEventListener('resize', onResize, false)
|
||||
current.value = id
|
||||
|
||||
@@ -25,12 +25,6 @@ const websites = ref<any>([])
|
||||
|
||||
const { data: installedDbAndPhp } = useRequest(dashboard.installedDbAndPhp, {
|
||||
initialData: {
|
||||
php: [
|
||||
{
|
||||
label: '不使用',
|
||||
value: 0
|
||||
}
|
||||
],
|
||||
db: [
|
||||
{
|
||||
label: '',
|
||||
@@ -48,17 +42,6 @@ const postgreSQLInstalled = computed(() => {
|
||||
return installedDbAndPhp.value.db.find((item: any) => item.value === 'postgresql')
|
||||
})
|
||||
|
||||
const getWebsiteList = async (page: number, limit: number) => {
|
||||
const data = await website.list(page, limit)
|
||||
for (const item of data.items) {
|
||||
websites.value.push({
|
||||
label: item.name,
|
||||
value: item.name
|
||||
})
|
||||
}
|
||||
createModel.value.target = websites.value[0]?.value
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
loading.value = true
|
||||
useRequest(cron.create(createModel.value))
|
||||
@@ -83,7 +66,15 @@ watch(createModel, (value) => {
|
||||
onMounted(() => {
|
||||
useRequest(app.isInstalled('nginx')).onSuccess(({ data }) => {
|
||||
if (data.installed) {
|
||||
getWebsiteList(1, 10000)
|
||||
useRequest(website.list(1, 10000)).onSuccess(({ data }: { data: any }) => {
|
||||
for (const item of data.items) {
|
||||
websites.value.push({
|
||||
label: item.name,
|
||||
value: item.name
|
||||
})
|
||||
}
|
||||
createModel.value.target = websites.value[0]?.value
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -134,7 +125,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<n-form-item v-if="createModel.type === 'backup'" label="备份类型">
|
||||
<n-radio-group v-model:value="createModel.backup_type">
|
||||
<n-radio value="website">网站目录</n-radio>
|
||||
<n-radio value="website">网站</n-radio>
|
||||
<n-radio value="mysql" :disabled="!mySQLInstalled"> MySQL 数据库</n-radio>
|
||||
<n-radio value="postgres" :disabled="!postgreSQLInstalled"> PostgreSQL 数据库 </n-radio>
|
||||
</n-radio-group>
|
||||
|
||||
@@ -303,17 +303,12 @@ const bulkDelete = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => {
|
||||
const site = data.value.find((item: any) => item.id === id)
|
||||
return useRequest(website.delete(id, true, false)).onSuccess(() => {
|
||||
window.$message.success('网站 ' + site?.name + ' 删除成功')
|
||||
})
|
||||
})
|
||||
|
||||
const promises = selectedRowKeys.value.map((id: any) => website.delete(id, true, false))
|
||||
await Promise.all(promises)
|
||||
|
||||
selectedRowKeys.value = []
|
||||
await refresh()
|
||||
refresh()
|
||||
window.$message.success('删除成功')
|
||||
}
|
||||
|
||||
const formatDbValue = (value: string) => {
|
||||
|
||||
Reference in New Issue
Block a user