├── .air.toml
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── 功能需求.md
│ └── 反馈bug.md
└── workflows
│ └── release.yml
├── .gitignore
├── .gitmodules
├── Dockerfile
├── Dockerfile-lite
├── LICENSE
├── Makefile
├── README.md
├── README_EN.md
├── SECURITY.md
├── app
├── application
│ ├── command
│ │ └── test.go
│ ├── http
│ │ ├── controller
│ │ │ ├── compose-container.go
│ │ │ ├── compose.go
│ │ │ ├── container-backup.go
│ │ │ ├── container-stat.go
│ │ │ ├── container-upgrade.go
│ │ │ ├── container.go
│ │ │ ├── explorer.go
│ │ │ ├── home.go
│ │ │ ├── image-build.go
│ │ │ ├── image-buildx.go
│ │ │ ├── image-tag.go
│ │ │ ├── image-template.go
│ │ │ ├── image.go
│ │ │ ├── network.go
│ │ │ ├── run-log.go
│ │ │ ├── site-cert.go
│ │ │ ├── site-domain.go
│ │ │ ├── site.go
│ │ │ └── volume.go
│ │ └── middleware
│ │ │ └── home.go
│ ├── logic
│ │ ├── compose.go
│ │ ├── docker-container-task.go
│ │ ├── docker-image-task.go
│ │ ├── docker-task.go
│ │ ├── image-template.go
│ │ ├── image.go
│ │ └── site.go
│ └── provider.go
├── common
│ ├── command
│ │ └── test.go
│ ├── http
│ │ ├── controller
│ │ │ ├── attach.go
│ │ │ ├── cron.go
│ │ │ ├── env.go
│ │ │ ├── event.go
│ │ │ ├── explorer.go
│ │ │ ├── home.go
│ │ │ ├── notice.go
│ │ │ ├── registry.go
│ │ │ ├── setting.go
│ │ │ ├── store.go
│ │ │ ├── tag.go
│ │ │ └── user.go
│ │ └── middleware
│ │ │ └── home.go
│ ├── logic
│ │ ├── cron-template.go
│ │ ├── cron.go
│ │ ├── docker-env.go
│ │ ├── event.go
│ │ ├── notice.go
│ │ ├── setting.go
│ │ ├── stat.go
│ │ ├── store.go
│ │ └── user.go
│ └── provider.go
└── ctrl
│ ├── command
│ ├── compose
│ │ └── deploy.go
│ ├── container
│ │ ├── backup.go
│ │ └── upgrade.go
│ ├── store
│ │ └── sync.go
│ └── user
│ │ └── reset.go
│ ├── provider.go
│ └── sdk
│ ├── proxy
│ ├── app-compose.go
│ ├── app-container.go
│ ├── app-image.go
│ ├── client.go
│ ├── common-env.go
│ └── common-store.go
│ ├── types
│ ├── app
│ │ ├── compose.go
│ │ ├── container.go
│ │ └── image.go
│ ├── common
│ │ ├── env.go
│ │ └── store.go
│ └── proxy.go
│ └── utils
│ └── result.go
├── asset
├── index.html
├── nginx
│ └── vhost.tpl
├── plugin
│ ├── backup
│ │ └── compose.yaml
│ ├── explorer
│ │ ├── compose.yaml
│ │ ├── image-amd64.tar
│ │ ├── image-arm.tar
│ │ └── image-arm64.tar
│ └── webshell
│ │ └── compose.yaml
└── static
│ ├── 110.f3d64f88.async.js
│ ├── 1239.2ff603f1.async.js
│ ├── 1406.45baa800.async.js
│ ├── 1482.90431ac7.async.js
│ ├── 1604.fa24433c.async.js
│ ├── 1876.a4999f7b.async.js
│ ├── 2131.0302f810.async.js
│ ├── 2136.0a6722b9.async.js
│ ├── 2181.253c402d.async.js
│ ├── 2391.a5e58e5d.async.js
│ ├── 2398.a49ab4c8.async.js
│ ├── 2406.eed6584f.async.js
│ ├── 2849.165921b2.async.js
│ ├── 3079.836d90d4.async.js
│ ├── 3158.881c78d5.async.js
│ ├── 3170.43ab622e.async.js
│ ├── 3203.36b9d233.async.js
│ ├── 3348.cac7a5b0.async.js
│ ├── 3379.3175fc32.chunk.css
│ ├── 3379.54aa12e8.async.js
│ ├── 3635.a0cd4d5e.async.js
│ ├── 3831.70763dc3.async.js
│ ├── 4262.1600f41e.async.js
│ ├── 4323.e1b6579d.async.js
│ ├── 4393.8eef8d84.async.js
│ ├── 4774.35c5d20a.async.js
│ ├── 4997.8576b40a.async.js
│ ├── 5083.a5b4b7f4.async.js
│ ├── 5137.752e3bc0.async.js
│ ├── 5139.ad8591f7.async.js
│ ├── 5145.05f1be4e.async.js
│ ├── 5213.1376d91a.async.js
│ ├── 5265.0a1d0132.async.js
│ ├── 5333.965a8877.async.js
│ ├── 5529.4ab750cf.async.js
│ ├── 5658.597d519d.async.js
│ ├── 5843.e9e1b99b.async.js
│ ├── 5976.67835c8c.async.js
│ ├── 6121.48085b3f.async.js
│ ├── 6153.7095f2d4.async.js
│ ├── 6196.47b7f2c7.async.js
│ ├── 6299.3b4ecac4.async.js
│ ├── 6357.d2b2e36f.async.js
│ ├── 6390.e3bcb4e0.async.js
│ ├── 6442.7ea6b515.async.js
│ ├── 6635.11aa525a.async.js
│ ├── 6845.62bd4d90.async.js
│ ├── 690.a621bd0c.async.js
│ ├── 7142.36cf0473.async.js
│ ├── 7143.91d5e9fc.async.js
│ ├── 7234.5f787c7f.async.js
│ ├── 78.d7d7fec2.async.js
│ ├── 8010.8777bf08.async.js
│ ├── 8171.f0ace9c2.async.js
│ ├── 8174.3da9d177.async.js
│ ├── 8195.efde9d4d.async.js
│ ├── 8345.c41706b6.async.js
│ ├── 8373.bb573054.async.js
│ ├── 8428.6a72bae3.async.js
│ ├── 8631.7a8bbcb7.async.js
│ ├── 8636.a92238c8.async.js
│ ├── 8762.a152ca56.async.js
│ ├── 9170.ab354367.async.js
│ ├── 9296.fbb1e23d.async.js
│ ├── 940.f2775de8.async.js
│ ├── 9490.1baf440f.async.js
│ ├── 9607.7968ff15.async.js
│ ├── 9831.f360be3b.async.js
│ ├── 9994.dacae9b4.async.js
│ ├── dpanel.ico
│ ├── index.html
│ ├── layouts__index.386958dc.async.js
│ ├── logo-small.png
│ ├── logo.png
│ ├── p__app__backup.0f553844.async.js
│ ├── p__app__create__image.b72be267.async.js
│ ├── p__app__cron__index.d68c764f.async.js
│ ├── p__app__cron__list.a3c29192.async.js
│ ├── p__app__cron__log.77bd83d4.async.js
│ ├── p__app__detail__edit.00bc1dd6.chunk.css
│ ├── p__app__detail__edit.6eab49ee.async.js
│ ├── p__app__detail__file.00bc1dd6.chunk.css
│ ├── p__app__detail__file.0f48eee0.async.js
│ ├── p__app__detail__index.fb3d00d5.async.js
│ ├── p__app__detail__log.00bc1dd6.chunk.css
│ ├── p__app__detail__log.af395098.async.js
│ ├── p__app__detail__network.3531e5be.async.js
│ ├── p__app__detail__stat.9f339f6b.async.js
│ ├── p__app__domain__cert.00409a36.async.js
│ ├── p__app__domain__cert.00bc1dd6.chunk.css
│ ├── p__app__domain__index.8bcd0540.async.js
│ ├── p__app__domain__list.00bc1dd6.chunk.css
│ ├── p__app__domain__list.30943b4a.async.js
│ ├── p__app__index.36c4b625.async.js
│ ├── p__app__list.00bc1dd6.chunk.css
│ ├── p__app__list.b05c94e8.async.js
│ ├── p__app__recycle.f1f91c8c.async.js
│ ├── p__compose__appstore.11c6deb9.async.js
│ ├── p__compose__create.d0d0c47f.async.js
│ ├── p__compose__index.17135f09.async.js
│ ├── p__compose__list.00bc1dd6.chunk.css
│ ├── p__compose__list.da32de81.async.js
│ ├── p__console__container-log.00bc1dd6.chunk.css
│ ├── p__console__container-log.f84011ac.async.js
│ ├── p__console__container.00bc1dd6.chunk.css
│ ├── p__console__container.0d9d9060.async.js
│ ├── p__console__host.00bc1dd6.chunk.css
│ ├── p__console__host.481d979d.async.js
│ ├── p__docker__index.3b1c9bfc.async.js
│ ├── p__docker__network.925abd8d.async.js
│ ├── p__docker__volume.bdad06e9.async.js
│ ├── p__image__create.00bc1dd6.chunk.css
│ ├── p__image__create.2b69e9b2.async.js
│ ├── p__image__index.e0ed1996.async.js
│ ├── p__image__list.af95d92f.async.js
│ ├── p__image__recycle.00bc1dd6.chunk.css
│ ├── p__image__recycle.163d6f97.async.js
│ ├── p__image__registry.f03db23d.async.js
│ ├── p__system__appstore.9b4460b2.async.js
│ ├── p__system__basic.fd523ef2.async.js
│ ├── p__system__env.737d2f3d.async.js
│ ├── p__system__event.11873484.async.js
│ ├── p__system__home__host.00bc1dd6.chunk.css
│ ├── p__system__home__host.276876dc.async.js
│ ├── p__system__home__overview.917ca295.async.js
│ ├── p__system__home__tag.00bc1dd6.chunk.css
│ ├── p__system__home__tag.65fb462d.async.js
│ ├── p__system__index.e144e227.async.js
│ ├── p__system__notice.38bf44c2.async.js
│ ├── p__system__upgrade.b9ebfc6f.async.js
│ ├── p__system__user.7d8b0a4a.async.js
│ ├── p__user__login__index.401a1582.async.js
│ ├── p__user__oauth__callback.92e13b57.async.js
│ ├── p__user__register__index.bf6ff728.async.js
│ ├── t__plugin-layout__Layout.3a1d5bb6.async.js
│ ├── t__plugin-layout__Layout.5012e1ab.chunk.css
│ ├── umi.277b1e59.css
│ └── umi.c6ba5b11.js
├── common
├── accessor
│ ├── backup_setting_option.go
│ ├── compose_setting_option.go
│ ├── cron_log_value_option.go
│ ├── cron_setting_option.go
│ ├── image_info_option.go
│ ├── image_setting_option.go
│ ├── permission_value_option.go
│ ├── registry_setting_option.go
│ ├── setting_value_option.go
│ ├── site_container_info_option.go
│ ├── site_domain_setting_option.go
│ ├── site_env_option.go
│ └── store_setting_option.go
├── dao
│ ├── gen.go
│ ├── ims_backup.gen.go
│ ├── ims_compose.gen.go
│ ├── ims_cron.gen.go
│ ├── ims_cron_log.gen.go
│ ├── ims_event.gen.go
│ ├── ims_image.gen.go
│ ├── ims_notice.gen.go
│ ├── ims_registry.gen.go
│ ├── ims_setting.gen.go
│ ├── ims_site.gen.go
│ ├── ims_site_domain.gen.go
│ ├── ims_store.gen.go
│ └── ims_user_permission.gen.go
├── entity
│ ├── ims_backup.gen.go
│ ├── ims_compose.gen.go
│ ├── ims_cron.gen.go
│ ├── ims_cron_log.gen.go
│ ├── ims_event.gen.go
│ ├── ims_image.gen.go
│ ├── ims_notice.gen.go
│ ├── ims_registry.gen.go
│ ├── ims_setting.gen.go
│ ├── ims_site.gen.go
│ ├── ims_site_domain.gen.go
│ ├── ims_store.gen.go
│ └── ims_user_permission.gen.go
├── function
│ ├── .keep
│ ├── array.go
│ ├── byte.go
│ ├── date.go
│ ├── encrypt.go
│ ├── error.go
│ ├── map.go
│ ├── ptr.go
│ ├── strings.go
│ ├── struct.go
│ ├── util.go
│ └── yaml.go
├── middleware
│ ├── auth.go
│ ├── cache.go
│ └── cors.go
├── migrate
│ ├── upgrade-20240909.go
│ ├── upgrade-20250106.go
│ ├── upgrade-20250113.go
│ ├── upgrade-20250401.go
│ ├── upgrade-20250521.go
│ └── upgrade.go
├── service
│ ├── acme
│ │ ├── acme.go
│ │ └── option.go
│ ├── compose
│ │ ├── compose.go
│ │ ├── create.go
│ │ ├── extension.go
│ │ ├── placehold.go
│ │ ├── task.go
│ │ └── types.go
│ ├── crontab
│ │ ├── job.go
│ │ └── wrapper.go
│ ├── docker
│ │ ├── backup
│ │ │ ├── builder.go
│ │ │ ├── option.go
│ │ │ ├── reader.go
│ │ │ └── writer.go
│ │ ├── container.go
│ │ ├── container
│ │ │ ├── builder.go
│ │ │ └── option.go
│ │ ├── docker.go
│ │ ├── exec.go
│ │ ├── image.go
│ │ ├── image
│ │ │ ├── builder.go
│ │ │ └── option.go
│ │ ├── import.go
│ │ ├── network.go
│ │ ├── types.go
│ │ └── utils.go
│ ├── exec
│ │ ├── command.go
│ │ ├── option.go
│ │ └── result.go
│ ├── family
│ │ ├── ce.go
│ │ ├── ee.go
│ │ ├── pe.go
│ │ ├── util.go
│ │ └── xk.go
│ ├── fs
│ │ ├── dockerfs
│ │ │ ├── docker.go
│ │ │ ├── file.go
│ │ │ └── option.go
│ │ └── fs.go
│ ├── notice
│ │ └── message.go
│ ├── plugin
│ │ ├── plugin.go
│ │ └── wrapper.go
│ ├── registry
│ │ ├── option.go
│ │ ├── registry.go
│ │ ├── repository.go
│ │ ├── types.go
│ │ └── util.go
│ ├── ssh
│ │ ├── client.go
│ │ ├── host.go
│ │ ├── option.go
│ │ └── types.go
│ ├── storage
│ │ ├── cache.go
│ │ └── local.go
│ └── ws
│ │ ├── client.go
│ │ ├── collection.go
│ │ ├── option.go
│ │ ├── progress.go
│ │ └── types.go
└── types
│ ├── define
│ └── message.go
│ ├── event
│ ├── Image_registry.go
│ ├── compose.go
│ ├── container.go
│ ├── env.go
│ ├── permission.go
│ └── store.go
│ ├── family.go
│ ├── fs
│ └── file_info.go
│ └── permission.go
├── config.yaml
├── database
└── gen-model.yaml
├── docker
├── entrypoint.sh
├── nginx
│ ├── dpanel.conf
│ ├── include
│ │ ├── assets.conf
│ │ ├── block-exploits.conf
│ │ ├── challenge.conf
│ │ └── force-ssl.conf
│ └── nginx.conf
└── script
│ ├── clear-upload
│ └── data.yaml
│ ├── compose-upgrade
│ └── data.yaml
│ ├── container-backup
│ └── data.yaml
│ ├── container-clear
│ └── data.yaml
│ ├── container-restart
│ └── data.yaml
│ ├── container-upgrade
│ └── data.yaml
│ ├── host-backup
│ └── data.yaml
│ ├── mysql-backup
│ └── data.yaml
│ ├── rclone-backup
│ └── data.yaml
│ └── store-sync
│ └── data.yaml
├── go.mod
├── go.sum
├── main.go
└── reload.sh
/.air.toml:
--------------------------------------------------------------------------------
1 | root = "."
2 | testdata_dir = "testdata"
3 | tmp_dir = "runtime"
4 |
5 | [build]
6 | args_bin = ["-f", "config.yaml"]
7 | bin = "./runtime/main"
8 | cmd = "go build -o ./runtime/main ."
9 | delay = 0
10 | exclude_dir = ["assets", "tmp", "vendor", "testdata"]
11 | exclude_file = []
12 | exclude_regex = ["_test.go"]
13 | exclude_unchanged = false
14 | follow_symlink = false
15 | full_bin = ""
16 | include_dir = []
17 | include_ext = ["go", "tpl", "tmpl", "html"]
18 | include_file = []
19 | kill_delay = "3s"
20 | log = "build-errors.log"
21 | rerun = false
22 | rerun_delay = 500
23 | send_interrupt = true
24 | stop_on_error = false
25 |
26 | [color]
27 | app = ""
28 | build = "yellow"
29 | main = "magenta"
30 | runner = "green"
31 | watcher = "cyan"
32 |
33 | [log]
34 | main_only = false
35 | time = false
36 |
37 | [misc]
38 | clean_on_exit = false
39 |
40 | [screen]
41 | clear_on_rebuild = false
42 | keep_scroll = true
43 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github:
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom:
16 | - https://afdian.com/a/dpanel
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/功能需求.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 功能需求
3 | about: 提交新功能需求
4 | title: "[feature]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | 完整填写以下信息,有助于快速理解新需求功能。请认真填写。
11 |
12 | **需求的出发点是什么?或是为了解决什么问题?**
13 |
14 | **详细描述功能需求?**
15 |
16 | **开发建议(示例、库、相关项目等)?**
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/反馈bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 反馈bug
3 | about: 反馈bug模板
4 | title: "[BUG]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | 完整填写以下信息,有助于快速定位和排查问题。请认真填写。
11 |
12 | **当前使用的dpanel版本?**
13 |
14 | 通过 【概览】- 【系统信息】- 【面板信息】
15 |
16 |
17 | ** 错误日志 **
18 |
19 | 【容器管理】 - 【容器列表】-【dpanel】-【运行日志】 或 docker logs dpanel
20 |
21 | ** 复现方式 **
22 |
23 | 描述一下出现问题的操作流程
24 |
25 | ** Q群反馈 **
26 |
27 | Q群:837583876
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .buildpath
2 | .settings/
3 | .project
4 | *.patch
5 | .idea/
6 | .git/
7 | .env*
8 | .DS_Store
9 | LICENSE
10 | tests/.phpunit.result.cache
11 | .phpunit.result.cache
12 | runtime/logs
13 | runtime/main
14 | runtime/
15 | bin/
16 | data
17 | tests
18 | app/pro/**/.DS_Store
19 | docker/**/*.log
20 | .run/**.*
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "app/pro"]
2 | path = app/pro
3 | url = https://github.com/donknap/dpanel-pro.git
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine
2 |
3 | ARG APP_VERSION
4 | ARG TARGETARCH
5 | ARG APP_FAMILY
6 | ARG PROXY="proxy=0"
7 |
8 | ENV APP_NAME=dpanel
9 | ENV APP_ENV=production
10 | ENV APP_FAMILY=$APP_FAMILY
11 | ENV APP_VERSION=$APP_VERSION
12 | ENV APP_SERVER_PORT=8080
13 |
14 | ENV DOCKER_HOST=unix:///var/run/docker.sock
15 | ENV STORAGE_LOCAL_PATH=/dpanel
16 | ENV DB_DATABASE=${STORAGE_LOCAL_PATH}/dpanel.db
17 | ENV TZ=Asia/Shanghai
18 | ENV ACME_OVERRIDE_CONFIG_HOME=/dpanel/acme
19 |
20 | COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
21 | COPY ./docker/nginx/include /etc/nginx/conf.d/include
22 | COPY ./docker/script /app/script
23 |
24 | COPY ./runtime/dpanel${APP_FAMILY:+"-${APP_FAMILY}"}-musl-${TARGETARCH} /app/server/dpanel
25 | COPY ./runtime/config.yaml /app/server/config.yaml
26 |
27 | COPY ./docker/entrypoint.sh /docker/entrypoint.sh
28 |
29 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
30 | apk add --no-cache --update nginx musl docker-compose curl openssl tzdata git && \
31 | mkdir -p /tmp/nginx/body /var/lib/nginx/cache/public /var/lib/nginx/cache/private && \
32 | export ${PROXY} && curl https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online --config-home /dpanel/acme && \
33 | chmod 755 /docker/entrypoint.sh
34 |
35 | WORKDIR /app/server
36 | VOLUME [ "/dpanel" ]
37 |
38 | EXPOSE 443
39 | EXPOSE 80
40 | EXPOSE 8080
41 |
42 | ENTRYPOINT [ "/docker/entrypoint.sh" ]
--------------------------------------------------------------------------------
/Dockerfile-lite:
--------------------------------------------------------------------------------
1 | FROM alpine
2 |
3 | ARG APP_VERSION
4 | ARG TARGETARCH
5 | ARG APP_FAMILY
6 |
7 | ENV APP_NAME=dpanel
8 | ENV APP_ENV=lite
9 | ENV APP_VERSION=$APP_VERSION
10 | ENV APP_FAMILY=$APP_FAMILY
11 | ENV APP_SERVER_PORT=8080
12 |
13 | ENV DOCKER_HOST=unix:///var/run/docker.sock
14 | ENV STORAGE_LOCAL_PATH=/dpanel
15 | ENV DB_DATABASE=${STORAGE_LOCAL_PATH}/dpanel.db
16 | ENV TZ=Asia/Shanghai
17 | ENV ACME_OVERRIDE_CONFIG_HOME=/dpanel/acme
18 |
19 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
20 | apk add --no-cache --update musl docker-compose tzdata git
21 |
22 | COPY ./runtime/dpanel${APP_FAMILY:+"-${APP_FAMILY}"}-musl-${TARGETARCH} /app/server/dpanel
23 | COPY ./runtime/config.yaml /app/server/config.yaml
24 | COPY ./docker/script /app/script
25 |
26 | WORKDIR /app/server
27 | VOLUME [ "/dpanel" ]
28 |
29 | EXPOSE 8080
30 |
31 | ENTRYPOINT [ "sh", "-c", "/app/server/dpanel server:start -f /app/server/config.yaml" ]
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ------- | ------------------ |
7 | | 1.x | :white_check_mark: |
8 |
9 | ## Reporting a Vulnerability
10 |
11 | All security bugs should be reported to the contact as below:
12 |
13 | - email: work.donknap@qq.com
14 | - qq: 914417117
15 |
16 | Thanks for your support!
17 |
--------------------------------------------------------------------------------
/app/application/command/test.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/gookit/color"
5 | "github.com/spf13/cobra"
6 | "github.com/we7coreteam/w7-rangine-go/v2/src/console"
7 | )
8 |
9 | type Test struct {
10 | console.Abstract
11 | }
12 |
13 | func (test Test) GetName() string {
14 | return "application:test"
15 | }
16 |
17 | func (test Test) GetDescription() string {
18 | return "application command"
19 | }
20 |
21 | func (test Test) Handle(cmd *cobra.Command, args []string) {
22 | color.Infoln("application test")
23 | }
24 |
--------------------------------------------------------------------------------
/app/application/http/controller/container-stat.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "fmt"
5 | "github.com/donknap/dpanel/common/service/docker"
6 | "github.com/donknap/dpanel/common/service/ws"
7 | "github.com/gin-gonic/gin"
8 | "io"
9 | "time"
10 | )
11 |
12 | func (self Container) GetStatInfo(http *gin.Context) {
13 | type ParamsValidate struct {
14 | Id string `json:"id" binding:"required"`
15 | }
16 | params := ParamsValidate{}
17 | if !self.Validate(http, ¶ms) {
18 | return
19 | }
20 | progress, err := ws.NewFdProgressPip(http, fmt.Sprintf(ws.MessageTypeContainerStat, params.Id))
21 | if err != nil {
22 | self.JsonResponseWithError(http, err, 500)
23 | return
24 | }
25 | response, err := docker.Sdk.Client.ContainerStats(progress.Context(), params.Id, true)
26 | if err != nil {
27 | self.JsonResponseWithError(http, err, 500)
28 | return
29 | }
30 | lastSendTime := time.Now()
31 |
32 | progress.OnWrite = func(p string) error {
33 | if time.Now().Sub(lastSendTime) < time.Second*2 {
34 | return nil
35 | }
36 | lastSendTime = time.Now()
37 | progress.BroadcastMessage(p)
38 | return nil
39 | }
40 | _, err = io.Copy(progress, response.Body)
41 | self.JsonSuccessResponse(http)
42 | return
43 | }
44 |
45 | func (self Container) GetProcessInfo(http *gin.Context) {
46 | type ParamsValidate struct {
47 | Id string `json:"id" binding:"required"`
48 | }
49 | params := ParamsValidate{}
50 | if !self.Validate(http, ¶ms) {
51 | return
52 | }
53 |
54 | psInfo, err := docker.Sdk.Client.ContainerTop(docker.Sdk.Ctx, params.Id, nil)
55 | if err != nil {
56 | self.JsonResponseWithError(http, err, 500)
57 | return
58 | }
59 | self.JsonResponseWithoutError(http, gin.H{
60 | "list": psInfo,
61 | })
62 | return
63 | }
64 |
--------------------------------------------------------------------------------
/app/application/http/controller/home.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller"
6 | )
7 |
8 | type Home struct {
9 | controller.Abstract
10 | }
11 |
12 | func (home Home) Index(ctx *gin.Context) {
13 | home.JsonResponseWithoutError(ctx, "hello world!")
14 | }
15 |
--------------------------------------------------------------------------------
/app/application/http/controller/image-build.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "errors"
5 | "github.com/donknap/dpanel/common/dao"
6 | registry2 "github.com/donknap/dpanel/common/service/registry"
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | func (self Image) GetBuildTask(http *gin.Context) {
11 | type ParamsValidate struct {
12 | Id int32 `form:"id" binding:"required"`
13 | }
14 | params := ParamsValidate{}
15 | if !self.Validate(http, ¶ms) {
16 | return
17 | }
18 | imageRow, _ := dao.Image.Where(dao.Image.ID.Eq(params.Id)).First()
19 | if imageRow == nil {
20 | self.JsonResponseWithError(http, errors.New("构建任务不存在"), 500)
21 | return
22 | }
23 | tagDetail := registry2.GetImageTagDetail(imageRow.Tag)
24 | imageRow.Tag = tagDetail.ImageName
25 |
26 | self.JsonResponseWithoutError(http, gin.H{
27 | "task": imageRow,
28 | })
29 | return
30 | }
31 |
32 | func (self Image) DeleteBuildTask(http *gin.Context) {
33 | type ParamsValidate struct {
34 | Id []int32 `form:"id" binding:"required"`
35 | }
36 | params := ParamsValidate{}
37 | if !self.Validate(http, ¶ms) {
38 | return
39 | }
40 | _, err := dao.Image.Where(dao.Image.ID.In(params.Id...)).Delete()
41 | if err != nil {
42 | self.JsonResponseWithError(http, err, 500)
43 | return
44 | }
45 | self.JsonSuccessResponse(http)
46 | return
47 | }
48 |
49 | func (self Image) GetListBuild(http *gin.Context) {
50 | type ParamsValidate struct {
51 | Page int `json:"page,default=1" binding:"omitempty,gt=0"`
52 | PageSize int `json:"pageSize" binding:"omitempty,gt=1"`
53 | All bool `json:"all"`
54 | }
55 | params := ParamsValidate{}
56 | if !self.Validate(http, ¶ms) {
57 | return
58 | }
59 | if params.Page < 1 {
60 | params.Page = 1
61 | }
62 | if params.PageSize < 1 {
63 | params.PageSize = 10
64 | }
65 |
66 | query := dao.Image.Order(dao.Image.ID.Desc())
67 | if !params.All {
68 | query = query.Where(dao.Image.BuildType.Neq("pull"))
69 | }
70 | list, total, _ := query.FindByPage((params.Page-1)*params.PageSize, params.PageSize)
71 | self.JsonResponseWithoutError(http, gin.H{
72 | "total": total,
73 | "page": params.Page,
74 | "list": list,
75 | })
76 | return
77 | }
78 |
--------------------------------------------------------------------------------
/app/application/http/controller/image-buildx.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import "github.com/gin-gonic/gin"
4 |
5 | func (self Image) CreateByBuildX(http *gin.Context) {
6 | type ParamsValidate struct {
7 | Id int32 `json:"id" binding:"required"`
8 | Platform []string `json:"platform" binding:"required"`
9 | ClearContainer bool `json:"clearContainer" binding:"required"`
10 | ClearCache bool `json:"clearCache" binding:"required"`
11 | Registry string `json:"registry"`
12 | }
13 |
14 | params := ParamsValidate{}
15 | if !self.Validate(http, ¶ms) {
16 | return
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/application/http/controller/image-template.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "github.com/donknap/dpanel/app/application/logic"
5 | "github.com/gin-gonic/gin"
6 | )
7 |
8 | func (self Image) GetTemplateList(http *gin.Context) {
9 | self.JsonResponseWithoutError(http, logic.ImageTemplate{}.GetSupportEnv())
10 | return
11 | }
12 |
13 | func (self Image) GetTemplateDockerfile(http *gin.Context) {
14 | type ParamsValidate struct {
15 | Language string `json:"language" binding:"required"`
16 | Version string `json:"version" binding:"required"`
17 | }
18 | params := ParamsValidate{}
19 | if !self.Validate(http, ¶ms) {
20 | return
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/application/http/controller/run-log.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/docker/docker/api/types/container"
7 | "github.com/docker/docker/pkg/stdcopy"
8 | "github.com/donknap/dpanel/common/service/docker"
9 | "github.com/donknap/dpanel/common/service/ws"
10 | "github.com/gin-gonic/gin"
11 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller"
12 | "io"
13 | "log/slog"
14 | "strconv"
15 | )
16 |
17 | type RunLog struct {
18 | controller.Abstract
19 | }
20 |
21 | func (self RunLog) Run(http *gin.Context) {
22 | type ParamsValidate struct {
23 | Id string `json:"id" binding:"required"`
24 | LineTotal int `json:"lineTotal" binding:"required,number,oneof=50 100 200 500 1000 5000 -1"`
25 | Download bool `json:"download"`
26 | ShowTime bool `json:"showTime"`
27 | }
28 |
29 | params := ParamsValidate{}
30 | if !self.Validate(http, ¶ms) {
31 | return
32 | }
33 | option := container.LogsOptions{
34 | ShowStderr: true,
35 | ShowStdout: true,
36 | Follow: !params.Download,
37 | Timestamps: params.ShowTime,
38 | }
39 | if params.LineTotal > 0 {
40 | option.Tail = strconv.Itoa(params.LineTotal)
41 | }
42 | progress, err := ws.NewFdProgressPip(http, fmt.Sprintf(ws.MessageTypeContainerLog, params.Id))
43 | if err != nil {
44 | self.JsonResponseWithError(http, err, 500)
45 | return
46 | }
47 |
48 | if progress.IsShadow() {
49 | option.Follow = false
50 | }
51 |
52 | response, err := docker.Sdk.Client.ContainerLogs(docker.Sdk.Ctx, params.Id, option)
53 | if err != nil {
54 | self.JsonResponseWithError(http, err, 500)
55 | return
56 | }
57 | if params.Download {
58 | buffer, err := docker.GetContentFromStdFormat(response)
59 | _ = response.Close()
60 | if err != nil {
61 | self.JsonResponseWithError(http, err, 500)
62 | return
63 | }
64 | http.Header("Content-Type", "text/plain")
65 | http.Header("Content-Disposition", "attachment; filename="+params.Id+".log")
66 | http.Data(200, "text/plain", buffer.Bytes())
67 | return
68 | }
69 |
70 | progress.OnWrite = func(p string) error {
71 | newReader := bytes.NewReader([]byte(p))
72 | stdout := new(bytes.Buffer)
73 | _, err = stdcopy.StdCopy(stdout, stdout, newReader)
74 | if err != nil {
75 | progress.BroadcastMessage(p)
76 | } else {
77 | progress.BroadcastMessage(stdout.String())
78 | }
79 | return nil
80 | }
81 |
82 | go func() {
83 | if progress.IsShadow() {
84 | return
85 | }
86 | select {
87 | case <-progress.Done():
88 | slog.Debug("container", "run log response close", fmt.Sprintf(ws.MessageTypeContainerLog, params.Id))
89 | _ = response.Close()
90 | }
91 | }()
92 |
93 | _, err = io.Copy(progress, response)
94 | //if err != nil {
95 | // self.JsonResponseWithError(http, errors.New("读取日志失败"), 500)
96 | // return
97 | //}
98 | //newReader := bytes.NewReader(out.Bytes())
99 | //
100 | //stdout := new(bytes.Buffer)
101 | //_, err = stdcopy.StdCopy(stdout, stdout, newReader)
102 | //
103 | //if err == nil {
104 | // out = stdout
105 | //}
106 | self.JsonResponseWithoutError(http, gin.H{
107 | "log": "",
108 | })
109 | return
110 | }
111 |
--------------------------------------------------------------------------------
/app/application/http/middleware/home.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
6 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware"
7 | "time"
8 | )
9 |
10 | type Home struct {
11 | middleware.Abstract
12 | }
13 |
14 | func (home Home) Process(ctx *gin.Context) {
15 | log, _ := facade.GetLoggerFactory().Channel("default")
16 | log.Info("route middleware test, req time: " + time.Now().String())
17 |
18 | ctx.Next()
19 | }
20 |
--------------------------------------------------------------------------------
/app/application/logic/docker-task.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/accessor"
5 | "github.com/donknap/dpanel/common/service/docker"
6 | )
7 |
8 | type CreateContainerOption struct {
9 | SiteId int32 `json:"id"` // 站点id
10 | SiteTitle string `json:"siteTitle" binding:"required"`
11 | SiteName string `json:"siteName" binding:"required"`
12 | ContainerId string `json:"containerId"`
13 | BuildParams *accessor.SiteEnvOption
14 | }
15 |
16 | type BuildImageOption struct {
17 | ZipPath string // 构建包
18 | DockerFileContent []byte // 自定义Dockerfile
19 | DockerFileInPath string // Dockerfile 所在路径
20 | GitUrl string
21 | Tag string // 镜像Tag
22 | ImageId int32
23 | Context string // Dockerfile 所在的目录
24 | Platform *docker.ImagePlatform
25 | }
26 |
27 | type ImageRemoteOption struct {
28 | Auth string
29 | Type string
30 | Tag string
31 | Platform string
32 | Proxy string
33 | }
34 |
35 | type NoticeOption struct {
36 | Message string
37 | }
38 |
39 | type DockerTask struct {
40 | sdk *docker.Builder
41 | }
42 |
--------------------------------------------------------------------------------
/app/application/logic/image-template.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | type ImageTemplate struct {
4 | }
5 |
6 | type envItem struct {
7 | Name string `json:"name"`
8 | Version string `json:"version"`
9 | BaseImage string `json:"baseImage"`
10 | }
11 |
12 | type env struct {
13 | Name string `json:"name"`
14 | Env []envItem `json:"env"`
15 | Ext []string `json:"ext"`
16 | }
17 |
18 | func (self ImageTemplate) GetSupportEnv() map[string]env {
19 | supportEnv := make(map[string]env)
20 |
21 | supportEnv[LangPhp] = env{
22 | Name: LangPhp,
23 | Env: []envItem{
24 | {
25 | Name: "php-72",
26 | BaseImage: "dpanel/base-image:php-72|7",
27 | },
28 | {
29 | Name: "php-74",
30 | BaseImage: "dpanel/base-image:php-74|7",
31 | },
32 | {
33 | Name: "php-81",
34 | BaseImage: "dpanel/base-image:php-81|81",
35 | },
36 | },
37 | Ext: []string{
38 | "intl", "pecl-apcu", "imap", "pecl-mongodb", "pdo_pgsql",
39 | },
40 | }
41 |
42 | supportEnv[LangJava] = env{
43 | Name: LangJava,
44 | Env: []envItem{
45 | {
46 | Name: "jdk8",
47 | Version: "8",
48 | BaseImage: "dpanel/base-image:java-8",
49 | },
50 | {
51 | Name: "jdk11",
52 | Version: "11",
53 | BaseImage: "dpanel/base-image:java-11",
54 | },
55 | {
56 | Name: "jdk12",
57 | Version: "12",
58 | BaseImage: "dpanel/base-image:java-12",
59 | },
60 | },
61 | }
62 |
63 | supportEnv[LangGolang] = env{
64 | Name: LangGolang,
65 | Env: []envItem{
66 | {
67 | Name: "go1.21",
68 | Version: "1.21",
69 | BaseImage: "dpanel/base-image:go-1.21|1.21",
70 | },
71 | },
72 | }
73 |
74 | supportEnv[LangNode] = env{
75 | Name: LangNode,
76 | Env: []envItem{
77 | {
78 | Name: "node12",
79 | Version: "12",
80 | BaseImage: "dpanel/base-image:node-12",
81 | },
82 | {
83 | Name: "node14",
84 | Version: "14",
85 | BaseImage: "dpanel/base-image:node-14",
86 | },
87 | {
88 | Name: "node16",
89 | Version: "16",
90 | BaseImage: "dpanel/base-image:node-16",
91 | },
92 | {
93 | Name: "node18",
94 | Version: "18",
95 | BaseImage: "dpanel/base-image:node-18",
96 | },
97 | },
98 | }
99 |
100 | supportEnv[LangHtml] = env{
101 | Name: LangHtml,
102 | Env: []envItem{
103 | {
104 | Name: "common",
105 | Version: "1.0.0",
106 | BaseImage: "dpanel/base-image:html-common",
107 | },
108 | },
109 | }
110 | return supportEnv
111 | }
112 |
--------------------------------------------------------------------------------
/app/application/logic/image.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "github.com/donknap/dpanel/common/dao"
6 | "github.com/donknap/dpanel/common/function"
7 | "github.com/donknap/dpanel/common/service/docker"
8 | registry2 "github.com/donknap/dpanel/common/service/registry"
9 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
10 | "log/slog"
11 | )
12 |
13 | type Image struct {
14 | }
15 |
16 | func (self Image) GetRegistryConfig(imageName string) registry2.Config {
17 | result := registry2.Config{
18 | Proxy: make([]string, 0),
19 | }
20 | imageNameDetail := registry2.GetImageTagDetail(imageName)
21 |
22 | if docker.Sdk.Client != nil && imageNameDetail.Registry == registry2.DefaultRegistryDomain {
23 | // 获取docker 配置中的加速地址
24 | // 暂时注释掉,直接取 daemon.json 的镜像地址问题太多
25 | //if dockerInfo, err := docker.Sdk.Client.Info(docker.Sdk.Ctx); err == nil && dockerInfo.RegistryConfig != nil && !function.IsEmptyArray(dockerInfo.RegistryConfig.Mirrors) {
26 | // result.Proxy = dockerInfo.RegistryConfig.Mirrors
27 | //}
28 | }
29 |
30 | registryList, _ := dao.Registry.Where(dao.Registry.ServerAddress.Eq(imageNameDetail.Registry)).Find()
31 | for _, item := range registryList {
32 | if item.Setting == nil {
33 | continue
34 | }
35 | if item.Setting.EnableHttp {
36 | result.Proxy = append(result.Proxy, fmt.Sprintf("http://"+item.ServerAddress))
37 | }
38 | if !function.IsEmptyArray(item.Setting.Proxy) {
39 | result.Proxy = append(result.Proxy, item.Setting.Proxy...)
40 | }
41 | if item.Setting.Username != "" && item.Setting.Password != "" {
42 | result.Username = item.Setting.Username
43 | password, err := function.AseDecode(facade.GetConfig().GetString("app.name"), item.Setting.Password)
44 | if err != nil {
45 | slog.Debug("image registry decode password", "error", err)
46 | }
47 | result.Password = password
48 | result.ExistsAuth = true
49 | }
50 | }
51 |
52 | if !function.InArray(result.Proxy, imageNameDetail.Registry) {
53 | result.Proxy = append(result.Proxy, imageNameDetail.Registry)
54 | }
55 | return result
56 | }
57 |
--------------------------------------------------------------------------------
/app/common/command/test.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/gookit/color"
5 | "github.com/spf13/cobra"
6 | "github.com/we7coreteam/w7-rangine-go/v2/src/console"
7 | )
8 |
9 | type Test struct {
10 | console.Abstract
11 | }
12 |
13 | func (test Test) GetName() string {
14 | return "home:test"
15 | }
16 |
17 | func (test Test) GetDescription() string {
18 | return "test command"
19 | }
20 |
21 | func (self Test) Configure(command *cobra.Command) {
22 | command.Flags().String("name", "test", "test name params")
23 | }
24 |
25 | func (test Test) Handle(cmd *cobra.Command, args []string) {
26 | color.Infoln("home test")
27 | }
28 |
--------------------------------------------------------------------------------
/app/common/http/controller/attach.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/donknap/dpanel/common/service/storage"
7 | "github.com/gin-gonic/gin"
8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller"
9 | "log/slog"
10 | "os"
11 | "path/filepath"
12 | )
13 |
14 | type Attach struct {
15 | controller.Abstract
16 | }
17 |
18 | func (self Attach) Upload(http *gin.Context) {
19 | fileUploader, fileHeader, _ := http.Request.FormFile("file")
20 | if fileHeader == nil {
21 | self.JsonResponseWithError(http, errors.New("请指定上传文件"), 500)
22 | return
23 | }
24 | defer func() {
25 | _ = fileUploader.Close()
26 | }()
27 | file, err := storage.Local{}.CreateTempFile("")
28 | if err != nil {
29 | self.JsonResponseWithError(http, err, 500)
30 | return
31 | }
32 | slog.Debug("upload file", "path", file.Name())
33 | err = http.SaveUploadedFile(fileHeader, file.Name())
34 | if err != nil {
35 | self.JsonResponseWithError(http, err, 500)
36 | return
37 | }
38 | self.JsonResponseWithoutError(http, gin.H{
39 | "path": filepath.Base(file.Name()),
40 | })
41 | return
42 | }
43 |
44 | func (self Attach) Delete(http *gin.Context) {
45 | type ParamsValidate struct {
46 | Path string `form:"path" binding:"required"`
47 | }
48 | params := ParamsValidate{}
49 | if !self.Validate(http, ¶ms) {
50 | return
51 | }
52 | path := storage.Local{}.GetRealPath(params.Path)
53 | fmt.Printf("%v \n", path)
54 | _, err := os.Stat(path)
55 | if err == nil {
56 | os.Remove(path)
57 | }
58 | self.JsonSuccessResponse(http)
59 | return
60 | }
61 |
--------------------------------------------------------------------------------
/app/common/http/controller/event.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/dao"
5 | "github.com/gin-gonic/gin"
6 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
7 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller"
8 | )
9 |
10 | type Event struct {
11 | controller.Abstract
12 | }
13 |
14 | func (self Event) GetList(http *gin.Context) {
15 | type ParamsValidate struct {
16 | Page int `form:"page,default=1" binding:"omitempty,gt=0"`
17 | PageSize int `form:"pageSize" binding:"omitempty"`
18 | Type string `form:"type" binding:"omitempty,oneof=builder config container daemon image network node plugin secret service volume"`
19 | }
20 |
21 | params := ParamsValidate{}
22 | if !self.Validate(http, ¶ms) {
23 | return
24 | }
25 | if params.Page < 1 {
26 | params.Page = 1
27 | }
28 | if params.PageSize < 1 {
29 | params.PageSize = 10
30 | }
31 | query := dao.Event.Order(dao.Event.ID.Desc())
32 | if params.Type != "" {
33 | query = query.Where(dao.Event.Type.Eq(params.Type))
34 | }
35 | list, total, _ := query.FindByPage((params.Page-1)*params.PageSize, params.PageSize)
36 | self.JsonResponseWithoutError(http, gin.H{
37 | "total": total,
38 | "page": params.Page,
39 | "list": list,
40 | })
41 | return
42 | }
43 |
44 | func (self Event) Prune(http *gin.Context) {
45 | if oldRow, _ := dao.Event.Last(); oldRow != nil {
46 | _, _ = dao.Event.Where(dao.Event.ID.Lte(oldRow.ID)).Delete()
47 | db, err := facade.GetDbFactory().Channel("default")
48 | if err != nil {
49 | self.JsonResponseWithError(http, err, 500)
50 | return
51 | }
52 | db.Exec("vacuum")
53 | }
54 | self.JsonSuccessResponse(http)
55 | return
56 | }
57 |
--------------------------------------------------------------------------------
/app/common/http/controller/notice.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/dao"
5 | "github.com/donknap/dpanel/common/entity"
6 | "github.com/donknap/dpanel/common/function"
7 | "github.com/gin-gonic/gin"
8 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
9 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller"
10 | "gorm.io/gorm"
11 | )
12 |
13 | type Notice struct {
14 | controller.Abstract
15 | }
16 |
17 | func (self Notice) Unread(http *gin.Context) {
18 | type ParamsValidate struct {
19 | Action string `form:"action" binding:"required,oneof=new clear init"`
20 | }
21 | params := ParamsValidate{}
22 | if !self.Validate(http, ¶ms) {
23 | return
24 | }
25 | var list []*entity.Notice
26 | var total int64
27 | if params.Action == "init" {
28 | list, total, _ = dao.Notice.Order(dao.Notice.ID.Desc()).FindByPage(0, 5)
29 | }
30 | if function.IsEmptyArray(list) {
31 | list = make([]*entity.Notice, 0)
32 | }
33 | if params.Action == "clear" {
34 | db, err := facade.GetDbFactory().Channel("default")
35 | if err != nil {
36 | self.JsonResponseWithError(http, err, 500)
37 | return
38 | }
39 | db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&entity.Notice{})
40 | }
41 | self.JsonResponseWithoutError(http, gin.H{
42 | "list": list,
43 | "unreadTotal": total,
44 | })
45 | return
46 | }
47 |
48 | func (self Notice) GetList(http *gin.Context) {
49 | type ParamsValidate struct {
50 | Page int `form:"page,default=1" binding:"omitempty,gt=0"`
51 | PageSize int `form:"pageSize" binding:"omitempty"`
52 | Type string `form:"type" binding:"omitempty"`
53 | }
54 |
55 | params := ParamsValidate{}
56 | if !self.Validate(http, ¶ms) {
57 | return
58 | }
59 | if params.Page < 1 {
60 | params.Page = 1
61 | }
62 | if params.PageSize < 1 {
63 | params.PageSize = 10
64 | }
65 | query := dao.Notice.Order(dao.Notice.ID.Desc())
66 | if params.Type != "" {
67 | query = query.Where(dao.Notice.Title.Eq(params.Type))
68 | }
69 | list, total, _ := query.FindByPage((params.Page-1)*params.PageSize, params.PageSize)
70 | self.JsonResponseWithoutError(http, gin.H{
71 | "total": total,
72 | "page": params.Page,
73 | "list": list,
74 | })
75 | return
76 | }
77 |
78 | func (self Notice) Delete(http *gin.Context) {
79 | type ParamsValidate struct {
80 | Id []int32 `form:"id" binding:"required"`
81 | }
82 | params := ParamsValidate{}
83 | if !self.Validate(http, ¶ms) {
84 | return
85 | }
86 | dao.Notice.Where(dao.Notice.ID.In(params.Id...)).Delete()
87 | self.JsonSuccessResponse(http)
88 | return
89 | }
90 |
--------------------------------------------------------------------------------
/app/common/http/middleware/home.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
6 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware"
7 | "time"
8 | )
9 |
10 | type Home struct {
11 | middleware.Abstract
12 | }
13 |
14 | func (home Home) Process(ctx *gin.Context) {
15 | log, _ := facade.GetLoggerFactory().Channel("default")
16 | log.Info("route middleware test, req time: " + time.Now().String())
17 |
18 | ctx.Next()
19 | }
20 |
--------------------------------------------------------------------------------
/app/common/logic/cron-template.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "github.com/donknap/dpanel/common/function"
6 | "github.com/donknap/dpanel/common/service/docker"
7 | "gopkg.in/yaml.v3"
8 | "io/fs"
9 | "os"
10 | "path/filepath"
11 | "strings"
12 | )
13 |
14 | type CronTemplateItem struct {
15 | Name string `json:"name"`
16 | Environment []docker.EnvItem `json:"environment"`
17 | Script string `json:"script"`
18 | Description string `json:"description"`
19 | Tag []string `json:"tag"`
20 | Project string `json:"project"`
21 | }
22 |
23 | type CronTemplate struct {
24 | }
25 |
26 | func (self CronTemplate) Template(dir string) ([]CronTemplateItem, error) {
27 | result := make([]CronTemplateItem, 0)
28 | err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
29 | if strings.HasSuffix(path, "data.yaml") {
30 | content, err := os.ReadFile(path)
31 | if err != nil {
32 | return err
33 | }
34 | yamlData := new(function.YamlGetter)
35 | err = yaml.Unmarshal(content, &yamlData)
36 | if err != nil {
37 | return err
38 | }
39 | item := CronTemplateItem{
40 | Name: yamlData.GetString("task.name"),
41 | Script: yamlData.GetString("task.script"),
42 | Environment: make([]docker.EnvItem, 0),
43 | Description: yamlData.GetString("task.descriptionZh"),
44 | Tag: yamlData.GetStringSlice("task.tag"),
45 | Project: "dpanel",
46 | }
47 | relPath, _ := filepath.Rel(dir, path)
48 | segments := strings.Split(filepath.Clean(relPath), string(filepath.Separator))
49 | if len(segments) == 3 {
50 | item.Project = segments[0]
51 | }
52 | fields := yamlData.GetSliceStringMapString("task.environment")
53 | for index, field := range fields {
54 | envItem := docker.EnvItem{
55 | Name: field["name"],
56 | Label: field["labelZh"],
57 | }
58 | envItem.Rule = Store{}.ParseSettingField(field, func(item *docker.ValueRuleItem) {
59 | if (item.Kind & docker.EnvValueTypeSelect) != 0 {
60 | item.Option = function.PluckArrayWalk(
61 | yamlData.GetSliceStringMapString(fmt.Sprintf("task.environment.%d.values", index)),
62 | func(i map[string]string) (docker.ValueItem, bool) {
63 | return docker.ValueItem{
64 | Name: i["label"],
65 | Value: i["value"],
66 | }, true
67 | },
68 | )
69 | }
70 | })
71 | item.Environment = append(item.Environment, envItem)
72 | }
73 | result = append(result, item)
74 | }
75 | return nil
76 | })
77 | if err != nil {
78 | return nil, err
79 | }
80 | return result, nil
81 | }
82 |
--------------------------------------------------------------------------------
/app/common/logic/docker-env.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "errors"
5 | "github.com/donknap/dpanel/common/accessor"
6 | "github.com/donknap/dpanel/common/entity"
7 | "github.com/donknap/dpanel/common/service/docker"
8 | "golang.org/x/exp/maps"
9 | )
10 |
11 | type DockerEnv struct {
12 | }
13 |
14 | func (self DockerEnv) UpdateEnv(data *docker.Client) {
15 | setting, err := Setting{}.GetValue(SettingGroupSetting, SettingGroupSettingDocker)
16 | if err != nil || setting.Value == nil || setting.Value.Docker == nil {
17 | setting = &entity.Setting{
18 | GroupName: SettingGroupSetting,
19 | Name: SettingGroupSettingDocker,
20 | Value: &accessor.SettingValueOption{
21 | Docker: make(map[string]*docker.Client, 0),
22 | },
23 | }
24 | }
25 | dockerList := map[string]*docker.Client{
26 | data.Name: data,
27 | }
28 | maps.Copy(setting.Value.Docker, dockerList)
29 | _ = Setting{}.Save(setting)
30 | return
31 | }
32 |
33 | func (self DockerEnv) GetEnvByName(name string) (*docker.Client, error) {
34 | dockerEnvSetting, err := Setting{}.GetValue(SettingGroupSetting, SettingGroupSettingDocker)
35 | if err != nil {
36 | return nil, err
37 | }
38 | if dockerEnv, ok := dockerEnvSetting.Value.Docker[name]; ok {
39 | return dockerEnv, nil
40 | } else {
41 | return nil, errors.New("docker env not found")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/common/logic/event.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "github.com/docker/docker/api/types/events"
6 | "github.com/donknap/dpanel/common/dao"
7 | "github.com/donknap/dpanel/common/entity"
8 | "github.com/donknap/dpanel/common/function"
9 | "github.com/donknap/dpanel/common/service/docker"
10 | "log/slog"
11 | "time"
12 | )
13 |
14 | type EventLogic struct {
15 | }
16 |
17 | func (self EventLogic) MonitorLoop() {
18 | slog.Debug("Event Monitor Loop")
19 | messageChan, errorChan := docker.Sdk.Client.Events(docker.Sdk.Ctx, events.ListOptions{})
20 | for {
21 | select {
22 | case <-docker.Sdk.Ctx.Done():
23 | slog.Debug("event", "loop", "exit event loop")
24 | return
25 | case message := <-messageChan:
26 | msg := ""
27 | switch string(message.Type) + "/" + string(message.Action) {
28 | case "image/tag", "image/save", "image/push", "image/pull", "image/load",
29 | "image/import", "image/delete",
30 | "container/destroy", "container/create",
31 | "container/stop", "container/start", "container/restart",
32 | "container/kill", "container/die",
33 | "container/extract-to-dir":
34 | msg += message.Actor.Attributes["name"]
35 | case "container/resize":
36 | msg += fmt.Sprintf("%s: %s-%s", message.Actor.Attributes["name"],
37 | message.Actor.Attributes["width"], message.Actor.Attributes["height"])
38 | case "volume/mount":
39 | msg += fmt.Sprintf("%s, %s:%s, %s", message.Actor.Attributes["container"],
40 | message.Actor.Attributes["driver"], message.Actor.Attributes["destination"], message.Actor.Attributes["read/write"])
41 | case "volume/destroy":
42 | msg += fmt.Sprintf("%s", message.Actor.ID)
43 | case "network/disconnect", "network/connect":
44 | msg += fmt.Sprintf("%s %s", message.Actor.Attributes["name"],
45 | message.Actor.Attributes["type"])
46 | }
47 | if msg != "" {
48 | eventRow := &entity.Event{
49 | Type: string(message.Type),
50 | Action: string(message.Action),
51 | Message: msg,
52 | CreatedAt: time.Unix(message.Time, 0).In(time.Local).Format("2006-01-02 15:04:05"),
53 | }
54 | _ = dao.Event.Create(eventRow)
55 | time.Sleep(time.Second * 1)
56 | }
57 | case err := <-errorChan:
58 | if err != nil {
59 | _ = dao.Event.Create(&entity.Event{
60 | Type: "error",
61 | Message: err.Error(),
62 | CreatedAt: time.Now().Format(function.ShowYmdHis),
63 | })
64 | }
65 | time.Sleep(time.Second)
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/common/logic/notice.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "crypto/tls"
5 | "github.com/donknap/dpanel/common/accessor"
6 | "github.com/jordan-wright/email"
7 | "log/slog"
8 | "net/smtp"
9 | "strconv"
10 | )
11 |
12 | type Notice struct {
13 | }
14 |
15 | func (self Notice) Send(emailServer accessor.EmailServer, toEmail string, subject string, htmlContent string) error {
16 | e := email.NewEmail()
17 | e.From = emailServer.Email
18 | e.Subject = subject
19 | e.To = []string{toEmail}
20 | e.HTML = []byte(htmlContent)
21 | err := e.SendWithTLS(
22 | emailServer.Host+":"+strconv.Itoa(emailServer.Port),
23 | smtp.PlainAuth("", emailServer.Email, emailServer.Code, emailServer.Host),
24 | &tls.Config{
25 | ServerName: emailServer.Host, // 必须与证书匹配的域名
26 | },
27 | )
28 | if err != nil {
29 | slog.Debug("email send", "error", err)
30 | }
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/app/common/logic/stat.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/docker/go-units"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | type Stat struct {
11 | }
12 |
13 | type statItemResult struct {
14 | Cpu float64 `json:"cpu"`
15 | Memory ioItemResult `json:"memory"`
16 | BlockIO ioItemResult `json:"blockIO"`
17 | NetworkIO ioItemResult `json:"networkIO"`
18 | Name string `json:"name"`
19 | Container string `json:"container"`
20 | }
21 |
22 | type ioItemResult struct {
23 | In int64 `json:"in"`
24 | Out int64 `json:"out"`
25 | }
26 |
27 | func (self Stat) GetStat(response string) ([]*statItemResult, error) {
28 | result := make([]*statItemResult, 0)
29 | statJsonItem := struct {
30 | BlockIO string
31 | CPUPerc string
32 | MemPerc string
33 | MemUsage string
34 | NetIO string
35 | Name string
36 | Container string
37 | }{}
38 | if response == "" {
39 | return result, nil
40 | }
41 | for _, line := range strings.Split(response, "\n") {
42 | if line == "" || !strings.Contains(line, "\"Name\":") {
43 | continue
44 | }
45 | // 只取 {} 之间的数据
46 | start := strings.Index(line, "{")
47 | end := strings.LastIndex(line, "}")
48 | if start == -1 || end == -1 {
49 | continue
50 | }
51 | line = line[start : end+1]
52 | err := json.Unmarshal([]byte(line), &statJsonItem)
53 | if err != nil {
54 | return nil, err
55 | }
56 | r := &statItemResult{
57 | Name: statJsonItem.Name,
58 | Container: statJsonItem.Container,
59 | }
60 | cpu, _ := strconv.ParseFloat(strings.TrimSuffix(statJsonItem.CPUPerc, "%"), 64)
61 | // 使用率超过100%时,代表该容器使用超过1核。需要将占用转换成100%之内的占用
62 | // 后端不进行转换,在前端计算在所有核心下的占用
63 | if cpu > 100 {
64 | //cpu = math.Round(cpu/float64(dockerInfo.NCPU)*100) / 100
65 | }
66 | r.Cpu += cpu
67 | if strings.Contains(statJsonItem.MemUsage, "/") {
68 | memory := strings.Split(statJsonItem.MemUsage, "/")
69 | use, _ := units.RAMInBytes(strings.TrimSpace(memory[0]))
70 | limit, _ := units.RAMInBytes(strings.TrimSpace(memory[1]))
71 |
72 | r.Memory.In = use
73 | r.Memory.Out = limit
74 | }
75 | if strings.Contains(statJsonItem.NetIO, "/") {
76 | networkIo := strings.Split(statJsonItem.NetIO, "/")
77 | in, _ := units.RAMInBytes(strings.TrimSpace(networkIo[0]))
78 | out, _ := units.RAMInBytes(strings.TrimSpace(networkIo[1]))
79 |
80 | r.NetworkIO.In = in
81 | r.NetworkIO.Out = out
82 | }
83 | if strings.Contains(statJsonItem.BlockIO, "/") {
84 | blockIo := strings.Split(statJsonItem.BlockIO, "/")
85 | in, _ := units.RAMInBytes(strings.TrimSpace(blockIo[0]))
86 | out, _ := units.RAMInBytes(strings.TrimSpace(blockIo[1]))
87 |
88 | r.BlockIO.In = in
89 | r.BlockIO.Out = out
90 | }
91 | result = append(result, r)
92 | }
93 | return result, nil
94 | }
95 |
--------------------------------------------------------------------------------
/app/ctrl/command/container/backup.go:
--------------------------------------------------------------------------------
1 | package container
2 |
3 | import (
4 | "github.com/donknap/dpanel/app/ctrl/sdk/proxy"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app"
6 | "github.com/donknap/dpanel/app/ctrl/sdk/utils"
7 | "github.com/spf13/cobra"
8 | "github.com/we7coreteam/w7-rangine-go/v2/src/console"
9 | )
10 |
11 | type Backup struct {
12 | console.Abstract
13 | }
14 |
15 | func (self Backup) GetName() string {
16 | return "container:backup"
17 | }
18 |
19 | func (self Backup) GetDescription() string {
20 | return "生成容器快照"
21 | }
22 |
23 | func (self Backup) Configure(command *cobra.Command) {
24 | command.Flags().String("name", "", "容器名称")
25 | command.Flags().Int("enable-image", 0, "是否快照容器镜像")
26 | command.Flags().String("docker-env", "", "指定 docker 环境")
27 | _ = command.MarkFlagRequired("name")
28 | }
29 |
30 | func (self Backup) Handle(cmd *cobra.Command, args []string) {
31 | name, _ := cmd.Flags().GetString("name")
32 | dockerEnv, _ := cmd.Flags().GetString("docker-env")
33 | enableImage, _ := cmd.Flags().GetInt("enable-image")
34 |
35 | proxyClient, err := proxy.NewProxyClient()
36 | if err != nil {
37 | utils.Result{}.Error(err)
38 | return
39 | }
40 | dockerEnvList, err := proxyClient.CommonEnvGetList()
41 | if err != nil {
42 | utils.Result{}.Error(err)
43 | return
44 | }
45 | if dockerEnv != "" && dockerEnv != dockerEnvList.CurrentName {
46 | err = proxyClient.CommonEnvSwitch(dockerEnv)
47 | if err != nil {
48 | utils.Result{}.Error(err)
49 | return
50 | }
51 | defer func() {
52 | _ = proxyClient.CommonEnvSwitch(dockerEnvList.CurrentName)
53 | }()
54 | }
55 | result, err := proxyClient.AppContainerBackupCreate(&app.ContainerBackupOption{
56 | Id: name,
57 | EnableImage: enableImage > 0,
58 | EnableCommitImage: false,
59 | Volume: make([]string, 0),
60 | })
61 | if err != nil {
62 | utils.Result{}.Error(err)
63 | return
64 | }
65 | utils.Result{}.Success(result)
66 | return
67 | }
68 |
--------------------------------------------------------------------------------
/app/ctrl/command/container/upgrade.go:
--------------------------------------------------------------------------------
1 | package container
2 |
3 | import (
4 | "github.com/donknap/dpanel/app/ctrl/sdk/proxy"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app"
6 | "github.com/donknap/dpanel/app/ctrl/sdk/utils"
7 | "github.com/spf13/cobra"
8 | "github.com/we7coreteam/w7-rangine-go/v2/src/console"
9 | )
10 |
11 | type Upgrade struct {
12 | console.Abstract
13 | }
14 |
15 | func (self Upgrade) GetName() string {
16 | return "container:upgrade"
17 | }
18 |
19 | func (self Upgrade) GetDescription() string {
20 | return "检测当前容器更新"
21 | }
22 |
23 | func (self Upgrade) Configure(command *cobra.Command) {
24 | command.Flags().String("name", "", "容器名称")
25 | command.Flags().String("docker-env", "", "指定 docker 环境")
26 | command.Flags().Int("upgrade", 0, "是否升级容器")
27 | _ = command.MarkFlagRequired("name")
28 | }
29 |
30 | func (self Upgrade) Handle(cmd *cobra.Command, args []string) {
31 | name, _ := cmd.Flags().GetString("name")
32 | dockerEnv, _ := cmd.Flags().GetString("docker-env")
33 | isUpgrade, _ := cmd.Flags().GetInt("upgrade")
34 |
35 | proxyClient, err := proxy.NewProxyClient()
36 | if err != nil {
37 | utils.Result{}.Error(err)
38 | return
39 | }
40 | dockerEnvList, err := proxyClient.CommonEnvGetList()
41 | if err != nil {
42 | utils.Result{}.Error(err)
43 | return
44 | }
45 | if dockerEnv != "" && dockerEnv != dockerEnvList.CurrentName {
46 | err = proxyClient.CommonEnvSwitch(dockerEnv)
47 | if err != nil {
48 | utils.Result{}.Error(err)
49 | return
50 | }
51 | defer func() {
52 | _ = proxyClient.CommonEnvSwitch(dockerEnvList.CurrentName)
53 | }()
54 | }
55 | containerInfo, err := proxyClient.AppContainerGetDetail(name)
56 | if err != nil {
57 | utils.Result{}.Error(err)
58 | return
59 | }
60 | result, err := proxyClient.AppImageCheckUpgrade(&app.ImageCheckUpgradeOption{
61 | Tag: containerInfo.Info.Config.Image,
62 | Md5: containerInfo.Info.Image,
63 | CacheTime: 0,
64 | })
65 | if err != nil {
66 | utils.Result{}.Error(err)
67 | return
68 | }
69 | if isUpgrade <= 0 {
70 | utils.Result{}.Success(result)
71 | return
72 | }
73 | _, err = proxyClient.AppImageTagRemote(&app.ImageTagRemoteOption{
74 | Tag: containerInfo.Info.Config.Image,
75 | Type: "pull",
76 | })
77 | if err != nil {
78 | utils.Result{}.Error(err)
79 | return
80 | }
81 | containerUpgradeResult, err := proxyClient.AppContainerUpgrade(&app.ContainerUpgradeOption{
82 | Md5: containerInfo.Info.ID,
83 | EnableBak: true,
84 | })
85 |
86 | if err != nil {
87 | utils.Result{}.Error(err)
88 | return
89 | }
90 |
91 | utils.Result{}.Success(containerUpgradeResult)
92 | return
93 | }
94 |
--------------------------------------------------------------------------------
/app/ctrl/command/store/sync.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "github.com/donknap/dpanel/app/ctrl/sdk/proxy"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/common"
6 | "github.com/donknap/dpanel/app/ctrl/sdk/utils"
7 | "github.com/donknap/dpanel/common/dao"
8 | "github.com/gin-gonic/gin"
9 | "github.com/spf13/cobra"
10 | "github.com/we7coreteam/w7-rangine-go/v2/src/console"
11 | )
12 |
13 | type Sync struct {
14 | console.Abstract
15 | }
16 |
17 | func (self Sync) GetName() string {
18 | return "store:sync"
19 | }
20 |
21 | func (self Sync) GetDescription() string {
22 | return "同步应用商店数据"
23 | }
24 |
25 | func (self Sync) Configure(command *cobra.Command) {
26 | command.Flags().String("name", "", "商店标识")
27 | _ = command.MarkFlagRequired("name")
28 | }
29 |
30 | func (self Sync) Handle(cmd *cobra.Command, args []string) {
31 | name, _ := cmd.Flags().GetString("name")
32 |
33 | store, _ := dao.Store.Where(dao.Store.Name.Eq(name)).First()
34 | if store == nil {
35 | utils.Result{}.Errorf("%s 商店不存在,请先添加", name)
36 | return
37 | }
38 | proxyClient, err := proxy.NewProxyClient()
39 | if err != nil {
40 | utils.Result{}.Error(err)
41 | return
42 | }
43 |
44 | appList, err := proxyClient.CommonStoreSync(&common.StoreSyncOption{
45 | Id: store.ID,
46 | Name: store.Name,
47 | Type: store.Setting.Type,
48 | Url: store.Setting.Url,
49 | })
50 | if err != nil {
51 | utils.Result{}.Error(err)
52 | return
53 | }
54 |
55 | utils.Result{}.Success(gin.H{
56 | "total": len(appList),
57 | })
58 | return
59 | }
60 |
--------------------------------------------------------------------------------
/app/ctrl/command/user/reset.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/donknap/dpanel/app/common/logic"
5 | "github.com/donknap/dpanel/common/accessor"
6 | "github.com/donknap/dpanel/common/dao"
7 | "github.com/donknap/dpanel/common/entity"
8 | "github.com/donknap/dpanel/common/function"
9 | "github.com/gookit/color"
10 | "github.com/spf13/cobra"
11 | "github.com/we7coreteam/w7-rangine-go/v2/src/console"
12 | )
13 |
14 | type Reset struct {
15 | console.Abstract
16 | }
17 |
18 | func (self Reset) GetName() string {
19 | return "user:reset"
20 | }
21 |
22 | func (self Reset) GetDescription() string {
23 | return "重置面板用户名或是密码"
24 | }
25 |
26 | func (self Reset) Configure(command *cobra.Command) {
27 | command.Flags().String("password", "", "重置管理员密码")
28 | command.Flags().String("username", "", "重置管理员用户名")
29 | }
30 |
31 | func (self Reset) Handle(cmd *cobra.Command, args []string) {
32 | founder, _ := dao.Setting.
33 | Where(dao.Setting.GroupName.Eq(logic.SettingGroupUser)).
34 | Where(dao.Setting.Name.Eq(logic.SettingGroupUserFounder)).First()
35 | if founder == nil {
36 | founder = &entity.Setting{
37 | GroupName: logic.SettingGroupUser,
38 | Name: logic.SettingGroupUserFounder,
39 | Value: &accessor.SettingValueOption{
40 | Username: "",
41 | Password: "",
42 | },
43 | }
44 | }
45 | username, err := cmd.Flags().GetString("username")
46 | if err != nil {
47 | color.Errorln("重置失败,", err.Error())
48 | return
49 | }
50 | password, err := cmd.Flags().GetString("password")
51 | if err != nil {
52 | color.Errorln("重置失败,", err.Error())
53 | return
54 | }
55 | if username == "" && password == "" {
56 | username = "admin"
57 | password = function.GetRandomString(10)
58 | }
59 |
60 | if username != "" && password == "" {
61 | color.Errorln("重置用户名时必须指定密码")
62 | return
63 | }
64 |
65 | if username != "" {
66 | founder.Value.Username = username
67 | }
68 |
69 | founder.Value.Password = logic.User{}.GetMd5Password(password, founder.Value.Username)
70 |
71 | err = dao.Setting.Save(founder)
72 | if err != nil {
73 | color.Errorln("重置失败,", err.Error())
74 | return
75 | }
76 | color.Println("用户名: ", username)
77 | color.Println("密 码: ", password)
78 | color.Successln("用户名或是密码重置成功")
79 | }
80 |
--------------------------------------------------------------------------------
/app/ctrl/provider.go:
--------------------------------------------------------------------------------
1 | package ctrl
2 |
3 | import (
4 | "github.com/donknap/dpanel/app/ctrl/command/compose"
5 | "github.com/donknap/dpanel/app/ctrl/command/container"
6 | "github.com/donknap/dpanel/app/ctrl/command/store"
7 | "github.com/donknap/dpanel/app/ctrl/command/user"
8 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/console"
9 | )
10 |
11 | type Provider struct {
12 | }
13 |
14 | func (provider *Provider) Register(console console.Console) {
15 | console.RegisterCommand(new(user.Reset))
16 | console.RegisterCommand(new(store.Sync))
17 |
18 | // 容器相关
19 | console.RegisterCommand(new(container.Upgrade))
20 | console.RegisterCommand(new(container.Backup))
21 |
22 | console.RegisterCommand(new(compose.Deploy))
23 | }
24 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/proxy/app-compose.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app"
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func (self *Client) AppComposeDeploy(params *app.ComposeDeployOption) error {
10 | _, err := self.Post("/api/app/compose/container-deploy", params)
11 | if err != nil {
12 | return err
13 | }
14 | return nil
15 | }
16 |
17 | func (self *Client) AppComposeTask(name string) (result app.ComposeDetailResult, err error) {
18 | data, err := self.Post("/api/app/compose/get-task", gin.H{
19 | "id": name,
20 | })
21 | if err != nil {
22 | return result, err
23 | }
24 | err = json.NewDecoder(data).Decode(&result)
25 | return result, err
26 | }
27 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/proxy/app-container.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app"
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func (self *Client) AppContainerGetDetail(containerName string) (result app.ContainerDetailResult, err error) {
10 | data, err := self.Post("/api/app/container/get-detail", gin.H{
11 | "md5": containerName,
12 | })
13 | if err != nil {
14 | return result, err
15 | }
16 | err = json.NewDecoder(data).Decode(&result)
17 | return result, err
18 | }
19 |
20 | func (self *Client) AppContainerUpgrade(params *app.ContainerUpgradeOption) (result app.ContainerUpgradeResult, err error) {
21 | data, err := self.Post("/api/app/container/upgrade", params)
22 | if err != nil {
23 | return result, err
24 | }
25 | err = json.NewDecoder(data).Decode(&result)
26 | return result, err
27 | }
28 |
29 | func (self *Client) AppContainerBackupCreate(params *app.ContainerBackupOption) (result app.ContainerBackupResult, err error) {
30 | data, err := self.Post("/api/app/container-backup/create", params)
31 | if err != nil {
32 | return result, err
33 | }
34 | err = json.NewDecoder(data).Decode(&result)
35 | return result, err
36 | }
37 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/proxy/app-image.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app"
6 | )
7 |
8 | func (self *Client) AppImageCheckUpgrade(params *app.ImageCheckUpgradeOption) (result app.ImageCheckUpgradeResult, err error) {
9 | data, err := self.Post("/api/app/image/check-upgrade", params)
10 | if err != nil {
11 | return result, err
12 | }
13 | err = json.NewDecoder(data).Decode(&result)
14 | return result, err
15 | }
16 |
17 | func (self *Client) AppImageTagRemote(params *app.ImageTagRemoteOption) (result app.ImageTagRemoteResult, err error) {
18 | data, err := self.Post("/api/app/image/tag-remote", params)
19 | if err != nil {
20 | return result, err
21 | }
22 | err = json.NewDecoder(data).Decode(&result)
23 | return result, err
24 | }
25 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/proxy/common-env.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/common"
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func (self *Client) CommonEnvGetList() (result common.EnvListResult, err error) {
10 | data, err := self.Post("/api/common/env/get-list", nil)
11 | if err != nil {
12 | return result, err
13 | }
14 | err = json.NewDecoder(data).Decode(&result)
15 | return result, err
16 | }
17 |
18 | func (self *Client) CommonEnvSwitch(name string) error {
19 | _, err := self.Post("/api/common/env/switch", gin.H{
20 | "name": name,
21 | })
22 | if err != nil {
23 | return err
24 | }
25 | return nil
26 | }
27 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/proxy/common-store.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/common"
6 | "github.com/donknap/dpanel/common/accessor"
7 | )
8 |
9 | func (self *Client) CommonStoreSync(option *common.StoreSyncOption) (list []accessor.StoreAppItem, err error) {
10 | data, err := self.Post("/api/common/store/sync", option)
11 | if err != nil {
12 | return list, err
13 | }
14 | result := struct {
15 | List []accessor.StoreAppItem `json:"list"`
16 | }{
17 | List: make([]accessor.StoreAppItem, 0),
18 | }
19 | err = json.NewDecoder(data).Decode(&result)
20 | return result.List, err
21 | }
22 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/types/app/compose.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/entity"
5 | "github.com/donknap/dpanel/common/service/compose"
6 | "github.com/donknap/dpanel/common/service/docker"
7 | )
8 |
9 | type ComposeDeployOption struct {
10 | Id string `json:"id" binding:"required"`
11 | Environment []docker.EnvItem `json:"environment"`
12 | DeployServiceName []string `json:"deployServiceName"`
13 | CreatePath bool `json:"createPath"`
14 | RemoveOrphans bool `json:"removeOrphans"`
15 | }
16 |
17 | type ComposeDetailResult struct {
18 | Detail *entity.Compose `json:"detail"`
19 | Yaml [2]string `json:"yaml"`
20 | ContainerList []*compose.ContainerResult `json:"containerList"`
21 | Project struct {
22 | Name string `json:"name"`
23 | Services map[string]struct {
24 | Image string `json:"image"`
25 | } `json:"services"`
26 | } `json:"project"`
27 | }
28 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/types/app/container.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "github.com/docker/docker/api/types/container"
5 | "github.com/donknap/dpanel/common/accessor"
6 | )
7 |
8 | type ContainerDetailResult struct {
9 | Info container.InspectResponse `json:"info"`
10 | Ignore accessor.ContainerCheckIgnoreUpgrade `json:"ignore"`
11 | }
12 |
13 | type ContainerUpgradeOption struct {
14 | Md5 string `json:"md5" binding:"required"`
15 | ImageTag string `json:"imageTag"`
16 | EnableBak bool `json:"enableBak"`
17 | }
18 |
19 | type ContainerUpgradeResult struct {
20 | ContainerId string `json:"containerId"`
21 | }
22 |
23 | type ContainerBackupOption struct {
24 | Id string `json:"id"`
25 | EnableImage bool `json:"enableImage"`
26 | EnableCommitImage bool `json:"enableCommitImage"`
27 | Volume []string `json:"volume"`
28 | }
29 |
30 | type ContainerBackupResult struct {
31 | Path string `json:"path"`
32 | }
33 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/types/app/image.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | type ImageCheckUpgradeOption struct {
4 | Tag string `json:"tag"` // 要检测的镜像 tag
5 | Md5 string `json:"md5"` // 镜像 id
6 | CacheTime int `json:"cacheTime"`
7 | }
8 |
9 | type ImageCheckUpgradeResult struct {
10 | Upgrade bool `json:"upgrade"`
11 | Digest string `json:"digest"`
12 | DigestLocal []string `json:"digestLocal"`
13 | }
14 |
15 | type ImageTagRemoteOption struct {
16 | Tag string `json:"tag"`
17 | Type string `json:"type"` // pull or push
18 | Platform string `json:"platform"`
19 | }
20 |
21 | type ImageTagRemoteResult struct {
22 | Tag string `json:"tag"`
23 | ProxyUrl string `json:"proxyUrl"`
24 | }
25 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/types/common/env.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import "github.com/donknap/dpanel/common/service/docker"
4 |
5 | type EnvListResult struct {
6 | CurrentName string `json:"currentName"`
7 | List []*docker.Client `json:"list"`
8 | }
9 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/types/common/store.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | type StoreSyncOption struct {
4 | Id int32 `json:"id"`
5 | Name string `json:"name"`
6 | Type string `json:"type"`
7 | Url string `json:"url"`
8 | }
9 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/types/proxy.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | type Message struct {
4 | Code int `json:"code"`
5 | Error string `json:"error"`
6 | Data interface{} `json:"data"`
7 | }
8 |
--------------------------------------------------------------------------------
/app/ctrl/sdk/utils/result.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | type Result struct{}
10 |
11 | func (self Result) Error(err error) {
12 | str, err := json.Marshal(gin.H{
13 | "error": err.Error(),
14 | "code": 500,
15 | })
16 | if err != nil {
17 | self.Error(err)
18 | return
19 | }
20 |
21 | fmt.Println(string(str))
22 | return
23 | }
24 |
25 | func (self Result) Errorf(format string, a ...any) {
26 | self.Error(fmt.Errorf(format, a))
27 | return
28 | }
29 |
30 | func (self Result) Success(data interface{}) {
31 | str, err := json.Marshal(data)
32 | if err != nil {
33 | self.Error(err)
34 | return
35 | }
36 | fmt.Println(string(str))
37 | }
38 |
--------------------------------------------------------------------------------
/asset/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Rangine Framework
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/asset/nginx/vhost.tpl:
--------------------------------------------------------------------------------
1 | map $scheme $hsts_header {
2 | https "max-age=63072000; preload";
3 | }
4 |
5 | upstream {{.TargetName}} {
6 | server {{.ServerAddress}}:{{.Port}};
7 | }
8 |
9 | {{if .EnableSSL}}
10 | server {
11 | listen 80;
12 | server_name {{.ServerName}} {{range $index, $value := .ServerNameAlias}}{{$value}} {{end}};
13 |
14 | location / {
15 | return 301 https://$host$request_uri;
16 | }
17 | }
18 | {{end}}
19 |
20 | server {
21 | set $forward_scheme {{.ServerProtocol}};
22 | {{if .EnableSSL}}
23 | listen 443 ssl;
24 | {{else}}
25 | listen 80;
26 | {{end}}
27 |
28 | server_name {{.ServerName}} {{range $index, $value := .ServerNameAlias}}{{$value}} {{end}};
29 |
30 | {{if .EnableSSL}}
31 | ssl_certificate {{.SslCrt}};
32 | ssl_certificate_key {{.SslKey}};
33 | ssl_session_cache shared:SSL:1m;
34 | ssl_session_timeout 5m;
35 | ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
36 | ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
37 | ssl_prefer_server_ciphers on;
38 | {{end}}
39 |
40 | {{if .EnableAssetCache}}
41 | # Asset Caching
42 | include /etc/nginx/conf.d/include/assets.conf;
43 | {{end}}
44 | {{if .EnableBlockCommonExploits}}
45 | # Block Exploits
46 | include /etc/nginx/conf.d/include/block-exploits.conf;
47 | {{end}}
48 |
49 | {{if .EnableWs}}
50 | proxy_set_header Upgrade $http_upgrade;
51 | proxy_set_header Connection $http_connection;
52 | proxy_http_version 1.1;
53 | {{end}}
54 |
55 | {{.ExtraNginx}}
56 |
57 | location / {
58 | {{if .EnableWs}}
59 | proxy_set_header Upgrade $http_upgrade;
60 | proxy_set_header Connection $http_connection;
61 | proxy_http_version 1.1;
62 | {{end}}
63 |
64 | add_header X-Served-By $host;
65 |
66 | proxy_set_header Host $host;
67 | proxy_set_header X-Forwarded-Scheme $scheme;
68 | proxy_set_header X-Forwarded-Proto $scheme;
69 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
70 | proxy_set_header X-Real-IP $remote_addr;
71 | proxy_pass $forward_scheme://{{.TargetName}}$request_uri;
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/asset/plugin/backup/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | backup:
3 | image: dpanel/explorer
4 | container_name: dpanel-plugin-backup
5 | restart: no
6 | labels:
7 | - "com.dpanel.container.title=dpanel 备份助手"
8 | - "com.dpanel.container.auto_remove=true"
9 | command:
10 | #{{- range $key, $val := .backup.Command }}
11 | - {{ $val | unescaped }}
12 | #{{- end}}
13 | #{{if .backup.Volumes}}
14 | volumes:
15 | #{{- range $key, $val := .backup.Volumes }}
16 | - {{ $val }}
17 | #{{- end}}
18 | #{{- end}}
19 | x-dpanel-service:
20 | image_tar:
21 | amd64: asset/plugin/explorer/image-amd64.tar
22 | arm64: asset/plugin/explorer/image-arm64.tar
23 | arm: asset/plugin/explorer/image-arm.tar
24 | auto_remove: true
25 | external:
26 | volumes_from:
27 | #{{- range $key, $val := .backup.External.VolumesFrom}}
28 | - {{ $val }}
29 | #{{- end}}
30 | volumes:
31 | #{{- range $key, $val := .backup.External.Volumes}}
32 | - {{ $val }}
33 | #{{- end}}
34 |
--------------------------------------------------------------------------------
/asset/plugin/explorer/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | explorer:
3 | image: dpanel/explorer
4 | container_name: dpanel-plugin-explorer
5 | restart: always
6 | privileged: false
7 | pid: host
8 | labels:
9 | - "com.dpanel.container.title=dpanel 文件管理助手"
10 | - "com.dpanel.container.auto_remove=true"
11 | x-dpanel-service:
12 | image_tar:
13 | amd64: asset/plugin/explorer/image-amd64.tar
14 | arm64: asset/plugin/explorer/image-arm64.tar
15 | arm: asset/plugin/explorer/image-arm.tar
--------------------------------------------------------------------------------
/asset/plugin/explorer/image-amd64.tar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/plugin/explorer/image-amd64.tar
--------------------------------------------------------------------------------
/asset/plugin/explorer/image-arm.tar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/plugin/explorer/image-arm.tar
--------------------------------------------------------------------------------
/asset/plugin/explorer/image-arm64.tar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/plugin/explorer/image-arm64.tar
--------------------------------------------------------------------------------
/asset/plugin/webshell/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | webshell:
3 | image: dpanel/explorer
4 | container_name: {{.webshell.ContainerName}}
5 | restart: no
6 | privileged: true
7 | pid: host
8 | labels:
9 | - "com.dpanel.container.title=dpanel webshell 助手"
10 | - "com.dpanel.container.auto_remove=true"
11 | command:
12 | - /bin/sh
13 | - "-c"
14 | - "nsenter --target 1 --net --mount"
15 | x-dpanel-service:
16 | image_tar:
17 | amd64: asset/plugin/explorer/image-amd64.tar
18 | arm64: asset/plugin/explorer/image-arm64.tar
19 | arm: asset/plugin/explorer/image-arm.tar
20 |
--------------------------------------------------------------------------------
/asset/static/6390.e3bcb4e0.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6390],{96390:function(e,n,t){t.r(n),t.d(n,{default:function(){return s}});var o=t(67294),u=t(82346),E=t(85893);function s(){var O=(0,u.useOutletContext)();return(0,E.jsx)(u.Outlet,{context:O})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/8171.f0ace9c2.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8171],{82885:function(s,n,c){c.r(n),c.d(n,{rpmChanges:function(){return t},rpmSpec:function(){return g}});var o=/^-+$/,a=/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /,i=/^[\w+.-]+@[\w.-]+/;const t={name:"rpmchanges",token:function(r){return r.sol()&&(r.match(o)||r.match(a))?"tag":r.match(i)?"string":(r.next(),null)}};var l=/^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/,u=/^[a-zA-Z0-9()]+:/,p=/^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/,f=/^%(ifnarch|ifarch|if)/,h=/^%(else|endif)/,d=/^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/;const g={name:"rpmspec",startState:function(){return{controlFlow:!1,macroParameters:!1,section:!1}},token:function(r,e){var m=r.peek();if(m=="#")return r.skipToEnd(),"comment";if(r.sol()){if(r.match(u))return"header";if(r.match(p))return"atom"}if(r.match(/^\$\w+/)||r.match(/^\$\{\w+\}/))return"def";if(r.match(h))return"keyword";if(r.match(f))return e.controlFlow=!0,"keyword";if(e.controlFlow){if(r.match(d))return"operator";if(r.match(/^(\d+)/))return"number";r.eol()&&(e.controlFlow=!1)}if(r.match(l))return r.eol()&&(e.controlFlow=!1),"number";if(r.match(/^%[\w]+/))return r.match("(")&&(e.macroParameters=!0),"keyword";if(e.macroParameters){if(r.match(/^\d+/))return"number";if(r.match(")"))return e.macroParameters=!1,"keyword"}return r.match(/^%\{\??[\w \-\:\!]+\}/)?(r.eol()&&(e.controlFlow=!1),"def"):(r.next(),null)}}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/dpanel.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/static/dpanel.ico
--------------------------------------------------------------------------------
/asset/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/asset/static/logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/static/logo-small.png
--------------------------------------------------------------------------------
/asset/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/static/logo.png
--------------------------------------------------------------------------------
/asset/static/p__app__cron__index.d68c764f.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[306],{26910:function(O,t,n){n.r(t),n.d(t,{default:function(){return s}});var u=n(82346),f=n(67294),E=n(85893);function s(){return(0,E.jsx)(u.Outlet,{})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/p__app__domain__index.8bcd0540.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4388],{4037:function(D,t,n){n.r(t),n.d(t,{default:function(){return s}});var u=n(82346),O=n(67294),E=n(85893);function s(){return(0,E.jsx)(u.Outlet,{})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/p__app__index.36c4b625.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8115],{35880:function(u,n,e){var r=e(67294),t=(0,r.createContext)({});n.Z=t},54823:function(u,n,e){e.r(n),e.d(n,{default:function(){return R}});var r=e(82346),t=e(67294),f=e(35880),a=e(85893);function R(){var s=(0,t.useRef)(),_=(0,t.useRef)(),v=(0,t.useRef)(),E=(0,t.useRef)(),c=(0,t.useRef)();return(0,a.jsx)(a.Fragment,{children:(0,a.jsx)(f.Z.Provider,{value:{createFormRef:s,volumeListRef:_,domainRef:v,createEnvRef:c,createLinkRef:E},children:(0,a.jsx)(r.Outlet,{})})})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/p__compose__index.17135f09.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6889],{96781:function(_,t,e){var a=e(67294),n=(0,a.createContext)({});t.Z=n},24943:function(_,t,e){e.r(t),e.d(t,{default:function(){return E}});var a=e(82346),n=e(67294),r=e(96781),s=e(85893);function E(){var f=(0,n.useRef)(),o=(0,a.useAccess)(),c=(0,a.useNavigate)(),u=(0,a.useLocation)();return(0,n.useEffect)(function(){u.pathname=="/compose"&&(!o.canSeeFounder&&!o.canSeeManage&&o.canSeeComposeStore?c("/compose/appstore"):c("/compose/list"))},[u.pathname]),(0,s.jsx)(r.Z.Provider,{value:{listTableRef:f},children:(0,s.jsx)(a.Outlet,{})})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/p__console__host.481d979d.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6856],{49610:function(C,u,e){var d=e(5574),l=e.n(d),a=e(67294),O=e(52626),f=e(78267),D=e.n(f),M=e(12323),g=e(87662),m=e(4244),W=e.n(m),T=e(24078),h=e(85893),t,o,n,i,U=(0,a.forwardRef)(function(B,I){var y,L=(0,a.useState)(),b=l()(L,2),S=b[0],j=b[1],K=(0,a.useState)(""),R=l()(K,2),v=R[0],x=R[1];(0,a.useImperativeHandle)(I,function(){return{start:function(c,_){P(),o.fit(),_.width=t.cols,_.height=t.rows;var A=_?Object.keys(_).filter(function(r){return _&&_[r]}).map(function(r){if(_&&_[r])return"".concat(r,"=").concat(_[r])}):[];n=new WebSocket((0,O.R)(c+"?"+A.join("&"))),n.addEventListener("message",function(r){var E=JSON.parse(r.data),p=E.type;if(p==c&&(t.write(E.data),E.data.indexOf("exec failed")!=-1)){P();return}}),i=t.onData(function(r){var E={content:{command:r},type:c};n&&n.send(JSON.stringify(E))}),x(c),j(_)},close:function(){P()},fit:function(){return o.fit(),{width:t.cols,height:t.rows}},run:function(c){S&&c&&c.forEach(function(_){var A={content:{command:_+`
2 | `},type:v};n&&n.send(JSON.stringify(A))})}}}),(0,a.useEffect)(function(){return t=new m.Terminal(M.X),o=new f.FitAddon,t.loadAddon(o),t.open(document.getElementById("terminal-container-console")),o.fit(),(0,g.pe)({groupName:"setting",name:"themeConsoleConfig"}).then(function(s){s.data&&s.data.fontSize&&Object.assign(t.options,s.data)}),function(){P()}},[]),(0,a.useEffect)(function(){window.addEventListener("resize",function(){o.fit();var s={size:{width:t.cols,height:t.rows},type:v};v&&n&&n.send(JSON.stringify(s))})},[v]);function P(){i&&(t.reset(),o.fit(),t.focus(),i.dispose()),n&&(n.close(),n=null)}return(0,h.jsx)("div",{style:{height:(y=B.height)!==null&&y!==void 0?y:"100vh"},id:"terminal-container-console"})});u.Z=U},12323:function(C,u,e){e.d(u,{X:function(){return d},n:function(){return l}});var d={convertEol:!0,fontFamily:'Menlo, Monaco, "Courier New", monospace',fontWeight:400,fontSize:16,lineHeight:1,cursorStyle:"block",cursorInactiveStyle:"outline",cursorBlink:!0,letterSpacing:0,theme:{foreground:"#bfbfbf",cursor:"gray",selectionForeground:"#ffffff"}},l=["black","red","green","yellow","blue","magenta","cyan","white","brightBlack","brightRed","brightGreen","brightYellow","brightBlue","brightMagenta","brightCyan","brightWhite"]},67359:function(C,u,e){e.r(u),e.d(u,{default:function(){return g}});var d=e(5574),l=e.n(d),a=e(49610),O=e(87662),f=e(82346),D=e(67294),M=e(85893);function g(){var m=(0,f.useParams)(),W=(0,f.useSearchParams)(),T=l()(W,2),h=T[0],t=T[1],o=(0,D.useRef)();return(0,D.useEffect)(function(){m.id&&(0,O.K3)({name:m.id}).then(function(n){var i;window.document.title="".concat(n.data.name," - console"),(i=o.current)===null||i===void 0||i.start("/console/host/".concat(n.data.name),{cmd:h.get("cmd"),workDir:h.get("workDir")})}),document.body.style.overflow="hidden"},[]),(0,M.jsx)(a.Z,{ref:o})}}}]);
3 |
--------------------------------------------------------------------------------
/asset/static/p__docker__index.3b1c9bfc.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9290],{64464:function(f,a,e){e.r(a),e.d(a,{default:function(){return r}});var n=e(82346),o=e(67294),s=e(85893);function r(){var t=(0,n.useAccess)(),c=(0,n.useNavigate)(),u=(0,n.useLocation)();return(0,o.useEffect)(function(){u.pathname=="/docker"&&(t.canSeeDockerVolume&&c("/docker/volume"),t.canSeeDockerNetwork&&c("/docker/network"))},[u.pathname]),(0,s.jsx)(n.Outlet,{})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/p__image__index.e0ed1996.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1764],{15888:function(f,n,u){u.r(n),u.d(n,{default:function(){return a}});var t=u(82346),s=u(85893);function a(){return(0,s.jsx)(t.Outlet,{})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/p__system__index.e144e227.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5071],{44775:function(f,o,a){a.r(o),a.d(o,{default:function(){return c}});var t=a(82346),m=a(67294),i=a(85893);function c(){var s=(0,t.useAccess)(),e=(0,t.useNavigate)(),n=(0,t.useLocation)();return(0,m.useEffect)(function(){(n.pathname=="/"||n.pathname=="/home")&&(s.canSeeEnableHomeTag?e("/home/tag"):s.canSeeHome?e("/home/overview"):s.canSeeComposeStore?e("/compose/appstore"):s.canSeeComposeList?e("/compose/list"):s.canSeeContainerList?e("/app/list"):e("/home/overview")),n.pathname=="/system"&&(s.canSeeSystemBasic?e("/system/basic"):s.canSeeSystemEnv?e("/system/env"):s.canSeeSystemAppstore?e("/system/appstore"):e("/"))},[n.pathname]),(0,i.jsx)(t.Outlet,{})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/p__user__oauth__callback.92e13b57.async.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2822],{76951:function(f,u,e){e.r(u),e.d(u,{default:function(){return P}});var l=e(5574),d=e.n(l),i=e(68602),s=e(82346),E=e(22181),m=e(28036),c=e(67294),_=e(85893);function P(){var B=(0,s.useSearchParams)(),r=d()(B,2),h=r[0],O=r[1],D=(0,s.useParams)(),a=(0,s.useModel)("@@initialState"),v=a.initialState,A=a.loading,T=a.error,M=a.refresh,C=a.setInitialState;return(0,c.useEffect)(function(){var t=D.name,n=h.get("code");!t||!n||(0,i.X6)({name:t,code:n}).then(function(o){if(o.data.accessToken){localStorage.setItem("token",o.data.accessToken),M(),window.location.href="/dpanel/ui";return}else return!1})},[]),(0,_.jsx)(E.ZP,{title:"\u200B\u200B\u60A8\u6B63\u5728\u6388\u6743\u5BB9\u5668\u7BA1\u7406\u5E94\u7528\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u200B\u200B",extra:(0,_.jsx)(m.ZP,{type:"primary",loading:!0,children:"\u6B63\u5728\u767B\u5F55\u4E2D..."},"console")})}}}]);
2 |
--------------------------------------------------------------------------------
/asset/static/t__plugin-layout__Layout.5012e1ab.chunk.css:
--------------------------------------------------------------------------------
1 | @media screen and (max-width: 480px){.umi-plugin-layout-container{width:100%!important}.umi-plugin-layout-container>*{border-radius:0!important}}.umi-plugin-layout-menu .anticon{margin-right:8px}.umi-plugin-layout-menu .ant-dropdown-menu-item{min-width:160px}.umi-plugin-layout-right{display:flex!important;float:right;height:100%;margin-left:auto;overflow:hidden}.umi-plugin-layout-right .umi-plugin-layout-action{display:flex;align-items:center;height:100%;padding:0 12px;cursor:pointer;transition:all .3s}.umi-plugin-layout-right .umi-plugin-layout-action>i{color:#ffffffd9;vertical-align:middle}.umi-plugin-layout-right .umi-plugin-layout-action:hover,.umi-plugin-layout-right .umi-plugin-layout-action.opened{background:#00000006}.umi-plugin-layout-right .umi-plugin-layout-search{padding:0 12px}.umi-plugin-layout-right .umi-plugin-layout-search:hover{background:transparent}.umi-plugin-layout-name{margin-left:8px}.umi-plugin-layout-name.umi-plugin-layout-hide-avatar-img{margin-left:0}
2 |
--------------------------------------------------------------------------------
/asset/static/umi.277b1e59.css:
--------------------------------------------------------------------------------
1 | html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{text-decoration:underline;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}
2 |
--------------------------------------------------------------------------------
/common/accessor/backup_setting_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | type BackupSettingOption struct {
4 | BackupTargetType string `json:"backupTargetType"` // 兼容旧的数据,将来统一都是 snapshot 类型
5 | BackupTar string `json:"backupTar,omitempty"` // tar 包的位置
6 | BackupPath string `json:"backupPath,omitempty"` // 废弃,快照默认放到 /dpanel/backup 目录下,不支持保存至主机
7 | VolumePathList []string `json:"volumePathList"`
8 | Size int64 `json:"size"`
9 | Error string `json:"error,omitempty"`
10 | Status int `json:"status,omitempty"`
11 | }
12 |
--------------------------------------------------------------------------------
/common/accessor/compose_setting_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/service/docker"
5 | "github.com/donknap/dpanel/common/service/storage"
6 | "os"
7 | "path/filepath"
8 | )
9 |
10 | const (
11 | ComposeTypeText = "text"
12 | ComposeTypeRemoteUrl = "remoteUrl"
13 | ComposeTypeStoragePath = "storagePath"
14 | ComposeTypeOutPath = "outPath"
15 | ComposeTypeDangling = "dangling"
16 | ComposeTypeStore = "store"
17 | ComposeStatusWaiting = "waiting"
18 | ComposeStatusDeploying = "deploying"
19 | ComposeStatusError = "error"
20 | )
21 |
22 | type ComposeSettingOption struct {
23 | Status string `json:"status,omitempty"`
24 | Type string `json:"type"`
25 | Uri []string `json:"uri,omitempty"`
26 | RemoteUrl string `json:"remoteUrl,omitempty"`
27 | Store string `json:"store,omitempty"`
28 | Environment []docker.EnvItem `json:"environment,omitempty"`
29 | DockerEnvName string `json:"dockerEnvName,omitempty"`
30 | DeployServiceName []string `json:"deployServiceName,omitempty"`
31 | CreatedAt string `json:"createdAt,omitempty"`
32 | UpdatedAt string `json:"updatedAt,omitempty"`
33 | Message string `json:"message,omitempty"`
34 | }
35 |
36 | func (self ComposeSettingOption) GetUriFilePath() string {
37 | if self.Type == ComposeTypeOutPath {
38 | return self.Uri[0]
39 | }
40 | return filepath.Join(self.GetWorkingDir(), self.Uri[0])
41 | }
42 |
43 | func (self ComposeSettingOption) GetWorkingDir() string {
44 | if self.DockerEnvName == docker.DefaultClientName {
45 | return storage.Local{}.GetComposePath()
46 | } else {
47 | return filepath.Join(filepath.Dir(storage.Local{}.GetComposePath()), "compose-"+self.DockerEnvName)
48 | }
49 | }
50 |
51 | func (self ComposeSettingOption) GetYaml() ([2]string, error) {
52 | yaml := [2]string{
53 | "", "",
54 | }
55 | for i, uri := range self.Uri {
56 | yamlFilePath := ""
57 | if self.Type == ComposeTypeOutPath {
58 | // 外部路径分两种,一种是原目录挂载,二是将Yaml文件放置到存储目录中
59 | if filepath.IsAbs(uri) {
60 | yamlFilePath = uri
61 | } else {
62 | yamlFilePath = filepath.Join(self.GetWorkingDir(), uri)
63 | }
64 | } else {
65 | yamlFilePath = filepath.Join(self.GetWorkingDir(), uri)
66 | }
67 | content, err := os.ReadFile(yamlFilePath)
68 | if err == nil {
69 | yaml[i] = string(content)
70 | }
71 | }
72 | return yaml, nil
73 | }
74 |
--------------------------------------------------------------------------------
/common/accessor/cron_log_value_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import "time"
4 |
5 | type CronLogValueOption struct {
6 | Error string `json:"error,omitempty"`
7 | Message string `json:"message,omitempty"`
8 | RunTime time.Time `json:"runTime,omitempty"`
9 | UseTime float64 `json:"useTime,omitempty"`
10 | }
11 |
--------------------------------------------------------------------------------
/common/accessor/cron_setting_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import (
4 | "fmt"
5 | "github.com/donknap/dpanel/common/service/docker"
6 | "github.com/robfig/cron/v3"
7 | "time"
8 | )
9 |
10 | const (
11 | CronUnitPreWeek = "preWeek"
12 | CronUnitPreMonth = "preMonth"
13 | CronUnitPreDay = "preDay"
14 | CronUnitPreHour = "preHour"
15 | CronUnitPreAtDay = "preAtDay"
16 | CronUnitPreAtHour = "preAtHour"
17 | CronUnitPreAtMinute = "preAtMinute"
18 | CronUnitPreAtSecond = "preAtSecond"
19 | CronUnitCode = "code"
20 | )
21 |
22 | type CronSettingExpression struct {
23 | Unit string `json:"unit"`
24 | Code string `json:"code,omitempty"`
25 | Seconds string `json:"seconds,omitempty"`
26 | Minutes string `json:"minutes,omitempty"`
27 | Hours string `json:"hours,omitempty"`
28 | DayOfMonth string `json:"dayOfMonth,omitempty"`
29 | Month string `json:"month,omitempty"`
30 | DayOfWeek string `json:"dayOfWeek,omitempty"`
31 | }
32 |
33 | func (self CronSettingExpression) ToString() string {
34 | switch self.Unit {
35 | case CronUnitPreWeek:
36 | return fmt.Sprintf("0 %s %s * * %s", self.Minutes, self.Hours, self.DayOfWeek)
37 | case CronUnitPreMonth:
38 | return fmt.Sprintf("0 %s %s %s * *", self.Minutes, self.Hours, self.DayOfMonth)
39 | case CronUnitPreDay:
40 | return fmt.Sprintf("0 %s %s * * *", self.Minutes, self.Hours)
41 | case CronUnitPreHour:
42 | return fmt.Sprintf("0 %s * * * *", self.Minutes)
43 | case CronUnitPreAtDay:
44 | return fmt.Sprintf("0 %s %s */%s * *", self.Minutes, self.Hours, self.DayOfMonth)
45 | case CronUnitPreAtHour:
46 | return fmt.Sprintf("0 %s 0-23/%s * * *", self.Minutes, self.Hours)
47 | case CronUnitPreAtMinute:
48 | return fmt.Sprintf("0 */%s * * * *", self.Minutes)
49 | case CronUnitPreAtSecond:
50 | return fmt.Sprintf("*/%s * * * * *", self.Seconds)
51 | case CronUnitCode:
52 | return self.Code
53 | }
54 | return "0 0 0 * * *"
55 | }
56 |
57 | type CronSettingOption struct {
58 | NextRunTime []time.Time `json:"nextRunTime,omitempty"`
59 | Expression []CronSettingExpression `json:"expression,omitempty"`
60 | ContainerName string `json:"containerName,omitempty"`
61 | Script string `json:"script,omitempty"`
62 | JobIds []cron.EntryID `json:"jobIds,omitempty"`
63 | Environment []docker.EnvItem `json:"environment,omitempty"`
64 | EnableRunBlock bool `json:"enableRunBlock,omitempty"`
65 | KeepLogTotal int `json:"keepLogTotal,omitempty"`
66 | Disable bool `json:"disable,omitempty"`
67 | DockerEnvName string `json:"dockerEnvName,omitempty"`
68 | }
69 |
--------------------------------------------------------------------------------
/common/accessor/image_info_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import (
4 | "database/sql/driver"
5 | "fmt"
6 | "github.com/docker/docker/api/types/image"
7 | "github.com/donknap/dpanel/common/service/docker"
8 | "log/slog"
9 | )
10 |
11 | type ImageInfoOption struct {
12 | Id string `json:"id"`
13 | Info image.InspectResponse `json:"info"`
14 | }
15 |
16 | func (c ImageInfoOption) Value() (driver.Value, error) {
17 | return c.Id, nil
18 | }
19 |
20 | func (c *ImageInfoOption) Scan(value interface{}) error {
21 | if value == nil {
22 | return nil
23 | }
24 | id, ok := value.(string)
25 | if !ok {
26 | return fmt.Errorf("value is not string id, value: %v", value)
27 | }
28 | if id == "" {
29 | slog.Debug("tag not found")
30 | return nil
31 | }
32 | c.Id = id
33 | imageInfo, err := docker.Sdk.Client.ImageInspect(docker.Sdk.Ctx, id)
34 | if err != nil {
35 | slog.Debug(err.Error())
36 | return nil
37 | }
38 | c.Info = imageInfo
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/common/accessor/image_setting_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | type ImageSettingOption struct {
4 | BuildGit string `json:"buildGit"`
5 | BuildDockerfile string `json:"buildDockerfile"`
6 | BuildRoot string `json:"buildRoot"`
7 | Platform string `json:"platform"`
8 | Registry string `json:"registry"`
9 | }
10 |
--------------------------------------------------------------------------------
/common/accessor/permission_value_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | type DataPermissionItem struct {
4 | Name string `json:"name"`
5 | Uri string `json:"uri" binding:"required"`
6 | Key string `json:"key,omitempty" binding:"required"`
7 | Value []interface{} `json:"value,omitempty" binding:"required"`
8 | ShowKey string `json:"showKey,omitempty"`
9 | }
10 |
11 | type ResourcePermissionItem struct {
12 | Name string `json:"name" binding:"required"`
13 | Uri string `json:"uri" binding:"required"`
14 | Limit int64 `json:"limit" binding:"required"`
15 | Created int64 `json:"created"`
16 | }
17 |
18 | type MenuPermission struct {
19 | Permissions map[string]struct{} `json:"permissions,omitempty"`
20 | Uris map[string]struct{} `json:"uris,omitempty"`
21 | }
22 |
23 | type DataPermission struct {
24 | Permissions map[string]DataPermissionItem `json:"permissions,omitempty"`
25 | }
26 |
27 | type ResourcesPermission struct {
28 | Permissions map[string]ResourcePermissionItem `json:"permissions,omitempty"`
29 | }
30 |
31 | type PermissionValueOption struct {
32 | MenuPermission *MenuPermission `json:"menuPermission,omitempty"`
33 | DataPermission *DataPermission `json:"dataPermission,omitempty"`
34 | ResourcesPermission *ResourcesPermission `json:"resources,omitempty"`
35 | }
36 |
--------------------------------------------------------------------------------
/common/accessor/registry_setting_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | type RegistrySettingOption struct {
4 | Username string `json:"username"`
5 | Password string `json:"password"`
6 | Email string `json:"email"`
7 | Proxy []string `json:"proxy"`
8 | EnableHttp bool `json:"enableHttp"`
9 | }
10 |
--------------------------------------------------------------------------------
/common/accessor/site_container_info_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import (
4 | "github.com/docker/docker/api/types/container"
5 | )
6 |
7 | type SiteContainerInfoOption struct {
8 | Id string
9 | Info container.InspectResponse
10 | }
11 |
--------------------------------------------------------------------------------
/common/accessor/site_domain_setting_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import (
4 | "html/template"
5 | )
6 |
7 | type SiteDomainSettingOption struct {
8 | ServerName string `json:"serverName"`
9 | ServerNameAlias []string `json:"serverNameAlias,omitempty"`
10 | ServerAddress string `json:"serverAddress"`
11 | ServerProtocol string `json:"serverProtocol"`
12 | TargetName string `json:"targetName"`
13 | Port int32 `json:"port"`
14 | EnableBlockCommonExploits bool `json:"enableBlockCommonExploits"`
15 | EnableAssetCache bool `json:"enableAssetCache"`
16 | EnableWs bool `json:"enableWs"`
17 | EnableSSL bool `json:"enableSSL"`
18 | ExtraNginx template.HTML `json:"extraNginx,omitempty"`
19 | Email string `json:"email,omitempty"`
20 | SslCrt string `json:"sslCrt,omitempty"`
21 | SslKey string `json:"sslKey,omitempty"`
22 | CertName string `json:"certName,omitempty"`
23 | }
24 |
--------------------------------------------------------------------------------
/common/accessor/site_env_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/service/docker"
5 | )
6 |
7 | type SiteEnvOption struct {
8 | DockerEnvName string `json:"dockerEnvName,omitempty"`
9 | Name string `json:"name,omitempty"`
10 | Hostname string `json:"hostname,omitempty"`
11 | ContainerName string `json:"containerName,omitempty"`
12 | Environment []docker.EnvItem `json:"environment,omitempty"`
13 | Links []docker.LinkItem `json:"links,omitempty"`
14 | Ports []docker.PortItem `json:"ports,omitempty"`
15 | Volumes []docker.VolumeItem `json:"volumes,omitempty"`
16 | Network []docker.NetworkItem `json:"network,omitempty"`
17 | ImageName string `json:"imageName,omitempty"` // 非表单提交
18 | ImageId string `json:"imageId,omitempty"` // 非表单提交
19 | Privileged bool `json:"privileged,omitempty"`
20 | AutoRemove bool `json:"autoRemove,omitempty"`
21 | Restart string `json:"restart,omitempty"`
22 | Cpus float32 `json:"cpus,omitempty"`
23 | Memory int `json:"memory,omitempty"`
24 | ShmSize string `json:"shmsize,omitempty"`
25 | WorkDir string `json:"workDir,omitempty"`
26 | User string `json:"user,omitempty"`
27 | Command string `json:"command,omitempty"`
28 | Entrypoint string `json:"entrypoint,omitempty"`
29 | UseHostNetwork bool `json:"useHostNetwork,omitempty"`
30 | BindIpV6 bool `json:"bindIpV6,omitempty"`
31 | Log *docker.LogDriverItem `json:"log,omitempty"`
32 | Dns []string `json:"dns,omitempty"`
33 | Label []docker.ValueItem `json:"label,omitempty"`
34 | PublishAllPorts bool `json:"publishAllPorts,omitempty"`
35 | ExtraHosts []docker.ValueItem `json:"extraHosts,omitempty"`
36 | IpV4 *docker.NetworkCreateItem `json:"ipV4,omitempty"`
37 | IpV6 *docker.NetworkCreateItem `json:"ipV6,omitempty"`
38 | Device []docker.DeviceItem `json:"device,omitempty"`
39 | Gpus *docker.GpusItem `json:"gpus,omitempty"`
40 | Hook *docker.HookItem `json:"hook,omitempty"`
41 | Healthcheck *docker.HealthcheckItem `json:"healthcheck,omitempty"`
42 | HostPid bool `json:"hostPid,omitempty"`
43 | CapAdd []string `json:"capAdd,omitempty"`
44 | }
45 |
--------------------------------------------------------------------------------
/common/accessor/store_setting_option.go:
--------------------------------------------------------------------------------
1 | package accessor
2 |
3 | import "github.com/donknap/dpanel/common/service/docker"
4 |
5 | var (
6 | StoreTypeOnePanel = "1panel"
7 | StoreTypeOnePanelLocal = "1panel-local"
8 | StoreTypePortainer = "portainer"
9 | StoreTypeCasaOs = "casaos"
10 | )
11 |
12 | type StoreAppItem struct {
13 | Title string `json:"title"`
14 | Name string `json:"name"`
15 | Logo string `json:"logo"`
16 | Content string `json:"content"`
17 | Description string `json:"description"`
18 | Tag []string `json:"tag"`
19 | Website string `json:"website"`
20 | Version map[string]StoreAppVersionItem `json:"version"`
21 | }
22 |
23 | type StoreAppVersionScriptItem struct {
24 | Install string `json:"install,omitempty"`
25 | Uninstall string `json:"uninstall,omitempty"`
26 | Upgrade string `json:"upgrade,omitempty"`
27 | }
28 |
29 | type StoreAppVersionItem struct {
30 | Name string `json:"name"`
31 | ComposeFile string `json:"composeFile" yaml:"composeFile"`
32 | Environment []docker.EnvItem `json:"environment,omitempty"`
33 | Script *StoreAppVersionScriptItem `json:"script,omitempty"`
34 | }
35 |
36 | type StoreSettingOption struct {
37 | Type string `json:"type,omitempty"`
38 | Url string `json:"url,omitempty"`
39 | Apps []StoreAppItem `json:"apps,omitempty"`
40 | UpdatedAt int64 `json:"updatedAt,omitempty"`
41 | }
42 |
--------------------------------------------------------------------------------
/common/entity/ims_backup.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "time"
9 |
10 | "github.com/donknap/dpanel/common/accessor"
11 | )
12 |
13 | const TableNameBackup = "ims_backup"
14 |
15 | // Backup mapped from table
16 | type Backup struct {
17 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
18 | ContainerID string `gorm:"column:container_id" json:"containerId"`
19 | Setting *accessor.BackupSettingOption `gorm:"column:setting;serializer:json" json:"setting"`
20 | CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
21 | }
22 |
23 | // TableName Backup's table name
24 | func (*Backup) TableName() string {
25 | return TableNameBackup
26 | }
27 |
--------------------------------------------------------------------------------
/common/entity/ims_compose.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameCompose = "ims_compose"
12 |
13 | // Compose mapped from table
14 | type Compose struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | Name string `gorm:"column:name" json:"name"`
17 | Title string `gorm:"column:title" json:"title"`
18 | Setting *accessor.ComposeSettingOption `gorm:"column:setting;serializer:json" json:"setting"`
19 | }
20 |
21 | // TableName Compose's table name
22 | func (*Compose) TableName() string {
23 | return TableNameCompose
24 | }
25 |
--------------------------------------------------------------------------------
/common/entity/ims_cron.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameCron = "ims_cron"
12 |
13 | // Cron mapped from table
14 | type Cron struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | Title string `gorm:"column:title" json:"title"`
17 | Setting *accessor.CronSettingOption `gorm:"column:setting;serializer:json" json:"setting"`
18 | }
19 |
20 | // TableName Cron's table name
21 | func (*Cron) TableName() string {
22 | return TableNameCron
23 | }
24 |
--------------------------------------------------------------------------------
/common/entity/ims_cron_log.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameCronLog = "ims_cron_log"
12 |
13 | // CronLog mapped from table
14 | type CronLog struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | CronID int32 `gorm:"column:cron_id" json:"cronId"`
17 | Value *accessor.CronLogValueOption `gorm:"column:value;serializer:json" json:"value"`
18 | }
19 |
20 | // TableName CronLog's table name
21 | func (*CronLog) TableName() string {
22 | return TableNameCronLog
23 | }
24 |
--------------------------------------------------------------------------------
/common/entity/ims_event.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | const TableNameEvent = "ims_event"
8 |
9 | // Event mapped from table
10 | type Event struct {
11 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
12 | Type string `gorm:"column:type" json:"type"`
13 | Action string `gorm:"column:action" json:"action"`
14 | Message string `gorm:"column:message" json:"message"`
15 | CreatedAt string `gorm:"column:created_at" json:"createdAt"`
16 | }
17 |
18 | // TableName Event's table name
19 | func (*Event) TableName() string {
20 | return TableNameEvent
21 | }
22 |
--------------------------------------------------------------------------------
/common/entity/ims_image.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameImage = "ims_image"
12 |
13 | // Image mapped from table
14 | type Image struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | Tag string `gorm:"column:tag" json:"tag"`
17 | Title string `gorm:"column:title" json:"title"`
18 | Setting *accessor.ImageSettingOption `gorm:"column:setting;serializer:json" json:"setting"`
19 | ImageInfo *accessor.ImageInfoOption `gorm:"column:image_info" json:"imageInfo"`
20 | BuildType string `gorm:"column:build_type" json:"buildType"`
21 | Status int32 `gorm:"column:status" json:"status"`
22 | Message string `gorm:"column:message" json:"message"`
23 | }
24 |
25 | // TableName Image's table name
26 | func (*Image) TableName() string {
27 | return TableNameImage
28 | }
29 |
--------------------------------------------------------------------------------
/common/entity/ims_notice.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "time"
9 | )
10 |
11 | const TableNameNotice = "ims_notice"
12 |
13 | // Notice mapped from table
14 | type Notice struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | Type string `gorm:"column:type" json:"type"`
17 | Title string `gorm:"column:title" json:"title"`
18 | Message string `gorm:"column:message" json:"message"`
19 | CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
20 | }
21 |
22 | // TableName Notice's table name
23 | func (*Notice) TableName() string {
24 | return TableNameNotice
25 | }
26 |
--------------------------------------------------------------------------------
/common/entity/ims_registry.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameRegistry = "ims_registry"
12 |
13 | // Registry mapped from table
14 | type Registry struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | Title string `gorm:"column:title" json:"title"`
17 | ServerAddress string `gorm:"column:server_address" json:"serverAddress"`
18 | Setting *accessor.RegistrySettingOption `gorm:"column:setting;serializer:json" json:"setting"`
19 | }
20 |
21 | // TableName Registry's table name
22 | func (*Registry) TableName() string {
23 | return TableNameRegistry
24 | }
25 |
--------------------------------------------------------------------------------
/common/entity/ims_setting.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameSetting = "ims_setting"
12 |
13 | // Setting mapped from table
14 | type Setting struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | GroupName string `gorm:"column:group_name" json:"groupName"`
17 | Name string `gorm:"column:name" json:"name"`
18 | Value *accessor.SettingValueOption `gorm:"column:value;serializer:json" json:"value"`
19 | }
20 |
21 | // TableName Setting's table name
22 | func (*Setting) TableName() string {
23 | return TableNameSetting
24 | }
25 |
--------------------------------------------------------------------------------
/common/entity/ims_site.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | "gorm.io/gorm"
10 | )
11 |
12 | const TableNameSite = "ims_site"
13 |
14 | // Site mapped from table
15 | type Site struct {
16 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
17 | SiteTitle string `gorm:"column:site_title" json:"siteTitle"`
18 | SiteName string `gorm:"column:site_name" json:"siteName"`
19 | Env *accessor.SiteEnvOption `gorm:"column:env;serializer:json" json:"env"`
20 | ContainerInfo *accessor.SiteContainerInfoOption `gorm:"column:container_info;serializer:json" json:"containerInfo"`
21 | Status int32 `gorm:"column:status" json:"status"`
22 | StatusStep string `gorm:"column:status_step" json:"statusStep"`
23 | Message string `gorm:"column:message" json:"message"`
24 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deletedAt"`
25 | }
26 |
27 | // TableName Site's table name
28 | func (*Site) TableName() string {
29 | return TableNameSite
30 | }
31 |
--------------------------------------------------------------------------------
/common/entity/ims_site_domain.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "time"
9 |
10 | "github.com/donknap/dpanel/common/accessor"
11 | )
12 |
13 | const TableNameSiteDomain = "ims_site_domain"
14 |
15 | // SiteDomain mapped from table
16 | type SiteDomain struct {
17 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
18 | ContainerID string `gorm:"column:container_id" json:"containerId"`
19 | ServerName string `gorm:"column:server_name" json:"serverName"`
20 | Setting *accessor.SiteDomainSettingOption `gorm:"column:setting;serializer:json" json:"setting"`
21 | CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
22 | }
23 |
24 | // TableName SiteDomain's table name
25 | func (*SiteDomain) TableName() string {
26 | return TableNameSiteDomain
27 | }
28 |
--------------------------------------------------------------------------------
/common/entity/ims_store.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameStore = "ims_store"
12 |
13 | // Store mapped from table
14 | type Store struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | Title string `gorm:"column:title" json:"title"`
17 | Name string `gorm:"column:name" json:"name"`
18 | Setting *accessor.StoreSettingOption `gorm:"column:setting;serializer:json" json:"setting"`
19 | }
20 |
21 | // TableName Store's table name
22 | func (*Store) TableName() string {
23 | return TableNameStore
24 | }
25 |
--------------------------------------------------------------------------------
/common/entity/ims_user_permission.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/donknap/dpanel/common/accessor"
9 | )
10 |
11 | const TableNameUserPermission = "ims_user_permission"
12 |
13 | // UserPermission mapped from table
14 | type UserPermission struct {
15 | ID int32 `gorm:"column:id;primaryKey" json:"id"`
16 | Username string `gorm:"column:username" json:"username"`
17 | Value *accessor.PermissionValueOption `gorm:"column:value;serializer:json" json:"value"`
18 | }
19 |
20 | // TableName UserPermission's table name
21 | func (*UserPermission) TableName() string {
22 | return TableNameUserPermission
23 | }
24 |
--------------------------------------------------------------------------------
/common/function/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/common/function/.keep
--------------------------------------------------------------------------------
/common/function/array.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import (
4 | "cmp"
5 | "reflect"
6 | )
7 |
8 | func IsEmptyArray[T interface{}](v []T) bool {
9 | if v == nil {
10 | return true
11 | }
12 | if len(v) == 0 {
13 | return true
14 | }
15 | return false
16 | }
17 |
18 | func InArray[T cmp.Ordered](v []T, item T) bool {
19 | if v == nil {
20 | return false
21 | }
22 | for _, t := range v {
23 | if t == item {
24 | return true
25 | }
26 | }
27 | return false
28 | }
29 |
30 | func InArrayArray[T cmp.Ordered](v []T, item ...T) bool {
31 | if v == nil {
32 | return false
33 | }
34 | for _, t := range item {
35 | if found := InArray(v, t); found {
36 | return true
37 | }
38 | }
39 | return false
40 | }
41 |
42 | func InArrayWalk[T interface{}](v []T, walk func(i T) bool) bool {
43 | exists, _ := IndexArrayWalk(v, walk)
44 | return exists
45 | }
46 |
47 | func IndexArrayWalk[T interface{}](v []T, walk func(i T) bool) (exists bool, index int) {
48 | if v == nil {
49 | return false, 0
50 | }
51 | for i, t := range v {
52 | if walk(t) {
53 | return true, i
54 | }
55 | }
56 | return false, 0
57 | }
58 |
59 | func PluckArrayWalk[T interface{}, R interface{}](v []T, walk func(i T) (R, bool)) []R {
60 | result := make([]R, 0)
61 | for _, item := range v {
62 | newItem, ok := walk(item)
63 | if ok {
64 | result = append(result, newItem)
65 | }
66 | }
67 | return result
68 | }
69 |
70 | func FindArrayValueIndex(items interface{}, value ...interface{}) (exists bool, pos []int) {
71 | pos = make([]int, 0)
72 | some := reflect.ValueOf(items)
73 |
74 | switch some.Kind() {
75 | case reflect.Slice, reflect.Array:
76 | for i := 0; i < some.Len(); i++ {
77 | if len(value) == 1 {
78 | if reflect.DeepEqual(some.Index(i).Interface(), value[0]) {
79 | pos = append(pos, i)
80 | }
81 | } else {
82 | someStruct := some.Index(i)
83 | if someStruct.Kind() == reflect.Ptr {
84 | someStruct = someStruct.Elem()
85 | }
86 | if someStruct.Kind() == reflect.Struct {
87 | fieldName, ok := value[0].(string)
88 | someValue := someStruct.FieldByName(fieldName)
89 | if ok {
90 | if someValue.Kind() == reflect.Slice || someValue.Kind() == reflect.Array {
91 | exists1, _ := FindArrayValueIndex(someValue.Interface(), value[1:]...)
92 | if exists1 {
93 | return exists1, append(pos, i)
94 | }
95 | } else {
96 | for j := 1; j < len(value)-1; j++ {
97 | fieldName, ok = value[j].(string)
98 | someValue = someValue.FieldByName(fieldName)
99 | }
100 | if reflect.DeepEqual(someValue.Interface(), value[len(value)-1]) {
101 | pos = append(pos, i)
102 | }
103 | }
104 | } else {
105 | return false, pos
106 | }
107 | } else {
108 | return false, pos
109 | }
110 | }
111 | }
112 | default:
113 | return false, pos
114 | }
115 | if len(pos) > 0 {
116 | return true, pos
117 | } else {
118 | return false, nil
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/common/function/byte.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | // 根据回调函数清除bytes中的字符
4 |
5 | func BytesCleanFunc[T byte | rune](data []T, callback func(b T) bool) []T {
6 | var result []T
7 | for _, b := range data {
8 | if !callback(b) {
9 | result = append(result, b)
10 | }
11 | }
12 | return result
13 | }
14 |
--------------------------------------------------------------------------------
/common/function/date.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | var (
4 | ShowYmdHis = "2006-01-02 15:04:05"
5 | YmdHis = "20060102150405"
6 | )
7 |
--------------------------------------------------------------------------------
/common/function/encrypt.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "encoding/hex"
8 | "fmt"
9 | )
10 |
11 | const CommonKey = "DPanelCommonAseKey20231208"
12 |
13 | func AseEncode(key string, str string) (result string, err error) {
14 | key = GetMd5(CommonKey + key)
15 | block, err := aes.NewCipher([]byte(key))
16 | if err != nil {
17 | return result, err
18 | }
19 | blockSize := block.BlockSize()
20 | origStr := PKCS5Padding([]byte(str), blockSize)
21 | blockMode := cipher.NewCBCEncrypter(block, []byte(key)[:blockSize])
22 | crypted := make([]byte, len(origStr))
23 | blockMode.CryptBlocks(crypted, origStr)
24 | return hex.EncodeToString(crypted), nil
25 | }
26 |
27 | func AseDecode(key string, str string) (result string, err error) {
28 | decodeStr, err := hex.DecodeString(str)
29 | key = GetMd5(CommonKey + key)
30 | block, err := aes.NewCipher([]byte(key))
31 | if err != nil {
32 | return result, err
33 | }
34 | blockSize := block.BlockSize()
35 | blockMode := cipher.NewCBCDecrypter(block, []byte(key)[:blockSize])
36 | origData := make([]byte, len(decodeStr))
37 | blockMode.CryptBlocks(origData, decodeStr)
38 | origData = PKCS5UnPadding(origData)
39 | return string(origData), nil
40 | }
41 |
42 | func PKCS5Padding(plaintext []byte, blockSize int) []byte {
43 | padding := blockSize - len(plaintext)%blockSize
44 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
45 | return append(plaintext, padtext...)
46 | }
47 |
48 | func PKCS5UnPadding(origData []byte) []byte {
49 | length := len(origData)
50 | unpadding := int(origData[length-1])
51 | return origData[:(length - unpadding)]
52 | }
53 |
54 | func URIEncodeComponent(s string, excluded ...[]byte) string {
55 | var b bytes.Buffer
56 | written := 0
57 | for i, n := 0, len(s); i < n; i++ {
58 | c := s[i]
59 | switch c {
60 | case '-', '_', '.', '!', '~', '*', '\'', '(', ')':
61 | continue
62 | default:
63 | // Unreserved according to RFC 3986 sec 2.3
64 | if 'a' <= c && c <= 'z' {
65 | continue
66 | }
67 | if 'A' <= c && c <= 'Z' {
68 | continue
69 | }
70 | if '0' <= c && c <= '9' {
71 | continue
72 | }
73 | if len(excluded) > 0 {
74 | conti := false
75 | for _, ch := range excluded[0] {
76 | if ch == c {
77 | conti = true
78 | break
79 | }
80 | }
81 | if conti {
82 | continue
83 | }
84 | }
85 | }
86 | b.WriteString(s[written:i])
87 | fmt.Fprintf(&b, "%%%02X", c)
88 | written = i + 1
89 | }
90 | if written == 0 {
91 | return s
92 | }
93 | b.WriteString(s[written:])
94 | return b.String()
95 | }
96 |
--------------------------------------------------------------------------------
/common/function/error.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "github.com/gin-gonic/gin"
7 | "strings"
8 | "time"
9 | )
10 |
11 | func ErrorHasKeyword(e error, keyword ...string) bool {
12 | for _, k := range keyword {
13 | if strings.Contains(e.Error(), k) {
14 | return true
15 | }
16 | }
17 | return false
18 | }
19 |
20 | func ErrorMessage(title string, message ...string) error {
21 | jsonMessage, _ := json.Marshal(message)
22 | row := &gin.H{
23 | "title": title,
24 | "message": string(jsonMessage),
25 | "type": "error",
26 | "createdAt": time.Now().Local(),
27 | }
28 | result, _ := json.Marshal(row)
29 | return errors.New(string(result))
30 | }
31 |
--------------------------------------------------------------------------------
/common/function/map.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import (
4 | "cmp"
5 | "sort"
6 | )
7 |
8 | func IsEmptyMap[K cmp.Ordered, V interface{}](v map[K]V) bool {
9 | if v == nil {
10 | return true
11 | }
12 | if len(v) == 0 {
13 | return true
14 | }
15 | return false
16 | }
17 |
18 | func PluckMapWalkArray[K cmp.Ordered, U interface{}, R interface{}](m map[K]U, walk func(k K, v U) (R, bool)) []R {
19 | var keys []K
20 | for key := range m {
21 | keys = append(keys, key)
22 | }
23 | sort.Slice(keys, func(i, j int) bool {
24 | return keys[i] < keys[j]
25 | })
26 | result := make([]R, 0)
27 | for _, key := range keys {
28 | newItem, ok := walk(key, m[key])
29 | if ok {
30 | result = append(result, newItem)
31 | }
32 | }
33 | return result
34 | }
35 |
--------------------------------------------------------------------------------
/common/function/ptr.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import "time"
4 |
5 | func PtrTime(v time.Time) *time.Time {
6 | return &v
7 | }
8 |
9 | func PtrString(str string) *string {
10 | return &str
11 | }
12 |
13 | func PtrBool(b bool) *bool {
14 | return &b
15 | }
16 |
--------------------------------------------------------------------------------
/common/function/strings.go:
--------------------------------------------------------------------------------
1 | package function
2 |
--------------------------------------------------------------------------------
/common/function/struct.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import "encoding/json"
4 |
5 | func StructToMap(obj interface{}) map[string]interface{} {
6 | b, _ := json.Marshal(obj)
7 | var m map[string]interface{}
8 | _ = json.Unmarshal(b, &m)
9 | return m
10 | }
11 |
--------------------------------------------------------------------------------
/common/function/util.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import (
4 | "crypto/md5"
5 | "crypto/sha256"
6 | "errors"
7 | "fmt"
8 | "math/rand"
9 | "net"
10 | "path/filepath"
11 | "strings"
12 | )
13 |
14 | func GetRandomString(n int) string {
15 | str := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789"
16 | bytes := []byte(str)
17 | var result []byte
18 | for i := 0; i < n; i++ {
19 | result = append(result, bytes[rand.Intn(len(bytes))])
20 | }
21 | return string(result)
22 | }
23 |
24 | func GetMd5(str string) string {
25 | return fmt.Sprintf("%x", md5.Sum([]byte(str)))
26 | }
27 |
28 | type pathInfoOut struct {
29 | DirName string
30 | BaseName string
31 | Extension string
32 | Filename string
33 | }
34 |
35 | func GetPathInfo(path string) *pathInfoOut {
36 | filename := filepath.Base(path)
37 | ext := filepath.Ext(filename)
38 |
39 | dirname, basename := filepath.Split(path)
40 | basename = basename[:len(basename)-len(ext)]
41 | result := &pathInfoOut{}
42 | result.DirName = dirname
43 | result.BaseName = basename
44 | result.Extension = ext
45 | result.Filename = filename
46 | return result
47 | }
48 |
49 | func CheckFileAllowUpload(filename string) bool {
50 | allowFileExt := []string{
51 | ".zip", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".cvs",
52 | ".jpg", ".png", ".jpeg", ".gif",
53 | }
54 | for _, s := range allowFileExt {
55 | if strings.HasSuffix(filename, s) {
56 | return true
57 | }
58 | }
59 | return false
60 | }
61 |
62 | func GetRootPath() string {
63 | rootPath, _ := filepath.Abs("./")
64 | return rootPath
65 | }
66 |
67 | func IpInSubnet(ipAddress, subnetAddress string) (bool, error) {
68 | ip := net.ParseIP(ipAddress)
69 | if ip == nil {
70 | return false, errors.New("错误的 ip 地址: " + ipAddress)
71 | }
72 | _, subnet, err := net.ParseCIDR(subnetAddress)
73 | if err != nil {
74 | return false, errors.New("错误的子网 CIDR 地址: " + subnetAddress)
75 | }
76 |
77 | if subnetAddress != subnet.String() {
78 | return false, errors.New("错误的子网 CIDR 地址, 应为: " + subnet.String())
79 | }
80 | if !subnet.Contains(ip) {
81 | return false, errors.New("ip 地址与子网地址不匹配")
82 | }
83 | return true, nil
84 | }
85 |
86 | func GetSha256(str []byte) string {
87 | hash := sha256.New()
88 | hash.Write(str)
89 | return fmt.Sprintf("sha256:%x", hash.Sum(nil))
90 | }
91 |
--------------------------------------------------------------------------------
/common/function/yaml.go:
--------------------------------------------------------------------------------
1 | package function
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/cast"
6 | "strings"
7 | )
8 |
9 | type YamlGetter map[string]interface{}
10 |
11 | func (self YamlGetter) GetString(path string) string {
12 | return cast.ToString(self.getValueInterface(path))
13 | }
14 |
15 | // GetStringSlice
16 | // 获取一个数组类型的 yaml 字段 tag
17 | // tag:
18 | // - a
19 | // - b
20 | func (self YamlGetter) GetStringSlice(path string) []string {
21 | return cast.ToStringSlice(self.toSlice(self.getValueInterface(path)))
22 | }
23 |
24 | // GetSliceStringMapString
25 | // 获取一个键值对数组 tag or tag.2.values
26 | // tag:
27 | // - name: a
28 | // age: 1
29 | // - name: b
30 | // age: 2
31 | // - name: c
32 | // age: 3
33 | // values:
34 | // - a
35 | // - b
36 | func (self YamlGetter) GetSliceStringMapString(path string) []map[string]string {
37 | result := make([]map[string]string, 0)
38 | slice := cast.ToSlice(self.toSlice(self.getValueInterface(path)))
39 | for _, item := range slice {
40 | temp := make(map[string]string)
41 | for key, value := range item.(YamlGetter) {
42 | temp[key] = cast.ToString(value)
43 | }
44 | result = append(result, temp)
45 | }
46 | return result
47 | }
48 |
49 | // GetStringMapString
50 | // 获取一个键值对
51 | // tag:
52 | //
53 | // name: a
54 | // age: 1
55 | func (self YamlGetter) GetStringMapString(path string) map[string]string {
56 | result := make(map[string]string)
57 | for key, value := range self.getValueInterface(path).(YamlGetter) {
58 | result[key] = cast.ToString(value)
59 | }
60 | return result
61 | }
62 |
63 | func (self YamlGetter) getValueInterface(path string) interface{} {
64 | if self == nil {
65 | return interface{}(nil)
66 | }
67 |
68 | current := self
69 | pathList := strings.Split(path, ".")
70 | pathLen := len(pathList)
71 |
72 | for i := 0; i < pathLen; i++ {
73 | switch t := current[pathList[i]].(type) {
74 | case []interface{}:
75 | // 断言是数组类型时,需要转换成 map 再继续下一步
76 | temp := make(YamlGetter)
77 | for j, v := range t {
78 | temp[fmt.Sprintf("%d", j)] = v
79 | }
80 | current = temp
81 | case YamlGetter:
82 | current = t
83 | default:
84 | // 类型非 map 或是 数组,直接返回数据上层再进行转换
85 | return t
86 | }
87 | if i == pathLen-1 {
88 | return current
89 | }
90 | }
91 | return interface{}(nil)
92 | }
93 |
94 | func (self YamlGetter) toSlice(data interface{}) []interface{} {
95 | if temp, ok := data.(YamlGetter); ok {
96 | result := make([]interface{}, len(temp))
97 | for key, value := range temp {
98 | k := cast.ToInt(key)
99 | result[k] = value
100 | }
101 | return result
102 | } else {
103 | return make([]interface{}, 0)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/common/middleware/auth.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/donknap/dpanel/app/common/logic"
7 | "github.com/donknap/dpanel/common/service/storage"
8 | "github.com/gin-gonic/gin"
9 | "github.com/golang-jwt/jwt/v5"
10 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware"
11 | "log/slog"
12 | "strings"
13 | )
14 |
15 | type AuthMiddleware struct {
16 | middleware.Abstract
17 | }
18 |
19 | var (
20 | ErrLogin = errors.New("请先登录")
21 | )
22 |
23 | func (self AuthMiddleware) Process(http *gin.Context) {
24 | currentUrlPath := http.Request.URL.Path
25 | if strings.Contains(currentUrlPath, "/api/common/user/login") ||
26 | strings.Contains(currentUrlPath, "/pro/home/login-info") ||
27 | strings.Contains(currentUrlPath, "/api/common/user/create-founder") ||
28 | strings.Contains(currentUrlPath, "/pro/user/reset-info") ||
29 | strings.Contains(currentUrlPath, "/xk/user/oauth/callback") ||
30 | (!strings.HasPrefix(currentUrlPath, "/api") && !strings.HasPrefix(currentUrlPath, "/ws")) {
31 | http.Next()
32 | return
33 | }
34 |
35 | var authToken = ""
36 | if strings.HasPrefix(currentUrlPath, "/ws/") {
37 | authToken = "Bearer " + http.Query("token")
38 | } else {
39 | authToken = http.GetHeader("Authorization")
40 | }
41 |
42 | if authToken == "" {
43 | self.JsonResponseWithError(http, ErrLogin, 401)
44 | http.AbortWithStatus(401)
45 | return
46 | }
47 | authCode := strings.Split(authToken, "Bearer ")
48 | if len(authCode) != 2 {
49 | self.JsonResponseWithError(http, ErrLogin, 401)
50 | http.AbortWithStatus(401)
51 | return
52 | }
53 |
54 | myUserInfo := logic.UserInfo{}
55 | jwtSecret := logic.User{}.GetJwtSecret()
56 | token, err := jwt.ParseWithClaims(authCode[1], &myUserInfo, func(t *jwt.Token) (interface{}, error) {
57 | return jwtSecret, nil
58 | }, jwt.WithValidMethods([]string{"HS256"}))
59 | if err != nil {
60 | self.JsonResponseWithError(http, ErrLogin, 401)
61 | http.AbortWithStatus(401)
62 | return
63 | }
64 | if token.Valid {
65 | if myUserInfo.AutoLogin {
66 | if _, err := new(logic.Setting).GetValueById(myUserInfo.UserId); err == nil {
67 | myUserInfo.Fd = http.GetHeader("AuthorizationFd")
68 | http.Set("userInfo", myUserInfo)
69 | http.Next()
70 | return
71 | }
72 | } else {
73 | if v, ok := storage.Cache.Get(fmt.Sprintf(storage.CacheKeyCommonUserInfo, myUserInfo.UserId)); ok {
74 | if _, ok := v.(logic.UserInfo); ok {
75 | myUserInfo.Fd = http.GetHeader("AuthorizationFd")
76 | http.Set("userInfo", myUserInfo)
77 | http.Next()
78 | return
79 | }
80 | }
81 | }
82 | slog.Debug("auth get cache user error", "jwt", authToken, "userInfo", myUserInfo)
83 | self.JsonResponseWithError(http, ErrLogin, 401)
84 | http.AbortWithStatus(401)
85 | return
86 | }
87 | self.JsonResponseWithError(http, ErrLogin, 401)
88 | http.AbortWithStatus(401)
89 | return
90 | }
91 |
--------------------------------------------------------------------------------
/common/middleware/cache.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware"
6 | http2 "net/http"
7 | "strconv"
8 | "strings"
9 | "time"
10 | )
11 |
12 | type CacheMiddleware struct {
13 | middleware.Abstract
14 | }
15 |
16 | func (self CacheMiddleware) Process(http *gin.Context) {
17 | if strings.HasPrefix(http.Request.URL.Path, "/dpanel/static") {
18 | defaultMaxAge := 604800
19 | http.Writer.Header().Add("Cache-Control", "public, max-age="+strconv.Itoa(defaultMaxAge))
20 | http.Writer.Header().Add("Expires", time.Now().Add(time.Duration(defaultMaxAge)*time.Second).UTC().Format(http2.TimeFormat))
21 | http.Writer.WriteHeader(304)
22 | }
23 | http.Next()
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/common/middleware/cors.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
6 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware"
7 | "net/http"
8 | "strings"
9 | )
10 |
11 | type CorsMiddleware struct {
12 | middleware.Abstract
13 | }
14 |
15 | func (self CorsMiddleware) Process(ctx *gin.Context) {
16 | if host, ok := self.isAllow(ctx); ok {
17 | ctx.Header("Access-Control-Allow-Origin", host)
18 | ctx.Header("Access-Control-Allow-Headers", "Content-Type, AccessToken, X-CSRF-Token, Authorization, ")
19 | ctx.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
20 | ctx.Header("Access-Control-Expose-Headers", self.getAllowHeader())
21 | ctx.Header("Access-Control-Allow-Credentials", "true")
22 | if ctx.Request.Method == "OPTIONS" {
23 | ctx.AbortWithStatus(http.StatusNoContent)
24 | }
25 | }
26 | ctx.Next()
27 | }
28 |
29 | func (self CorsMiddleware) isAllow(ctx *gin.Context) (string, bool) {
30 | host := ctx.Request.Header.Get("origin")
31 | if host == "" {
32 | host = ctx.Request.Header.Get("referer")
33 | }
34 | if host == "" {
35 | return "", false
36 | }
37 | allowUrl := facade.GetConfig().GetStringSlice("app.cors")
38 | for _, value := range allowUrl {
39 | if value == host {
40 | return host, true
41 | }
42 | }
43 | return "", false
44 | }
45 |
46 | func (self CorsMiddleware) getAllowHeader() string {
47 | allowHeader := []string{
48 | "Content-Length",
49 | "Content-Type",
50 | "X-Auth-Token",
51 | "Origin",
52 | "Authorization",
53 | "X-Requested-With",
54 | "x-requested-with",
55 | "x-xsrf-token",
56 | "x-csrf-token",
57 | "x-w7-from",
58 | "access-token",
59 | "Api-Version",
60 | "Access-Control-Allow-Origin",
61 | "Access-Control-Allow-Headers",
62 | "Access-Control-Allow-Methods",
63 | "authority",
64 | "uid",
65 | "uuid",
66 | }
67 | return strings.Join(allowHeader, ",")
68 | }
69 |
--------------------------------------------------------------------------------
/common/migrate/upgrade-20240909.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/dao"
5 | )
6 |
7 | type Upgrade20240909 struct{}
8 |
9 | func (self Upgrade20240909) Version() string {
10 | return "1.1.1"
11 | }
12 |
13 | func (self Upgrade20240909) Upgrade() error {
14 | composeList, _ := dao.Compose.Find()
15 | for _, compose := range composeList {
16 | if compose.Setting == nil || compose.Setting.Status == "" {
17 | continue
18 | }
19 | compose.Setting.Status = ""
20 | err := dao.Compose.Save(compose)
21 | if err != nil {
22 | return err
23 | }
24 | }
25 | return nil
26 | }
27 |
--------------------------------------------------------------------------------
/common/migrate/upgrade-20250106.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/dao"
5 | "github.com/donknap/dpanel/common/service/docker"
6 | "gorm.io/datatypes"
7 | "gorm.io/gen"
8 | "log/slog"
9 | )
10 |
11 | type Upgrade20250106 struct{}
12 |
13 | func (self Upgrade20250106) Version() string {
14 | return "1.5.2"
15 | }
16 |
17 | func (self Upgrade20250106) Upgrade() error {
18 | composeList, _ := dao.Compose.Find()
19 | for _, compose := range composeList {
20 | if compose.Setting == nil || compose.Setting.DockerEnvName != "" {
21 | continue
22 | }
23 | compose.Setting.DockerEnvName = docker.DefaultClientName
24 | err := dao.Compose.Save(compose)
25 | if err != nil {
26 | return err
27 | }
28 | }
29 | query := dao.Compose.Where(gen.Cond(
30 | datatypes.JSONQuery("setting").Equals("outPath", "type"),
31 | )...)
32 | if list, err := query.Find(); err == nil && list != nil && len(list) > 0 {
33 | ids := make([]int32, 0)
34 | for _, compose := range list {
35 | ids = append(ids, compose.ID)
36 | }
37 | slog.Debug("清理 outPath 数据", "ids", ids)
38 | _, err := dao.Compose.Where(dao.Compose.ID.In(ids...)).Delete()
39 | if err != nil {
40 | return err
41 | }
42 | }
43 | return nil
44 | }
45 |
--------------------------------------------------------------------------------
/common/migrate/upgrade-20250113.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/dao"
5 | "github.com/donknap/dpanel/common/service/docker"
6 | )
7 |
8 | type Upgrade20250113 struct{}
9 |
10 | func (self Upgrade20250113) Version() string {
11 | return "1.5.3"
12 | }
13 |
14 | func (self Upgrade20250113) Upgrade() error {
15 | list, _ := dao.Cron.Find()
16 | for _, item := range list {
17 | if item.Setting == nil || item.Setting.DockerEnvName != "" {
18 | continue
19 | }
20 | item.Setting.DockerEnvName = docker.DefaultClientName
21 | err := dao.Cron.Save(item)
22 | if err != nil {
23 | return err
24 | }
25 | }
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/common/migrate/upgrade-20250401.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/function"
5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
6 | )
7 |
8 | type Upgrade20250401 struct{}
9 |
10 | func (self Upgrade20250401) Version() string {
11 | return "1.6.3"
12 | }
13 |
14 | func (self Upgrade20250401) Upgrade() error {
15 | // 如果获取不到数据,尝试将 container_info 中的数据变更成 json 串
16 | db, err := facade.GetDbFactory().Channel("default")
17 | if err != nil {
18 | return err
19 | }
20 |
21 | var ids []int32
22 | db.Table("ims_site").Select("id").Where("LENGTH(container_info) = ?", 64).Pluck("id", &ids)
23 | if !function.IsEmptyArray(ids) {
24 | db.Exec(`UPDATE ims_site SET container_info = CONCAT('{"Id": "', container_info, '"}') WHERE id IN (?)`, ids)
25 | }
26 |
27 | db.Table("ims_site").Select("id").Where("LENGTH(container_info) = ?", 0).Pluck("id", &ids)
28 | if !function.IsEmptyArray(ids) {
29 | db.Exec(`UPDATE ims_site SET container_info = '{}' WHERE id IN (?)`, ids)
30 | }
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/common/migrate/upgrade-20250521.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/function"
5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
6 | )
7 |
8 | type Upgrade20250521 struct{}
9 |
10 | func (self Upgrade20250521) Version() string {
11 | return "1.7.0"
12 | }
13 |
14 | func (self Upgrade20250521) Upgrade() error {
15 | // 如果获取不到数据,尝试将 container_info 中的数据变更成 json 串
16 | db, err := facade.GetDbFactory().Channel("default")
17 | if err != nil {
18 | return err
19 | }
20 |
21 | replaceName := []map[string]string{
22 | {
23 | "old": "Tag",
24 | "new": "tag",
25 | },
26 | {
27 | "old": "checkContainerAllUpgrade",
28 | "new": "containerCheckAllUpgrade",
29 | },
30 | {
31 | "old": "checkContainerIgnore",
32 | "new": "containerCheckIgnoreUpgrade",
33 | },
34 | }
35 |
36 | for _, item := range replaceName {
37 | var ids []int32
38 | db.Table("ims_setting").Where("group_name = ? AND name = ?", "setting", item["old"]).Pluck("id", &ids)
39 | if !function.IsEmptyArray(ids) {
40 | db.Exec(`UPDATE ims_setting SET name = ? WHERE id IN (?)`, item["new"], ids)
41 | }
42 | }
43 |
44 | replaceFieldName := []map[string]string{
45 | {
46 | "old": "themeUser",
47 | "new": "themeUserConfig",
48 | },
49 | {
50 | "old": "theme",
51 | "new": "themeConfig",
52 | },
53 | {
54 | "old": "ignoreCheckUpgrade",
55 | "new": "containerCheckIgnoreUpgrade",
56 | },
57 | }
58 |
59 | for _, item := range replaceFieldName {
60 | var ids []int32
61 | db.Table("ims_setting").Where("group_name = ? AND name = ?", "setting", item["new"]).Pluck("id", &ids)
62 | if !function.IsEmptyArray(ids) {
63 | db.Exec(`UPDATE ims_setting SET value = REPLACE(value, ?, ?) WHERE id IN (?)`, item["old"], item["new"], ids)
64 | }
65 | }
66 |
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/common/migrate/upgrade.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | type Updater interface {
4 | Upgrade() error
5 | Version() string
6 | }
7 |
--------------------------------------------------------------------------------
/common/service/acme/option.go:
--------------------------------------------------------------------------------
1 | package acme
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/function"
5 | )
6 |
7 | type Option func(self *Acme) error
8 |
9 | func WithDomain(list ...string) Option {
10 | if function.IsEmptyArray(list) {
11 | return nil
12 | }
13 | return func(self *Acme) error {
14 | for _, d := range list {
15 | self.argv = append(self.argv, "--domain", d)
16 | }
17 | return nil
18 | }
19 | }
20 |
21 | func WithCertServer(server string) Option {
22 | if server == "" {
23 | server = "letsencrypt"
24 | }
25 | return func(self *Acme) error {
26 | self.argv = append(self.argv, "--server", server)
27 | return nil
28 | }
29 | }
30 |
31 | func WithEmail(email string) Option {
32 | return func(self *Acme) error {
33 | self.argv = append(self.argv, "--email", email)
34 | return nil
35 | }
36 | }
37 |
38 | func WithAutoUpgrade(b bool) Option {
39 | return func(self *Acme) error {
40 | if b {
41 | self.argv = append(self.argv, "--auto-upgrade", "1")
42 | } else {
43 | self.argv = append(self.argv, "--auto-upgrade", "0")
44 | }
45 | return nil
46 | }
47 | }
48 |
49 | func WithForce() Option {
50 | return func(self *Acme) error {
51 | self.argv = append(self.argv, "--force")
52 | return nil
53 | }
54 | }
55 |
56 | func WithDnsNginx() Option {
57 | return func(self *Acme) error {
58 | self.argv = append(self.argv, "--nginx")
59 | return nil
60 | }
61 | }
62 |
63 | func WithRenew() Option {
64 | return func(self *Acme) error {
65 | self.argv = append(self.argv, "--renew")
66 | return nil
67 | }
68 | }
69 |
70 | func WithIssue() Option {
71 | return func(self *Acme) error {
72 | self.argv = append(self.argv, "--issue")
73 | return nil
74 | }
75 | }
76 |
77 | func WithCertRootPath(path string) Option {
78 | return func(self *Acme) error {
79 | self.argv = append(self.argv, "--key-file", path, "--fullchain-file", path)
80 | return nil
81 | }
82 | }
83 |
84 | func WithDnsApi(apiType string, env []string) Option {
85 | return func(self *Acme) error {
86 | self.argv = append(self.argv, "--dns", apiType)
87 | self.env = append(self.env, env...)
88 | return nil
89 | }
90 | }
91 |
92 | func WithConfigHomePath(path string) Option {
93 | return func(self *Acme) error {
94 | self.argv = append(self.argv, "--config-home", path)
95 | return nil
96 | }
97 | }
98 |
99 | func WithDebug() Option {
100 | return func(self *Acme) error {
101 | self.argv = append(self.argv, "--debug")
102 | return nil
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/common/service/compose/compose.go:
--------------------------------------------------------------------------------
1 | package compose
2 |
3 | import (
4 | "github.com/compose-spec/compose-go/v2/types"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | type Wrapper struct {
10 | Project *types.Project
11 | }
12 |
13 | // 区别于 Project.GetService 方法,此方法会将扩展信息一起返回
14 | func (self Wrapper) GetService(name string) (types.ServiceConfig, ExtService, error) {
15 | service, err := self.Project.GetService(name)
16 | if err != nil {
17 | return types.ServiceConfig{}, ExtService{}, err
18 | }
19 |
20 | ext := ExtService{}
21 | exists, err := service.Extensions.Get(ExtensionServiceName, &ext)
22 | if err == nil && exists {
23 | return service, ext, nil
24 | }
25 | return service, ExtService{}, nil
26 | }
27 |
28 | func (self Wrapper) GetBaseCommand() []string {
29 | cmd := make([]string, 0)
30 | for _, file := range self.Project.ComposeFiles {
31 | cmd = append(cmd, "-f", file)
32 | }
33 | cmd = append(cmd, "-p", self.Project.Name)
34 | for _, envFileName := range []string{
35 | ".env",
36 | } {
37 | envFilePath := filepath.Join(self.Project.WorkingDir, envFileName)
38 | _, err := os.Stat(envFilePath)
39 | if err == nil {
40 | cmd = append(cmd, "--env-file", envFilePath)
41 | }
42 | }
43 | return cmd
44 | }
45 |
--------------------------------------------------------------------------------
/common/service/compose/extension.go:
--------------------------------------------------------------------------------
1 | package compose
2 |
3 | import "github.com/compose-spec/compose-go/v2/types"
4 |
5 | const ExtensionServiceName = "x-dpanel-service"
6 | const ExtensionName = "x-dpanel"
7 |
8 | const VolumeTypeDPanel = "dpanel"
9 |
10 | type ExternalItem struct {
11 | VolumesFrom []string `yaml:"volumes_from,omitempty" json:"volumes_from"`
12 | Volumes []string `yaml:"volumes,omitempty" json:"volumes"`
13 | Networks map[string]*types.ServiceNetworkConfig `yaml:"networks,omitempty" json:"networks"`
14 | }
15 |
16 | type PortsItem struct {
17 | BindIPV6 bool `yaml:"bind_ipv6,omitempty" json:"bind_ipv6"`
18 | PublishAll bool `yaml:"publish_all,omitempty" json:"publish_all"`
19 | }
20 | type ExtService struct {
21 | ImageTar map[string]string `yaml:"image_tar,omitempty" json:"image_tar"`
22 | AutoRemove bool `yaml:"auto_remove,omitempty" json:"auto_remove"`
23 | External ExternalItem `yaml:"external,omitempty" json:"external"` // 关联外部容器资源
24 | Ports PortsItem `yaml:"ports,omitempty" json:"ports"`
25 | }
26 |
27 | type Ext struct {
28 | DisabledServices []string `yaml:"disabledServices,omitempty" json:"disabledServices"` // 过滤掉不需要部署的服务
29 | }
30 |
--------------------------------------------------------------------------------
/common/service/compose/placehold.go:
--------------------------------------------------------------------------------
1 | package compose
2 |
3 | import (
4 | "errors"
5 | "github.com/donknap/dpanel/common/function"
6 | "strings"
7 | "time"
8 | )
9 |
10 | // 仅在应用商店中的配置文件 data.yml 中支持
11 | const (
12 | ContainerDefaultName = "%CONTAINER_DEFAULT_NAME%"
13 | CurrentUsername = "%CURRENT_USERNAME%"
14 | TaskIndex = "%TASK_INDEX%"
15 | CurrentDate = "%CURRENT_DATE%"
16 | )
17 |
18 | type ReplaceFunc func(placeholder string) (string, error)
19 | type ReplaceTable map[string]ReplaceFunc
20 |
21 | func NewReplaceTable(rt ...ReplaceTable) ReplaceTable {
22 | defaultTable := ReplaceTable{
23 | ContainerDefaultName: func(placeholder string) (string, error) {
24 | return "", nil
25 | },
26 | CurrentUsername: func(placeholder string) (string, error) {
27 | return "", errors.New("not implemented")
28 | },
29 | CurrentDate: func(placeholder string) (string, error) {
30 | return time.Now().Format(function.YmdHis), nil
31 | },
32 | }
33 | for _, item := range rt {
34 | for k, v := range item {
35 | defaultTable[k] = v
36 | }
37 | }
38 |
39 | return defaultTable
40 | }
41 |
42 | func (self ReplaceTable) Replace(replace *string) error {
43 | var err error
44 | for key, replaceFunc := range self {
45 | if v, err := replaceFunc(key); err == nil {
46 | *replace = strings.Replace(*replace, key, v, -1)
47 | }
48 | }
49 | return err
50 | }
51 |
--------------------------------------------------------------------------------
/common/service/compose/types.go:
--------------------------------------------------------------------------------
1 | package compose
2 |
3 | import (
4 | "github.com/compose-spec/compose-go/v2/types"
5 | )
6 |
7 | type Service struct {
8 | types.ServiceConfig
9 | XDPanelService ExtService `yaml:"x-dpanel-service,omitempty" json:"x-dpanel-service,omitempty"`
10 | }
11 |
--------------------------------------------------------------------------------
/common/service/crontab/job.go:
--------------------------------------------------------------------------------
1 | package crontab
2 |
3 | import (
4 | "log/slog"
5 | )
6 |
7 | type RunFunc func() error
8 |
9 | type Option func(job *Job)
10 |
11 | func WithRunFunc(callback RunFunc) Option {
12 | return func(job *Job) {
13 | job.runFunc = append(job.runFunc, callback)
14 | }
15 | }
16 |
17 | func WithName(name string) Option {
18 | return func(job *Job) {
19 | job.Name = name
20 | }
21 | }
22 |
23 | func New(opts ...Option) *Job {
24 | c := &Job{
25 | runFunc: make([]RunFunc, 0),
26 | }
27 | for _, opt := range opts {
28 | opt(c)
29 | }
30 | return c
31 | }
32 |
33 | type Job struct {
34 | Name string
35 | runFunc []RunFunc
36 | }
37 |
38 | func (self Job) Run() {
39 | if self.runFunc != nil {
40 | for _, runFunc := range self.runFunc {
41 | err := runFunc()
42 | if err != nil {
43 | slog.Debug("crontab crash ", "err", err.Error())
44 | return
45 | }
46 | }
47 | } else {
48 | slog.Debug("invalid crontab job")
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/common/service/crontab/wrapper.go:
--------------------------------------------------------------------------------
1 | package crontab
2 |
3 | import (
4 | "errors"
5 | "github.com/robfig/cron/v3"
6 | "log/slog"
7 | "os"
8 | "time"
9 | )
10 |
11 | var (
12 | Wrapper = NewCronWrapper()
13 | )
14 |
15 | func NewCronWrapper() *wrapper {
16 | timeLocation := time.Local
17 | if os.Getenv("TZ") != "" {
18 | timeLocation, _ = time.LoadLocation(os.Getenv("TZ"))
19 | }
20 | specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
21 | cronWrapper := &wrapper{
22 | Cron: cron.New(
23 | cron.WithParser(specParser),
24 | cron.WithLocation(timeLocation),
25 | ),
26 | parser: specParser,
27 | }
28 |
29 | cronWrapper.Cron.Start()
30 | return cronWrapper
31 | }
32 |
33 | type wrapper struct {
34 | Cron *cron.Cron
35 | parser cron.Parser
36 | }
37 |
38 | func (self wrapper) CheckExpression(expression []string) error {
39 | for _, exp := range expression {
40 | _, err := self.parser.Parse(exp)
41 | if err != nil {
42 | return err
43 | }
44 | }
45 | return nil
46 | }
47 |
48 | func (self wrapper) AddJob(job *Job, expression ...string) ([]cron.EntryID, error) {
49 | if job == nil {
50 | return nil, errors.New("invalid job")
51 | }
52 | ids := make([]cron.EntryID, 0)
53 | for _, item := range expression {
54 | id, err := self.Cron.AddJob(item, job)
55 | ids = append(ids, id)
56 | if err != nil {
57 | self.RemoveJob(ids...)
58 | return nil, err
59 | }
60 | }
61 | slog.Debug("cron add job", "name", job.Name, "next run time", self.GetNextRunTime(ids...))
62 | return ids, nil
63 | }
64 |
65 | func (self wrapper) RemoveJob(ids ...cron.EntryID) {
66 | for _, entryID := range ids {
67 | self.Cron.Remove(entryID)
68 | }
69 | }
70 |
71 | func (self wrapper) GetNextRunTime(ids ...cron.EntryID) []time.Time {
72 | result := make([]time.Time, 0)
73 | for _, entryID := range ids {
74 | result = append(result, self.Cron.Entry(entryID).Next)
75 | }
76 | return result
77 | }
78 |
--------------------------------------------------------------------------------
/common/service/docker/backup/builder.go:
--------------------------------------------------------------------------------
1 | package backup
2 |
3 | import (
4 | "github.com/docker/docker/api/types"
5 | "github.com/donknap/dpanel/common/entity"
6 | "log/slog"
7 | )
8 |
9 | func New(opts ...Option) (*Builder, error) {
10 | var err error
11 | c := &Builder{}
12 |
13 | for _, opt := range opts {
14 | err = opt(c)
15 | if err != nil {
16 | return nil, err
17 | }
18 | }
19 |
20 | return c, nil
21 | }
22 |
23 | type Builder struct {
24 | tarFilePath string
25 | tarPathPrefix string
26 | Writer *writer
27 | Reader *reader
28 | }
29 |
30 | func (self Builder) Close() (err error) {
31 | defer func() {
32 | if e := recover(); e != nil {
33 | err = e.(error)
34 | }
35 | if self.Writer != nil {
36 | _ = self.Writer.tarWriter.Close()
37 | }
38 | if self.Reader != nil {
39 | _ = self.Reader.file.Close()
40 | }
41 | }()
42 | if self.Writer != nil {
43 | _ = self.Writer.tarWriter.Close()
44 | _ = self.Writer.file.Close()
45 | }
46 | if self.Reader != nil {
47 | err = self.Reader.file.Close()
48 | if err != nil {
49 | slog.Warn("container backup reader close file", "error", err)
50 | }
51 | }
52 | return err
53 | }
54 |
55 | type Manifest struct {
56 | Config string
57 | Image string
58 | Volume []string
59 | Network []string
60 | }
61 |
62 | type Info struct {
63 | Docker types.Version
64 | Backup *entity.Backup
65 | }
66 |
--------------------------------------------------------------------------------
/common/service/docker/backup/option.go:
--------------------------------------------------------------------------------
1 | package backup
2 |
3 | import (
4 | "archive/tar"
5 | "errors"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | )
10 |
11 | type Option func(self *Builder) error
12 |
13 | func WithTarPathPrefix(prefix string) Option {
14 | return func(self *Builder) error {
15 | if prefix != "" {
16 | self.tarPathPrefix = strings.TrimLeft(prefix, "/")
17 | }
18 | return nil
19 | }
20 | }
21 |
22 | func WithPath(path string) Option {
23 | return func(self *Builder) error {
24 | if path == "" {
25 | return errors.New("invalid path")
26 | }
27 | self.tarFilePath = path
28 | return nil
29 | }
30 | }
31 |
32 | func WithWriter() Option {
33 | return func(self *Builder) error {
34 | dir := filepath.Dir(self.tarFilePath)
35 | if _, err := os.Stat(dir); err != nil {
36 | _ = os.MkdirAll(dir, os.ModePerm)
37 | }
38 | file, err := os.OpenFile(self.tarFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o644)
39 | if err != nil {
40 | return err
41 | }
42 | self.Writer = &writer{
43 | tarPathPrefix: self.tarPathPrefix,
44 | file: file,
45 | }
46 | self.Writer.tarWriter = tar.NewWriter(file)
47 | return nil
48 | }
49 | }
50 |
51 | func WithReader() Option {
52 | return func(self *Builder) error {
53 | var err error
54 | if _, err := os.Stat(self.tarFilePath); err != nil {
55 | return err
56 | }
57 | file, err := os.OpenFile(self.tarFilePath, os.O_RDWR, 0o644)
58 | if err != nil {
59 | return err
60 | }
61 | self.Reader = &reader{
62 | file: file,
63 | }
64 | if err != nil {
65 | return err
66 | }
67 | return nil
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/common/service/docker/backup/reader.go:
--------------------------------------------------------------------------------
1 | package backup
2 |
3 | import (
4 | "archive/tar"
5 | "compress/gzip"
6 | "encoding/json"
7 | "errors"
8 | "github.com/donknap/dpanel/common/function"
9 | "io"
10 | "os"
11 | "strings"
12 | )
13 |
14 | type blobItem struct {
15 | Name string
16 | Offset int64
17 | }
18 |
19 | type reader struct {
20 | file *os.File
21 | blobs []blobItem
22 | }
23 |
24 | func (self *reader) Info() (*Info, error) {
25 | tarReader := tar.NewReader(self.file)
26 | info := &Info{}
27 | for {
28 | header, err := tarReader.Next()
29 | if err != nil {
30 | break
31 | }
32 | headerName := strings.TrimLeft(header.Name, "/")
33 | if strings.HasSuffix(headerName, "info.json") {
34 | content, err := io.ReadAll(tarReader)
35 | if err != nil {
36 | return nil, err
37 | }
38 | err = json.Unmarshal(content, &info)
39 | if err != nil {
40 | return nil, err
41 | }
42 | return info, nil
43 | }
44 | }
45 | return nil, errors.New("info file not found in archive")
46 | }
47 |
48 | func (self *reader) Manifest() ([]Manifest, error) {
49 | var offset int64
50 | tarReader := tar.NewReader(self.file)
51 | m := make([]Manifest, 0)
52 | for {
53 | header, err := tarReader.Next()
54 | if err != nil {
55 | break
56 | }
57 | if strings.HasSuffix(header.Name, "manifest.json") {
58 | content, err := io.ReadAll(tarReader)
59 | if err != nil {
60 | return nil, err
61 | }
62 | err = json.Unmarshal(content, &m)
63 | if err != nil {
64 | return nil, err
65 | }
66 | }
67 | if strings.Contains(header.Name, "blobs/sha256/") {
68 | offset, _ = self.file.Seek(0, io.SeekCurrent)
69 | self.blobs = append(self.blobs, blobItem{
70 | Name: header.Name,
71 | Offset: offset - 512,
72 | })
73 | }
74 | }
75 | if function.IsEmptyArray(m) {
76 | return nil, errors.New("manifest file not found in archive")
77 | }
78 | return m, nil
79 | }
80 |
81 | func (self *reader) ReadBlobs(fileName string) (io.Reader, error) {
82 | var index int
83 | var ok bool
84 | if ok, index = function.IndexArrayWalk(self.blobs, func(i blobItem) bool {
85 | return strings.HasSuffix(i.Name, fileName)
86 | }); !ok {
87 | return nil, errors.New("blob file not found in archive")
88 | }
89 | _, err := self.file.Seek(self.blobs[index].Offset, io.SeekStart)
90 | if err != nil {
91 | return nil, err
92 | }
93 | tarReader := tar.NewReader(self.file)
94 | _, err = tarReader.Next()
95 | if err != nil {
96 | return nil, err
97 | }
98 | return tarReader, nil
99 | }
100 |
101 | func (self *reader) ReadBlobsContent(fileName string) ([]byte, error) {
102 | out, err := self.ReadBlobs(fileName)
103 | if err != nil {
104 | return nil, err
105 | }
106 | gzReader, err := gzip.NewReader(out)
107 | if err != nil {
108 | return nil, err
109 | }
110 | defer func() {
111 | _ = gzReader.Close()
112 | }()
113 | return io.ReadAll(gzReader)
114 | }
115 |
--------------------------------------------------------------------------------
/common/service/docker/container/builder.go:
--------------------------------------------------------------------------------
1 | package container
2 |
3 | import (
4 | "github.com/docker/docker/api/types/container"
5 | "github.com/docker/docker/api/types/network"
6 | "github.com/docker/go-connections/nat"
7 | "github.com/donknap/dpanel/common/service/docker"
8 | v1 "github.com/opencontainers/image-spec/specs-go/v1"
9 | )
10 |
11 | func New(opts ...Option) (*Builder, error) {
12 | var err error
13 | c := &Builder{
14 | containerConfig: &container.Config{
15 | ExposedPorts: make(nat.PortSet),
16 | Labels: map[string]string{
17 | "maintainer": docker.BuilderAuthor,
18 | "com.dpanel.description": docker.BuildDesc,
19 | "com.dpanel.website": docker.BuildWebSite,
20 | },
21 | },
22 | hostConfig: &container.HostConfig{
23 | PortBindings: make(nat.PortMap),
24 | NetworkMode: "default",
25 | },
26 | platform: &v1.Platform{},
27 | networkingConfig: &network.NetworkingConfig{
28 | EndpointsConfig: map[string]*network.EndpointSettings{},
29 | },
30 | }
31 | for _, opt := range opts {
32 | err = opt(c)
33 | if err != nil {
34 | return nil, err
35 | }
36 | }
37 | return c, nil
38 | }
39 |
40 | type Builder struct {
41 | containerConfig *container.Config
42 | hostConfig *container.HostConfig
43 | networkingConfig *network.NetworkingConfig
44 | platform *v1.Platform
45 | containerName string
46 | }
47 |
48 | func (self *Builder) Execute() (response container.CreateResponse, err error) {
49 | return docker.Sdk.Client.ContainerCreate(
50 | docker.Sdk.Ctx,
51 | self.containerConfig,
52 | self.hostConfig,
53 | self.networkingConfig,
54 | self.platform,
55 | self.containerName,
56 | )
57 | }
58 |
59 | func (self *Builder) GetConfig() (*container.Config, *container.HostConfig, *network.NetworkingConfig) {
60 | return self.containerConfig, self.hostConfig, self.networkingConfig
61 | }
62 |
--------------------------------------------------------------------------------
/common/service/docker/exec.go:
--------------------------------------------------------------------------------
1 | package docker
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/function"
5 | "github.com/donknap/dpanel/common/service/exec"
6 | exec2 "os/exec"
7 | )
8 |
9 | func (self Builder) GetRunCmd(command ...string) []exec.Option {
10 | return []exec.Option{
11 | exec.WithCommandName("docker"),
12 | exec.WithArgs(append(
13 | self.runParams,
14 | command...,
15 | )...),
16 | }
17 | }
18 |
19 | func (self Builder) GetComposeCmd(command ...string) []exec.Option {
20 | if _, err := exec2.LookPath("docker-compose"); err == nil {
21 | return []exec.Option{
22 | exec.WithCommandName("docker-compose"),
23 | exec.WithArgs(command...),
24 | exec.WithEnv(self.runEnv),
25 | }
26 | } else {
27 | return []exec.Option{
28 | exec.WithCommandName("docker"),
29 | exec.WithArgs(append(append(self.runParams, "compose"), command...)...),
30 | }
31 | }
32 | }
33 |
34 | func (self Builder) ExecCleanResult(str []byte) string {
35 | // 执行命令时返回的结果应该以 utf8 字符返回,并过滤掉不可见字符
36 | out := function.BytesCleanFunc(str, func(b byte) bool {
37 | return b < 32 && b != '\n' && b != '\r' && b != '\t'
38 | })
39 | return string(out)
40 | }
41 |
--------------------------------------------------------------------------------
/common/service/docker/image/builder.go:
--------------------------------------------------------------------------------
1 | package image
2 |
3 | import (
4 | "github.com/docker/docker/api/types"
5 | "github.com/donknap/dpanel/common/service/docker"
6 | "io"
7 | )
8 |
9 | func New(opts ...Option) (*Builder, error) {
10 | var err error
11 | c := &Builder{
12 | imageBuildOption: types.ImageBuildOptions{
13 | Dockerfile: "Dockerfile", // 默认在根目录
14 | Remove: true,
15 | NoCache: true,
16 | Labels: map[string]string{
17 | "BuildAuthor": docker.BuilderAuthor,
18 | "BuildDesc": docker.BuildDesc,
19 | "BuildWebSite": docker.BuildWebSite,
20 | "buildVersion": docker.BuildVersion,
21 | },
22 | BuildArgs: map[string]*string{},
23 | },
24 | }
25 | for _, opt := range opts {
26 | err = opt(c)
27 | if err != nil {
28 | return nil, err
29 | }
30 | }
31 | return c, nil
32 | }
33 |
34 | type Builder struct {
35 | imageBuildOption types.ImageBuildOptions
36 | buildContext io.Reader
37 | }
38 |
39 | func (self *Builder) Execute() (response types.ImageBuildResponse, err error) {
40 | response, err = docker.Sdk.Client.ImageBuild(
41 | docker.Sdk.Ctx,
42 | self.buildContext,
43 | self.imageBuildOption,
44 | )
45 | if err != nil {
46 | return response, err
47 | }
48 | return response, nil
49 | }
50 |
--------------------------------------------------------------------------------
/common/service/docker/image/option.go:
--------------------------------------------------------------------------------
1 | package image
2 |
3 | import (
4 | "archive/tar"
5 | "archive/zip"
6 | "bytes"
7 | "errors"
8 | "github.com/donknap/dpanel/common/function"
9 | "github.com/donknap/dpanel/common/service/docker"
10 | "io"
11 | "os"
12 | "strings"
13 | "time"
14 | )
15 |
16 | type Option func(builder *Builder) error
17 |
18 | func WithDockerFileContent(content []byte) Option {
19 | return func(self *Builder) error {
20 | if content == nil {
21 | return nil
22 | }
23 | buf := new(bytes.Buffer)
24 | tarWriter := tar.NewWriter(buf)
25 | defer func() {
26 | _ = tarWriter.Close()
27 | }()
28 | if err := tarWriter.WriteHeader(&tar.Header{
29 | Name: "Dockerfile",
30 | Size: int64(len(content)),
31 | Mode: int64(os.ModePerm),
32 | ModTime: time.Now(),
33 | }); err != nil {
34 | return err
35 | }
36 | if _, err := tarWriter.Write(content); err != nil {
37 | return err
38 | }
39 | self.buildContext = buf
40 | return nil
41 | }
42 | }
43 |
44 | func WithGitUrl(url string) Option {
45 | return func(self *Builder) error {
46 | if url == "" {
47 | return nil
48 | }
49 | self.imageBuildOption.RemoteContext = url
50 | return nil
51 | }
52 | }
53 |
54 | func WithDockerFilePath(path string) Option {
55 | return func(self *Builder) error {
56 | if path == "" {
57 | return nil
58 | }
59 | self.imageBuildOption.Dockerfile = path
60 | return nil
61 | }
62 | }
63 |
64 | func WithTag(name ...string) Option {
65 | return func(self *Builder) error {
66 | if name == nil {
67 | return errors.New("tag name is required")
68 | }
69 | self.imageBuildOption.Tags = append(self.imageBuildOption.Tags, name...)
70 | return nil
71 | }
72 | }
73 |
74 | func WithPlatform(item *docker.ImagePlatform) Option {
75 | return func(self *Builder) error {
76 | if item == nil {
77 | return nil
78 | }
79 | self.imageBuildOption.Platform = item.Type
80 | self.imageBuildOption.BuildArgs["TARGETARCH"] = function.PtrString(item.Arch)
81 | return nil
82 | }
83 | }
84 |
85 | func WithZipFilePath(path string) Option {
86 | return func(self *Builder) error {
87 | if path == "" {
88 | return nil
89 | }
90 | zipArchive, err := zip.OpenReader(path)
91 | if err != nil {
92 | return err
93 | }
94 | defer func() {
95 | _ = zipArchive.Close()
96 | _ = os.Remove(path)
97 | }()
98 |
99 | buf := new(bytes.Buffer)
100 | tarWriter := tar.NewWriter(buf)
101 | defer func() {
102 | _ = tarWriter.Close()
103 | }()
104 |
105 | for _, zipFile := range zipArchive.File {
106 | if strings.HasPrefix(zipFile.Name, "__MACOSX") {
107 | continue
108 | }
109 | fileInfoHeader, err := tar.FileInfoHeader(zipFile.FileInfo(), "")
110 | fileInfoHeader.Name = zipFile.Name
111 |
112 | if err != nil {
113 | return err
114 | }
115 | err = tarWriter.WriteHeader(fileInfoHeader)
116 | if err != nil {
117 | return err
118 | }
119 | zipFileReader, err := zipFile.Open()
120 | _, err = io.Copy(tarWriter, zipFileReader)
121 | if err != nil {
122 | return err
123 | }
124 | }
125 | self.buildContext = buf
126 | return nil
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/common/service/docker/network.go:
--------------------------------------------------------------------------------
1 | package docker
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/docker/docker/api/types/network"
7 | "github.com/donknap/dpanel/common/function"
8 | "strings"
9 | )
10 |
11 | func (self Builder) NetworkRemove(ctx context.Context, networkName string) error {
12 | if networkRow, err := self.Client.NetworkInspect(ctx, networkName, network.InspectOptions{}); err == nil {
13 | for _, item := range networkRow.Containers {
14 | err = self.Client.NetworkDisconnect(ctx, networkName, item.Name, true)
15 | }
16 | if err != nil {
17 | return err
18 | }
19 | return self.Client.NetworkRemove(ctx, networkName)
20 | }
21 | return nil
22 | }
23 |
24 | func (self Builder) NetworkCreate(ctx context.Context, networkName string, ipV4, ipV6 *NetworkCreateItem) (string, error) {
25 | option := network.CreateOptions{
26 | Driver: "bridge",
27 | Options: map[string]string{
28 | "name": networkName,
29 | },
30 | EnableIPv6: function.PtrBool(false),
31 | IPAM: &network.IPAM{
32 | Driver: "default",
33 | Options: map[string]string{},
34 | Config: []network.IPAMConfig{},
35 | },
36 | }
37 | if ipV4 != nil && ipV4.Gateway != "" && ipV4.Subnet != "" {
38 | option.IPAM.Config = append(option.IPAM.Config, network.IPAMConfig{
39 | Subnet: ipV4.Subnet,
40 | Gateway: ipV4.Gateway,
41 | })
42 | }
43 | if ipV6 != nil && ipV6.Gateway != "" && ipV6.Subnet != "" {
44 | option.EnableIPv6 = function.PtrBool(true)
45 | option.IPAM.Config = append(option.IPAM.Config, network.IPAMConfig{
46 | Subnet: ipV6.Subnet,
47 | Gateway: ipV6.Gateway,
48 | })
49 | }
50 | response, err := self.Client.NetworkCreate(ctx, networkName, option)
51 | if err != nil {
52 | return "", err
53 | }
54 | return response.ID, nil
55 | }
56 |
57 | func (self Builder) NetworkConnect(ctx context.Context, networkRow NetworkItem, containerName string) error {
58 | // 关联网络时,重新退出加入
59 | _ = self.Client.NetworkDisconnect(ctx, networkRow.Name, containerName, true)
60 |
61 | if networkRow.Alise == nil {
62 | networkRow.Alise = make([]string, 0)
63 | }
64 | dpanelHostName := fmt.Sprintf("%s.pod.dpanel.local", strings.TrimLeft(containerName, "/"))
65 | if !function.InArray(networkRow.Alise, dpanelHostName) {
66 | networkRow.Alise = append(networkRow.Alise, dpanelHostName)
67 | }
68 | endpointSetting := &network.EndpointSettings{
69 | Aliases: networkRow.Alise,
70 | IPAMConfig: &network.EndpointIPAMConfig{},
71 | DNSNames: make([]string, 0),
72 | }
73 | if networkRow.IpV4 != "" {
74 | endpointSetting.IPAMConfig.IPv4Address = networkRow.IpV4
75 | }
76 | if networkRow.IpV6 != "" {
77 | endpointSetting.IPAMConfig.IPv6Address = networkRow.IpV6
78 | }
79 | if !function.IsEmptyArray(networkRow.DnsName) {
80 | endpointSetting.DNSNames = networkRow.DnsName
81 | }
82 | if networkRow.MacAddress != "" {
83 | endpointSetting.MacAddress = networkRow.MacAddress
84 | }
85 | return self.Client.NetworkConnect(ctx, networkRow.Name, containerName, endpointSetting)
86 | }
87 |
--------------------------------------------------------------------------------
/common/service/docker/utils.go:
--------------------------------------------------------------------------------
1 | package docker
2 |
3 | import (
4 | "bytes"
5 | "github.com/docker/docker/api/types/container"
6 | "github.com/docker/docker/pkg/stdcopy"
7 | "io"
8 | "strings"
9 | )
10 |
11 | func GetContentFromStdFormat(reader io.Reader) (*bytes.Buffer, error) {
12 | buffer := new(bytes.Buffer)
13 | _, err := io.Copy(buffer, reader)
14 | if err != nil {
15 | return nil, err
16 | }
17 | newReader := bytes.NewReader(buffer.Bytes())
18 | stdout := new(bytes.Buffer)
19 | _, err = stdcopy.StdCopy(stdout, stdout, newReader)
20 | if err == nil {
21 | return stdout, nil
22 | } else {
23 | return buffer, nil
24 | }
25 | }
26 |
27 | func GetRestartPolicyByString(restartType string) (mode container.RestartPolicyMode) {
28 | restartPolicyMap := map[string]container.RestartPolicyMode{
29 | "always": container.RestartPolicyAlways,
30 | "no": container.RestartPolicyDisabled,
31 | "unless-stopped": container.RestartPolicyUnlessStopped,
32 | "on-failure": container.RestartPolicyOnFailure,
33 | }
34 | if mode, ok := restartPolicyMap[restartType]; ok {
35 | return mode
36 | } else {
37 | return container.RestartPolicyDisabled
38 | }
39 | }
40 |
41 | func CommandSplit(cmd string) []string {
42 | result := make([]string, 0)
43 | field := ""
44 | ignoreSpace := false
45 | for _, s := range strings.Split(cmd, "") {
46 | if s == " " && !ignoreSpace {
47 | result = append(result, field)
48 | field = ""
49 | continue
50 | }
51 | if s == "\"" || s == "'" {
52 | ignoreSpace = !ignoreSpace
53 | continue
54 | }
55 | field += s
56 | }
57 | if field != "" {
58 | result = append(result, field)
59 | }
60 | return result
61 | }
62 |
63 | func NewValueItemFromMap(maps map[string]string) (r []ValueItem) {
64 | for name, value := range maps {
65 | r = append(r, ValueItem{
66 | Name: name,
67 | Value: value,
68 | })
69 | }
70 | return r
71 | }
72 |
73 | func DefaultCapabilities() []string {
74 | return []string{
75 | "CAP_CHOWN",
76 | "CAP_DAC_OVERRIDE",
77 | "CAP_FSETID",
78 | "CAP_FOWNER",
79 | "CAP_MKNOD",
80 | "CAP_NET_RAW",
81 | "CAP_SETGID",
82 | "CAP_SETUID",
83 | "CAP_SETFCAP",
84 | "CAP_SETPCAP",
85 | "CAP_NET_BIND_SERVICE",
86 | "CAP_SYS_CHROOT",
87 | "CAP_KILL",
88 | "CAP_AUDIT_WRITE",
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/common/service/exec/command.go:
--------------------------------------------------------------------------------
1 | package exec
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "github.com/creack/pty"
7 | "io"
8 | "log/slog"
9 | "os"
10 | "os/exec"
11 | "runtime"
12 | )
13 |
14 | var cmd *exec.Cmd
15 |
16 | func New(opts ...Option) (*Command, error) {
17 | var err error
18 | c := &Command{
19 | cmd: &exec.Cmd{},
20 | }
21 |
22 | for _, opt := range opts {
23 | err = opt(c)
24 | if err != nil {
25 | return nil, err
26 | }
27 | }
28 |
29 | slog.Debug("run command", "args", c.cmd.Args, "env", c.cmd.Env)
30 |
31 | if c.cmd.Cancel == nil {
32 | // 没有配置超时时间,则先杀掉上一个进程
33 | // 如果配置了超时时间,自行处理命令的终止问题,不会在下次执行命令时被清理掉
34 | _ = Kill()
35 | cmd = c.cmd
36 | }
37 |
38 | return c, nil
39 | }
40 |
41 | type Command struct {
42 | cmd *exec.Cmd
43 | }
44 |
45 | func (self Command) RunInTerminal(size *pty.Winsize) (io.ReadCloser, error) {
46 | var out *os.File
47 | var err error
48 |
49 | if runtime.GOOS == "windows" {
50 | // 不支持 Pty,利用管道模拟读取
51 | return self.RunInPip()
52 | }
53 |
54 | out, err = pty.StartWithSize(self.cmd, size)
55 | if err != nil {
56 | return nil, err
57 | }
58 | return TerminalResult{
59 | Conn: out,
60 | cmd: self.cmd,
61 | }, err
62 | }
63 |
64 | func (self Command) RunInPip() (stdout io.ReadCloser, err error) {
65 | stdout, err = self.cmd.StdoutPipe()
66 | if err != nil {
67 | return nil, err
68 | }
69 | self.cmd.Stderr = self.cmd.Stdout
70 | if err = self.cmd.Start(); err != nil {
71 | return nil, err
72 | }
73 | return stdout, nil
74 | }
75 |
76 | func (self Command) Run() (io.Reader, error) {
77 | out := new(bytes.Buffer)
78 | self.cmd.Stdout = out
79 | self.cmd.Stderr = out
80 | err := self.cmd.Run()
81 | if err != nil {
82 | return nil, errors.Join(err, errors.New(out.String()))
83 | }
84 | return out, nil
85 | }
86 |
87 | func (self Command) RunWithResult() string {
88 | out, err := self.cmd.CombinedOutput()
89 | if err != nil {
90 | slog.Debug("run command with result", "arg", self.cmd.Args, "error", err.Error())
91 | return string(out)
92 | }
93 | return string(out)
94 | }
95 |
96 | func Kill() error {
97 | var err error
98 |
99 | if cmd != nil && cmd.Process != nil && cmd.Process.Pid > 0 {
100 | err = cmd.Process.Kill()
101 | if err == nil {
102 | _, _ = cmd.Process.Wait()
103 | }
104 | slog.Debug("run command kill global cmd", "pid", cmd.Process.Pid, "name", cmd.String(), "error", err)
105 | }
106 | return nil
107 | }
108 |
109 | func (self Command) Cmd() *exec.Cmd {
110 | return self.cmd
111 | }
112 |
113 | func (self Command) Close() {
114 | var err error
115 | slog.Debug("run command kill self cmd", "cmd", self.cmd)
116 | if self.cmd != nil && self.cmd.Process != nil && self.cmd.Process.Pid > 0 {
117 | err = self.cmd.Process.Kill()
118 | if err == nil {
119 | _, _ = self.cmd.Process.Wait()
120 | }
121 | slog.Debug("run command kill self cmd", "pid", self.cmd.Process.Pid, "name", self.cmd.String(), "error", err)
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/common/service/exec/option.go:
--------------------------------------------------------------------------------
1 | package exec
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "github.com/donknap/dpanel/common/function"
7 | "os/exec"
8 | )
9 |
10 | type Option func(command *Command) error
11 |
12 | func WithArgs(args ...string) Option {
13 | return func(self *Command) error {
14 | if self.cmd.Path == "" {
15 | self.cmd = exec.Command(args[0], args[1:]...)
16 | } else {
17 | self.cmd = exec.Command(self.cmd.Path, args...)
18 | }
19 | return nil
20 | }
21 | }
22 |
23 | func WithCommandName(commandName string) Option {
24 | return func(self *Command) error {
25 | if commandName == "" {
26 | return nil
27 | }
28 | if function.IsEmptyArray(self.cmd.Args) {
29 | self.cmd = exec.Command(commandName)
30 | } else {
31 | self.cmd = exec.Command(commandName, self.cmd.Args...)
32 | }
33 | return nil
34 | }
35 | }
36 |
37 | func WithDir(dir string) Option {
38 | return func(self *Command) error {
39 | if dir == "" {
40 | return nil
41 | }
42 | self.cmd.Dir = dir
43 | return nil
44 | }
45 | }
46 |
47 | func WithEnv(env []string) Option {
48 | return func(self *Command) error {
49 | self.cmd.Env = env
50 | return nil
51 | }
52 | }
53 |
54 | func WithCtx(ctx context.Context) Option {
55 | return func(self *Command) error {
56 | if function.IsEmptyArray(self.cmd.Args) {
57 | return errors.New("invalid arguments")
58 | }
59 | newCmd := exec.CommandContext(ctx, self.cmd.Args[0], self.cmd.Args[1:]...)
60 | newCmd.Env = self.cmd.Env
61 | newCmd.Dir = self.cmd.Dir
62 | self.cmd = newCmd
63 | return nil
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/common/service/exec/result.go:
--------------------------------------------------------------------------------
1 | package exec
2 |
3 | import (
4 | "log/slog"
5 | "os"
6 | "os/exec"
7 | )
8 |
9 | type TerminalResult struct {
10 | Conn *os.File
11 | cmd *exec.Cmd
12 | }
13 |
14 | func (self TerminalResult) Close() error {
15 | if self.Conn != nil {
16 | err := self.Conn.Close()
17 | if err != nil {
18 | slog.Debug("terminal result close", "error", err.Error())
19 | }
20 | }
21 | if self.cmd != nil {
22 | err := self.cmd.Process.Kill()
23 | if err != nil {
24 | slog.Debug("terminal result close", "error", err.Error())
25 | } else {
26 | return self.cmd.Wait()
27 | }
28 | }
29 | return nil
30 | }
31 |
32 | func (self TerminalResult) Read(p []byte) (n int, err error) {
33 | return self.Conn.Read(p)
34 | }
35 |
--------------------------------------------------------------------------------
/common/service/family/ce.go:
--------------------------------------------------------------------------------
1 | //go:build !pe && !ee && !xk
2 |
3 | package family
4 |
5 | import (
6 | "github.com/donknap/dpanel/common/function"
7 | "github.com/donknap/dpanel/common/types"
8 | "github.com/gin-gonic/gin"
9 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server"
10 | "log/slog"
11 | )
12 |
13 | type Provider struct {
14 | }
15 |
16 | func (self Provider) Register(httpServer *server.Server) {
17 | slog.Debug("provider load community edition")
18 | httpServer.RegisterRouters(func(engine *gin.Engine) {
19 | engine.POST("/api/pro/*path", notSupportedApi)
20 | })
21 | }
22 |
23 | func (self Provider) Feature() []string {
24 | return []string{
25 | types.FeatureFamilyCe,
26 | }
27 | }
28 |
29 | func (self Provider) Check(name string) bool {
30 | return function.InArray(self.Feature(), name)
31 | }
32 |
--------------------------------------------------------------------------------
/common/service/family/ee.go:
--------------------------------------------------------------------------------
1 | //go:build ee
2 |
3 | package family
4 |
5 | import (
6 | "github.com/donknap/dpanel/app/pro/ee"
7 | "github.com/donknap/dpanel/common/function"
8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server"
9 | "log/slog"
10 | )
11 |
12 | type Provider struct {
13 | }
14 |
15 | func (self *Provider) Register(httpServer *server.Server) {
16 | slog.Debug("provider load enterprise edition")
17 | new(ee.Provider).Register(httpServer)
18 | }
19 |
20 | func (self Provider) Feature() []string {
21 | return new(ee.Provider).Feature()
22 | }
23 |
24 | func (self Provider) Check(name string) bool {
25 | return function.InArray(self.Feature(), name)
26 | }
27 |
--------------------------------------------------------------------------------
/common/service/family/pe.go:
--------------------------------------------------------------------------------
1 | //go:build pe
2 |
3 | package family
4 |
5 | import (
6 | "github.com/donknap/dpanel/app/pro/pe"
7 | "github.com/donknap/dpanel/common/function"
8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server"
9 | "log/slog"
10 | )
11 |
12 | type Provider struct {
13 | }
14 |
15 | func (self *Provider) Register(httpServer *server.Server) {
16 | slog.Debug("provider load professional edition")
17 | new(pe.Provider).Register(httpServer)
18 | }
19 |
20 | func (self Provider) Feature() []string {
21 | return new(pe.Provider).Feature()
22 | }
23 |
24 | func (self Provider) Check(name string) bool {
25 | return function.InArray(self.Feature(), name)
26 | }
27 |
--------------------------------------------------------------------------------
/common/service/family/util.go:
--------------------------------------------------------------------------------
1 | package family
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/function"
5 | "github.com/gin-gonic/gin"
6 | )
7 |
8 | func notSupportedApi(http *gin.Context) {
9 | http.JSON(500, map[string]interface{}{
10 | "error": function.ErrorMessage(".proLicenseFileIsCorrect").Error(),
11 | "code": 500,
12 | })
13 | http.Abort()
14 | return
15 | }
16 |
--------------------------------------------------------------------------------
/common/service/family/xk.go:
--------------------------------------------------------------------------------
1 | //go:build xk
2 |
3 | package family
4 |
5 | import (
6 | "github.com/donknap/dpanel/app/pro/xk"
7 | "github.com/donknap/dpanel/common/function"
8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server"
9 | "log/slog"
10 | )
11 |
12 | type Provider struct {
13 | }
14 |
15 | func (self *Provider) Register(httpServer *server.Server) {
16 | slog.Debug("provider load xk edition")
17 | new(xk.Provider).Register(httpServer)
18 | }
19 |
20 | func (self Provider) Feature() []string {
21 | return new(xk.Provider).Feature()
22 | }
23 |
24 | func (self Provider) Check(name string) bool {
25 | return function.InArray(self.Feature(), name)
26 | }
27 |
--------------------------------------------------------------------------------
/common/service/fs/dockerfs/file.go:
--------------------------------------------------------------------------------
1 | package dockerfs
2 |
3 | import (
4 | "errors"
5 | "github.com/spf13/afero/mem"
6 | "os"
7 | )
8 |
9 | type File struct {
10 | info os.FileInfo
11 | fd *mem.File
12 | fs *Fs
13 | }
14 |
15 | func (self *File) Close() error {
16 | if self.fd == nil {
17 | return nil
18 | }
19 | return self.fd.Close()
20 | }
21 |
22 | func (self *File) Read(p []byte) (n int, err error) {
23 | if self.fd == nil {
24 | return 0, &os.PathError{Op: "close", Path: self.info.Name(), Err: nil}
25 | }
26 | return self.fd.Read(p)
27 | }
28 |
29 | func (self *File) ReadAt(p []byte, off int64) (n int, err error) {
30 | return self.fd.ReadAt(p, off)
31 | }
32 |
33 | func (self *File) Seek(offset int64, whence int) (int64, error) {
34 | return self.fd.Seek(offset, whence)
35 | }
36 |
37 | func (self *File) Write(p []byte) (n int, err error) {
38 | return self.fd.Write(p)
39 | }
40 |
41 | func (self *File) WriteAt(p []byte, off int64) (n int, err error) {
42 | return self.fd.WriteAt(p, off)
43 | }
44 |
45 | func (self *File) Name() string {
46 | return self.fd.Name()
47 | }
48 |
49 | func (self *File) Readdir(count int) ([]os.FileInfo, error) {
50 | if self.info.Mode().IsRegular() {
51 | return nil, errors.New("targe is not a directory")
52 | }
53 | return self.fs.readDirFromContainer(self.info.Name())
54 | }
55 |
56 | func (self *File) Readdirnames(n int) ([]string, error) {
57 | return self.fd.Readdirnames(n)
58 | }
59 |
60 | func (self *File) Stat() (os.FileInfo, error) {
61 | return self.fd.Stat()
62 | }
63 |
64 | func (self *File) Sync() error {
65 | return self.fd.Sync()
66 | }
67 |
68 | func (self *File) Truncate(size int64) error {
69 | return self.fd.Truncate(size)
70 | }
71 |
72 | func (self *File) WriteString(s string) (ret int, err error) {
73 | return self.fd.WriteString(s)
74 | }
75 |
--------------------------------------------------------------------------------
/common/service/fs/dockerfs/option.go:
--------------------------------------------------------------------------------
1 | package dockerfs
2 |
3 | import "github.com/donknap/dpanel/common/service/docker"
4 |
5 | type Option func(self *Fs) error
6 |
7 | func WithTargetContainer(name, root string) Option {
8 | return func(self *Fs) error {
9 | self.targetContainerRootPath = root
10 | self.targetContainerName = name
11 | return nil
12 | }
13 | }
14 |
15 | func WithProxyContainer(name string) Option {
16 | return func(self *Fs) error {
17 | self.proxyContainerName = name
18 | return nil
19 | }
20 | }
21 |
22 | func WithDockerSdk(sdk *docker.Builder) Option {
23 | return func(self *Fs) error {
24 | self.sdk = sdk
25 | return nil
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/common/service/fs/fs.go:
--------------------------------------------------------------------------------
1 | package fs
2 |
3 | import (
4 | "fmt"
5 | logic2 "github.com/donknap/dpanel/app/common/logic"
6 | "github.com/donknap/dpanel/common/function"
7 | "github.com/donknap/dpanel/common/service/docker"
8 | "github.com/donknap/dpanel/common/service/fs/dockerfs"
9 | "github.com/donknap/dpanel/common/service/plugin"
10 | "github.com/donknap/dpanel/common/service/ssh"
11 | "github.com/spf13/afero"
12 | "github.com/spf13/afero/sftpfs"
13 | )
14 |
15 | func NewContainerExplorer(containerName string) (*afero.Afero, error) {
16 | o := &afero.Afero{}
17 |
18 | // todo 判断一下是否可以直接使用主机 sftp ,从而替换掉代理容器
19 |
20 | explorerPlugin, err := plugin.NewPlugin(plugin.PluginExplorer, nil)
21 | if err != nil {
22 | return nil, err
23 | }
24 | pluginName, err := explorerPlugin.Create()
25 | if err != nil {
26 | return nil, err
27 | }
28 |
29 | containerInfo, err := docker.Sdk.Client.ContainerInspect(docker.Sdk.Ctx, containerName)
30 | if err != nil {
31 | return nil, err
32 | }
33 | if containerInfo.State.Pid == 0 {
34 | return nil, fmt.Errorf("the %s container does not exist or is not running", containerName)
35 | }
36 |
37 | dfs, err := dockerfs.New(
38 | dockerfs.WithDockerSdk(docker.Sdk),
39 | dockerfs.WithProxyContainer(pluginName),
40 | dockerfs.WithTargetContainer(containerName, fmt.Sprintf("/proc/%d/root", containerInfo.State.Pid)),
41 | )
42 | if err != nil {
43 | return nil, err
44 | }
45 | o.Fs = dfs
46 | return o, nil
47 | }
48 |
49 | func NewSshExplorer(dockerEnvName string) (*ssh.Client, *afero.Afero, error) {
50 | dockerEnv, err := logic2.DockerEnv{}.GetEnvByName(dockerEnvName)
51 | if err != nil {
52 | return nil, nil, err
53 | }
54 | if !dockerEnv.EnableSSH || dockerEnv.SshServerInfo == nil {
55 | return nil, nil, function.ErrorMessage(".commonDataNotFoundOrDeleted")
56 | }
57 | option := []ssh.Option{
58 | ssh.WithSftpClient(),
59 | }
60 | option = append(option, ssh.WithServerInfo(dockerEnv.SshServerInfo)...)
61 | sshClient, err := ssh.NewClient(option...)
62 | if err != nil {
63 | return nil, nil, err
64 | }
65 | afs := &afero.Afero{
66 | Fs: sftpfs.New(sshClient.SftpConn),
67 | }
68 | return sshClient, afs, nil
69 | }
70 |
--------------------------------------------------------------------------------
/common/service/notice/message.go:
--------------------------------------------------------------------------------
1 | package notice
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/donknap/dpanel/common/dao"
7 | "github.com/donknap/dpanel/common/entity"
8 | "github.com/donknap/dpanel/common/function"
9 | "runtime"
10 | "time"
11 | )
12 |
13 | var (
14 | QueueNoticePushMessage = make(chan *entity.Notice)
15 | )
16 |
17 | const (
18 | TypeError = "error"
19 | TypeInfo = "info"
20 | TypeSuccess = "success"
21 | )
22 |
23 | type Message struct {
24 | }
25 |
26 | func (self Message) Error(title string, message ...string) error {
27 | return self.push(TypeError, title, message)
28 | }
29 |
30 | func (self Message) Info(title string, message ...string) error {
31 | return self.push(TypeInfo, title, message)
32 | }
33 |
34 | func (self Message) Success(title string, message ...string) error {
35 | return self.push(TypeSuccess, title, message)
36 | }
37 |
38 | func (self Message) push(level string, title string, message []string) error {
39 | jsonMessage, _ := json.Marshal(message)
40 | row := &entity.Notice{
41 | Title: title,
42 | Message: string(jsonMessage),
43 | Type: level,
44 | CreatedAt: time.Now().Local(),
45 | }
46 | err := dao.Notice.Create(row)
47 | fmt.Printf("协程数,%v \n", runtime.NumGoroutine())
48 | QueueNoticePushMessage <- row
49 | return err
50 | }
51 |
52 | func (self Message) New(title string, message ...string) error {
53 | return function.ErrorMessage(title, message...)
54 | }
55 |
--------------------------------------------------------------------------------
/common/service/plugin/wrapper.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "github.com/donknap/dpanel/common/service/docker"
5 | )
6 |
7 | type Wrapper struct {
8 | }
9 |
10 | type pluginItem struct {
11 | Name string `json:"name"`
12 | IsCreated bool `json:"isCreated"`
13 | ContainerId string `json:"containerId"`
14 | }
15 |
16 | func (self Wrapper) GetPluginList() map[string]pluginItem {
17 | pluginList := []string{
18 | PluginExplorer, PluginBackup,
19 | }
20 | r := make(map[string]pluginItem)
21 | for _, name := range pluginList {
22 | containerRow, err := docker.Sdk.Client.ContainerInspect(docker.Sdk.Ctx, "dpanel-plugin-"+name)
23 | if err == nil {
24 | r[name] = pluginItem{
25 | Name: name,
26 | IsCreated: true,
27 | ContainerId: containerRow.ID,
28 | }
29 | } else {
30 | r[name] = pluginItem{
31 | Name: name,
32 | IsCreated: false,
33 | }
34 | }
35 | }
36 | return r
37 | }
38 |
--------------------------------------------------------------------------------
/common/service/registry/option.go:
--------------------------------------------------------------------------------
1 | package registry
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "strings"
7 | "time"
8 | )
9 |
10 | type Option func(*Registry)
11 |
12 | func WithCredentials(username, password string) Option {
13 | return func(registry *Registry) {
14 | registry.authString = base64.StdEncoding.EncodeToString([]byte(
15 | fmt.Sprintf("%s:%s",
16 | username, password,
17 | )),
18 | )
19 | }
20 | }
21 |
22 | func WithCredentialsString(auth string) Option {
23 | return func(registry *Registry) {
24 | if auth != "" {
25 | registry.authString = auth
26 | }
27 | }
28 | }
29 |
30 | func WithRequestCacheTime(cacheTime time.Duration) Option {
31 | return func(registry *Registry) {
32 | registry.cacheTime = cacheTime
33 | }
34 | }
35 |
36 | func WithRegistryHost(host string) Option {
37 | return func(registry *Registry) {
38 | if strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(host, "http://"), "https://"), "/") == DefaultRegistryDomain {
39 | host = DefaultRegistryHost
40 | }
41 | registry.url = GetRegistryUrl(host)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/common/service/registry/repository.go:
--------------------------------------------------------------------------------
1 | package registry
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/mcuadros/go-version"
8 | "io"
9 | "net/http"
10 | "sort"
11 | )
12 |
13 | const ContentDigestHeader = "Docker-Content-Digest"
14 |
15 | type repository struct {
16 | registry *Registry
17 | }
18 |
19 | func (self repository) GetImageDigest(imageName string) (string, error) {
20 | imageDetail := GetImageTagDetail(imageName)
21 |
22 | u := self.registry.url.JoinPath(imageDetail.BaseName, "manifests", imageDetail.Version)
23 | req, err := http.NewRequest("HEAD", u.String(), nil)
24 | if err != nil {
25 | return "", err
26 | }
27 | req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
28 | req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.list.v2+json")
29 | req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v1+json")
30 | req.Header.Add("Accept", "application/vnd.oci.image.index.v1+json")
31 |
32 | res, err := self.registry.request(req, fmt.Sprintf(ScopeRepositoryPull, imageDetail.BaseName))
33 | if err != nil {
34 | return "", err
35 | }
36 | defer func() {
37 | _ = res.Body.Close()
38 | }()
39 |
40 | return res.Header.Get(ContentDigestHeader), nil
41 | }
42 |
43 | func (self repository) GetImageTagList(basename string) ([]string, error) {
44 | u := self.registry.url.JoinPath(basename, "tags", "list")
45 | //if limit > 0 {
46 | // u.RawQuery = fmt.Sprintf("n=%d", limit)
47 | //}
48 | req, _ := http.NewRequest("GET", u.String(), nil)
49 |
50 | res, err := self.registry.request(req, fmt.Sprintf(ScopeRepositoryPull, basename))
51 | if err != nil {
52 | return nil, err
53 | }
54 | defer res.Body.Close()
55 |
56 | buffer := new(bytes.Buffer)
57 | _, err = io.Copy(buffer, res.Body)
58 | if err != nil {
59 | return nil, err
60 | }
61 | result := ImageTagListResult{}
62 | err = json.Unmarshal(buffer.Bytes(), &result)
63 | if err != nil {
64 | return nil, err
65 | }
66 | sort.Slice(result.Tags, func(i, j int) bool {
67 | return version.CompareSimple(result.Tags[i], result.Tags[j]) == 1
68 | })
69 | return result.Tags, nil
70 | }
71 |
--------------------------------------------------------------------------------
/common/service/registry/types.go:
--------------------------------------------------------------------------------
1 | package registry
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "github.com/docker/docker/api/types/registry"
7 | "log/slog"
8 | "net/http"
9 | "strings"
10 | )
11 |
12 | const (
13 | DefaultRegistryDomain = "docker.io"
14 | DefaultRegistryHost = "index.docker.io"
15 | )
16 |
17 | type TokenResponse struct {
18 | Token string `json:"token"`
19 | AccessToken string `json:"access_token"`
20 | }
21 |
22 | // {registryUrl}/{namespace-可能有多个路径}/{imageName}:{version}
23 | type ImageTagDetail struct {
24 | Registry string
25 | Namespace string
26 | ImageName string
27 | Version string
28 | BaseName string
29 | }
30 |
31 | func (self ImageTagDetail) Uri() string {
32 | if self.Registry == "" {
33 | self.Registry = DefaultRegistryDomain
34 | }
35 | self.Registry = strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(self.Registry, "http://"), "https://"), "/")
36 | split := ":"
37 | if self.Namespace == "" {
38 | return fmt.Sprintf("%s/%s%s%s", self.Registry, self.ImageName, split, self.Version)
39 | } else {
40 | return fmt.Sprintf("%s/%s/%s%s%s", self.Registry, self.Namespace, self.ImageName, split, self.Version)
41 | }
42 | }
43 |
44 | type ImageTagListResult struct {
45 | Name string `json:"name"`
46 | Tags []string `json:"tags"`
47 | }
48 |
49 | type Config struct {
50 | Username string `json:"username"`
51 | Password string `json:"password"`
52 | Host string `json:"host"`
53 | Proxy []string `json:"proxy"`
54 | ExistsAuth bool `json:"existsAuth"`
55 | }
56 |
57 | func (self Config) GetAuthString() string {
58 | if self.Username == "" || self.Password == "" {
59 | authString, _ := registry.EncodeAuthConfig(registry.AuthConfig{
60 | Username: "",
61 | Password: "",
62 | })
63 | return authString
64 | }
65 | authString, err := registry.EncodeAuthConfig(registry.AuthConfig{
66 | Username: self.Username,
67 | Password: self.Password,
68 | })
69 | if err != nil {
70 | slog.Debug("get registry auth string", err.Error())
71 | return ""
72 | }
73 | return authString
74 | }
75 |
76 | func (self Config) GetRegistryAuthString() string {
77 | if self.Username == "" || self.Password == "" {
78 | return ""
79 | }
80 | return base64.StdEncoding.EncodeToString([]byte(
81 | fmt.Sprintf("%s:%s",
82 | self.Username, self.Password,
83 | )),
84 | )
85 | }
86 |
87 | type cacheItem struct {
88 | header http.Header
89 | body []byte
90 | }
91 |
--------------------------------------------------------------------------------
/common/service/registry/util.go:
--------------------------------------------------------------------------------
1 | package registry
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "strings"
7 | )
8 |
9 | func GetImageTagDetail(tag string) *ImageTagDetail {
10 | tag = strings.TrimPrefix(strings.TrimPrefix(tag, "http://"), "https://")
11 | result := &ImageTagDetail{}
12 |
13 | // 如果没有指定仓库地址,则默认为 docker.io
14 | noRegistryUrl := false
15 | temp := strings.Split(tag, "/")
16 | if !strings.Contains(temp[0], ".") || len(temp) == 1 {
17 | noRegistryUrl = true
18 | tag = DefaultRegistryDomain + "/" + tag
19 | }
20 | temp = strings.Split(tag, "/")
21 | // 先补齐 registry 地址后再判断是否有标签,仓库地址中可能包含端口号
22 | if !strings.Contains(strings.Join(temp[1:], "/"), ":") {
23 | tag += ":latest"
24 | }
25 | temp = strings.Split(tag, "/")
26 | result.Registry = temp[0]
27 |
28 | name := strings.Split(temp[len(temp)-1], ":")
29 | result.ImageName, result.Version = name[0], strings.Join(name[1:], ":")
30 |
31 | // 兼容使用 digest 标识版本号的情况
32 | if strings.Contains(result.Version, "@") {
33 | //result.Version = strings.Split(result.Version, "@")[1]
34 | }
35 |
36 | if len(temp) <= 2 {
37 | if noRegistryUrl {
38 | result.Namespace = "library"
39 | }
40 | } else {
41 | result.Namespace = strings.Join(temp[1:len(temp)-1], "/")
42 | }
43 | if result.Namespace != "" {
44 | result.BaseName = fmt.Sprintf("%s/%s", result.Namespace, result.ImageName)
45 | } else {
46 | result.BaseName = result.ImageName
47 | }
48 |
49 | return result
50 | }
51 |
52 | func GetRegistryUrl(address string) url.URL {
53 | host := strings.TrimSuffix(address, "/")
54 | protocol := "https"
55 |
56 | parseUrl, err := url.Parse(host)
57 | if err == nil && parseUrl.Host != "" {
58 | host = parseUrl.Host
59 | protocol = parseUrl.Scheme
60 | }
61 |
62 | return url.URL{
63 | Scheme: protocol,
64 | Host: host,
65 | Path: "/v2/",
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/common/service/ssh/host.go:
--------------------------------------------------------------------------------
1 | package ssh
2 |
3 | import (
4 | "errors"
5 | "golang.org/x/crypto/ssh"
6 | "golang.org/x/crypto/ssh/knownhosts"
7 | "net"
8 | "os"
9 | )
10 |
11 | var keyErr *knownhosts.KeyError
12 |
13 | type DefaultKnownHostsCallback struct {
14 | path string
15 | }
16 |
17 | func (self DefaultKnownHostsCallback) Handler(hostname string, remote net.Addr, key ssh.PublicKey) error {
18 | var ok bool
19 | var err error
20 |
21 | // 如果找到了并且有错才表示有问题,否则正常添加 host
22 | if ok, err = self.check(hostname, remote, key); ok && err != nil {
23 | return err
24 | }
25 | if !ok {
26 | err = self.add(hostname, remote, key)
27 | if err != nil {
28 | return err
29 | }
30 | }
31 | return nil
32 | }
33 |
34 | func (self DefaultKnownHostsCallback) check(hostname string, remote net.Addr, key ssh.PublicKey) (found bool, err error) {
35 | if _, err = os.Stat(self.path); err != nil {
36 | _, _ = os.Create(self.path)
37 | }
38 |
39 | callback, err := knownhosts.New(self.path)
40 | if err != nil {
41 | return false, err
42 | }
43 | err = callback(hostname, remote, key)
44 | if err == nil {
45 | return true, nil
46 | }
47 | // Make sure that the error returned from the callback is host not in file error.
48 | // If keyErr.Want is greater than 0 length, that means host is in file with different key.
49 | if errors.As(err, &keyErr) && len(keyErr.Want) > 0 {
50 | return true, keyErr
51 | }
52 | if err != nil {
53 | return false, err
54 | }
55 | return false, nil
56 | }
57 |
58 | func (self DefaultKnownHostsCallback) add(hostname string, remote net.Addr, key ssh.PublicKey) error {
59 | var err error
60 | f, err := os.OpenFile(self.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
61 | if err != nil {
62 | return err
63 | }
64 | defer func() {
65 | _ = f.Close()
66 | }()
67 | remoteNormalized := knownhosts.Normalize(remote.String())
68 | hostNormalized := knownhosts.Normalize(hostname)
69 | addresses := []string{remoteNormalized}
70 |
71 | if hostNormalized != remoteNormalized {
72 | addresses = append(addresses, hostNormalized)
73 | }
74 | _, err = f.WriteString(knownhosts.Line(addresses, key) + "\n")
75 | return err
76 | }
77 |
--------------------------------------------------------------------------------
/common/service/ssh/option.go:
--------------------------------------------------------------------------------
1 | package ssh
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/sftp"
6 | "golang.org/x/crypto/ssh"
7 | "strings"
8 | )
9 |
10 | func WithAuthBasic(username, password string) Option {
11 | return func(self *Client) error {
12 | self.sshClientConfig.User = username
13 | self.sshClientConfig.Auth = []ssh.AuthMethod{
14 | ssh.Password(password),
15 | }
16 | return nil
17 | }
18 | }
19 |
20 | func WithAuthPem(username string, privateKeyPem string, password string) Option {
21 | return func(self *Client) error {
22 | var signer ssh.Signer
23 | var err error
24 | self.sshClientConfig.User = username
25 | if password != "" {
26 | signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKeyPem), []byte(password))
27 | } else {
28 | signer, err = ssh.ParsePrivateKey([]byte(privateKeyPem))
29 | }
30 | if err != nil {
31 | return err
32 | }
33 | self.sshClientConfig.Auth = []ssh.AuthMethod{
34 | ssh.PublicKeys(signer),
35 | }
36 | return nil
37 | }
38 | }
39 |
40 | func WithAddress(address string, port int) Option {
41 | return func(self *Client) error {
42 | if strings.Contains(address, ":") {
43 | address = strings.Trim(strings.Trim(address, "["), "]")
44 | self.address = fmt.Sprintf("[%s]:%d", address, port)
45 | self.protocol = "tcp6"
46 | } else {
47 | self.address = fmt.Sprintf("%s:%d", address, port)
48 | self.protocol = "tcp"
49 | }
50 | return nil
51 | }
52 | }
53 |
54 | func WithServerInfo(info *ServerInfo) []Option {
55 | option := make([]Option, 0)
56 | option = append(option, WithAddress(info.Address, info.Port))
57 | if info.AuthType == SshAuthTypePem {
58 | option = append(option, WithAuthPem(info.Username, info.PrivateKey, info.Password))
59 | } else if info.AuthType == SshAuthTypeBasic {
60 | option = append(option, WithAuthBasic(info.Username, info.Password))
61 | }
62 | return option
63 | }
64 |
65 | func WithSftpClient() Option {
66 | return func(self *Client) error {
67 | self.SftpConn = &sftp.Client{}
68 | return nil
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/common/service/ssh/types.go:
--------------------------------------------------------------------------------
1 | package ssh
2 |
3 | import (
4 | "context"
5 | "github.com/pkg/sftp"
6 | "golang.org/x/crypto/ssh"
7 | )
8 |
9 | const (
10 | SshAuthTypeBasic = "basic"
11 | SshAuthTypePem = "pem"
12 | )
13 |
14 | type Client struct {
15 | Conn *ssh.Client
16 | SftpConn *sftp.Client
17 | ctx context.Context
18 | ctxCancel context.CancelFunc
19 | sshClientConfig *ssh.ClientConfig
20 | address string
21 | protocol string // 连接协议,tcp or tpc6
22 | }
23 | type Option func(self *Client) error
24 |
25 | type ServerInfo struct {
26 | Username string `json:"username,omitempty"`
27 | Password string `json:"password,omitempty"`
28 | PrivateKey string `json:"privateKey,omitempty"`
29 | Address string `json:"address,omitempty"`
30 | Port int `json:"port,omitempty"`
31 | AuthType string `json:"authType,omitempty"`
32 | }
33 |
--------------------------------------------------------------------------------
/common/service/storage/cache.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "github.com/patrickmn/go-cache"
5 | "time"
6 | )
7 |
8 | var (
9 | CacheKeyImageRemoteList = "image:remoteTag:%s"
10 | CacheKeyExplorerUsername = "explorer:%s:uid:%d"
11 | CacheKeyCommonUserInfo = "user:%d"
12 | )
13 |
14 | var (
15 | Cache = cache.New(cache.DefaultExpiration, 5*time.Minute)
16 | )
17 |
--------------------------------------------------------------------------------
/common/service/ws/option.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | func WithMessageRecvHandler(messageType string, call RecvMessageHandlerFn) Option {
4 | return func(self *Client) error {
5 | if self.recvMessageHandler == nil {
6 | self.recvMessageHandler = make(map[string]RecvMessageHandlerFn)
7 | }
8 | if call != nil {
9 | self.recvMessageHandler[messageType] = call
10 | }
11 | return nil
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/common/service/ws/types.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "time"
7 | )
8 |
9 | const (
10 | MessageTypeEvent = "event"
11 | MessageTypeEventFd = "event:fd"
12 | MessageTypeCompose = "compose:%s"
13 | MessageTypeComposeLog = "compose:log:%s"
14 | MessageTypeConsole = "/console/container/%s"
15 | MessageTypeConsoleHost = "/console/host/%s"
16 | MessageTypeContainerLog = "container:log:%s"
17 | MessageTypeContainerAllStat = "container:stat"
18 | MessageTypeContainerStat = "container:stat:%s"
19 | MessageTypeContainerBackup = "container:backup:%d"
20 | MessageTypeImagePull = "image:pull:%s"
21 | MessageTypeImageBuild = "image:build:%d"
22 | MessageTypeImageImport = "image:import"
23 | MessageTypeProgressClose = "progress:close"
24 | MessageTypeDomainApply = "domain:apply"
25 | MessageTypeUserPermission = "user:permission:%s"
26 | )
27 |
28 | type RespMessage struct {
29 | Fd string `json:"fd"`
30 | Type string `json:"type"`
31 | Data interface{} `json:"data"`
32 | RespAt time.Time `json:"respAt,omitempty"`
33 | }
34 |
35 | func (self RespMessage) ToJson() []byte {
36 | jsonStr, _ := json.Marshal(self)
37 | return jsonStr
38 | }
39 |
40 | type RecvMessage struct {
41 | Fd string `json:"fd"` // 发送消息id
42 | Message []byte `json:"message"`
43 | RecvAt int64 `json:"recv_at"`
44 | Type int `json:"type"` // 消息类型
45 | }
46 |
47 | func (self RecvMessage) IsPing() bool {
48 | if bytes.Equal(self.Message, []byte("ping")) || bytes.Equal(self.Message, []byte("pong")) {
49 | return true
50 | } else {
51 | return false
52 | }
53 | }
54 |
55 | type recvMessageContent struct {
56 | Type string
57 | }
58 |
59 | type RecvMessageHandlerFn func(message *RecvMessage)
60 |
61 | type SendMessageQueue chan *RespMessage // 普通队列,有数据即推送客户端
62 |
63 | type Option func(self *Client) error
64 |
--------------------------------------------------------------------------------
/common/types/define/message.go:
--------------------------------------------------------------------------------
1 | package define
2 |
3 | import "github.com/donknap/dpanel/common/function"
4 |
5 | var (
6 | ErrorMessageCommonDataNotFoundOrDeleted = function.ErrorMessage(".commonDataNotFoundOrDeleted")
7 | ErrorMessageCommonNotFoundDPanel = function.ErrorMessage(".commonNotFoundDPanel")
8 | )
9 |
--------------------------------------------------------------------------------
/common/types/event/Image_registry.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "context"
5 | "github.com/donknap/dpanel/common/entity"
6 | )
7 |
8 | var ImageRegistryCreateEvent = "image_registry_create"
9 | var ImageRegistryDeleteEvent = "image_registry_delete"
10 | var ImageRegistryEditEvent = "image_registry_edit"
11 |
12 | type ImageRegistryPayload struct {
13 | Registry *entity.Registry
14 | OldRegistry *entity.Registry
15 | Ctx context.Context
16 | }
17 |
--------------------------------------------------------------------------------
/common/types/event/compose.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "context"
5 | "github.com/donknap/dpanel/common/entity"
6 | )
7 |
8 | var ComposeCreateEvent = "compose_create"
9 | var ComposeDeleteEvent = "compose_delete"
10 |
11 | type ComposePayload struct {
12 | Compose *entity.Compose
13 | Ctx context.Context
14 | }
15 |
--------------------------------------------------------------------------------
/common/types/event/container.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "context"
5 | "github.com/docker/docker/api/types/container"
6 | )
7 |
8 | var ContainerCreateEvent = "container_create"
9 | var ContainerDeleteEvent = "container_delete"
10 | var ContainerEditEvent = "container_edit"
11 |
12 | type ContainerPayload struct {
13 | InspectInfo *container.InspectResponse
14 | OldInspectInfo *container.InspectResponse
15 | Ctx context.Context
16 | }
17 |
--------------------------------------------------------------------------------
/common/types/event/env.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import "context"
4 |
5 | var EnvDeleteEvent = "env_delete"
6 |
7 | type EnvPayload struct {
8 | Name string
9 | Ctx context.Context
10 | }
11 |
--------------------------------------------------------------------------------
/common/types/event/permission.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import "github.com/donknap/dpanel/common/accessor"
4 |
5 | var DataPermissionAddEvent = "data_permission_add"
6 | var DataPermissionDeleteEvent = "data_permission_delete"
7 | var DataPermissionEditEvent = "data_permission_edit"
8 |
9 | type DataPermissionAddPayload struct {
10 | Usernames []string
11 | Permission accessor.DataPermissionItem
12 | PermissionType string
13 | Append bool
14 | }
15 |
16 | type DataPermissionDeletePayload struct {
17 | Username string
18 | Permission accessor.DataPermissionItem
19 | }
20 |
21 | type DataPermissionEditPayload struct {
22 | Username string
23 | Permission accessor.DataPermissionItem
24 | OldPermission accessor.DataPermissionItem
25 | }
26 |
--------------------------------------------------------------------------------
/common/types/event/store.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "context"
5 | "github.com/donknap/dpanel/common/entity"
6 | )
7 |
8 | var StoreDeleteEvent = "store_delete"
9 |
10 | type StorePayload struct {
11 | Store *entity.Store
12 | Ctx context.Context
13 | }
14 |
--------------------------------------------------------------------------------
/common/types/family.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | const (
4 | FeatureFamilyPe = "canSeeEnablePe"
5 | FeatureFamilyEe = "canSeeEnableEe"
6 | FeatureFamilyCe = "canSeeEnableCe"
7 | FeatureFamilyXk = "canSeeEnableXk"
8 | FeatureVersionEnvLite = "canSeeEnableVersionEnvLite"
9 | FeatureVersionEnvStandard = "canSeeEnableVersionEnvStandard"
10 | FeatureDockerEnvLocal = "canSeeEnableDockerEnvLocal"
11 | FeatureInDPanel = "canSeeEnableInDPanel"
12 | )
13 |
--------------------------------------------------------------------------------
/common/types/fs/file_info.go:
--------------------------------------------------------------------------------
1 | package fs
2 |
3 | import (
4 | "io/fs"
5 | "os"
6 | "time"
7 | )
8 |
9 | type ChangeType int
10 |
11 | const (
12 | ChangeDefault = -1
13 | ChangeModified = 0
14 | ChangeAdd = 1
15 | ChangeDeleted = 2
16 | ChangeVolume = 100
17 | )
18 |
19 | func NewFileInfo(data *FileData) os.FileInfo {
20 | return &FileInfo{
21 | stat: data,
22 | }
23 | }
24 |
25 | type FileData struct {
26 | Path string `json:"path"` // 完整的目录地址
27 | Name string `json:"name"` // 目录名
28 | Mod os.FileMode `json:"mod"`
29 | ModStr string `json:"modStr"` // 权限字符形式
30 | ModTime time.Time `json:"modTime"`
31 | Change ChangeType `json:"change"`
32 | Size int64 `json:"size"`
33 | User string `json:"user"`
34 | Group string `json:"group"`
35 | LinkName string `json:"linkName"`
36 | IsDir bool `json:"isDir"`
37 | IsSymlink bool `json:"isSymlink"`
38 | }
39 |
40 | func (self *FileData) CheckIsSymlink() bool {
41 | return (self.Mod & os.ModeSymlink) != 0
42 | }
43 |
44 | func (self *FileData) CheckIsBlockDevice() bool {
45 | return self.Mod&os.ModeDevice != 0 && self.Mod&os.ModeCharDevice == 0
46 | }
47 |
48 | type FileInfo struct {
49 | os.FileInfo
50 | stat *FileData
51 | }
52 |
53 | func (self *FileInfo) Name() string {
54 | return self.stat.Name
55 | }
56 |
57 | func (self *FileInfo) Size() int64 {
58 | return self.stat.Size
59 | }
60 |
61 | func (self *FileInfo) Mode() fs.FileMode {
62 | return self.stat.Mod
63 | }
64 |
65 | func (self *FileInfo) ModTime() time.Time {
66 | return self.stat.ModTime
67 | }
68 |
69 | func (self *FileInfo) IsDir() bool {
70 | return self.stat.Mod.IsDir()
71 | }
72 |
73 | func (self *FileInfo) Sys() any {
74 | return self.stat
75 | }
76 |
--------------------------------------------------------------------------------
/common/types/permission.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/docker/docker/api/types/image"
5 | "github.com/donknap/dpanel/common/service/registry"
6 | )
7 |
8 | type PermissionDataValue interface {
9 | GetPermissionDataValue(item any) interface{}
10 | }
11 |
12 | type ImgList []image.Summary
13 |
14 | func (list ImgList) GetPermissionDataValue(item any) interface{} {
15 | return registry.GetImageTagDetail(item.(image.Summary).RepoTags[0]).Registry
16 | }
17 |
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | app:
2 | name: ${APP_NAME-dpanel}
3 | version: ${APP_VERSION}
4 | env: ${APP_ENV-lite}
5 | family: ${APP_FAMILY-ce}
6 | server: http
7 | cors:
8 | - http://localhost:8000
9 | server:
10 | http:
11 | host: 0.0.0.0
12 | port: ${APP_SERVER_PORT-8086}
13 | log:
14 | default:
15 | driver: stack
16 | channels:
17 | - file
18 | - console
19 | file:
20 | driver: file
21 | path: run.log
22 | level: info
23 | console:
24 | driver: console
25 | level: debug
26 | database:
27 | default:
28 | driver: sqlite
29 | user_name: ${DB_USERNAME-root}
30 | password: ${DB_PASSWORD-123456}
31 | db_name: ${STORAGE_LOCAL_PATH}/dpanel.db
32 | charset: utf8mb4
33 | prefix: ims_
34 | options:
35 | mode: ${DB_MODE}
36 | storage:
37 | local:
38 | path: ${STORAGE_LOCAL_PATH}
39 | jwt:
40 | secret: ${DP_JWT_SECRET}
41 | common:
42 | public_user_name: ${PUBLIC_USERNAME-__public__}
--------------------------------------------------------------------------------
/database/gen-model.yaml:
--------------------------------------------------------------------------------
1 | config:
2 | tag_json_camel: lower
3 | relation:
4 | - table: ims_site
5 | column:
6 | env:
7 | serializer: json
8 | type: SiteEnvOption
9 | container_info:
10 | type: SiteContainerInfoOption
11 | serializer: json
12 | - table: ims_image
13 | column:
14 | image_info:
15 | type: ImageInfoOption
16 | setting:
17 | type: ImageSettingOption
18 | serializer: json
19 | - table: ims_setting
20 | column:
21 | value:
22 | serializer: json
23 | type: SettingValueOption
24 | - table: ims_registry
25 | column:
26 | setting:
27 | serializer: json
28 | type: RegistrySettingOption
29 | - table: ims_site_domain
30 | column:
31 | setting:
32 | type: SiteDomainSettingOption
33 | serializer: json
34 | - table: ims_backup
35 | column:
36 | setting:
37 | type: BackupSettingOption
38 | serializer: json
39 | - table: ims_event
40 | - table: ims_notice
41 | - table: ims_store
42 | column:
43 | setting:
44 | type: StoreSettingOption
45 | serializer: json
46 | - table: ims_compose
47 | column:
48 | setting:
49 | type: ComposeSettingOption
50 | serializer: json
51 | - table: ims_cron
52 | column:
53 | setting:
54 | type: CronSettingOption
55 | serializer: json
56 | - table: ims_cron_log
57 | column:
58 | value:
59 | type: CronLogValueOption
60 | serializer: json
61 | - table: ims_user_permission
62 | column:
63 | value:
64 | type: PermissionValueOption
65 | serializer: json
66 |
--------------------------------------------------------------------------------
/docker/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | NGINX_CONFIG_DIR="/dpanel/nginx"
4 |
5 | chmod 755 /app/server/dpanel && mkdir -p /dpanel/nginx/default_host /dpanel/nginx/proxy_host \
6 | /dpanel/nginx/redirection_host /dpanel/nginx/dead_host /dpanel/nginx/temp \
7 | /dpanel/cert /dpanel/storage
8 |
9 | reload_nginx() {
10 | echo "Reloading Nginx configuration..."
11 | nginx -s reload
12 | if [ $? -ne 0 ]; then
13 | echo "Failed to reload Nginx configuration."
14 | fi
15 | }
16 |
17 | #while true; do
18 | # inotifywait -r -e modify,create,delete,move "$NGINX_CONFIG_DIR"
19 | # reload_nginx
20 | #done &
21 |
22 | crond
23 | nginx
24 | /app/server/dpanel server:start -f /app/server/config.yaml > /dev/stdout
--------------------------------------------------------------------------------
/docker/nginx/dpanel.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen [::]:8080 default_server;
3 | listen 8080 default_server;
4 | server_name _;
5 |
6 | sendfile off;
7 | tcp_nodelay on;
8 | absolute_redirect off;
9 | client_max_body_size 1024M;
10 |
11 | location / {
12 | proxy_http_version 1.1;
13 | proxy_set_header Upgrade $http_upgrade;
14 | proxy_set_header Connection "upgrade";
15 | proxy_pass http://127.0.0.1:8086;
16 | }
17 |
18 | error_page 500 502 503 504 /50x.html;
19 | location = /50x.html {
20 | root /var/lib/nginx/html;
21 | }
22 |
23 | location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ {
24 | expires 5d;
25 | }
26 |
27 | location ~ /\. {
28 | log_not_found off;
29 | deny all;
30 | }
31 | }
--------------------------------------------------------------------------------
/docker/nginx/include/assets.conf:
--------------------------------------------------------------------------------
1 | location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
2 | if_modified_since off;
3 |
4 | # use the public cache
5 | proxy_cache public-cache;
6 | proxy_cache_key $host$request_uri;
7 |
8 | # ignore these headers for media
9 | proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires;
10 |
11 | # cache 200s and also 404s (not ideal but there are a few 404 images for some reason)
12 | proxy_cache_valid any 30m;
13 | proxy_cache_valid 404 1m;
14 |
15 | # strip this header to avoid If-Modified-Since requests
16 | proxy_hide_header Last-Modified;
17 | proxy_hide_header Cache-Control;
18 | proxy_hide_header Vary;
19 |
20 | proxy_cache_bypass 0;
21 | proxy_no_cache 0;
22 |
23 | proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_404;
24 | proxy_connect_timeout 5s;
25 | proxy_read_timeout 45s;
26 |
27 | expires @30m;
28 | access_log off;
29 | }
--------------------------------------------------------------------------------
/docker/nginx/include/challenge.conf:
--------------------------------------------------------------------------------
1 | # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
2 | # We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
3 | # other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
4 | location ^~ /.well-known/acme-challenge/ {
5 | # Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
6 | # we need to open up access by turning off auth and IP ACL for this location.
7 | auth_basic off;
8 | auth_request off;
9 | allow all;
10 |
11 | # Set correct content type. According to this:
12 | # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
13 | # Current specification requires "text/plain" or no content header at all.
14 | # It seems that "text/plain" is a safe option.
15 | default_type "text/plain";
16 |
17 | # This directory must be the same as in /etc/letsencrypt/cli.ini
18 | # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
19 | # there to "webroot".
20 | # Do NOT use alias, use root! Target directory is located here:
21 | # /var/www/common/letsencrypt/.well-known/acme-challenge/
22 | root /dpanel/challenge;
23 | }
24 |
25 | # Hide /acme-challenge subdirectory and return 404 on all requests.
26 | # It is somewhat more secure than letting Nginx return 403.
27 | # Ending slash is important!
28 | location = /.well-known/acme-challenge/ {
29 | return 404;
30 | }
31 |
--------------------------------------------------------------------------------
/docker/nginx/include/force-ssl.conf:
--------------------------------------------------------------------------------
1 | set $test "";
2 | if ($scheme = "http") {
3 | set $test "H";
4 | }
5 | if ($request_uri = /.well-known/acme-challenge/test-challenge) {
6 | set $test "${test}T";
7 | }
8 | if ($test = H) {
9 | return 301 https://$host$request_uri;
10 | }
11 |
--------------------------------------------------------------------------------
/docker/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes auto;
3 |
4 |
5 | error_log /dev/stdout warn;
6 | pid /var/run/nginx.pid;
7 |
8 | events {
9 | worker_connections 1024;
10 | }
11 |
12 | http {
13 | include /etc/nginx/mime.types;
14 | default_type application/octet-stream;
15 |
16 | sendfile on;
17 | server_tokens off;
18 | tcp_nopush on;
19 | tcp_nodelay on;
20 | client_body_temp_path /tmp/nginx/body 1 2;
21 | keepalive_timeout 90s;
22 | proxy_connect_timeout 90s;
23 | proxy_send_timeout 90s;
24 | proxy_read_timeout 90s;
25 | ssl_prefer_server_ciphers on;
26 | gzip on;
27 | proxy_ignore_client_abort off;
28 | client_max_body_size 2000m;
29 | server_names_hash_bucket_size 1024;
30 | proxy_http_version 1.1;
31 | proxy_set_header X-Forwarded-Scheme $scheme;
32 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
33 | proxy_set_header Accept-Encoding "";
34 | proxy_cache off;
35 | proxy_cache_path /var/lib/nginx/cache/public levels=1:2 keys_zone=public-cache:30m max_size=192m;
36 | proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m;
37 |
38 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
39 | '$status $body_bytes_sent "$http_referer" '
40 | '"$http_user_agent" "$http_x_forwarded_for"';
41 |
42 | access_log /var/log/nginx/access.log main;
43 |
44 | map $host $forward_scheme {
45 | default http;
46 | }
47 | # Local subnets:
48 | set_real_ip_from 10.0.0.0/8;
49 | set_real_ip_from 172.16.0.0/12; # Includes Docker subnet
50 | set_real_ip_from 192.168.0.0/16;
51 | real_ip_header X-Real-IP;
52 | real_ip_recursive on;
53 |
54 | include /etc/nginx/http.d/*.conf;
55 |
56 | include /dpanel/nginx/default_host/*.conf;
57 | include /dpanel/nginx/proxy_host/*.conf;
58 | include /dpanel/nginx/redirection_host/*.conf;
59 | include /dpanel/nginx/dead_host/*.conf;
60 | include /dpanel/nginx/temp/*.conf;
61 | }
--------------------------------------------------------------------------------
/docker/script/clear-upload/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: clear-upload
3 | descriptionZh: |
4 | 清理上传临时附件
5 | descriptionEn: |
6 | prune upload temp file
7 | script: |
8 | #!/bin/sh
9 | DIR="/dpanel/storage"
10 | EXPIRE_DAYS=3
11 | find "$DIR" -type f -name "dpanel-*" | while read -r file; do
12 | file_mtime=$(stat -c %Y "$file")
13 | current_time=$(date +%s)
14 | time_diff=$((current_time - file_mtime))
15 | timeout_seconds=$((EXPIRE_DAYS * 24 * 60 * 60))
16 | if [ "$time_diff" -gt "$timeout_seconds" ]; then
17 | echo "Deleting $file (older than $EXPIRE_DAYS days)"
18 | rm -f "$file"
19 | fi
20 | done
21 | tag:
22 | - dpanel
23 | - run-in-dpanel
--------------------------------------------------------------------------------
/docker/script/compose-upgrade/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: compose-upgrade
3 | descriptionZh: |
4 | 更新一个 compose 任务
5 | descriptionEn: |
6 | upgrade compose task
7 | script: |
8 | /app/server/dpanel compose:deploy -f /app/server/config.yaml --name=${COMPOSE_NAME} --pull-image ${PULl_IMAGE} --remove-orphans ${REMOVE_ORPHANS}
9 | tag:
10 | - dpanel
11 | - run-in-dpanel
12 | environment:
13 | - name: COMPOSE_NAME
14 | labelZh: compose 任务名称
15 | labelEn: compose task name
16 | required: true
17 | - name: PULl_IMAGE
18 | type: select
19 | required: true
20 | values:
21 | - label: 使用面板拉取
22 | value: "dpanel"
23 | - label: 使用命令拉取
24 | value: "command"
25 | labelZh: 拉取镜像方式
26 | labelEn: pull image type
27 | - name: REMOVE_ORPHANS
28 | type: select
29 | required: true
30 | values:
31 | - label: 清理
32 | value: "1"
33 | - label: 保留
34 | value: "0"
35 | labelZh: 清理失效的容器
36 | labelEn: pull image type
--------------------------------------------------------------------------------
/docker/script/container-backup/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: container-backup
3 | descriptionZh: |
4 | 生成容器快照,支持备份容器配置存储,一键恢复。
5 | descriptionEn: |
6 | Create container snapshot
7 | script: |
8 | /app/server/dpanel container:backup -f /app/server/config.yaml --name=${CONTAINER_NAME} --enable-image ${ENABLE_IMAGE}
9 | tag:
10 | - dpanel
11 | - run-in-dpanel
12 | environment:
13 | - name: CONTAINER_NAME
14 | labelZh: 容器名称
15 | labelEn: container name
16 | required: true
17 | - name: ENABLE_IMAGE
18 | labelZh: 是否备份容器镜像
19 | required: true
20 | labelEn: backup container image
21 | type: select
22 | values:
23 | - label: 是
24 | value: "1"
25 | - label: 否
26 | value: "0"
--------------------------------------------------------------------------------
/docker/script/container-clear/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: container-clear
3 | descriptionZh: 清理删除容器内的目录或是文件
4 | descriptionEn: rm container path or file
5 | script: |
6 | docker exec -it ${CONTAINER_NAME} \
7 | ls ${PATH_OR_FILE} && rm -rf ${PATH_OR_FILE} && \
8 | echo 上方文件或是目录已被删除
9 | tag:
10 | - dpanel
11 | - run-in-dpanel
12 | environment:
13 | - name: CONTAINER_NAME
14 | labelZh: 容器名称
15 | labelEn: container name
16 | - name: PATH_OR_FILE
17 | labelZh: 容器内的目录或是文件
18 | labelEn: path or file in container
19 |
--------------------------------------------------------------------------------
/docker/script/container-restart/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: container-restart
3 | descriptionZh: 重启一个容器
4 | descriptionEn: restart container
5 | script: |
6 | docker restart ${CONTAINER_NAME} && \
7 | echo 重启 ${CONTAINER_NAME} 容器完成
8 | tag:
9 | - dpanel
10 | - run-in-dpanel
11 | environment:
12 | - name: CONTAINER_NAME
13 | labelZh: 容器名称
14 | labelEn: container name
15 |
--------------------------------------------------------------------------------
/docker/script/container-upgrade/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: container-upgrade
3 | descriptionZh: |
4 | 检测容器更新并升级
5 | descriptionEn: |
6 | Check container update and upgrade
7 | script: |
8 | /app/server/dpanel container:upgrade -f /app/server/config.yaml --name=${CONTAINER_NAME} --upgrade ${UPGRADE}
9 | tag:
10 | - dpanel
11 | - run-in-dpanel
12 | environment:
13 | - name: CONTAINER_NAME
14 | labelZh: 容器名称
15 | labelEn: container name
16 | required: true
17 | - name: UPGRADE
18 | type: select
19 | required: true
20 | values:
21 | - label: 执行升级
22 | value: "1"
23 | - label: 只检测更新
24 | value: "0"
25 | labelZh: 是否执行升级
26 | labelEn: upgrade container
--------------------------------------------------------------------------------
/docker/script/host-backup/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: host-backup
3 | descriptionZh: |
4 | 备份宿主机的某个目录
5 | descriptionEn: |
6 | Backup a directory in the host
7 | script: |
8 | BACKUP_FILE_NAME=$(echo $BACKUP_SOURCE_PATH | tr "/" "_")_$(date +"%Y%m%d%H%M%S").tar
9 | docker run -it --rm --name dpanel-host-backup \
10 | -v ${BACKUP_SOURCE_PATH}:/source -v ${BACKUP_TARGET_PATH}:/backup busybox \
11 | tar czvf /backup/${BACKUP_FILE_NAME} /source
12 | echo "备份完成,备份文件位于: ${BACKUP_PATH}/${BACKUP_FILE_NAME}"
13 | tag:
14 | - dpanel
15 | - run-in-dpanel
16 | environment:
17 | - name: BACKUP_SOURCE_PATH
18 | labelZh: 需要备份的主机目录
19 | labelEn: backup source path of host
20 | - name: BACKUP_TARGET_PATH
21 | labelZh: 备份到主机目录
22 | labelEn: backup target path of host
--------------------------------------------------------------------------------
/docker/script/mysql-backup/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: mysql-backup
3 | descriptionZh: |
4 | 备份 mysql 数据库
5 | descriptionEn: |
6 | Backup mysql database
7 | script: |
8 | BACKUP_FILE_NAME=${CONTAINER_NAME}_$(date +"%Y%m%d%H%M%S").sql
9 | docker exec ${CONTAINER_NAME} mysqldump -u root -p${MYSQL_ROOT_PASSWORD} --all-databases > ${BACKUP_TARGET_PATH%/}/${BACKUP_FILE_NAME}
10 | echo "✅ 备份完成,备份文件位于: ${BACKUP_TARGET_PATH%/}/${BACKUP_FILE_NAME}"
11 | tag:
12 | - dpanel
13 | - run-in-dpanel
14 | environment:
15 | - name: CONTAINER_NAME
16 | labelZh: 需要备份的 mysql 数据库容器名称
17 | labelEn: backup container name of mysql
18 | - name: MYSQL_ROOT_PASSWORD
19 | labelZh: mysql 的 root 密码
20 | labelEn: root password
21 | - name: BACKUP_TARGET_PATH
22 | labelZh: 备份到宿主机的目录
23 | labelEn: backup target path of host
--------------------------------------------------------------------------------
/docker/script/store-sync/data.yaml:
--------------------------------------------------------------------------------
1 | task:
2 | name: store-sync
3 | descriptionZh: |
4 | 同步某个应用商店应用
5 | descriptionEn: |
6 | sync appstore
7 | script: |
8 | /app/server/dpanel store:sync -f /app/server/config.yaml --name=${STORE_NAME}
9 | tag:
10 | - dpanel
11 | - run-in-dpanel
12 | environment:
13 | - name: STORE_NAME
14 | labelZh: 商店名称
15 | labelEn: name of appstore
--------------------------------------------------------------------------------
/reload.sh:
--------------------------------------------------------------------------------
1 | go get github.com/cosmtrek/air
2 | go run github.com/cosmtrek/air -c .air.toml server:start
--------------------------------------------------------------------------------