mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
238 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 637fb83398 | |||
| ff67ace165 | |||
| c1580afadf | |||
| 9a2e68e24e | |||
| 3fbaecbe9e | |||
| a2c96993d3 | |||
| 2208ff1c29 | |||
| 146c9aadbd | |||
| 929219e487 | |||
| 7aca6587af | |||
| a8575bcda9 | |||
| bdaa3c005b | |||
| e6fe1e48f7 | |||
| 33cb41cf42 | |||
| 0df2a9107d | |||
| 7f61793b36 | |||
| 7d9de56a82 | |||
| 18148b15b0 | |||
| ad989337b3 | |||
| 81d32420c0 | |||
| 5538f7498b | |||
| 371d2b3ac0 | |||
| ff17aee372 | |||
| 9e1d525b20 | |||
| f6526c15fa | |||
| be3ae54efe | |||
| 214b804062 | |||
| e1f074f116 | |||
| a96bbf3dfb | |||
| 39d2eee676 | |||
| 8c17b295a2 | |||
| 452ef92d8f | |||
| df49e167a9 | |||
| f3aa240aa9 | |||
| d81f4df8b6 | |||
| 4c2f9f1bbc | |||
| e2080b0603 | |||
| 53eb005531 | |||
| 9ae7b5201c | |||
| 119411a3ce | |||
| 112fc30437 | |||
| dea96649d7 | |||
| 2d136e40c5 | |||
| 11e4d05181 | |||
| 50381dff0c | |||
| 8e40d9a75d | |||
| 8251a9a70e | |||
| 0297ccbf28 | |||
| a9113f3e4c | |||
| 17918c0f1f | |||
| 9509cfa4e2 | |||
| 1e32f9addc | |||
| abe2e1fd3d | |||
| cbf9bdc926 | |||
| ab4a94c32d | |||
| 0ba29c7c40 | |||
| e99c71b8e1 | |||
| b95b035991 | |||
| 5959cab965 | |||
| d4668f2a7e | |||
| e1fb498aa5 | |||
| 6413d6462c | |||
| 3f9fce7841 | |||
| 58d100e636 | |||
| 98d57b6c23 | |||
| 3a8c7784f5 | |||
| 02bfddcf03 | |||
| 55750796b3 | |||
| 692f8ca030 | |||
| fbe99ea4dd | |||
| d34438d90e | |||
| 47e114d43e | |||
| 0b9bba45d7 | |||
| 00ea2f40e2 | |||
| 5183653dbb | |||
| 9f7d8e413e | |||
| 4450d2c8d1 | |||
| b5f7458344 | |||
| da44f1a8fe | |||
| 0317aa1401 | |||
| 90fc48f478 | |||
| a8178cbcd6 | |||
| 91ecd04c27 | |||
| 6743ac9621 | |||
| 5606d980be | |||
| 34300431a4 | |||
| 6f0f4c69ef | |||
| 5fba45e3a4 | |||
| bb140ade7b | |||
| 1316c50a57 | |||
| f14b4f5c54 | |||
| b08eb6bf1f | |||
| 7f5869f805 | |||
| ecadaa9bbc | |||
| 60b9350625 | |||
| d64f099f8e | |||
| 294a78cd95 | |||
| 8294a1a74e | |||
| c403efd762 | |||
| cf8dfca4c0 | |||
| e552caa38f | |||
| ddff13990a | |||
| 27f79ea7a3 | |||
| 04d570197a | |||
| 8e74428023 | |||
| f4c09324b7 | |||
| ee6a12fac2 | |||
| ba429068ad | |||
| 711ccb4504 | |||
| 7d6e078e20 | |||
| d3a7c9e29c | |||
| 10d22bfbc0 | |||
| 724d05d980 | |||
| 690e7f0391 | |||
| a28fc592d1 | |||
| ff7e76ac2b | |||
| f2eb3a180c | |||
| 020a4ad870 | |||
| b5e7cfc069 | |||
| 52f8a6f045 | |||
| 3831d47a23 | |||
| 3a579f2346 | |||
| e85e8dffb3 | |||
| e18d40b0ab | |||
| c308b8e42c | |||
| cafded526f | |||
| 99aa6c99d8 | |||
| e0daee4766 | |||
| bf8c832b32 | |||
| 2d35cbc176 | |||
| 6b51f58856 | |||
| f58c680ced | |||
| 9c6d0b5837 | |||
| d069cbec75 | |||
| 9b252aa8f6 | |||
| a52d06e3cb | |||
| c25c78bfef | |||
| 567f3ab693 | |||
| aa162e12db | |||
| 7169b90c1d | |||
| 5e1001f10f | |||
| 8e26589c80 | |||
| 27ec65650d | |||
| e2345a8285 | |||
| 4ebe20d7f8 | |||
| 8f5872b284 | |||
| 75c6522453 | |||
| ff34cc72cc | |||
| e2fb1080da | |||
| b96c6ad899 | |||
| c1a4fb0904 | |||
| 5c50da27c5 | |||
| 336a9bc675 | |||
| ea3bf37687 | |||
| 79e09f9cb7 | |||
| d73c988073 | |||
| 9641ae7f34 | |||
| fd2c89a480 | |||
| 526f192200 | |||
| 6423953aeb | |||
| 1091ec8a23 | |||
| 2cbc641b2d | |||
| f4d23211e3 | |||
| 1b6086bb0b | |||
| f399e9fcec | |||
| 17cf1ada0e | |||
| 85bd71aefe | |||
| f01afacf34 | |||
| 52e8830605 | |||
| e90c6f4f7b | |||
| eb94ef89b3 | |||
| adc6dccaae | |||
| 855b2e87f9 | |||
| 3c601b808f | |||
| 8652501e95 | |||
| 43b67f67be | |||
| 4fff0b84d8 | |||
| fd73982f86 | |||
| 10191ae5d6 | |||
| b162f71d47 | |||
| a9f34a5783 | |||
| 1e4285b516 | |||
| 44c370a119 | |||
| a1c0b4a399 | |||
| 805f6c0002 | |||
| 5066446053 | |||
| 0c31641ebd | |||
| 343d451752 | |||
| 12ecf0707e | |||
| 899ce1fa18 | |||
| 9ef56f6ede | |||
| 4b3ebc082c | |||
| fb3c8ccc85 | |||
| f620a8737e | |||
| d0d5b237ef | |||
| 11f5adc5a4 | |||
| 6927a0256c | |||
| dbc78343dc | |||
| d8347310ea | |||
| 8d6b3fe95e | |||
| 036e01795e | |||
| 2d7faaa653 | |||
| 5921a88c75 | |||
| d6f4b8736b | |||
| 227f10751c | |||
| 54cc556d17 | |||
| 0d8a26ccb6 | |||
| eb18b1080a | |||
| ffe7b95b9c | |||
| ca097cac1e | |||
| f3a578380a | |||
| beb271b3b9 | |||
| 310316c8e4 | |||
| 7521b0f788 | |||
| 2783e2b28e | |||
| cf26063287 | |||
| 2a61f8939f | |||
| 513a298541 | |||
| 1cbc53b311 | |||
| aed35990bc | |||
| 9179543b7f | |||
| f40ad8bdf9 | |||
| 56ae7ebfa0 | |||
| cf5f7bd866 | |||
| bd6496c1cb | |||
| 3c75c1c49d | |||
| 2b0f8a57bb | |||
| 658b6a79eb | |||
| 42dd00c5ea | |||
| 8fa5cec568 | |||
| 2af4b9569d | |||
| 408b9ce19b | |||
| b2845fba15 | |||
| ba819a5a9c | |||
| bf1359e3d6 | |||
| 2970aaaa47 | |||
| a65bcf582a | |||
| 145387b3df |
@@ -19,9 +19,7 @@ body:
|
||||
options:
|
||||
- label: 我已经搜索了现有的 Issues, Discussions 和 Google (I've searched the existing Issues, Discussions and Google)
|
||||
required: true
|
||||
- label: 我已经阅读了 README 中除版本记录以外的所有内容 (I've read everything in the README except the version notes)
|
||||
required: true
|
||||
- label: 我在提问题之前至少花费了 5 分钟来思考和准备 (I spend at least 5 minutes thinking and preparing before asking a question)
|
||||
- label: 我已经阅读了文档中除版本记录以外的所有内容 (I've read everything in the documentation except the version notes)
|
||||
required: true
|
||||
- label: 我没有改动过面板及系统的任何文件 (I have not changed any files of the panel and system)
|
||||
required: false
|
||||
@@ -37,9 +35,10 @@ body:
|
||||
请提供安装面板的操作系统。
|
||||
Please provide the operating system on which the panel is installed.
|
||||
options:
|
||||
- RockyLinux
|
||||
- AlmaLinux
|
||||
- Debian
|
||||
- Ubuntu
|
||||
- AlmaLinux
|
||||
- RockyLinux
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
@@ -50,17 +49,17 @@ body:
|
||||
description: |
|
||||
请提供操作系统的版本号。
|
||||
Please provide the version number of the operating system.
|
||||
placeholder: "9.0"
|
||||
placeholder: "12"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 耗子面板版本 (HaoZi Panel Version)
|
||||
label: 耗子面板版本 (Rat Panel Version)
|
||||
description: |
|
||||
请提供面板的版本号。
|
||||
Please provide the version number of the panel.
|
||||
placeholder: "2.0.0"
|
||||
placeholder: "2.3.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.1 KiB |
@@ -1,26 +0,0 @@
|
||||
name: Codecov
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
jobs:
|
||||
codecov:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install dependencies
|
||||
run: go mod tidy
|
||||
- name: Run tests with coverage
|
||||
run: sudo go test -v -coverprofile="coverage.out" ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.out
|
||||
token: ${{ secrets.CODECOV }}
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Build frontend
|
||||
|
||||
@@ -3,9 +3,6 @@ on:
|
||||
schedule:
|
||||
- cron: "0 */6 * * *"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
issues:
|
||||
types: [ opened ]
|
||||
permissions:
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Run pnpm lint
|
||||
|
||||
@@ -21,4 +21,9 @@ jobs:
|
||||
run: |
|
||||
cp config.example.yml config.yml
|
||||
- name: Run tests
|
||||
run: sudo go test ./...
|
||||
run: sudo go test -v -coverprofile="coverage.out" ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./coverage.out
|
||||
token: ${{ secrets.CODECOV }}
|
||||
@@ -4,10 +4,6 @@
|
||||
|
||||
<h1 align="center" style="font-size: 40px">耗子面板</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
|
||||
<a href="https://github.com/TheTNB/panel/actions"><img src="https://github.com/TheTNB/panel/actions/workflows/test.yml/badge.svg"></a>
|
||||
@@ -16,9 +12,9 @@
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
|
||||
</p>
|
||||
|
||||
耗子面板是使用 Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板。
|
||||
耗子面板是新一代企业级全能服务器运维管理面板。简单轻量,高效运维。
|
||||
|
||||
QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | 论坛:[tom.moe](https://tom.moe) | 赞助:[爱发电](https://afdian.com/a/TheTNB)
|
||||
**赞助:[爱发电](https://afdian.com/a/TheTNB)** | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | 论坛:[tom.moe](https://tom.moe)
|
||||
|
||||
## 优势
|
||||
|
||||
@@ -63,10 +59,7 @@ QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制
|
||||
|
||||
随着系统版本的不断更新,我们亦可能会终止部分过于老旧的系统的支持,以保证面板的健壮性。
|
||||
|
||||
## 安装面板
|
||||
|
||||
> **Warning**
|
||||
> 安装面板前,您需要了解 LNMP 环境的基本知识,以及如何处理常见的 LNMP 环境问题,我们不建议 0 基础的用户安装和使用耗子面板。
|
||||
## 挂载硬盘
|
||||
|
||||
如果您的服务器有未挂载的数据盘,可在安装前以`root`用户登录服务器运行以下命令自动挂载,面板安装后不支持跨目录迁移。
|
||||
|
||||
@@ -74,9 +67,12 @@ QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制
|
||||
curl -fsLm 10 -o auto_mount.sh https://dl.cdn.haozi.net/panel/auto_mount.sh && bash auto_mount.sh
|
||||
```
|
||||
|
||||
准备就绪后,请以`root`用户登录服务器,运行以下命令安装面板:
|
||||
## 安装面板
|
||||
|
||||
**当前 v2.3.x 为测试版本,欢迎帮助我们测试在不同操作系统下的兼容性。**
|
||||
> **Warning**
|
||||
> 安装面板前,您需要了解 LNMP 环境的基本知识,以及如何处理常见的 LNMP 环境问题,我们不建议 0 基础的用户安装和使用耗子面板。
|
||||
|
||||
以`root`用户登录服务器,运行以下命令安装面板:
|
||||
|
||||
```shell
|
||||
curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
|
||||
@@ -140,6 +136,12 @@ panel-cli
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
|
||||
### 合作伙伴
|
||||
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
|
||||
### 赞助商们
|
||||
|
||||
<p align="center">
|
||||
@@ -152,18 +154,23 @@ panel-cli
|
||||
|
||||
这个项目的存在要归功于所有做出贡献的人,参与贡献请先查看贡献代码部分。
|
||||
|
||||
<a href="https://github.com/TheTNB/panel/graphs/contributors">
|
||||
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
|
||||
</a>
|
||||
<p align="center">
|
||||
<a href="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors?repo_id=572922963&limit=30" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=dark" width="655" height="auto">
|
||||
<img alt="Active Contributors of TheTNB/panel - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=light" width="655" height="auto">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Star 历史
|
||||
|
||||
<a href="https://star-history.com/#TheTNB/panel&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## 免责声明
|
||||
|
||||
+27
-20
@@ -4,10 +4,6 @@
|
||||
|
||||
<h1 align="center" style="font-size: 40px">Rat Panel</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
|
||||
<a href="https://github.com/TheTNB/panel/actions"><img src="https://github.com/TheTNB/panel/actions/workflows/test.yml/badge.svg"></a>
|
||||
@@ -16,9 +12,9 @@
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
|
||||
</p>
|
||||
|
||||
The Rat Panel is an open source lightweight Linux server operation and maintenance management panel developed using Golang and Vue.
|
||||
Rat Panel is a new generation of enterprise-level all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
|
||||
|
||||
QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | Forum: [tom.moe](https://tom.moe) | Sponsor: [Open Collective](https://opencollective.com/tnb)
|
||||
**Sponsor: [Open Collective](https://opencollective.com/tnb)** | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | Forum: [tom.moe](https://tom.moe)
|
||||
|
||||
## Advantages
|
||||
|
||||
@@ -63,10 +59,7 @@ For other systems not listed in the table below, you can try to install them by
|
||||
|
||||
As system versions are constantly updated, we may also terminate support for some older systems to ensure the robustness of the panel.
|
||||
|
||||
## Install Panel
|
||||
|
||||
> **Warning**
|
||||
> Before installing the panel, you need to understand the basic knowledge of the LNMP environment and how to deal with common LNMP environment problems. We are not recommended for users with zero basic knowledge to install and use Rat Panel.
|
||||
## Mount Disk
|
||||
|
||||
If your server has an unmounted data disk, you can run the following command as the `root` user to automatically mount it before installation. The panel does not support cross-directory migration after installation.
|
||||
|
||||
@@ -74,9 +67,12 @@ If your server has an unmounted data disk, you can run the following command as
|
||||
curl -fsLm 10 -o auto_mount.sh https://dl.cdn.haozi.net/panel/auto_mount.sh && bash auto_mount.sh
|
||||
```
|
||||
|
||||
After you are ready, log in to the server as the `root` user and run the following command to install the panel:
|
||||
## Install Panel
|
||||
|
||||
**Current v2.3.x is a test version, welcome to help us test compatibility on different operating systems.**
|
||||
> **Warning**
|
||||
> Before installing the panel, you need to understand the basic knowledge of the LNMP environment and how to deal with common LNMP environment problems. We are not recommended for users with zero basic knowledge to install and use Rat Panel.
|
||||
|
||||
Login to the server as the `root` user and run the following command to install the panel:
|
||||
|
||||
```shell
|
||||
curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
|
||||
@@ -140,6 +136,12 @@ If the Rat Panel is helpful to you, welcome to [sponsor us](https://opencollecti
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
|
||||
### Partners
|
||||
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
|
||||
### Sponsors
|
||||
|
||||
<p align="center">
|
||||
@@ -152,18 +154,23 @@ If the Rat Panel is helpful to you, welcome to [sponsor us](https://opencollecti
|
||||
|
||||
This project owes its existence to all those who have contributed. To contribute, please check the contributed code section first.
|
||||
|
||||
<a href="https://github.com/TheTNB/panel/graphs/contributors">
|
||||
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
|
||||
</a>
|
||||
<p align="center">
|
||||
<a href="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors?repo_id=572922963&limit=30" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=dark" width="655" height="auto">
|
||||
<img alt="Active Contributors of TheTNB/panel - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=light" width="655" height="auto">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#TheTNB/panel&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# 警告:未经指导私自修改此配置文件可能导致严重后果
|
||||
# Warning: Modifying this configuration file without guidance may cause serious consequences
|
||||
app:
|
||||
debug: false
|
||||
key: a-long-string-with-32-characters
|
||||
|
||||
@@ -3,127 +3,91 @@ module github.com/TheTNB/panel
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/bddjr/hlfhr v1.2.4
|
||||
github.com/beevik/ntp v1.4.3
|
||||
github.com/docker/docker v27.3.1+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/expr-lang/expr v1.16.9
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.22.1
|
||||
github.com/go-playground/validator/v10 v10.23.0
|
||||
github.com/go-rat/chix v1.1.4
|
||||
github.com/go-rat/gormstore v1.0.6
|
||||
github.com/go-rat/sessions v1.0.11
|
||||
github.com/go-rat/utils v1.0.3
|
||||
github.com/go-resty/resty/v2 v2.15.3
|
||||
github.com/go-rat/utils v1.0.7
|
||||
github.com/go-resty/resty/v2 v2.16.2
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/goccy/go-yaml v1.12.0
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
|
||||
github.com/gomodule/redigo v1.9.2
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/klauspost/compress v1.17.11
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0
|
||||
github.com/knadh/koanf/providers/file v1.1.2
|
||||
github.com/knadh/koanf/v2 v2.1.1
|
||||
github.com/knadh/koanf/v2 v2.1.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/libdns/huaweicloud v0.2.2
|
||||
github.com/libdns/libdns v0.2.2
|
||||
github.com/libdns/tencentcloud v1.0.0
|
||||
github.com/libdns/tencentcloud v1.1.0
|
||||
github.com/mholt/acmez/v2 v2.0.3
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||
github.com/orandin/slog-gorm v1.4.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/do/v2 v2.0.0-beta.7
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/sethvargo/go-limiter v1.0.0
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible
|
||||
github.com/spf13/cast v1.7.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241115180907-128af6df1765
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.5
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/net v0.30.0
|
||||
go.uber.org/zap/exp v0.3.0
|
||||
golang.org/x/crypto v0.29.0
|
||||
golang.org/x/net v0.31.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.12
|
||||
moul.io/zapgorm2 v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/bodgit/plumbing v1.2.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.3.0 // indirect
|
||||
github.com/bodgit/windows v1.0.0 // indirect
|
||||
github.com/connesc/cipherio v0.2.1 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/devhaozi/huaweicloud-sdk-go-v3 v0.0.0-20241018211007-bbebb6de5db7 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.10.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-rat/securecookie v1.0.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gofiber/schema v1.2.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jaevor/go-nanoid v1.4.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.597 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/samber/go-type-to-string v1.4.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1033 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1033 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
|
||||
go.opentelemetry.io/otel v1.30.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.30.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.30.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.30.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.60.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
|
||||
@@ -1,99 +1,36 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/TheTNB/acmez/v2 v2.0.0-20241025203320-cc718c4c870b h1:1iVxCglpEJ/kq48YcujkWq8SeOacMuMmuekMmGyOSPA=
|
||||
github.com/TheTNB/acmez/v2 v2.0.0-20241025203320-cc718c4c870b/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/bddjr/hlfhr v1.2.4 h1:1KZcqvzeWJdB20REsfKuCsdnRAeVC113cONlhswypek=
|
||||
github.com/bddjr/hlfhr v1.2.4/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
|
||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
||||
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
|
||||
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
|
||||
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
|
||||
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
|
||||
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/devhaozi/huaweicloud-sdk-go-v3 v0.0.0-20241018211007-bbebb6de5db7 h1:AjCMD0AEGHHbcIpvRcrI8rMA+Us5F4uUt3B3KSpiYN8=
|
||||
github.com/devhaozi/huaweicloud-sdk-go-v3 v0.0.0-20241018211007-bbebb6de5db7/go.mod h1:Gz/Ng6WCvYc1Q2smuPvow9kszfyH47wtnpZzvdD2juA=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
|
||||
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3 h1:ei3Vq/rpPI/jCJY9mRHJAKg5vU+EhZyWhBAkaAomQuw=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3/go.mod h1:VJ9FIOBAur+NmQ8c4tDVwOuiJcgupTG105FexPFrXzA=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@@ -102,8 +39,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-rat/chix v1.1.4 h1:YVDwF4vrlkm5hmlDGWKoiWPgP98pJooBvhsto8UWq+o=
|
||||
github.com/go-rat/chix v1.1.4/go.mod h1:eKUTG7GXqMS278PH0QZi8yMWZddvMfxqWu7mverM6kQ=
|
||||
github.com/go-rat/gormstore v1.0.6 h1:YxmaY6n3JoTk6o9RXoZ1+cQ6HQL+ayf/6d8D76ydCAc=
|
||||
@@ -112,99 +49,46 @@ github.com/go-rat/securecookie v1.0.1 h1:HW0fpKmB+FjJzXTw8ABOwBJ+XrPmRBSZqHhmrv8
|
||||
github.com/go-rat/securecookie v1.0.1/go.mod h1:tP/ObWYyjmcpabQ7WTon/i2lBSip/Aolliw2llXuPDU=
|
||||
github.com/go-rat/sessions v1.0.11 h1:2eTUvhEBtymrAtwv71LlGHNylz+Ad2SgY3jg/sSUboA=
|
||||
github.com/go-rat/sessions v1.0.11/go.mod h1:Ray/GCbuhm4U9xpjFFSCfOTCEn91puEhAXX5creHE9g=
|
||||
github.com/go-rat/utils v1.0.3 h1:SqH/O0KYq4SBv8naSAjJA4hC45/d8NLNrTCONfqjbFM=
|
||||
github.com/go-rat/utils v1.0.3/go.mod h1:4WNPlrF57KmeGZN3HOeBgdBVLJL3xgba4QRP/t6pKrE=
|
||||
github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8=
|
||||
github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||
github.com/go-rat/utils v1.0.7 h1:hZUe+n1W/tvF3j+oP/TeAHX3AF98SjVB1FuF11zkCAI=
|
||||
github.com/go-rat/utils v1.0.7/go.mod h1:b0S/vd2bXWvPUQpw8z6RmJrUG7G7BkMuNSrsz6JRBHs=
|
||||
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
|
||||
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
|
||||
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
|
||||
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a h1:BAyyIK6rc6Tq9vT2KVlPJXD8EVv/LpaFwn4fgfLwtHg=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a/go.mod h1:bgk4Ij/0OQ89UeoFFAQrSNhbbr4rKJ0fwWfo7wc+TCc=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jaevor/go-nanoid v1.4.0 h1:mPz0oi3CrQyEtRxeRq927HHtZCJAAtZ7zdy7vOkrvWs=
|
||||
github.com/jaevor/go-nanoid v1.4.0/go.mod h1:GIpPtsvl3eSBsjjIEFQdzzgpi50+Bo1Luk+aYlbJzlc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
||||
github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=
|
||||
github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
|
||||
github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
|
||||
github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
|
||||
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -224,231 +108,99 @@ github.com/libdns/huaweicloud v0.2.2/go.mod h1:F9vNIca+Cq1ZJiMCSkHQUdvXEGY6UbLoa
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/tencentcloud v1.0.0 h1:u4LXnYu/lu/9P5W+MCVPeSDnwI+6w+DxYhQ1wSnQOuU=
|
||||
github.com/libdns/tencentcloud v1.0.0/go.mod h1:NlCgPumzUsZWSOo1+Q/Hfh8G6TNRAaTUeWQdg6LbtUI=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/libdns/tencentcloud v1.1.0 h1:UvrU9HR3Cc87vIkj/FrTGkaZigGcRyZFdLCYqKDac1c=
|
||||
github.com/libdns/tencentcloud v1.1.0/go.mod h1:PuP62hTGHSI3OqSUsrzuvsCWMD+9BlPtVjKasOrAtJ4=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsTUU=
|
||||
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/samber/do/v2 v2.0.0-beta.7 h1:tmdLOVSCbTA6uGWLU5poi/nZvMRh5QxXFJ9vHytU+Jk=
|
||||
github.com/samber/do/v2 v2.0.0-beta.7/go.mod h1:+LpV3vu4L81Q1JMZNSkMvSkW9lt4e5eJoXoZHkeBS4c=
|
||||
github.com/samber/go-type-to-string v1.4.0 h1:KXphToZgiFdnJQxryU25brhlh/CqY/cwJVeX2rfmow0=
|
||||
github.com/samber/go-type-to-string v1.4.0/go.mod h1:jpU77vIDoIxkahknKDoEx9C8bQ1ADnh2sotZ8I4QqBU=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4=
|
||||
github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible h1:lOGOyCG67a5dv2hq5Z1BLDUqqKp3HkbjPcz5j6XMS0U=
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.597 h1:C0GHdLTfikLVoEzfhgPfrZ7LwlG0xiCmk6iwNKE+xs0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.597/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1033 h1:g263/dapUpOAZJa1Y9x07WgfOl7Yy+FM5Mrf4WyttS8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1033/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1033 h1:bRCo+X6i/6B+5Q/G4+rexcMRxpSq+9XS/8+Usn6OcOA=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1033/go.mod h1:ta9FOsQP/SvQAhDD6IhVhYWyY+eboPEbPWq7zpiNwZA=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8 h1:7yw1yHkylDcu/CwY4hEEe8MycDLo7B9LjcqwhoL8NrM=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8/go.mod h1:itu4KWRgrfEwGcfNka+rV4houuirUau53i0diN4lG5g=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.1 h1:1fJU+bltkwN8lF4Sni/X0i1d8XwPIrS82ivZ8qsp/q4=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241115180907-128af6df1765 h1:nnw6RpVhgrUvV6BLysdedhxaALJY1JqRFr3S85XXpF4=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241115180907-128af6df1765/go.mod h1:itu4KWRgrfEwGcfNka+rV4houuirUau53i0diN4lG5g=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.5 h1:Y693/B4H1nO6s5Xxns2AVLWNZ650HCF7AjThgvkP0bw=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.5/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
|
||||
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
|
||||
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8=
|
||||
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
|
||||
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
|
||||
go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
|
||||
go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
|
||||
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
|
||||
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU=
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -457,133 +209,46 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
|
||||
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
@@ -608,8 +273,3 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
moul.io/zapgorm2 v1.3.0 h1:+CzUTMIcnafd0d/BvBce8T4uPn6DQnpIrz64cyixlkk=
|
||||
moul.io/zapgorm2 v1.3.0/go.mod h1:nPVy6U9goFKHR4s+zfSo1xVFaoU7Qgd5DoCdOfzoCqs=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/go-rat/sessions"
|
||||
"github.com/go-rat/utils/crypt"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/robfig/cron/v3"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/queue"
|
||||
@@ -22,7 +24,8 @@ var (
|
||||
Session *sessions.Manager
|
||||
Cron *cron.Cron
|
||||
Queue *queue.Queue
|
||||
Logger *zap.Logger
|
||||
Logger *slog.Logger
|
||||
Crypter crypt.Crypter
|
||||
)
|
||||
|
||||
// 定义面板状态常量
|
||||
@@ -36,6 +39,7 @@ const (
|
||||
|
||||
// 面板全局变量
|
||||
var (
|
||||
Key string
|
||||
Root string
|
||||
Version string
|
||||
Locale string
|
||||
|
||||
@@ -2,6 +2,7 @@ package benchmark
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
cryptorand "crypto/rand"
|
||||
@@ -20,8 +21,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
)
|
||||
|
||||
@@ -351,9 +350,9 @@ func (s *Service) compressionTestTask(numThreads int) {
|
||||
end = len(data)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
zw, _ := zstd.NewWriter(&buf)
|
||||
_, _ = zw.Write(data[start:end])
|
||||
_ = zw.Close()
|
||||
w := gzip.NewWriter(&buf)
|
||||
_, _ = w.Write(data[start:end])
|
||||
_ = w.Close()
|
||||
compressedChunks[i] = buf
|
||||
}(i)
|
||||
}
|
||||
@@ -365,15 +364,15 @@ func (s *Service) compressionTestTask(numThreads int) {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
zr, err := zstd.NewReader(&compressedChunks[i])
|
||||
r, err := gzip.NewReader(&compressedChunks[i])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(io.Discard, zr)
|
||||
_, err = io.Copy(io.Discard, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
zr.Close()
|
||||
_ = r.Close()
|
||||
}(i)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/apploader"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
apploader.Register(&types.App{
|
||||
Slug: "docker",
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService()
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package docker
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type Service struct{}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read("/etc/docker/daemon.json")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/docker/daemon.json", req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("docker"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
@@ -14,9 +15,7 @@ import (
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
@@ -55,7 +54,6 @@ func (s *Service) List(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
jailEnabled := strings.Contains(jailRaw, "enabled = true")
|
||||
jailLogPath := regexp.MustCompile(`logpath = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailMaxRetry := regexp.MustCompile(`maxretry = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailFindTime := regexp.MustCompile(`findtime = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailBanTime := regexp.MustCompile(`bantime = (.*)`).FindStringSubmatch(jailRaw)
|
||||
@@ -63,7 +61,6 @@ func (s *Service) List(w http.ResponseWriter, r *http.Request) {
|
||||
jails = append(jails, Jail{
|
||||
Name: jailName,
|
||||
Enabled: jailEnabled,
|
||||
LogPath: jailLogPath[1],
|
||||
MaxRetry: cast.ToInt(jailMaxRetry[1]),
|
||||
FindTime: cast.ToInt(jailFindTime[1]),
|
||||
BanTime: cast.ToInt(jailBanTime[1]),
|
||||
@@ -128,7 +125,6 @@ port = ` + ports + `
|
||||
maxretry = ` + jailMaxRetry + `
|
||||
findtime = ` + jailFindTime + `
|
||||
bantime = ` + jailBanTime + `
|
||||
action = %(action_mwl)s
|
||||
logpath = ` + app.Root + `/wwwlogs/` + website.Name + `.log
|
||||
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
|
||||
`
|
||||
@@ -158,25 +154,17 @@ ignoreregex =
|
||||
}
|
||||
|
||||
case "service":
|
||||
var logPath string
|
||||
var filter string
|
||||
var port string
|
||||
var err error
|
||||
switch jailName {
|
||||
case "ssh":
|
||||
if os.IsDebian() || os.IsUbuntu() {
|
||||
logPath = "/var/log/auth.log"
|
||||
} else {
|
||||
logPath = "/var/log/secure"
|
||||
}
|
||||
filter = "sshd"
|
||||
port, err = shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
|
||||
case "mysql":
|
||||
logPath = app.Root + "/server/mysql/mysql-error.log"
|
||||
filter = "mysqld-auth"
|
||||
port, err = shell.Execf("cat %s/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'", app.Root)
|
||||
case "pure-ftpd":
|
||||
logPath = "/var/log/messages"
|
||||
filter = "pure-ftpd"
|
||||
port, err = shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
|
||||
default:
|
||||
@@ -197,8 +185,6 @@ port = ` + port + `
|
||||
maxretry = ` + jailMaxRetry + `
|
||||
findtime = ` + jailFindTime + `
|
||||
bantime = ` + jailBanTime + `
|
||||
action = %(action_mwl)s
|
||||
logpath = ` + logPath + `
|
||||
# ` + jailName + `-END
|
||||
`
|
||||
raw += rule
|
||||
@@ -208,7 +194,7 @@ logpath = ` + logPath + `
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
|
||||
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package fail2ban
|
||||
type Jail struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
LogPath string `json:"log_path"`
|
||||
MaxRetry int `json:"max_retry"`
|
||||
FindTime int `json:"find_time"`
|
||||
BanTime int `json:"ban_time"`
|
||||
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
_ "github.com/TheTNB/panel/internal/apps/benchmark"
|
||||
_ "github.com/TheTNB/panel/internal/apps/docker"
|
||||
_ "github.com/TheTNB/panel/internal/apps/fail2ban"
|
||||
_ "github.com/TheTNB/panel/internal/apps/frp"
|
||||
_ "github.com/TheTNB/panel/internal/apps/gitea"
|
||||
_ "github.com/TheTNB/panel/internal/apps/memcached"
|
||||
_ "github.com/TheTNB/panel/internal/apps/mysql"
|
||||
_ "github.com/TheTNB/panel/internal/apps/nginx"
|
||||
_ "github.com/TheTNB/panel/internal/apps/php"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package memcached
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/apploader"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
apploader.Register(&types.App{
|
||||
Slug: "memcached",
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService()
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package memcached
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package memcached
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type Service struct{}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := systemctl.Status("memcached")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get Memcached status: %v", err)
|
||||
return
|
||||
}
|
||||
if !status {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:11211")
|
||||
if err != nil {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.Write([]byte("stats\nquit\n"))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to write to Memcached: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]types.NV, 0)
|
||||
re := regexp.MustCompile(`STAT\s(\S+)\s(\S+)`)
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if matches := re.FindStringSubmatch(line); len(matches) == 3 {
|
||||
data = append(data, types.NV{
|
||||
Name: matches[1],
|
||||
Value: matches[2],
|
||||
})
|
||||
}
|
||||
if line == "END" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to read from Memcached: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
}
|
||||
|
||||
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read("/etc/systemd/system/memcached.service")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/systemd/system/memcached.service", req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("memcached"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -15,7 +15,6 @@ func init() {
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
r.Get("/errorLog", service.ErrorLog)
|
||||
r.Post("/clearErrorLog", service.ClearErrorLog)
|
||||
r.Get("/slowLog", service.SlowLog)
|
||||
r.Post("/clearSlowLog", service.ClearSlowLog)
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
@@ -123,7 +123,7 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
if len(matches) > 1 {
|
||||
d := map[string]string{"name": expression.name, "value": matches[1]}
|
||||
if expression.name == "发送" || expression.name == "接收" {
|
||||
d["value"] = str.FormatBytes(cast.ToFloat64(matches[1]))
|
||||
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
|
||||
}
|
||||
|
||||
load = append(load, d)
|
||||
@@ -142,14 +142,9 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, load)
|
||||
}
|
||||
|
||||
// ErrorLog 获取错误日志
|
||||
func (s *Service) ErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/server/mysql/mysql-error.log", app.Root))
|
||||
}
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (s *Service) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("echo '' > %s/server/mysql/mysql-error.log", app.Root); err != nil {
|
||||
if err := systemctl.LogsClear("mysqld"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -204,7 +199,7 @@ func (s *Service) SetRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = mysql.UserPassword("root", req.Password); err != nil {
|
||||
if err = mysql.UserPassword("root", req.Password, "localhost"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
@@ -95,7 +95,7 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
return
|
||||
}
|
||||
mem := str.FormatBytes(cast.ToFloat64(out))
|
||||
mem := tools.FormatBytes(cast.ToFloat64(out))
|
||||
data = append(data, types.NV{
|
||||
Name: "内存占用",
|
||||
Value: mem,
|
||||
|
||||
+24
-72
@@ -1,6 +1,8 @@
|
||||
package php
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/apploader"
|
||||
@@ -8,76 +10,26 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
apploader.Register(&types.App{
|
||||
Slug: "php80",
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService(80)
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
r.Get("/fpmConfig", service.GetFPMConfig)
|
||||
r.Post("/fpmConfig", service.UpdateFPMConfig)
|
||||
r.Get("/errorLog", service.ErrorLog)
|
||||
r.Get("/slowLog", service.SlowLog)
|
||||
r.Post("/clearErrorLog", service.ClearErrorLog)
|
||||
r.Post("/clearSlowLog", service.ClearSlowLog)
|
||||
r.Get("/extensions", service.ExtensionList)
|
||||
r.Post("/extensions", service.InstallExtension)
|
||||
r.Delete("/extensions", service.UninstallExtension)
|
||||
},
|
||||
})
|
||||
apploader.Register(&types.App{
|
||||
Slug: "php81",
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService(81)
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
r.Get("/fpmConfig", service.GetFPMConfig)
|
||||
r.Post("/fpmConfig", service.UpdateFPMConfig)
|
||||
r.Get("/errorLog", service.ErrorLog)
|
||||
r.Get("/slowLog", service.SlowLog)
|
||||
r.Post("/clearErrorLog", service.ClearErrorLog)
|
||||
r.Post("/clearSlowLog", service.ClearSlowLog)
|
||||
r.Get("/extensions", service.ExtensionList)
|
||||
r.Post("/extensions", service.InstallExtension)
|
||||
r.Delete("/extensions", service.UninstallExtension)
|
||||
},
|
||||
})
|
||||
apploader.Register(&types.App{
|
||||
Slug: "php82",
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService(82)
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
r.Get("/fpmConfig", service.GetFPMConfig)
|
||||
r.Post("/fpmConfig", service.UpdateFPMConfig)
|
||||
r.Get("/errorLog", service.ErrorLog)
|
||||
r.Get("/slowLog", service.SlowLog)
|
||||
r.Post("/clearErrorLog", service.ClearErrorLog)
|
||||
r.Post("/clearSlowLog", service.ClearSlowLog)
|
||||
r.Get("/extensions", service.ExtensionList)
|
||||
r.Post("/extensions", service.InstallExtension)
|
||||
r.Delete("/extensions", service.UninstallExtension)
|
||||
},
|
||||
})
|
||||
apploader.Register(&types.App{
|
||||
Slug: "php83",
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService(83)
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
r.Get("/fpmConfig", service.GetFPMConfig)
|
||||
r.Post("/fpmConfig", service.UpdateFPMConfig)
|
||||
r.Get("/errorLog", service.ErrorLog)
|
||||
r.Get("/slowLog", service.SlowLog)
|
||||
r.Post("/clearErrorLog", service.ClearErrorLog)
|
||||
r.Post("/clearSlowLog", service.ClearSlowLog)
|
||||
r.Get("/extensions", service.ExtensionList)
|
||||
r.Post("/extensions", service.InstallExtension)
|
||||
r.Delete("/extensions", service.UninstallExtension)
|
||||
},
|
||||
})
|
||||
php := []uint{74, 80, 81, 82, 83, 84}
|
||||
for _, version := range php {
|
||||
apploader.Register(&types.App{
|
||||
Slug: fmt.Sprintf("php%d", version),
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService(version)
|
||||
r.Post("/setCli", service.SetCli)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
r.Get("/fpmConfig", service.GetFPMConfig)
|
||||
r.Post("/fpmConfig", service.UpdateFPMConfig)
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/errorLog", service.ErrorLog)
|
||||
r.Get("/slowLog", service.SlowLog)
|
||||
r.Post("/clearErrorLog", service.ClearErrorLog)
|
||||
r.Post("/clearSlowLog", service.ClearSlowLog)
|
||||
r.Get("/extensions", service.ExtensionList)
|
||||
r.Post("/extensions", service.InstallExtension)
|
||||
r.Delete("/extensions", service.UninstallExtension)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -33,6 +32,15 @@ func NewService(version uint) *Service {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SetCli(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("ln -sf %s/server/php/%d/bin/php /usr/bin/php", app.Root, s.version); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, s.version))
|
||||
if err != nil {
|
||||
@@ -84,26 +92,25 @@ func (s *Service) UpdateFPMConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
var raw map[string]any
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d", s.version))
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载状态失败:%v", err)
|
||||
_, err := client.R().SetResult(&raw).Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d?json", s.version))
|
||||
if err != nil {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
raw := resp.String()
|
||||
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
|
||||
regexKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
|
||||
rawKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
|
||||
|
||||
loads := make([]types.NV, len(dataKeys))
|
||||
loads := make([]types.NV, 0)
|
||||
for i := range dataKeys {
|
||||
loads[i].Name = dataKeys[i]
|
||||
|
||||
r := regexp.MustCompile(fmt.Sprintf("%s:\\s+(.*)", regexKeys[i]))
|
||||
match := r.FindStringSubmatch(raw)
|
||||
|
||||
if len(match) > 1 {
|
||||
loads[i].Value = strings.TrimSpace(match[1])
|
||||
v, ok := raw[rawKeys[i]]
|
||||
if ok {
|
||||
loads = append(loads, types.NV{
|
||||
Name: dataKeys[i],
|
||||
Value: cast.ToString(v),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,17 +145,6 @@ func (s *Service) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Service) ExtensionList(w http.ResponseWriter, r *http.Request) {
|
||||
extensions := s.getExtensions()
|
||||
|
||||
// ionCube 只支持 PHP 8.3 以下版本
|
||||
if cast.ToUint(s.version) < 83 {
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: "ionCube 是一个专业级的 PHP 加密解密工具。",
|
||||
Installed: false,
|
||||
})
|
||||
}
|
||||
|
||||
raw, err := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, s.version)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
@@ -182,10 +178,10 @@ func (s *Service) InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/%s.sh" | bash -s -- "install" "%d" >> /tmp/%s.log 2>&1`, url.QueryEscape(req.Slug), s.version, req.Slug)
|
||||
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'install' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/official.sh" | bash -s -- "install" "%d" "%s" >> /tmp/%s.log 2>&1`, s.version, req.Slug, req.Slug)
|
||||
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'install' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
@@ -213,10 +209,10 @@ func (s *Service) UninstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/%s.sh" | bash -s -- "uninstall" "%d" >> /tmp/%s.log 2>&1`, url.QueryEscape(req.Slug), s.version, req.Slug)
|
||||
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'uninstall' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/official.sh" | bash -s -- "uninstall" "%d" "%s" >> /tmp/%s.log 2>&1`, s.version, req.Slug, req.Slug)
|
||||
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'uninstall' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
@@ -237,137 +233,141 @@ func (s *Service) getExtensions() []Extension {
|
||||
{
|
||||
Name: "fileinfo",
|
||||
Slug: "fileinfo",
|
||||
Description: "Fileinfo 是一个用于识别文件类型的库。",
|
||||
Description: "Fileinfo 是一个用于识别文件类型的库",
|
||||
},
|
||||
{
|
||||
Name: "OPcache",
|
||||
Slug: "Zend OPcache",
|
||||
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销。",
|
||||
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销",
|
||||
},
|
||||
{
|
||||
Name: "PhpRedis",
|
||||
Name: "Redis",
|
||||
Slug: "redis",
|
||||
Description: "PhpRedis 是一个用 C 语言编写的 PHP 模块,用来连接并操作 Redis 数据库上的数据。",
|
||||
Description: "PhpRedis 是一个用 C 语言编写的 PHP 模块,用来连接并操作 Redis 数据库上的数据",
|
||||
},
|
||||
{
|
||||
Name: "Memcached",
|
||||
Slug: "memcached",
|
||||
Description: "Memcached 使用 libmemcached 库连接 Memcached 服务器",
|
||||
},
|
||||
{
|
||||
Name: "ImageMagick",
|
||||
Slug: "imagick",
|
||||
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件。",
|
||||
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件",
|
||||
},
|
||||
{
|
||||
Name: "exif",
|
||||
Slug: "exif",
|
||||
Description: "通过 exif 扩展,您可以操作图像元数据。",
|
||||
Description: "通过 exif 扩展,您可以操作图像元数据",
|
||||
},
|
||||
{
|
||||
Name: "pdo_pgsql",
|
||||
Slug: "pdo_pgsql",
|
||||
Description: "(需先安装PostgreSQL)pdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问。",
|
||||
Description: "pdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问(需先安装PostgreSQL)",
|
||||
},
|
||||
{
|
||||
Name: "imap",
|
||||
Slug: "imap",
|
||||
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件。",
|
||||
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件",
|
||||
},
|
||||
{
|
||||
Name: "zip",
|
||||
Slug: "zip",
|
||||
Description: "Zip 是一个用于处理 ZIP 文件的库。",
|
||||
Description: "Zip 是一个用于处理 ZIP 文件的库",
|
||||
},
|
||||
{
|
||||
Name: "bz2",
|
||||
Slug: "bz2",
|
||||
Description: "Bzip2 是一个用于压缩和解压缩文件的库。",
|
||||
Description: "Bzip2 是一个用于压缩和解压缩文件的库",
|
||||
},
|
||||
{
|
||||
Name: "readline",
|
||||
Slug: "readline",
|
||||
Description: "Readline 是一个库,它提供了一种用于处理文本的接口。",
|
||||
Description: "Readline 是一个库,它提供了一种用于处理文本的接口",
|
||||
},
|
||||
{
|
||||
Name: "snmp",
|
||||
Slug: "snmp",
|
||||
Description: "SNMP 是一种用于网络管理的协议。",
|
||||
Description: "SNMP 是一种用于网络管理的协议",
|
||||
},
|
||||
{
|
||||
Name: "ldap",
|
||||
Slug: "ldap",
|
||||
Description: "LDAP 是一种用于访问目录服务的协议。",
|
||||
Description: "LDAP 是一种用于访问目录服务的协议",
|
||||
},
|
||||
{
|
||||
Name: "enchant",
|
||||
Slug: "enchant",
|
||||
Description: "Enchant 是一个拼写检查库。",
|
||||
Description: "Enchant 是一个拼写检查库",
|
||||
},
|
||||
{
|
||||
Name: "pspell",
|
||||
Slug: "pspell",
|
||||
Description: "Pspell 是一个拼写检查库。",
|
||||
Description: "Pspell 是一个拼写检查库",
|
||||
},
|
||||
{
|
||||
Name: "calendar",
|
||||
Slug: "calendar",
|
||||
Description: "Calendar 是一个用于处理日期的库。",
|
||||
Description: "Calendar 是一个用于处理日期的库",
|
||||
},
|
||||
{
|
||||
Name: "gmp",
|
||||
Slug: "gmp",
|
||||
Description: "GMP 是一个用于处理大整数的库。",
|
||||
Description: "GMP 是一个用于处理大整数的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvmsg",
|
||||
Slug: "sysvmsg",
|
||||
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库。",
|
||||
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvsem",
|
||||
Slug: "sysvsem",
|
||||
Description: "Sysvsem 是一个用于处理 System V 信号量的库。",
|
||||
Description: "Sysvsem 是一个用于处理 System V 信号量的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvshm",
|
||||
Slug: "sysvshm",
|
||||
Description: "Sysvshm 是一个用于处理 System V 共享内存的库。",
|
||||
Description: "Sysvshm 是一个用于处理 System V 共享内存的库",
|
||||
},
|
||||
{
|
||||
Name: "xsl",
|
||||
Slug: "xsl",
|
||||
Description: "XSL 是一个用于处理 XML 文档的库。",
|
||||
Description: "XSL 是一个用于处理 XML 文档的库",
|
||||
},
|
||||
{
|
||||
Name: "intl",
|
||||
Slug: "intl",
|
||||
Description: "Intl 是一个用于处理国际化和本地化的库。",
|
||||
Description: "Intl 是一个用于处理国际化和本地化的库",
|
||||
},
|
||||
{
|
||||
Name: "gettext",
|
||||
Slug: "gettext",
|
||||
Description: "Gettext 是一个用于处理多语言的库。",
|
||||
Description: "Gettext 是一个用于处理多语言的库",
|
||||
},
|
||||
{
|
||||
Name: "igbinary",
|
||||
Slug: "igbinary",
|
||||
Description: "Igbinary 是一个用于序列化和反序列化数据的库。",
|
||||
Description: "Igbinary 是一个用于序列化和反序列化数据的库",
|
||||
},
|
||||
{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: "ionCube 是一个专业级的 PHP 加密解密工具(需在 OPcache 之后安装)",
|
||||
},
|
||||
{
|
||||
Name: "Swoole",
|
||||
Slug: "swoole",
|
||||
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展。",
|
||||
},
|
||||
{
|
||||
Name: "Swow",
|
||||
Slug: "Swow",
|
||||
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展。",
|
||||
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
|
||||
},
|
||||
}
|
||||
|
||||
// ionCube 只支持 PHP 8.3 以下版本
|
||||
if cast.ToUint(s.version) < 83 {
|
||||
// Swow 不支持 PHP 8.0 以下版本
|
||||
if cast.ToUint(s.version) >= 80 {
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: "ionCube 是一个专业级的 PHP 加密解密工具。",
|
||||
Installed: false,
|
||||
Name: "Swow",
|
||||
Slug: "Swow",
|
||||
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展。",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,5 +16,5 @@ type ChangePassword struct {
|
||||
}
|
||||
|
||||
type UpdatePort struct {
|
||||
Port uint `form:"port" json:"port" validate:"required"`
|
||||
Port uint `form:"port" json:"port" validate:"required,number,gte=1,lte=65535"`
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/utils/str"
|
||||
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
@@ -114,11 +114,11 @@ secrets file = /etc/rsyncd.secrets
|
||||
# ` + req.Name + `-END
|
||||
`
|
||||
|
||||
if err = io.WriteAppend("/etc/rsyncd.conf", conf); err != nil {
|
||||
if err = io.WriteAppend("/etc/rsyncd.conf", conf, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret)); err != nil {
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -216,7 +216,7 @@ secrets file = /etc/rsyncd.secrets
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret)); err != nil {
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/ntp"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ func (s *Service) GetSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
size = file.Size() / 1024 / 1024
|
||||
total = str.FormatBytes(float64(file.Size()))
|
||||
total = tools.FormatBytes(float64(file.Size()))
|
||||
} else {
|
||||
size = 0
|
||||
total = "0.00 B"
|
||||
@@ -91,8 +91,8 @@ func (s *Service) GetSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
match := regexp.MustCompile(`Swap:\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) >= 4 {
|
||||
used = str.FormatBytes(cast.ToFloat64(match[2]) * 1024)
|
||||
free = str.FormatBytes(cast.ToFloat64(match[3]) * 1024)
|
||||
used = tools.FormatBytes(cast.ToFloat64(match[2]) * 1024)
|
||||
free = tools.FormatBytes(cast.ToFloat64(match[3]) * 1024)
|
||||
}
|
||||
|
||||
service.Success(w, chix.M{
|
||||
@@ -134,7 +134,7 @@ func (s *Service) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if cast.ToInt64(free)*1024 < req.Size*1024*1024 {
|
||||
service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", str.FormatBytes(cast.ToFloat64(free)))
|
||||
service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", tools.FormatBytes(cast.ToFloat64(free)))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type Cert struct {
|
||||
@@ -27,14 +28,16 @@ type Cert struct {
|
||||
}
|
||||
|
||||
type CertRepo interface {
|
||||
List(page, limit uint) ([]*Cert, int64, error)
|
||||
List(page, limit uint) ([]*types.CertList, int64, error)
|
||||
Get(id uint) (*Cert, error)
|
||||
GetByWebsite(WebsiteID uint) (*Cert, error)
|
||||
Upload(req *request.CertUpload) (*Cert, error)
|
||||
Create(req *request.CertCreate) (*Cert, error)
|
||||
Update(req *request.CertUpdate) error
|
||||
Delete(id uint) error
|
||||
ObtainAuto(id uint) (*acme.Certificate, error)
|
||||
ObtainManual(id uint) (*acme.Certificate, error)
|
||||
ObtainSelfSigned(id uint) error
|
||||
Renew(id uint) (*acme.Certificate, error)
|
||||
ManualDNS(id uint) ([]acme.DNSRecord, error)
|
||||
Deploy(ID, WebsiteID uint) error
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerRepo interface {
|
||||
ListAll() ([]types.Container, error)
|
||||
ListByNames(names []string) ([]types.Container, error)
|
||||
ListByName(name string) ([]types.Container, error)
|
||||
Create(req *request.ContainerCreate) (string, error)
|
||||
Remove(id string) error
|
||||
Start(id string) error
|
||||
@@ -17,12 +15,8 @@ type ContainerRepo interface {
|
||||
Restart(id string) error
|
||||
Pause(id string) error
|
||||
Unpause(id string) error
|
||||
Inspect(id string) (types.ContainerJSON, error)
|
||||
Kill(id string) error
|
||||
Rename(id string, newName string) error
|
||||
Stats(id string) (container.StatsResponseReader, error)
|
||||
Exist(name string) (bool, error)
|
||||
Update(id string, config container.UpdateConfig) error
|
||||
Logs(id string) (string, error)
|
||||
Prune() error
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerImageRepo interface {
|
||||
List() ([]image.Summary, error)
|
||||
Exist(id string) (bool, error)
|
||||
List() ([]types.ContainerImage, error)
|
||||
Pull(req *request.ContainerImagePull) error
|
||||
Remove(id string) error
|
||||
Prune() error
|
||||
Inspect(id string) (types.ImageInspect, error)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types/network"
|
||||
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerNetworkRepo interface {
|
||||
List() ([]network.Inspect, error)
|
||||
List() ([]types.ContainerNetwork, error)
|
||||
Create(req *request.ContainerNetworkCreate) (string, error)
|
||||
Remove(id string) error
|
||||
Exist(name string) (bool, error)
|
||||
Inspect(id string) (network.Inspect, error)
|
||||
Connect(networkID string, containerID string) error
|
||||
Disconnect(networkID string, containerID string) error
|
||||
Prune() error
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerVolumeRepo interface {
|
||||
List() ([]*volume.Volume, error)
|
||||
Create(req *request.ContainerVolumeCreate) (volume.Volume, error)
|
||||
Exist(name string) (bool, error)
|
||||
Inspect(id string) (volume.Volume, error)
|
||||
List() ([]types.ContainerVolume, error)
|
||||
Create(req *request.ContainerVolumeCreate) (string, error)
|
||||
Remove(id string) error
|
||||
Prune() error
|
||||
}
|
||||
|
||||
+24
-11
@@ -1,16 +1,29 @@
|
||||
package biz
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type DatabaseType string
|
||||
|
||||
const (
|
||||
DatabaseTypeMysql DatabaseType = "mysql"
|
||||
DatabaseTypePostgresql DatabaseType = "postgresql"
|
||||
DatabaseTypeMongoDB DatabaseType = "mongodb"
|
||||
DatabaseSQLite DatabaseType = "sqlite"
|
||||
DatabaseTypeRedis DatabaseType = "redis"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port int `gorm:"not null" json:"port"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Type DatabaseType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Server string `json:"server"`
|
||||
ServerID uint `json:"server_id"`
|
||||
Encoding string `json:"encoding"`
|
||||
}
|
||||
|
||||
type DatabaseRepo interface {
|
||||
List(page, limit uint) ([]*Database, int64, error)
|
||||
Create(req *request.DatabaseCreate) error
|
||||
Delete(serverID uint, name string) error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type DatabaseServerStatus string
|
||||
|
||||
const (
|
||||
DatabaseServerStatusValid DatabaseServerStatus = "valid"
|
||||
DatabaseServerStatusInvalid DatabaseServerStatus = "invalid"
|
||||
)
|
||||
|
||||
type DatabaseServer struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Type DatabaseType `gorm:"not null" json:"type"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port uint `gorm:"not null" json:"port"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Status DatabaseServerStatus `gorm:"-:all" json:"status"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (r *DatabaseServer) BeforeSave(tx *gorm.DB) error {
|
||||
var err error
|
||||
r.Password, err = app.Crypter.Encrypt([]byte(r.Password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *DatabaseServer) AfterFind(tx *gorm.DB) error {
|
||||
password, err := app.Crypter.Decrypt(r.Password)
|
||||
if err == nil {
|
||||
r.Password = string(password)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type DatabaseServerRepo interface {
|
||||
Count() (int64, error)
|
||||
List(page, limit uint) ([]*DatabaseServer, int64, error)
|
||||
Get(id uint) (*DatabaseServer, error)
|
||||
Create(req *request.DatabaseServerCreate) error
|
||||
Update(req *request.DatabaseServerUpdate) error
|
||||
Delete(id uint) error
|
||||
Sync(id uint) error
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type DatabaseUserStatus string
|
||||
|
||||
const (
|
||||
DatabaseUserStatusValid DatabaseUserStatus = "valid"
|
||||
DatabaseUserStatusInvalid DatabaseUserStatus = "invalid"
|
||||
)
|
||||
|
||||
type DatabaseUser struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
ServerID uint `gorm:"not null" json:"server_id"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Host string `gorm:"not null" json:"host"` // 仅 mysql
|
||||
Status DatabaseUserStatus `gorm:"-:all" json:"status"` // 仅显示
|
||||
Privileges map[string][]string `gorm:"-:all" json:"privileges"` // 仅显示
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
Server *DatabaseServer `gorm:"foreignKey:ServerID;references:ID" json:"server"`
|
||||
}
|
||||
|
||||
func (r *DatabaseUser) BeforeSave(tx *gorm.DB) error {
|
||||
var err error
|
||||
r.Password, err = app.Crypter.Encrypt([]byte(r.Password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *DatabaseUser) AfterFind(tx *gorm.DB) error {
|
||||
password, err := app.Crypter.Decrypt(r.Password)
|
||||
if err == nil {
|
||||
r.Password = string(password)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type DatabaseUserRepo interface {
|
||||
Count() (int64, error)
|
||||
List(page, limit uint) ([]*DatabaseUser, int64, error)
|
||||
Get(id uint) (*DatabaseUser, error)
|
||||
Create(req *request.DatabaseUserCreate) error
|
||||
Update(req *request.DatabaseUserUpdate) error
|
||||
Delete(id uint) error
|
||||
DeleteByServerID(serverID uint) error
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package biz
|
||||
|
||||
type FirewallRepo interface {
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package biz
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/ssh"
|
||||
)
|
||||
@@ -18,6 +21,34 @@ type SSH struct {
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (r *SSH) BeforeSave(tx *gorm.DB) error {
|
||||
var err error
|
||||
r.Config.Key, err = app.Crypter.Encrypt([]byte(r.Config.Key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Config.Password, err = app.Crypter.Encrypt([]byte(r.Config.Password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *SSH) AfterFind(tx *gorm.DB) error {
|
||||
key, err := app.Crypter.Decrypt(r.Config.Key)
|
||||
if err == nil {
|
||||
r.Config.Key = string(key)
|
||||
}
|
||||
password, err := app.Crypter.Decrypt(r.Config.Password)
|
||||
if err == nil {
|
||||
r.Config.Password = string(password)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SSHRepo interface {
|
||||
List(page, limit uint) ([]*SSH, int64, error)
|
||||
Get(id uint) (*SSH, error)
|
||||
|
||||
@@ -18,6 +18,7 @@ func boot() {
|
||||
initLogger()
|
||||
initOrm()
|
||||
runMigrate()
|
||||
bootCrypter()
|
||||
}
|
||||
|
||||
func BootWeb() {
|
||||
|
||||
@@ -25,8 +25,13 @@ func initConf() {
|
||||
}
|
||||
|
||||
func initGlobal() {
|
||||
app.Key = app.Conf.MustString("app.key")
|
||||
if len(app.Key) != 32 {
|
||||
log.Fatalf("app key must be 32 characters")
|
||||
}
|
||||
|
||||
app.Root = app.Conf.MustString("app.root")
|
||||
app.Version = "2.3.14"
|
||||
app.Version = "2.3.20"
|
||||
app.Locale = app.Conf.MustString("app.locale")
|
||||
|
||||
// 初始化时区
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/go-rat/utils/crypt"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
)
|
||||
|
||||
func bootCrypter() {
|
||||
crypter, err := crypt.NewXChacha20Poly1305([]byte(app.Key))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create crypter: %v", err)
|
||||
}
|
||||
|
||||
app.Crypter = crypter
|
||||
}
|
||||
@@ -6,25 +6,16 @@ import (
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
slogGorm "github.com/orandin/slog-gorm"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"moul.io/zapgorm2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/migration"
|
||||
)
|
||||
|
||||
func initOrm() {
|
||||
logLevel := logger.Error
|
||||
if app.Conf.Bool("database.debug") {
|
||||
logLevel = logger.Info
|
||||
}
|
||||
zapLogger := zapgorm2.New(app.Logger)
|
||||
zapLogger.LogMode(logLevel)
|
||||
zapLogger.SetAsDefault()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
|
||||
Logger: zapLogger,
|
||||
Logger: slogGorm.New(slogGorm.WithHandler(app.Logger.Handler())),
|
||||
SkipDefaultTransaction: true,
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bddjr/hlfhr"
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/apps"
|
||||
"github.com/TheTNB/panel/internal/http/middleware"
|
||||
"github.com/TheTNB/panel/internal/route"
|
||||
)
|
||||
@@ -24,17 +24,19 @@ func initHttp() {
|
||||
// add route
|
||||
route.Http(app.Http)
|
||||
route.Ws(app.Http)
|
||||
apps.Boot(app.Http)
|
||||
|
||||
srv := &http.Server{
|
||||
srv := hlfhr.New(&http.Server{
|
||||
Addr: fmt.Sprintf(":%d", app.Conf.MustInt("http.port")),
|
||||
Handler: http.AllowQuerySemicolons(app.Http),
|
||||
MaxHeaderBytes: 2048 << 20,
|
||||
}
|
||||
})
|
||||
srv.HttpOnHttpsPortErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
hlfhr.RedirectToHttps(w, r, http.StatusTemporaryRedirect)
|
||||
})
|
||||
|
||||
if app.Conf.Bool("http.tls") {
|
||||
srv.TLSConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
cert := filepath.Join(app.Root, "panel/storage/cert.pem")
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/exp/zapslog"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
@@ -13,16 +15,15 @@ import (
|
||||
|
||||
func initLogger() {
|
||||
writeSyncer := zapcore.AddSync(&lumberjack.Logger{
|
||||
Filename: filepath.Join(app.Root, "panel/storage/logs/app.log"),
|
||||
MaxSize: 10,
|
||||
MaxBackups: 10,
|
||||
MaxAge: 30,
|
||||
Compress: true,
|
||||
Filename: filepath.Join(app.Root, "panel/storage/logs/app.log"),
|
||||
MaxSize: 10,
|
||||
MaxAge: 30,
|
||||
Compress: true,
|
||||
})
|
||||
|
||||
level := zapcore.InfoLevel
|
||||
level := zap.InfoLevel
|
||||
if app.Conf.Bool("app.debug") {
|
||||
level = zapcore.DebugLevel
|
||||
level = zap.DebugLevel
|
||||
}
|
||||
|
||||
config := zap.NewProductionEncoderConfig()
|
||||
@@ -34,8 +35,7 @@ func initLogger() {
|
||||
)
|
||||
|
||||
logger := zap.New(core)
|
||||
defer func(logger *zap.Logger) {
|
||||
_ = logger.Sync()
|
||||
}(logger)
|
||||
app.Logger = logger
|
||||
zap.ReplaceGlobals(logger)
|
||||
app.Logger = slog.New(zapslog.NewHandler(logger.Core()))
|
||||
slog.SetDefault(app.Logger)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func initQueue() {
|
||||
app.Queue = queue.New(40)
|
||||
app.Queue = queue.New(100)
|
||||
go app.Queue.Run(context.Background())
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func initSession() {
|
||||
// initialize session manager
|
||||
manager, err := sessions.NewManager(&sessions.ManagerOptions{
|
||||
Key: app.Conf.String("app.key"),
|
||||
Key: app.Key,
|
||||
Lifetime: 120,
|
||||
GcInterval: 30,
|
||||
DisableDefaultDriver: true,
|
||||
|
||||
+13
-18
@@ -7,7 +7,9 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/go-rat/utils/collect"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/samber/do/v2"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
@@ -15,25 +17,18 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/api"
|
||||
"github.com/TheTNB/panel/pkg/apploader"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
)
|
||||
|
||||
type appRepo struct {
|
||||
cacheRepo biz.CacheRepo
|
||||
taskRepo biz.TaskRepo
|
||||
api *api.API
|
||||
api *api.API
|
||||
}
|
||||
|
||||
func NewAppRepo() biz.AppRepo {
|
||||
return &appRepo{
|
||||
cacheRepo: NewCacheRepo(),
|
||||
taskRepo: NewTaskRepo(),
|
||||
api: api.NewAPI(app.Version),
|
||||
}
|
||||
return do.MustInvoke[biz.AppRepo](injector)
|
||||
}
|
||||
|
||||
func (r *appRepo) All() api.Apps {
|
||||
cached, err := r.cacheRepo.Get(biz.CacheKeyApps)
|
||||
cached, err := NewCacheRepo().Get(biz.CacheKeyApps)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -65,7 +60,7 @@ func (r *appRepo) UpdateExist(slug string) bool {
|
||||
|
||||
for channel := range slices.Values(item.Channels) {
|
||||
if channel.Slug == installed.Channel {
|
||||
current := str.FirstElement(channel.Subs)
|
||||
current := collect.First(channel.Subs)
|
||||
if current != nil && current.Version != installed.Version {
|
||||
return true
|
||||
}
|
||||
@@ -109,7 +104,7 @@ func (r *appRepo) GetHomeShow() ([]map[string]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var filtered []map[string]string
|
||||
filtered := make([]map[string]string, 0)
|
||||
for item := range slices.Values(apps) {
|
||||
loaded, err := r.Get(item.Slug)
|
||||
if err != nil {
|
||||
@@ -168,7 +163,7 @@ func (r *appRepo) Install(channel, slug string) error {
|
||||
}
|
||||
shellUrl = ch.Install
|
||||
shellChannel = ch.Slug
|
||||
shellVersion = str.FirstElement(ch.Subs).Version
|
||||
shellVersion = collect.First(ch.Subs).Version
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -190,7 +185,7 @@ func (r *appRepo) Install(channel, slug string) error {
|
||||
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
|
||||
task.Log = "/tmp/" + item.Slug + ".log"
|
||||
|
||||
return r.taskRepo.Push(task)
|
||||
return NewTaskRepo().Push(task)
|
||||
}
|
||||
|
||||
func (r *appRepo) UnInstall(slug string) error {
|
||||
@@ -245,7 +240,7 @@ func (r *appRepo) UnInstall(slug string) error {
|
||||
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
|
||||
task.Log = "/tmp/" + item.Slug + ".log"
|
||||
|
||||
return r.taskRepo.Push(task)
|
||||
return NewTaskRepo().Push(task)
|
||||
}
|
||||
|
||||
func (r *appRepo) Update(slug string) error {
|
||||
@@ -278,7 +273,7 @@ func (r *appRepo) Update(slug string) error {
|
||||
}
|
||||
shellUrl = ch.Update
|
||||
shellChannel = ch.Slug
|
||||
shellVersion = str.FirstElement(ch.Subs).Version
|
||||
shellVersion = collect.First(ch.Subs).Version
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -300,7 +295,7 @@ func (r *appRepo) Update(slug string) error {
|
||||
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
|
||||
task.Log = "/tmp/" + item.Slug + ".log"
|
||||
|
||||
return r.taskRepo.Push(task)
|
||||
return NewTaskRepo().Push(task)
|
||||
}
|
||||
|
||||
func (r *appRepo) UpdateShow(slug string, show bool) error {
|
||||
@@ -331,7 +326,7 @@ func (r *appRepo) UpdateCache() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.cacheRepo.Set(biz.CacheKeyApps, string(encoded))
|
||||
return NewCacheRepo().Set(biz.CacheKeyApps, string(encoded))
|
||||
}
|
||||
|
||||
func (r *appRepo) preCheck(app *api.App) error {
|
||||
|
||||
+43
-51
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
@@ -16,20 +17,14 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type backupRepo struct {
|
||||
setting biz.SettingRepo
|
||||
website biz.WebsiteRepo
|
||||
}
|
||||
type backupRepo struct{}
|
||||
|
||||
func NewBackupRepo() biz.BackupRepo {
|
||||
return &backupRepo{
|
||||
setting: NewSettingRepo(),
|
||||
website: NewWebsiteRepo(),
|
||||
}
|
||||
return do.MustInvoke[biz.BackupRepo](injector)
|
||||
}
|
||||
|
||||
// List 备份列表
|
||||
@@ -53,7 +48,7 @@ func (r *backupRepo) List(typ biz.BackupType) ([]*types.BackupFile, error) {
|
||||
list = append(list, &types.BackupFile{
|
||||
Name: file.Name(),
|
||||
Path: filepath.Join(path, file.Name()),
|
||||
Size: str.FormatBytes(float64(info.Size())),
|
||||
Size: tools.FormatBytes(float64(info.Size())),
|
||||
Time: info.ModTime(),
|
||||
})
|
||||
}
|
||||
@@ -134,7 +129,7 @@ func (r *backupRepo) CutoffLog(path, target string) error {
|
||||
}
|
||||
|
||||
to := filepath.Join(path, fmt.Sprintf("%s_%s.zip", time.Now().Format("20060102150405"), filepath.Base(target)))
|
||||
if err := io.Compress([]string{target}, to, io.Zip); err != nil {
|
||||
if err := io.Compress(filepath.Dir(target), []string{filepath.Base(target)}, to); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -197,7 +192,7 @@ func (r *backupRepo) ClearExpired(path, prefix string, save int) error {
|
||||
|
||||
// GetPath 获取备份路径
|
||||
func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) {
|
||||
backupPath, err := r.setting.Get(biz.SettingKeyBackupPath)
|
||||
backupPath, err := NewSettingRepo().Get(biz.SettingKeyBackupPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -217,7 +212,7 @@ func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) {
|
||||
|
||||
// createWebsite 创建网站备份
|
||||
func (r *backupRepo) createWebsite(to string, name string) error {
|
||||
website, err := r.website.GetByName(name)
|
||||
website, err := NewWebsiteRepo().GetByName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -226,18 +221,9 @@ func (r *backupRepo) createWebsite(to string, name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var paths []string
|
||||
dirs, err := io.ReadDir(website.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range dirs {
|
||||
paths = append(paths, filepath.Join(website.Path, item.Name()))
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
backup := filepath.Join(to, fmt.Sprintf("%s_%s.zip", website.Name, time.Now().Format("20060102150405")))
|
||||
if err = io.Compress(paths, backup, io.Zip); err != nil {
|
||||
if err = io.Compress(website.Path, nil, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -250,7 +236,7 @@ func (r *backupRepo) createWebsite(to string, name string) error {
|
||||
|
||||
// createMySQL 创建 MySQL 备份
|
||||
func (r *backupRepo) createMySQL(to string, name string) error {
|
||||
rootPassword, err := r.setting.Get(biz.SettingKeyMySQLRootPassword)
|
||||
rootPassword, err := NewSettingRepo().Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -281,7 +267,7 @@ func (r *backupRepo) createMySQL(to string, name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = io.Compress([]string{backup}, backup+".zip", io.Zip); err != nil {
|
||||
if err = io.Compress(filepath.Dir(backup), []string{filepath.Base(backup)}, backup+".zip"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Remove(backup); err != nil {
|
||||
@@ -297,7 +283,7 @@ func (r *backupRepo) createMySQL(to string, name string) error {
|
||||
|
||||
// createPostgres 创建 PostgreSQL 备份
|
||||
func (r *backupRepo) createPostgres(to string, name string) error {
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), 5432)
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -318,7 +304,7 @@ func (r *backupRepo) createPostgres(to string, name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = io.Compress([]string{backup}, backup+".zip", io.Zip); err != nil {
|
||||
if err = io.Compress(filepath.Dir(backup), []string{filepath.Base(backup)}, backup+".zip"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Remove(backup); err != nil {
|
||||
@@ -341,14 +327,27 @@ func (r *backupRepo) createPanel(to string) error {
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if err := io.Compress([]string{
|
||||
filepath.Join(app.Root, "panel"),
|
||||
"/usr/local/sbin/panel-cli",
|
||||
"/usr/local/etc/panel/config.yml",
|
||||
}, backup, io.Zip); err != nil {
|
||||
|
||||
temp, err := io.TempDir("panel-backup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := io.Chmod(backup, 0600); err != nil {
|
||||
|
||||
if err = io.Cp(filepath.Join(app.Root, "panel"), temp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Cp("/usr/local/sbin/panel-cli", temp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Cp("/usr/local/etc/panel/config.yml", temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = io.Chmod(temp, 0600)
|
||||
if err = io.Compress(temp, nil, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Chmod(backup, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -356,7 +355,8 @@ func (r *backupRepo) createPanel(to string) error {
|
||||
fmt.Printf("|-备份耗时:%s\n", time.Since(start).String())
|
||||
fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup))
|
||||
}
|
||||
return nil
|
||||
|
||||
return io.Remove(temp)
|
||||
}
|
||||
|
||||
// restoreWebsite 恢复网站备份
|
||||
@@ -365,11 +365,7 @@ func (r *backupRepo) restoreWebsite(backup, target string) error {
|
||||
return errors.New("备份文件不存在")
|
||||
}
|
||||
|
||||
website, err := r.website.GetByName(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
format, err := io.FormatArchiveByPath(backup)
|
||||
website, err := NewWebsiteRepo().GetByName(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -377,7 +373,7 @@ func (r *backupRepo) restoreWebsite(backup, target string) error {
|
||||
if err = io.Remove(website.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.UnCompress(backup, website.Path, format); err != nil {
|
||||
if err = io.UnCompress(backup, website.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Chmod(website.Path, 0755); err != nil {
|
||||
@@ -396,7 +392,7 @@ func (r *backupRepo) restoreMySQL(backup, target string) error {
|
||||
return errors.New("备份文件不存在")
|
||||
}
|
||||
|
||||
rootPassword, err := r.setting.Get(biz.SettingKeyMySQLRootPassword)
|
||||
rootPassword, err := NewSettingRepo().Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -435,7 +431,7 @@ func (r *backupRepo) restorePostgres(backup, target string) error {
|
||||
return errors.New("备份文件不存在")
|
||||
}
|
||||
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), 5432)
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -477,9 +473,9 @@ func (r *backupRepo) preCheckPath(to, path string) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-目标大小:%s\n", str.FormatBytes(float64(size)))
|
||||
fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size)))
|
||||
fmt.Printf("|-目标文件数:%d\n", files)
|
||||
fmt.Printf("|-备份目录可用空间:%s\n", str.FormatBytes(float64(usage.Free)))
|
||||
fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free)))
|
||||
fmt.Printf("|-备份目录可用Inode:%d\n", usage.InodesFree)
|
||||
}
|
||||
|
||||
@@ -503,8 +499,8 @@ func (r *backupRepo) preCheckDB(to string, size int64) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-目标大小:%s\n", str.FormatBytes(float64(size)))
|
||||
fmt.Printf("|-备份目录可用空间:%s\n", str.FormatBytes(float64(usage.Free)))
|
||||
fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size)))
|
||||
fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free)))
|
||||
fmt.Printf("|-备份目录可用Inode:%d\n", usage.InodesFree)
|
||||
}
|
||||
|
||||
@@ -522,11 +518,7 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
format, err := io.FormatArchiveByPath(backup)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = io.UnCompress(backup, temp, format); err != nil {
|
||||
if err = io.UnCompress(backup, temp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package data
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
type cacheRepo struct{}
|
||||
|
||||
func NewCacheRepo() biz.CacheRepo {
|
||||
return &cacheRepo{}
|
||||
return do.MustInvoke[biz.CacheRepo](injector)
|
||||
}
|
||||
|
||||
func (r *cacheRepo) Get(key biz.CacheKey, defaultValue ...string) (string, error) {
|
||||
|
||||
+97
-12
@@ -4,16 +4,21 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
pkgcert "github.com/TheTNB/panel/pkg/cert"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type certRepo struct {
|
||||
@@ -21,14 +26,40 @@ type certRepo struct {
|
||||
}
|
||||
|
||||
func NewCertRepo() biz.CertRepo {
|
||||
return &certRepo{}
|
||||
return do.MustInvoke[biz.CertRepo](injector)
|
||||
}
|
||||
|
||||
func (r *certRepo) List(page, limit uint) ([]*biz.Cert, int64, error) {
|
||||
func (r *certRepo) List(page, limit uint) ([]*types.CertList, int64, error) {
|
||||
var certs []*biz.Cert
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.Cert{}).Preload("Website").Preload("Account").Preload("DNS").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&certs).Error
|
||||
return certs, total, err
|
||||
|
||||
list := make([]*types.CertList, 0)
|
||||
for cert := range slices.Values(certs) {
|
||||
item := &types.CertList{
|
||||
ID: cert.ID,
|
||||
AccountID: cert.AccountID,
|
||||
WebsiteID: cert.WebsiteID,
|
||||
DNSID: cert.DNSID,
|
||||
Type: cert.Type,
|
||||
Domains: cert.Domains,
|
||||
AutoRenew: cert.AutoRenew,
|
||||
Cert: cert.Cert,
|
||||
Key: cert.Key,
|
||||
CreatedAt: cert.CreatedAt,
|
||||
UpdatedAt: cert.UpdatedAt,
|
||||
}
|
||||
if decode, err := pkgcert.ParseCert(cert.Cert); err == nil {
|
||||
item.NotBefore = decode.NotBefore
|
||||
item.NotAfter = decode.NotAfter
|
||||
item.Issuer = decode.Issuer.CommonName
|
||||
item.OCSPServer = decode.OCSPServer
|
||||
item.DNSNames = decode.DNSNames
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
return list, total, err
|
||||
}
|
||||
|
||||
func (r *certRepo) Get(id uint) (*biz.Cert, error) {
|
||||
@@ -43,6 +74,28 @@ func (r *certRepo) GetByWebsite(WebsiteID uint) (*biz.Cert, error) {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
func (r *certRepo) Upload(req *request.CertUpload) (*biz.Cert, error) {
|
||||
info, err := pkgcert.ParseCert(req.Cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate: %v", err)
|
||||
}
|
||||
if _, err = pkgcert.ParseKey(req.Key); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse private key: %v", err)
|
||||
}
|
||||
|
||||
cert := &biz.Cert{
|
||||
Type: "upload",
|
||||
Domains: info.DNSNames,
|
||||
Cert: req.Cert,
|
||||
Key: req.Key,
|
||||
}
|
||||
if err = app.Orm.Create(cert).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (r *certRepo) Create(req *request.CertCreate) (*biz.Cert, error) {
|
||||
cert := &biz.Cert{
|
||||
AccountID: req.AccountID,
|
||||
@@ -59,11 +112,19 @@ func (r *certRepo) Create(req *request.CertCreate) (*biz.Cert, error) {
|
||||
}
|
||||
|
||||
func (r *certRepo) Update(req *request.CertUpdate) error {
|
||||
return app.Orm.Model(&biz.Cert{}).Where("id = ?", req.ID).Updates(&biz.Cert{
|
||||
info, err := pkgcert.ParseCert(req.Cert)
|
||||
if err == nil && req.Type == "upload" {
|
||||
req.Domains = info.DNSNames
|
||||
}
|
||||
|
||||
return app.Orm.Model(&biz.Cert{}).Where("id = ?", req.ID).Select("*").Updates(&biz.Cert{
|
||||
ID: req.ID,
|
||||
AccountID: req.AccountID,
|
||||
WebsiteID: req.WebsiteID,
|
||||
DNSID: req.DNSID,
|
||||
Type: req.Type,
|
||||
Cert: req.Cert,
|
||||
Key: req.Key,
|
||||
Domains: req.Domains,
|
||||
AutoRenew: req.AutoRenew,
|
||||
}).Error
|
||||
@@ -88,11 +149,11 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
|
||||
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
|
||||
} else {
|
||||
if cert.Website == nil {
|
||||
return nil, errors.New("该证书没有关联网站,无法自动签发")
|
||||
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
|
||||
} else {
|
||||
for _, domain := range cert.Domains {
|
||||
if strings.Contains(domain, "*") {
|
||||
return nil, errors.New("通配符域名无法使用 HTTP 验证")
|
||||
return nil, errors.New("wildcard domains cannot use HTTP verification")
|
||||
}
|
||||
}
|
||||
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
|
||||
@@ -126,7 +187,7 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
|
||||
}
|
||||
|
||||
if r.client == nil {
|
||||
return nil, errors.New("请重新获取 DNS 解析记录")
|
||||
return nil, errors.New("please retry the manual obtain operation")
|
||||
}
|
||||
|
||||
ssl, err := r.client.ObtainCertificateManual()
|
||||
@@ -148,6 +209,30 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
|
||||
return &ssl, nil
|
||||
}
|
||||
|
||||
func (r *certRepo) ObtainSelfSigned(id uint) error {
|
||||
cert, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crt, key, err := pkgcert.GenerateSelfSigned(cert.Domains)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cert.Cert = string(crt)
|
||||
cert.Key = string(key)
|
||||
if err = app.Orm.Save(cert).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cert.Website != nil {
|
||||
return r.Deploy(cert.ID, cert.WebsiteID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
|
||||
cert, err := r.Get(id)
|
||||
if err != nil {
|
||||
@@ -160,18 +245,18 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
|
||||
}
|
||||
|
||||
if cert.CertURL == "" {
|
||||
return nil, errors.New("该证书没有签发成功,无法续签")
|
||||
return nil, errors.New("this certificate has not been signed successfully and cannot be renewed")
|
||||
}
|
||||
|
||||
if cert.DNS != nil {
|
||||
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
|
||||
} else {
|
||||
if cert.Website == nil {
|
||||
return nil, errors.New("该证书没有关联网站,无法续签,可以尝试手动签发")
|
||||
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
|
||||
} else {
|
||||
for _, domain := range cert.Domains {
|
||||
if strings.Contains(domain, "*") {
|
||||
return nil, errors.New("通配符域名无法使用 HTTP 验证")
|
||||
return nil, errors.New("wildcard domains cannot use HTTP verification")
|
||||
}
|
||||
}
|
||||
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
|
||||
@@ -231,7 +316,7 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
|
||||
}
|
||||
|
||||
if cert.Cert == "" || cert.Key == "" {
|
||||
return errors.New("该证书没有签发成功,无法部署")
|
||||
return errors.New("this certificate has not been signed successfully and cannot be deployed")
|
||||
}
|
||||
|
||||
website, err := NewWebsiteRepo().Get(WebsiteID)
|
||||
@@ -255,7 +340,7 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
|
||||
|
||||
func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
|
||||
if cert.Account == nil {
|
||||
return nil, errors.New("该证书没有关联账号,无法签发")
|
||||
return nil, errors.New("this certificate is not associated with an ACME account and cannot be signed")
|
||||
}
|
||||
|
||||
var ca string
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
type certAccountRepo struct{}
|
||||
|
||||
func NewCertAccountRepo() biz.CertAccountRepo {
|
||||
return &certAccountRepo{}
|
||||
return do.MustInvoke[biz.CertAccountRepo](injector)
|
||||
}
|
||||
|
||||
func (r certAccountRepo) List(page, limit uint) ([]*biz.CertAccount, int64, error) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
@@ -9,7 +11,7 @@ import (
|
||||
type certDNSRepo struct{}
|
||||
|
||||
func NewCertDNSRepo() biz.CertDNSRepo {
|
||||
return &certDNSRepo{}
|
||||
return do.MustInvoke[biz.CertDNSRepo](injector)
|
||||
}
|
||||
|
||||
func (r certDNSRepo) List(page, limit uint) ([]*biz.CertDNS, int64, error) {
|
||||
|
||||
+130
-156
@@ -1,235 +1,209 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
paneltypes "github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/container"
|
||||
)
|
||||
|
||||
type containerRepo struct {
|
||||
client *client.Client
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerRepo(sock ...string) biz.ContainerRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/run/podman/podman.sock")
|
||||
}
|
||||
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
|
||||
return &containerRepo{
|
||||
client: cli,
|
||||
}
|
||||
func NewContainerRepo() biz.ContainerRepo {
|
||||
return do.MustInvoke[biz.ContainerRepo](injector)
|
||||
}
|
||||
|
||||
// ListAll 列出所有容器
|
||||
func (r *containerRepo) ListAll() ([]types.Container, error) {
|
||||
containers, err := r.client.ContainerList(context.Background(), container.ListOptions{
|
||||
All: true,
|
||||
})
|
||||
var resp []container.Container
|
||||
_, err := r.client.R().SetResult(&resp).SetQueryParam("all", "true").Get("/containers/json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var containers []types.Container
|
||||
for _, item := range resp {
|
||||
ports := make([]types.ContainerPort, 0)
|
||||
for _, port := range item.Ports {
|
||||
ports = append(ports, types.ContainerPort{
|
||||
ContainerStart: uint(port.PrivatePort),
|
||||
ContainerEnd: uint(port.PublicPort),
|
||||
HostStart: uint(port.PublicPort),
|
||||
HostEnd: uint(port.PublicPort),
|
||||
Protocol: port.Type,
|
||||
Host: port.IP,
|
||||
})
|
||||
}
|
||||
if len(item.Names) == 0 {
|
||||
item.Names = append(item.Names, "")
|
||||
}
|
||||
containers = append(containers, types.Container{
|
||||
ID: item.ID,
|
||||
Name: strings.TrimPrefix(item.Names[0], "/"), // https://github.com/moby/moby/issues/7519
|
||||
Image: item.Image,
|
||||
ImageID: item.ImageID,
|
||||
Command: item.Command,
|
||||
CreatedAt: time.Unix(item.Created, 0),
|
||||
State: item.State,
|
||||
Status: item.Status,
|
||||
Ports: ports,
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
})
|
||||
}
|
||||
|
||||
slices.SortFunc(containers, func(a types.Container, b types.Container) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
// ListByNames 根据名称列出容器
|
||||
func (r *containerRepo) ListByNames(names []string) ([]types.Container, error) {
|
||||
var options container.ListOptions
|
||||
options.All = true
|
||||
if len(names) > 0 {
|
||||
var array []filters.KeyValuePair
|
||||
for _, n := range names {
|
||||
array = append(array, filters.Arg("name", n))
|
||||
}
|
||||
options.Filters = filters.NewArgs(array...)
|
||||
}
|
||||
containers, err := r.client.ContainerList(context.Background(), options)
|
||||
// ListByName 根据名称搜索容器
|
||||
func (r *containerRepo) ListByName(names string) ([]types.Container, error) {
|
||||
containers, err := r.ListAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containers = slices.DeleteFunc(containers, func(item types.Container) bool {
|
||||
return !strings.Contains(item.Name, names)
|
||||
})
|
||||
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
// Create 创建容器
|
||||
func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
|
||||
var hostConf container.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
|
||||
portMap := make(nat.PortMap)
|
||||
for _, port := range req.Ports {
|
||||
if port.ContainerStart-port.ContainerEnd != port.HostStart-port.HostEnd {
|
||||
return "", fmt.Errorf("容器端口和主机端口数量不匹配(容器: %d 主机: %d)", port.ContainerStart-port.ContainerEnd, port.HostStart-port.HostEnd)
|
||||
}
|
||||
if port.ContainerStart > port.ContainerEnd || port.HostStart > port.HostEnd || port.ContainerStart < 1 || port.HostStart < 1 {
|
||||
return "", fmt.Errorf("端口范围不正确")
|
||||
}
|
||||
|
||||
count := 0
|
||||
for host := port.HostStart; host <= port.HostEnd; host++ {
|
||||
bindItem := nat.PortBinding{HostPort: strconv.Itoa(host), HostIP: port.Host}
|
||||
portMap[nat.Port(fmt.Sprintf("%d/%s", port.ContainerStart+count, port.Protocol))] = []nat.PortBinding{bindItem}
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
exposed := make(nat.PortSet)
|
||||
for port := range portMap {
|
||||
exposed[port] = struct{}{}
|
||||
}
|
||||
|
||||
if req.Network != "" {
|
||||
switch req.Network {
|
||||
case "host", "none", "bridge":
|
||||
hostConf.NetworkMode = container.NetworkMode(req.Network)
|
||||
}
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("docker run -d --name %s", req.Name))
|
||||
if req.PublishAllPorts {
|
||||
sb.WriteString(" -P")
|
||||
} else {
|
||||
networkConf = network.NetworkingConfig{}
|
||||
for _, port := range req.Ports {
|
||||
sb.WriteString(" -p ")
|
||||
if port.Host != "" {
|
||||
sb.WriteString(fmt.Sprintf("%s:", port.Host))
|
||||
}
|
||||
if port.HostStart == port.HostEnd || port.ContainerStart == port.ContainerEnd {
|
||||
sb.WriteString(fmt.Sprintf("%d:%d/%s", port.HostStart, port.ContainerStart, port.Protocol))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%d-%d:%d-%d/%s", port.HostStart, port.HostEnd, port.ContainerStart, port.ContainerEnd, port.Protocol))
|
||||
}
|
||||
}
|
||||
}
|
||||
if req.Network != "" {
|
||||
sb.WriteString(fmt.Sprintf(" --network %s", req.Network))
|
||||
}
|
||||
for _, volume := range req.Volumes {
|
||||
sb.WriteString(fmt.Sprintf(" -v %s:%s:%s", volume.Host, volume.Container, volume.Mode))
|
||||
}
|
||||
for _, label := range req.Labels {
|
||||
sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value))
|
||||
}
|
||||
for _, env := range req.Env {
|
||||
sb.WriteString(fmt.Sprintf(" -e %s=%s", env.Key, env.Value))
|
||||
}
|
||||
if len(req.Entrypoint) > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" --entrypoint '%s'", strings.Join(req.Entrypoint, " ")))
|
||||
}
|
||||
if len(req.Command) > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" '%s'", strings.Join(req.Command, " ")))
|
||||
}
|
||||
if req.RestartPolicy != "" {
|
||||
sb.WriteString(fmt.Sprintf(" --restart %s", req.RestartPolicy))
|
||||
}
|
||||
if req.AutoRemove {
|
||||
sb.WriteString(" --rm")
|
||||
}
|
||||
if req.Privileged {
|
||||
sb.WriteString(" --privileged")
|
||||
}
|
||||
if req.OpenStdin {
|
||||
sb.WriteString(" -i")
|
||||
}
|
||||
if req.Tty {
|
||||
sb.WriteString(" -t")
|
||||
}
|
||||
if req.CPUShares > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" --cpu-shares %d", req.CPUShares))
|
||||
}
|
||||
if req.CPUs > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" --cpus %d", req.CPUs))
|
||||
}
|
||||
if req.Memory > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" --memory %d", req.Memory))
|
||||
}
|
||||
|
||||
hostConf.Privileged = req.Privileged
|
||||
hostConf.AutoRemove = req.AutoRemove
|
||||
hostConf.CPUShares = req.CPUShares
|
||||
hostConf.PublishAllPorts = req.PublishAllPorts
|
||||
hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)}
|
||||
if req.RestartPolicy == "on-failure" {
|
||||
hostConf.RestartPolicy.MaximumRetryCount = 5
|
||||
}
|
||||
hostConf.NanoCPUs = req.CPUs * 1000000000
|
||||
hostConf.Memory = req.Memory * 1024 * 1024
|
||||
hostConf.MemorySwap = 0
|
||||
hostConf.PortBindings = portMap
|
||||
hostConf.Binds = []string{}
|
||||
|
||||
volumes := make(map[string]struct{})
|
||||
for _, v := range req.Volumes {
|
||||
volumes[v.Container] = struct{}{}
|
||||
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", v.Host, v.Container, v.Mode))
|
||||
}
|
||||
|
||||
resp, err := r.client.ContainerCreate(context.Background(), &container.Config{
|
||||
Image: req.Image,
|
||||
Env: paneltypes.KVToSlice(req.Env),
|
||||
Entrypoint: req.Entrypoint,
|
||||
Cmd: req.Command,
|
||||
Labels: paneltypes.KVToMap(req.Labels),
|
||||
ExposedPorts: exposed,
|
||||
OpenStdin: req.OpenStdin,
|
||||
Tty: req.Tty,
|
||||
Volumes: volumes,
|
||||
}, &hostConf, &networkConf, nil, req.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resp.ID, err
|
||||
sb.WriteString(" %s bash")
|
||||
return shell.ExecfWithTTY(sb.String(), req.Image)
|
||||
}
|
||||
|
||||
// Remove 移除容器
|
||||
func (r *containerRepo) Remove(id string) error {
|
||||
return r.client.ContainerRemove(context.Background(), id, container.RemoveOptions{
|
||||
Force: true,
|
||||
})
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Start 启动容器
|
||||
func (r *containerRepo) Start(id string) error {
|
||||
return r.client.ContainerStart(context.Background(), id, container.StartOptions{})
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker start %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop 停止容器
|
||||
func (r *containerRepo) Stop(id string) error {
|
||||
return r.client.ContainerStop(context.Background(), id, container.StopOptions{})
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker stop %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart 重启容器
|
||||
func (r *containerRepo) Restart(id string) error {
|
||||
return r.client.ContainerRestart(context.Background(), id, container.StopOptions{})
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker restart %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Pause 暂停容器
|
||||
func (r *containerRepo) Pause(id string) error {
|
||||
return r.client.ContainerPause(context.Background(), id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker pause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Unpause 恢复容器
|
||||
func (r *containerRepo) Unpause(id string) error {
|
||||
return r.client.ContainerUnpause(context.Background(), id)
|
||||
}
|
||||
|
||||
// Inspect 查看容器
|
||||
func (r *containerRepo) Inspect(id string) (types.ContainerJSON, error) {
|
||||
return r.client.ContainerInspect(context.Background(), id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker unpause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Kill 杀死容器
|
||||
func (r *containerRepo) Kill(id string) error {
|
||||
return r.client.ContainerKill(context.Background(), id, "KILL")
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker kill %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename 重命名容器
|
||||
func (r *containerRepo) Rename(id string, newName string) error {
|
||||
return r.client.ContainerRename(context.Background(), id, newName)
|
||||
}
|
||||
|
||||
// Stats 查看容器状态
|
||||
func (r *containerRepo) Stats(id string) (container.StatsResponseReader, error) {
|
||||
return r.client.ContainerStats(context.Background(), id, false)
|
||||
}
|
||||
|
||||
// Exist 判断容器是否存在
|
||||
func (r *containerRepo) Exist(name string) (bool, error) {
|
||||
var options container.ListOptions
|
||||
options.Filters = filters.NewArgs(filters.Arg("name", name))
|
||||
containers, err := r.client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(containers) > 0, nil
|
||||
}
|
||||
|
||||
// Update 更新容器
|
||||
func (r *containerRepo) Update(id string, config container.UpdateConfig) error {
|
||||
_, err := r.client.ContainerUpdate(context.Background(), id, config)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rename %s %s", id, newName)
|
||||
return err
|
||||
}
|
||||
|
||||
// Logs 查看容器日志
|
||||
func (r *containerRepo) Logs(id string) (string, error) {
|
||||
options := container.LogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
}
|
||||
reader, err := r.client.ContainerLogs(context.Background(), id, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
return shell.ExecfWithTimeout(120*time.Second, "docker logs %s", id)
|
||||
}
|
||||
|
||||
// Prune 清理未使用的容器
|
||||
func (r *containerRepo) Prune() error {
|
||||
_, err := r.client.ContainersPrune(context.Background(), filters.NewArgs())
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker container prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,97 +1,87 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/image"
|
||||
)
|
||||
|
||||
type containerImageRepo struct {
|
||||
client *client.Client
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerImageRepo(sock ...string) biz.ContainerImageRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/run/podman/podman.sock")
|
||||
}
|
||||
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
|
||||
return &containerImageRepo{
|
||||
client: cli,
|
||||
}
|
||||
func NewContainerImageRepo() biz.ContainerImageRepo {
|
||||
return do.MustInvoke[biz.ContainerImageRepo](injector)
|
||||
}
|
||||
|
||||
// List 列出镜像
|
||||
func (r *containerImageRepo) List() ([]image.Summary, error) {
|
||||
return r.client.ImageList(context.Background(), image.ListOptions{
|
||||
All: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Exist 判断镜像是否存在
|
||||
func (r *containerImageRepo) Exist(id string) (bool, error) {
|
||||
var options image.ListOptions
|
||||
options.Filters = filters.NewArgs(filters.Arg("reference", id))
|
||||
images, err := r.client.ImageList(context.Background(), options)
|
||||
func (r *containerImageRepo) List() ([]types.ContainerImage, error) {
|
||||
var resp []image.Image
|
||||
_, err := r.client.R().SetResult(&resp).SetQueryParam("all", "true").Get("/images/json")
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return len(images) > 0, nil
|
||||
var images []types.ContainerImage
|
||||
for _, item := range resp {
|
||||
images = append(images, types.ContainerImage{
|
||||
ID: item.ID,
|
||||
Containers: item.Containers,
|
||||
RepoTags: item.RepoTags,
|
||||
RepoDigests: item.RepoDigests,
|
||||
Size: tools.FormatBytes(float64(item.Size)),
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
CreatedAt: time.Unix(item.Created, 0),
|
||||
})
|
||||
}
|
||||
|
||||
slices.SortFunc(images, func(a types.ContainerImage, b types.ContainerImage) int {
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// Pull 拉取镜像
|
||||
func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
|
||||
options := image.PullOptions{}
|
||||
var sb strings.Builder
|
||||
|
||||
if req.Auth {
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
sb.WriteString(fmt.Sprintf("docker login -u %s -p %s", req.Username, req.Password))
|
||||
if _, err := shell.Exec(sb.String()); err != nil {
|
||||
return fmt.Errorf("login failed: %w", err)
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
options.RegistryAuth = authStr
|
||||
sb.Reset()
|
||||
}
|
||||
|
||||
out, err := r.client.ImagePull(context.Background(), req.Name, options)
|
||||
if err != nil {
|
||||
sb.WriteString(fmt.Sprintf("docker pull %s", req.Name))
|
||||
|
||||
if _, err := shell.Exec(sb.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(io.Discard, out)
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除镜像
|
||||
func (r *containerImageRepo) Remove(id string) error {
|
||||
_, err := r.client.ImageRemove(context.Background(), id, image.RemoveOptions{
|
||||
Force: true,
|
||||
PruneChildren: true,
|
||||
})
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rmi %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的镜像
|
||||
func (r *containerImageRepo) Prune() error {
|
||||
_, err := r.client.ImagesPrune(context.Background(), filters.NewArgs())
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker image prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
// Inspect 查看镜像
|
||||
func (r *containerImageRepo) Inspect(id string) (types.ImageInspect, error) {
|
||||
img, _, err := r.client.ImageInspectWithRaw(context.Background(), id)
|
||||
return img, err
|
||||
}
|
||||
|
||||
@@ -1,104 +1,114 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/network"
|
||||
)
|
||||
|
||||
type containerNetworkRepo struct {
|
||||
client *client.Client
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerNetworkRepo(sock ...string) biz.ContainerNetworkRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/run/podman/podman.sock")
|
||||
}
|
||||
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
|
||||
return &containerNetworkRepo{
|
||||
client: cli,
|
||||
}
|
||||
func NewContainerNetworkRepo() biz.ContainerNetworkRepo {
|
||||
return do.MustInvoke[biz.ContainerNetworkRepo](injector)
|
||||
}
|
||||
|
||||
// List 列出网络
|
||||
func (r *containerNetworkRepo) List() ([]network.Inspect, error) {
|
||||
return r.client.NetworkList(context.Background(), network.ListOptions{})
|
||||
func (r *containerNetworkRepo) List() ([]types.ContainerNetwork, error) {
|
||||
var resp []network.Network
|
||||
_, err := r.client.R().SetResult(&resp).Get("/networks")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var networks []types.ContainerNetwork
|
||||
for _, item := range resp {
|
||||
ipamConfigs := make([]types.ContainerNetworkIPAMConfig, 0)
|
||||
for _, ipam := range item.IPAM.Config {
|
||||
ipamConfigs = append(ipamConfigs, types.ContainerNetworkIPAMConfig{
|
||||
Subnet: ipam.Subnet,
|
||||
IPRange: ipam.IPRange,
|
||||
Gateway: ipam.Gateway,
|
||||
AuxAddress: ipam.AuxAddress,
|
||||
})
|
||||
}
|
||||
networks = append(networks, types.ContainerNetwork{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
IPv6: item.EnableIPv6,
|
||||
Internal: item.Internal,
|
||||
Attachable: item.Attachable,
|
||||
Ingress: item.Ingress,
|
||||
Scope: item.Scope,
|
||||
CreatedAt: item.Created,
|
||||
IPAM: types.ContainerNetworkIPAM{
|
||||
Driver: item.IPAM.Driver,
|
||||
Options: types.MapToKV(item.IPAM.Options),
|
||||
Config: ipamConfigs,
|
||||
},
|
||||
Options: types.MapToKV(item.Options),
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
})
|
||||
}
|
||||
|
||||
slices.SortFunc(networks, func(a types.ContainerNetwork, b types.ContainerNetwork) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// Create 创建网络
|
||||
func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (string, error) {
|
||||
var ipamConfigs []network.IPAMConfig
|
||||
if req.Ipv4.Enabled {
|
||||
ipamConfigs = append(ipamConfigs, network.IPAMConfig{
|
||||
Subnet: req.Ipv4.Subnet,
|
||||
Gateway: req.Ipv4.Gateway,
|
||||
IPRange: req.Ipv4.IPRange,
|
||||
})
|
||||
}
|
||||
if req.Ipv6.Enabled {
|
||||
ipamConfigs = append(ipamConfigs, network.IPAMConfig{
|
||||
Subnet: req.Ipv6.Subnet,
|
||||
Gateway: req.Ipv6.Gateway,
|
||||
IPRange: req.Ipv6.IPRange,
|
||||
})
|
||||
}
|
||||
var sb strings.Builder
|
||||
|
||||
options := network.CreateOptions{
|
||||
EnableIPv6: &req.Ipv6.Enabled,
|
||||
Driver: req.Driver,
|
||||
Options: types.KVToMap(req.Options),
|
||||
Labels: types.KVToMap(req.Labels),
|
||||
}
|
||||
if len(ipamConfigs) > 0 {
|
||||
options.IPAM = &network.IPAM{
|
||||
Config: ipamConfigs,
|
||||
sb.WriteString(fmt.Sprintf("docker network create --driver %s", req.Driver))
|
||||
sb.WriteString(fmt.Sprintf(" %s", req.Name))
|
||||
|
||||
if req.Ipv4.Enabled {
|
||||
sb.WriteString(fmt.Sprintf(" --subnet %s", req.Ipv4.Subnet))
|
||||
sb.WriteString(fmt.Sprintf(" --gateway %s", req.Ipv4.Gateway))
|
||||
if req.Ipv4.IPRange != "" {
|
||||
sb.WriteString(fmt.Sprintf(" --ip-range %s", req.Ipv4.IPRange))
|
||||
}
|
||||
}
|
||||
if req.Ipv6.Enabled {
|
||||
sb.WriteString(fmt.Sprintf(" --subnet %s", req.Ipv6.Subnet))
|
||||
sb.WriteString(fmt.Sprintf(" --gateway %s", req.Ipv6.Gateway))
|
||||
if req.Ipv6.IPRange != "" {
|
||||
sb.WriteString(fmt.Sprintf(" --ip-range %s", req.Ipv6.IPRange))
|
||||
}
|
||||
}
|
||||
for _, label := range req.Labels {
|
||||
sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value))
|
||||
}
|
||||
for _, option := range req.Options {
|
||||
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
|
||||
}
|
||||
|
||||
resp, err := r.client.NetworkCreate(context.Background(), req.Name, options)
|
||||
return resp.ID, err
|
||||
return shell.Exec(sb.String())
|
||||
}
|
||||
|
||||
// Remove 删除网络
|
||||
func (r *containerNetworkRepo) Remove(id string) error {
|
||||
return r.client.NetworkRemove(context.Background(), id)
|
||||
}
|
||||
|
||||
// Exist 判断网络是否存在
|
||||
func (r *containerNetworkRepo) Exist(name string) (bool, error) {
|
||||
var options network.ListOptions
|
||||
options.Filters = filters.NewArgs(filters.Arg("name", name))
|
||||
networks, err := r.client.NetworkList(context.Background(), options)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(networks) > 0, nil
|
||||
}
|
||||
|
||||
// Inspect 查看网络
|
||||
func (r *containerNetworkRepo) Inspect(id string) (network.Inspect, error) {
|
||||
return r.client.NetworkInspect(context.Background(), id, network.InspectOptions{})
|
||||
}
|
||||
|
||||
// Connect 连接网络
|
||||
func (r *containerNetworkRepo) Connect(networkID string, containerID string) error {
|
||||
return r.client.NetworkConnect(context.Background(), networkID, containerID, nil)
|
||||
}
|
||||
|
||||
// Disconnect 断开网络
|
||||
func (r *containerNetworkRepo) Disconnect(networkID string, containerID string) error {
|
||||
return r.client.NetworkDisconnect(context.Background(), networkID, containerID, true)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的网络
|
||||
func (r *containerNetworkRepo) Prune() error {
|
||||
_, err := r.client.NetworksPrune(context.Background(), filters.NewArgs())
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,74 +1,88 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/volume"
|
||||
)
|
||||
|
||||
type containerVolumeRepo struct {
|
||||
client *client.Client
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerVolumeRepo(sock ...string) biz.ContainerVolumeRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/run/podman/podman.sock")
|
||||
}
|
||||
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
|
||||
return &containerVolumeRepo{
|
||||
client: cli,
|
||||
}
|
||||
func NewContainerVolumeRepo() biz.ContainerVolumeRepo {
|
||||
return do.MustInvoke[biz.ContainerVolumeRepo](injector)
|
||||
}
|
||||
|
||||
// List 列出存储卷
|
||||
func (r *containerVolumeRepo) List() ([]*volume.Volume, error) {
|
||||
volumes, err := r.client.VolumeList(context.Background(), volume.ListOptions{})
|
||||
func (r *containerVolumeRepo) List() ([]types.ContainerVolume, error) {
|
||||
var resp volume.ListResponse
|
||||
_, err := r.client.R().SetResult(&resp).Get("/volumes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumes.Volumes, err
|
||||
|
||||
var volumes []types.ContainerVolume
|
||||
for _, item := range resp.Volumes {
|
||||
volumes = append(volumes, types.ContainerVolume{
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
Scope: item.Scope,
|
||||
MountPoint: item.Mountpoint,
|
||||
CreatedAt: item.CreatedAt,
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
Options: types.MapToKV(item.Options),
|
||||
RefCount: item.UsageData.RefCount,
|
||||
Size: tools.FormatBytes(float64(item.UsageData.Size)),
|
||||
})
|
||||
}
|
||||
|
||||
slices.SortFunc(volumes, func(a types.ContainerVolume, b types.ContainerVolume) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
// Create 创建存储卷
|
||||
func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (volume.Volume, error) {
|
||||
return r.client.VolumeCreate(context.Background(), volume.CreateOptions{
|
||||
Name: req.Name,
|
||||
Driver: req.Driver,
|
||||
DriverOpts: types.KVToMap(req.Options),
|
||||
Labels: types.KVToMap(req.Labels),
|
||||
})
|
||||
}
|
||||
func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (string, error) {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("docker volume create")
|
||||
sb.WriteString(fmt.Sprintf(" %s", req.Name))
|
||||
|
||||
// Exist 判断存储卷是否存在
|
||||
func (r *containerVolumeRepo) Exist(id string) (bool, error) {
|
||||
var options volume.ListOptions
|
||||
options.Filters = filters.NewArgs(filters.Arg("name", id))
|
||||
volumes, err := r.client.VolumeList(context.Background(), options)
|
||||
if err != nil {
|
||||
return false, err
|
||||
if req.Driver != "" {
|
||||
sb.WriteString(fmt.Sprintf(" --driver %s", req.Driver))
|
||||
}
|
||||
for _, label := range req.Labels {
|
||||
sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value))
|
||||
}
|
||||
|
||||
return len(volumes.Volumes) > 0, nil
|
||||
}
|
||||
for _, option := range req.Options {
|
||||
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
|
||||
}
|
||||
|
||||
// Inspect 查看存储卷
|
||||
func (r *containerVolumeRepo) Inspect(id string) (volume.Volume, error) {
|
||||
return r.client.VolumeInspect(context.Background(), id)
|
||||
return shell.Exec(sb.String())
|
||||
}
|
||||
|
||||
// Remove 删除存储卷
|
||||
func (r *containerVolumeRepo) Remove(id string) error {
|
||||
return r.client.VolumeRemove(context.Background(), id, true)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的存储卷
|
||||
func (r *containerVolumeRepo) Prune() error {
|
||||
_, err := r.client.VolumesPrune(context.Background(), filters.NewArgs())
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,24 +7,22 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type cronRepo struct {
|
||||
settingRepo biz.SettingRepo
|
||||
}
|
||||
type cronRepo struct{}
|
||||
|
||||
func NewCronRepo() biz.CronRepo {
|
||||
return &cronRepo{
|
||||
settingRepo: NewSettingRepo(),
|
||||
}
|
||||
return do.MustInvoke[biz.CronRepo](injector)
|
||||
}
|
||||
|
||||
func (r *cronRepo) Count() (int64, error) {
|
||||
@@ -99,7 +97,7 @@ panel-cli cutoff clear -t website -f %s -s %d -p %s
|
||||
if !io.Exists(shellLogDir) {
|
||||
return errors.New("计划任务日志目录不存在")
|
||||
}
|
||||
shellFile := strconv.Itoa(int(time.Now().Unix())) + str.RandomString(16)
|
||||
shellFile := strconv.Itoa(int(time.Now().Unix())) + str.Random(16)
|
||||
if err := io.Write(filepath.Join(shellDir, shellFile+".sh"), script, 0700); err != nil {
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
)
|
||||
|
||||
type databaseRepo struct{}
|
||||
|
||||
func NewDatabaseRepo() biz.DatabaseRepo {
|
||||
return do.MustInvoke[biz.DatabaseRepo](injector)
|
||||
}
|
||||
|
||||
func (r databaseRepo) List(page, limit uint) ([]*biz.Database, int64, error) {
|
||||
var databaseServer []*biz.DatabaseServer
|
||||
if err := app.Orm.Model(&biz.DatabaseServer{}).Order("id desc").Find(&databaseServer).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
database := make([]*biz.Database, 0)
|
||||
for _, server := range databaseServer {
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
if databases, err := mysql.Databases(); err == nil {
|
||||
for item := range slices.Values(databases) {
|
||||
database = append(database, &biz.Database{
|
||||
Type: biz.DatabaseTypeMysql,
|
||||
Name: item.Name,
|
||||
Server: server.Name,
|
||||
ServerID: server.ID,
|
||||
Encoding: item.CharSet,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err == nil {
|
||||
if databases, err := postgres.Databases(); err == nil {
|
||||
for item := range slices.Values(databases) {
|
||||
database = append(database, &biz.Database{
|
||||
Type: biz.DatabaseTypePostgresql,
|
||||
Name: item.Name,
|
||||
Server: server.Name,
|
||||
ServerID: server.ID,
|
||||
Encoding: item.Encoding,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return database[(page-1)*limit:], int64(len(database)), nil
|
||||
}
|
||||
|
||||
func (r databaseRepo) Create(req *request.DatabaseCreate) error {
|
||||
server, err := NewDatabaseServerRepo().Get(req.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.CreateUser {
|
||||
if err = mysql.UserCreate(req.Username, req.Password, req.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = mysql.DatabaseCreate(req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Username != "" {
|
||||
if err = mysql.PrivilegesGrant(req.Username, req.Name, req.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.CreateUser {
|
||||
if err = postgres.UserCreate(req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = postgres.DatabaseCreate(req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Username != "" {
|
||||
if err = postgres.PrivilegesGrant(req.Username, req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r databaseRepo) Delete(serverID uint, name string) error {
|
||||
server, err := NewDatabaseServerRepo().Get(serverID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mysql.DatabaseDrop(name)
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return postgres.DatabaseDrop(name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
)
|
||||
|
||||
type databaseServerRepo struct{}
|
||||
|
||||
func NewDatabaseServerRepo() biz.DatabaseServerRepo {
|
||||
return do.MustInvoke[biz.DatabaseServerRepo](injector)
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Count() (int64, error) {
|
||||
var count int64
|
||||
if err := app.Orm.Model(&biz.DatabaseServer{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) List(page, limit uint) ([]*biz.DatabaseServer, int64, error) {
|
||||
var databaseServer []*biz.DatabaseServer
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.DatabaseServer{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&databaseServer).Error
|
||||
|
||||
for server := range slices.Values(databaseServer) {
|
||||
r.checkServer(server)
|
||||
}
|
||||
|
||||
return databaseServer, total, err
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Get(id uint) (*biz.DatabaseServer, error) {
|
||||
databaseServer := new(biz.DatabaseServer)
|
||||
if err := app.Orm.Where("id = ?", id).First(databaseServer).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.checkServer(databaseServer)
|
||||
|
||||
return databaseServer, nil
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
|
||||
databaseServer := &biz.DatabaseServer{
|
||||
Name: req.Name,
|
||||
Type: biz.DatabaseType(req.Type),
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
if !r.checkServer(databaseServer) {
|
||||
return fmt.Errorf("check server connection failed")
|
||||
}
|
||||
|
||||
return app.Orm.Create(databaseServer).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
|
||||
server, err := r.Get(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server.Name = req.Name
|
||||
server.Host = req.Host
|
||||
server.Port = req.Port
|
||||
server.Username = req.Username
|
||||
server.Password = req.Password
|
||||
server.Remark = req.Remark
|
||||
|
||||
if !r.checkServer(server) {
|
||||
return fmt.Errorf("check server connection failed")
|
||||
}
|
||||
|
||||
return app.Orm.Save(server).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Delete(id uint) error {
|
||||
// 删除服务器下的所有用户
|
||||
if err := NewDatabaseUserRepo().DeleteByServerID(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Orm.Where("id = ?", id).Delete(&biz.DatabaseServer{}).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Sync(id uint) error {
|
||||
server, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users := make([]*biz.DatabaseUser, 0)
|
||||
if err = app.Orm.Where("server_id = ?", id).Find(&users).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allUsers, err := mysql.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for user := range slices.Values(allUsers) {
|
||||
if !slices.ContainsFunc(users, func(a *biz.DatabaseUser) bool {
|
||||
return a.Username == user.User && a.Host == user.Host
|
||||
}) && !slices.Contains([]string{"root", "mysql.sys", "mysql.session", "mysql.infoschema"}, user.User) {
|
||||
newUser := &biz.DatabaseUser{
|
||||
ServerID: id,
|
||||
Username: user.User,
|
||||
Host: user.Host,
|
||||
Remark: fmt.Sprintf("sync from server %s", server.Name),
|
||||
}
|
||||
if err = app.Orm.Create(newUser).Error; err != nil {
|
||||
app.Logger.Warn("sync database user failed", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allUsers, err := postgres.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for user := range slices.Values(allUsers) {
|
||||
if !slices.ContainsFunc(users, func(a *biz.DatabaseUser) bool {
|
||||
return a.Username == user.Role
|
||||
}) && !slices.Contains([]string{"postgres"}, user.Role) {
|
||||
newUser := &biz.DatabaseUser{
|
||||
ServerID: id,
|
||||
Username: user.Role,
|
||||
Remark: fmt.Sprintf("sync from server %s", server.Name),
|
||||
}
|
||||
app.Orm.Create(newUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkServer 检查服务器连接
|
||||
func (r databaseServerRepo) checkServer(server *biz.DatabaseServer) bool {
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
_, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
server.Status = biz.DatabaseServerStatusValid
|
||||
return true
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
_, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err == nil {
|
||||
server.Status = biz.DatabaseServerStatusValid
|
||||
return true
|
||||
}
|
||||
case biz.DatabaseTypeRedis:
|
||||
_, err := db.NewRedis(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
server.Status = biz.DatabaseServerStatusValid
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
server.Status = biz.DatabaseServerStatusInvalid
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
)
|
||||
|
||||
type databaseUserRepo struct{}
|
||||
|
||||
func NewDatabaseUserRepo() biz.DatabaseUserRepo {
|
||||
return do.MustInvoke[biz.DatabaseUserRepo](injector)
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Count() (int64, error) {
|
||||
var count int64
|
||||
if err := app.Orm.Model(&biz.DatabaseUser{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) List(page, limit uint) ([]*biz.DatabaseUser, int64, error) {
|
||||
var user []*biz.DatabaseUser
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.DatabaseUser{}).Preload("Server").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&user).Error
|
||||
|
||||
for u := range slices.Values(user) {
|
||||
r.fillUser(u)
|
||||
}
|
||||
|
||||
return user, total, err
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Get(id uint) (*biz.DatabaseUser, error) {
|
||||
user := new(biz.DatabaseUser)
|
||||
if err := app.Orm.Preload("Server").Where("id = ?", id).First(user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.fillUser(user)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Create(req *request.DatabaseUserCreate) error {
|
||||
server, err := NewDatabaseServerRepo().Get(req.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mysql.UserCreate(req.Username, req.Password, req.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = mysql.PrivilegesGrant(req.Username, name, req.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = postgres.UserCreate(req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = postgres.PrivilegesGrant(req.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user := &biz.DatabaseUser{
|
||||
ServerID: req.ServerID,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Host: req.Host,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
return app.Orm.Create(user).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Update(req *request.DatabaseUserUpdate) error {
|
||||
user, err := r.Get(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := NewDatabaseServerRepo().Get(user.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Password != "" {
|
||||
if err = mysql.UserPassword(user.Username, req.Password, user.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = mysql.PrivilegesGrant(user.Username, name, user.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Password != "" {
|
||||
if err = postgres.UserPassword(user.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = postgres.PrivilegesGrant(user.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.Password = req.Password
|
||||
user.Remark = req.Remark
|
||||
|
||||
return app.Orm.Save(user).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Delete(id uint) error {
|
||||
user, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := NewDatabaseServerRepo().Get(user.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = mysql.UserDrop(user.Username)
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = postgres.DatabaseDrop(user.Username)
|
||||
}
|
||||
|
||||
return app.Orm.Where("id = ?", id).Delete(&biz.DatabaseUser{}).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) DeleteByServerID(serverID uint) error {
|
||||
return app.Orm.Where("server_id = ?", serverID).Delete(&biz.DatabaseUser{}).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) fillUser(user *biz.DatabaseUser) {
|
||||
server, err := NewDatabaseServerRepo().Get(user.ServerID)
|
||||
if err == nil {
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
privileges, _ := mysql.UserPrivileges(user.Username, user.Host)
|
||||
user.Privileges = privileges
|
||||
}
|
||||
if _, err := db.NewMySQL(user.Username, user.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)); err == nil {
|
||||
user.Status = biz.DatabaseUserStatusValid
|
||||
} else {
|
||||
user.Status = biz.DatabaseUserStatusInvalid
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err == nil {
|
||||
privileges, _ := postgres.UserPrivileges(user.Username)
|
||||
user.Privileges = privileges
|
||||
}
|
||||
if _, err := db.NewPostgres(user.Username, user.Password, server.Host, server.Port); err == nil {
|
||||
user.Status = biz.DatabaseUserStatusValid
|
||||
} else {
|
||||
user.Status = biz.DatabaseUserStatusInvalid
|
||||
}
|
||||
}
|
||||
}
|
||||
// 初始化,防止 nil
|
||||
if user.Privileges == nil {
|
||||
user.Privileges = make(map[string][]string)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/pkg/api"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
)
|
||||
|
||||
var injector = do.New()
|
||||
|
||||
func init() {
|
||||
do.Provide(injector, func(i do.Injector) (biz.AppRepo, error) {
|
||||
return &appRepo{
|
||||
api: api.NewAPI(app.Version),
|
||||
}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.BackupRepo, error) {
|
||||
return &backupRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.CacheRepo, error) {
|
||||
return &cacheRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.CertRepo, error) {
|
||||
return &certRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.CertAccountRepo, error) {
|
||||
return &certAccountRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.CertDNSRepo, error) {
|
||||
return &certDNSRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.ContainerRepo, error) {
|
||||
return &containerRepo{
|
||||
client: getDockerClient("/var/run/docker.sock"),
|
||||
}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.ContainerImageRepo, error) {
|
||||
return &containerImageRepo{
|
||||
client: getDockerClient("/var/run/docker.sock"),
|
||||
}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.ContainerNetworkRepo, error) {
|
||||
return &containerNetworkRepo{
|
||||
client: getDockerClient("/var/run/docker.sock"),
|
||||
}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.ContainerVolumeRepo, error) {
|
||||
return &containerVolumeRepo{
|
||||
client: getDockerClient("/var/run/docker.sock"),
|
||||
}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.CronRepo, error) {
|
||||
return &cronRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.DatabaseServerRepo, error) {
|
||||
return &databaseServerRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.DatabaseUserRepo, error) {
|
||||
return &databaseUserRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.DatabaseRepo, error) {
|
||||
return &databaseRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.MonitorRepo, error) {
|
||||
return &monitorRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.SafeRepo, error) {
|
||||
var ssh string
|
||||
if os.IsRHEL() {
|
||||
ssh = "sshd"
|
||||
} else {
|
||||
ssh = "ssh"
|
||||
}
|
||||
return &safeRepo{
|
||||
ssh: ssh,
|
||||
}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.SettingRepo, error) {
|
||||
return &settingRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.SSHRepo, error) {
|
||||
return &sshRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.TaskRepo, error) {
|
||||
task := &taskRepo{}
|
||||
task.DispatchWaiting()
|
||||
return task, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.UserRepo, error) {
|
||||
return &userRepo{
|
||||
hasher: hash.NewArgon2id(),
|
||||
}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.WebsiteRepo, error) {
|
||||
return &websiteRepo{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func getDockerClient(sock string) *resty.Client {
|
||||
client := resty.New()
|
||||
client.SetTimeout(1 * time.Minute)
|
||||
client.SetRetryCount(2)
|
||||
client.SetTransport(&http.Transport{
|
||||
DialContext: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, "unix", sock)
|
||||
},
|
||||
})
|
||||
client.SetBaseURL("http://d/v1.40")
|
||||
return client
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
@@ -11,22 +12,19 @@ import (
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type monitorRepo struct {
|
||||
settingRepo biz.SettingRepo
|
||||
}
|
||||
type monitorRepo struct{}
|
||||
|
||||
func NewMonitorRepo() biz.MonitorRepo {
|
||||
return &monitorRepo{
|
||||
settingRepo: NewSettingRepo(),
|
||||
}
|
||||
return do.MustInvoke[biz.MonitorRepo](injector)
|
||||
}
|
||||
|
||||
func (r monitorRepo) GetSetting() (*request.MonitorSetting, error) {
|
||||
monitor, err := r.settingRepo.Get(biz.SettingKeyMonitor)
|
||||
repo := NewSettingRepo()
|
||||
monitor, err := repo.Get(biz.SettingKeyMonitor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
monitorDays, err := r.settingRepo.Get(biz.SettingKeyMonitorDays)
|
||||
monitorDays, err := repo.Get(biz.SettingKeyMonitorDays)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,10 +37,11 @@ func (r monitorRepo) GetSetting() (*request.MonitorSetting, error) {
|
||||
}
|
||||
|
||||
func (r monitorRepo) UpdateSetting(setting *request.MonitorSetting) error {
|
||||
if err := r.settingRepo.Set(biz.SettingKeyMonitor, cast.ToString(setting.Enabled)); err != nil {
|
||||
repo := NewSettingRepo()
|
||||
if err := repo.Set(biz.SettingKeyMonitor, cast.ToString(setting.Enabled)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.settingRepo.Set(biz.SettingKeyMonitorDays, cast.ToString(setting.Days)); err != nil {
|
||||
if err := repo.Set(biz.SettingKeyMonitorDays, cast.ToString(setting.Days)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+2
-11
@@ -4,11 +4,11 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/pkg/firewall"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
@@ -18,16 +18,7 @@ type safeRepo struct {
|
||||
}
|
||||
|
||||
func NewSafeRepo() biz.SafeRepo {
|
||||
var ssh string
|
||||
if os.IsRHEL() {
|
||||
ssh = "sshd"
|
||||
} else {
|
||||
ssh = "ssh"
|
||||
}
|
||||
|
||||
return &safeRepo{
|
||||
ssh: ssh,
|
||||
}
|
||||
return do.MustInvoke[biz.SafeRepo](injector)
|
||||
}
|
||||
|
||||
func (r *safeRepo) GetSSH() (uint, bool, error) {
|
||||
|
||||
+33
-22
@@ -8,28 +8,27 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/samber/do/v2"
|
||||
"github.com/spf13/cast"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/cert"
|
||||
"github.com/TheTNB/panel/pkg/firewall"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type settingRepo struct {
|
||||
taskRepo biz.TaskRepo
|
||||
}
|
||||
type settingRepo struct{}
|
||||
|
||||
func NewSettingRepo() biz.SettingRepo {
|
||||
return &settingRepo{
|
||||
taskRepo: NewTaskRepo(),
|
||||
}
|
||||
return do.MustInvoke[biz.SettingRepo](injector)
|
||||
}
|
||||
|
||||
func (r *settingRepo) Get(key biz.SettingKey, defaultValue ...string) (string, error) {
|
||||
@@ -108,7 +107,7 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
|
||||
crt, err := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -126,9 +125,9 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
|
||||
BackupPath: backupPath,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Port: app.Conf.Int("http.port"),
|
||||
Port: uint(app.Conf.Int("http.port")),
|
||||
HTTPS: app.Conf.Bool("http.tls"),
|
||||
Cert: cert,
|
||||
Cert: crt,
|
||||
Key: key,
|
||||
}, nil
|
||||
}
|
||||
@@ -168,16 +167,23 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
}
|
||||
|
||||
// 下面是需要需要重启的设置
|
||||
task := NewTaskRepo()
|
||||
// 面板HTTPS
|
||||
restartFlag := false
|
||||
oldCert, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
|
||||
oldKey, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.key"))
|
||||
if oldCert != setting.Cert || oldKey != setting.Key {
|
||||
if r.taskRepo.HasRunningTask() {
|
||||
if task.HasRunningTask() {
|
||||
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
|
||||
}
|
||||
restartFlag = true
|
||||
}
|
||||
if _, err := cert.ParseCert(setting.Cert); err != nil {
|
||||
return false, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
if _, err := cert.ParseKey(setting.Key); err != nil {
|
||||
return false, fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), setting.Cert, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -187,15 +193,20 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
|
||||
// 面板主配置
|
||||
config := new(types.PanelConfig)
|
||||
cm := yaml.CommentMap{}
|
||||
raw, err := io.Read("/usr/local/etc/panel/config.yml")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
|
||||
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if setting.Port != config.HTTP.Port {
|
||||
if os.TCPPortInUse(setting.Port) {
|
||||
return false, errors.New("端口已被占用")
|
||||
}
|
||||
}
|
||||
|
||||
config.App.Locale = setting.Locale
|
||||
config.HTTP.Port = setting.Port
|
||||
config.HTTP.Entrance = setting.Entrance
|
||||
@@ -214,12 +225,12 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
return false, err
|
||||
}
|
||||
|
||||
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if raw != string(encoded) {
|
||||
if r.taskRepo.HasRunningTask() {
|
||||
if task.HasRunningTask() {
|
||||
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
|
||||
}
|
||||
restartFlag = true
|
||||
@@ -291,7 +302,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
|
||||
}
|
||||
return fmt.Errorf("备份面板失败:%w", err)
|
||||
}
|
||||
if err := io.Compress([]string{filepath.Join(app.Root, "panel/storage")}, "/tmp/panel-storage.zip", io.Zip); err != nil {
|
||||
if err := io.Compress(filepath.Join(app.Root, "panel/storage"), nil, "/tmp/panel-storage.zip"); err != nil {
|
||||
if app.IsCli {
|
||||
fmt.Println("|-备份面板数据失败:", err)
|
||||
}
|
||||
@@ -311,7 +322,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
|
||||
if app.IsCli {
|
||||
fmt.Println("|-解压新版本...")
|
||||
}
|
||||
if err := io.UnCompress(filepath.Join("/tmp", name), filepath.Join(app.Root, "panel"), io.Zip); err != nil {
|
||||
if err := io.UnCompress(filepath.Join("/tmp", name), filepath.Join(app.Root, "panel")); err != nil {
|
||||
return fmt.Errorf("解压失败:%w", err)
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel", "web")) {
|
||||
@@ -321,7 +332,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
|
||||
if app.IsCli {
|
||||
fmt.Println("|-恢复面板数据...")
|
||||
}
|
||||
if err := io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel"), io.Zip); err != nil {
|
||||
if err := io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel", "storage")); err != nil {
|
||||
return fmt.Errorf("恢复面板数据失败:%w", err)
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel/storage/app.db")) {
|
||||
@@ -392,7 +403,7 @@ func (r *settingRepo) FixPanel() error {
|
||||
return fmt.Errorf("清理临时文件失败:%w", err)
|
||||
}
|
||||
if app.IsCli {
|
||||
fmt.Println("已清理临时文件,请运行 panel-cli update 更新面板")
|
||||
fmt.Println("|-已清理临时文件,请运行 panel-cli update 更新面板")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -422,7 +433,7 @@ func (r *settingRepo) FixPanel() error {
|
||||
if err = io.Remove("/tmp/panel-fix"); err != nil {
|
||||
return fmt.Errorf("清理临时目录失败:%w", err)
|
||||
}
|
||||
if err = io.UnCompress(latest.Path, "/tmp/panel-fix", io.Zip); err != nil {
|
||||
if err = io.UnCompress(latest.Path, "/tmp/panel-fix"); err != nil {
|
||||
return fmt.Errorf("解压备份文件失败:%w", err)
|
||||
}
|
||||
|
||||
@@ -449,12 +460,12 @@ func (r *settingRepo) FixPanel() error {
|
||||
}
|
||||
}
|
||||
|
||||
// tmp目录下如果有storage备份,则解压回去
|
||||
// tmp 目录下如果有 storage 备份,则解压回去
|
||||
if app.IsCli {
|
||||
fmt.Println("|-恢复面板数据...")
|
||||
}
|
||||
if io.Exists("/tmp/panel-storage.zip") {
|
||||
if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel"), io.Zip); err != nil {
|
||||
if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel")); err != nil {
|
||||
return fmt.Errorf("恢复面板数据失败:%w", err)
|
||||
}
|
||||
if err = io.Remove("/tmp/panel-storage.zip"); err != nil {
|
||||
|
||||
@@ -3,20 +3,18 @@ package data
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
pkgssh "github.com/TheTNB/panel/pkg/ssh"
|
||||
)
|
||||
|
||||
type sshRepo struct {
|
||||
settingRepo biz.SettingRepo
|
||||
}
|
||||
type sshRepo struct{}
|
||||
|
||||
func NewSSHRepo() biz.SSHRepo {
|
||||
return &sshRepo{
|
||||
settingRepo: NewSettingRepo(),
|
||||
}
|
||||
return do.MustInvoke[biz.SSHRepo](injector)
|
||||
}
|
||||
|
||||
func (r *sshRepo) List(page, limit uint) ([]*biz.SSH, int64, error) {
|
||||
@@ -81,7 +79,7 @@ func (r *sshRepo) Update(req *request.SSHUpdate) error {
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
return app.Orm.Model(ssh).Updates(ssh).Error
|
||||
return app.Orm.Model(ssh).Where("id = ?", req.ID).Select("*").Updates(ssh).Error
|
||||
}
|
||||
|
||||
func (r *sshRepo) Delete(id uint) error {
|
||||
|
||||
+20
-9
@@ -1,23 +1,20 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/queuejob"
|
||||
)
|
||||
|
||||
var taskDispatchOnce sync.Once
|
||||
|
||||
type taskRepo struct{}
|
||||
|
||||
func NewTaskRepo() biz.TaskRepo {
|
||||
task := &taskRepo{}
|
||||
taskDispatchOnce.Do(task.DispatchWaiting)
|
||||
return &taskRepo{}
|
||||
return do.MustInvoke[biz.TaskRepo](injector)
|
||||
}
|
||||
|
||||
func (r *taskRepo) HasRunningTask() bool {
|
||||
@@ -48,9 +45,18 @@ func (r *taskRepo) UpdateStatus(id uint, status biz.TaskStatus) error {
|
||||
}
|
||||
|
||||
func (r *taskRepo) Push(task *biz.Task) error {
|
||||
var count int64
|
||||
if err := app.Orm.Model(&biz.Task{}).Where("shell = ? and (status = ? or status = ?)", task.Shell, biz.TaskStatusWaiting, biz.TaskStatusRunning).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return fmt.Errorf("duplicate submission, please wait for the previous task to end")
|
||||
}
|
||||
|
||||
if err := app.Orm.Create(task).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Queue.Push(queuejob.NewProcessTask(r), []any{
|
||||
task.ID,
|
||||
})
|
||||
@@ -62,9 +68,14 @@ func (r *taskRepo) DispatchWaiting() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.Orm.Model(&biz.Task{}).Where("status = ?", biz.TaskStatusRunning).Update("status", biz.TaskStatusFailed).Error; err != nil {
|
||||
app.Logger.Warn("failed to mark running tasks as failed", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
var tasks []biz.Task
|
||||
if err := app.Orm.Where("status = ?", biz.TaskStatusWaiting).Find(&tasks).Error; err != nil {
|
||||
app.Logger.Error("获取待处理任务失败", zap.Error(err))
|
||||
app.Logger.Warn("failed to get pending tasks", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,7 +83,7 @@ func (r *taskRepo) DispatchWaiting() {
|
||||
if err := app.Queue.Push(queuejob.NewProcessTask(r), []any{
|
||||
task.ID,
|
||||
}); err != nil {
|
||||
app.Logger.Error("推送任务失败", zap.Error(err))
|
||||
app.Logger.Warn("failed to push task", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/samber/do/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
@@ -15,9 +16,7 @@ type userRepo struct {
|
||||
}
|
||||
|
||||
func NewUserRepo() biz.UserRepo {
|
||||
return &userRepo{
|
||||
hasher: hash.NewArgon2id(),
|
||||
}
|
||||
return do.MustInvoke[biz.UserRepo](injector)
|
||||
}
|
||||
|
||||
func (r *userRepo) Create(username, password string) (*biz.User, error) {
|
||||
|
||||
+54
-28
@@ -7,7 +7,9 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
@@ -29,7 +31,7 @@ import (
|
||||
type websiteRepo struct{}
|
||||
|
||||
func NewWebsiteRepo() biz.WebsiteRepo {
|
||||
return &websiteRepo{}
|
||||
return do.MustInvoke[biz.WebsiteRepo](injector)
|
||||
}
|
||||
|
||||
func (r *websiteRepo) UpdateDefaultConfig(req *request.WebsiteDefaultConfig) error {
|
||||
@@ -137,8 +139,8 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
setting.SSLCertificateKey = key
|
||||
// 解析证书信息
|
||||
if decode, err := cert.ParseCert(crt); err == nil {
|
||||
setting.SSLNotBefore = decode.NotBefore.Format("2006-01-02 15:04:05")
|
||||
setting.SSLNotAfter = decode.NotAfter.Format("2006-01-02 15:04:05")
|
||||
setting.SSLNotBefore = decode.NotBefore.Format(time.DateTime)
|
||||
setting.SSLNotAfter = decode.NotAfter.Format(time.DateTime)
|
||||
setting.SSLIssuer = decode.Issuer.CommonName
|
||||
setting.SSLOCSPServer = decode.OCSPServer
|
||||
setting.SSLDNSNames = decode.DNSNames
|
||||
@@ -271,12 +273,24 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// PHP 网站默认开启防跨站
|
||||
if req.PHP > 0 {
|
||||
userIni := filepath.Join(req.Path, ".user.ini")
|
||||
if !io.Exists(userIni) {
|
||||
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Path), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, _ = shell.Execf(`chattr +i '%s'`, userIni)
|
||||
}
|
||||
|
||||
// 创建面板网站
|
||||
w := &biz.Website{
|
||||
Name: req.Name,
|
||||
Status: true,
|
||||
Path: req.Path,
|
||||
Https: false,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
if err = app.Orm.Create(w).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -294,24 +308,30 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = mysql.UserCreate(req.DBUser, req.DBPassword, "localhost"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = mysql.DatabaseCreate(req.DBName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = mysql.UserCreate(req.DBUser, req.DBPassword); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = mysql.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
|
||||
if err = mysql.PrivilegesGrant(req.DBUser, req.DBName, "localhost"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if req.DB && req.DBType == "postgresql" {
|
||||
_, _ = shell.Execf(`echo "CREATE DATABASE '%s';" | su - postgres -c "psql"`, req.DBName)
|
||||
_, _ = shell.Execf(`echo "CREATE USER '%s' WITH PASSWORD '%s';" | su - postgres -c "psql"`, req.DBUser, req.DBPassword)
|
||||
_, _ = shell.Execf(`echo "ALTER DATABASE '%s' OWNER TO '%s';" | su - postgres -c "psql"`, req.DBName, req.DBUser)
|
||||
_, _ = shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE '%s' TO '%s';" | su - postgres -c "psql"`, req.DBName, req.DBUser)
|
||||
userConfig := "host " + req.DBName + " " + req.DBUser + " 127.0.0.1/32 scram-sha-256"
|
||||
_, _ = shell.Execf(`echo "`+userConfig+`" >> %s/server/postgresql/data/pg_hba.conf`, app.Root)
|
||||
_ = systemctl.Reload("postgresql")
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = postgres.UserCreate(req.DBUser, req.DBPassword); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = postgres.DatabaseCreate(req.DBName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = postgres.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return w, nil
|
||||
@@ -417,11 +437,6 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
if err = p.SetOCSP(req.OCSP); err != nil {
|
||||
return err
|
||||
}
|
||||
if quic {
|
||||
if err = p.SetAltSvc(`'h3=":$server_port"; ma=2592000'`); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err = p.ClearSetHTTPS(); err != nil {
|
||||
return err
|
||||
@@ -435,6 +450,12 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
if err = p.SetOCSP(false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if quic {
|
||||
if err = p.SetAltSvc(`'h3=":$server_port"; ma=2592000'`); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = p.SetAltSvc(``); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -445,14 +466,14 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
}
|
||||
userIni := filepath.Join(req.Root, ".user.ini")
|
||||
if req.OpenBasedir {
|
||||
_, _ = shell.Execf(`chattr -i '%s'`, userIni)
|
||||
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Root), 0644); err != nil {
|
||||
return err
|
||||
if !io.Exists(userIni) {
|
||||
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Path), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, _ = shell.Execf(`chattr +i '%s'`, userIni)
|
||||
} else {
|
||||
if io.Exists(userIni) {
|
||||
_, _ = shell.Execf(`chattr -i '%s'`, userIni)
|
||||
if err = io.Remove(userIni); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -503,13 +524,14 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix")
|
||||
if err == nil {
|
||||
_ = mysql.DatabaseDrop(website.Name)
|
||||
if mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix"); err == nil {
|
||||
_ = mysql.UserDrop(website.Name)
|
||||
_ = mysql.DatabaseDrop(website.Name)
|
||||
}
|
||||
if postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432); err == nil {
|
||||
_ = postgres.UserDrop(website.Name)
|
||||
_ = postgres.DatabaseDrop(website.Name)
|
||||
}
|
||||
_, _ = shell.Execf(`echo "DROP DATABASE IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
|
||||
_, _ = shell.Execf(`echo "DROP USER IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
|
||||
}
|
||||
|
||||
if err := app.Orm.Delete(website).Error; err != nil {
|
||||
@@ -708,6 +730,10 @@ func (r *websiteRepo) ObtainCert(ctx context.Context, id uint) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newCert.Domains = website.Domains
|
||||
if err = app.Orm.Save(newCert).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cRepo.ObtainAuto(newCert.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
sessionmiddleware "github.com/go-rat/sessions/middleware"
|
||||
"github.com/golang-cz/httplog"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
)
|
||||
@@ -16,11 +18,15 @@ func GlobalMiddleware() []func(http.Handler) http.Handler {
|
||||
//middleware.SupressNotFound(app.Http),// bug https://github.com/go-chi/chi/pull/940
|
||||
middleware.CleanPath,
|
||||
middleware.StripSlashes,
|
||||
middleware.Logger,
|
||||
middleware.Recoverer,
|
||||
middleware.Compress(5),
|
||||
Entrance,
|
||||
httplog.RequestLogger(app.Logger, &httplog.Options{
|
||||
Level: slog.LevelInfo,
|
||||
LogRequestHeaders: []string{"User-Agent"},
|
||||
}),
|
||||
middleware.Recoverer,
|
||||
Status,
|
||||
Entrance,
|
||||
MustLogin,
|
||||
MustInstall,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/spf13/cast"
|
||||
@@ -12,6 +14,14 @@ import (
|
||||
|
||||
// MustLogin 确保已登录
|
||||
func MustLogin(next http.Handler) http.Handler {
|
||||
// 白名单
|
||||
whiteList := []string{
|
||||
"/api/user/login",
|
||||
"/api/user/logout",
|
||||
"/api/user/isLogin",
|
||||
"/api/dashboard/panel",
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sess, err := app.Session.GetSession(r)
|
||||
if err != nil {
|
||||
@@ -22,6 +32,12 @@ func MustLogin(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// 对白名单和非 API 请求放行
|
||||
if slices.Contains(whiteList, r.URL.Path) || !strings.HasPrefix(r.URL.Path, "/api") {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if sess.Missing("user_id") {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusUnauthorized)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package request
|
||||
|
||||
type CertUpload struct {
|
||||
Cert string `form:"cert" json:"cert" validate:"required"`
|
||||
Key string `form:"key" json:"key" validate:"required"`
|
||||
}
|
||||
|
||||
type CertCreate struct {
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=P256 P384 2048 3072 4096"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
@@ -11,8 +16,10 @@ type CertCreate struct {
|
||||
|
||||
type CertUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required,exists=certs id"`
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=P256 P384 2048 3072 4096"`
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=upload P256 P384 2048 3072 4096"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
Cert string `form:"cert" json:"cert"`
|
||||
Key string `form:"key" json:"key"`
|
||||
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
|
||||
AccountID uint `form:"account_id" json:"account_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
|
||||
@@ -12,22 +12,22 @@ type ContainerRename struct {
|
||||
}
|
||||
|
||||
type ContainerCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Image string `form:"image" json:"image" validate:"required"`
|
||||
Ports []types.ContainerPort `form:"ports" json:"ports"`
|
||||
Network string `form:"network" json:"network"`
|
||||
Volumes []types.ContainerVolume `form:"volumes" json:"volumes"`
|
||||
Labels []types.KV `form:"labels" json:"labels"`
|
||||
Env []types.KV `form:"env" json:"env"`
|
||||
Entrypoint []string `form:"entrypoint" json:"entrypoint"`
|
||||
Command []string `form:"command" json:"command"`
|
||||
RestartPolicy string `form:"restart_policy" json:"restart_policy"`
|
||||
AutoRemove bool `form:"auto_remove" json:"auto_remove"`
|
||||
Privileged bool `form:"privileged" json:"privileged"`
|
||||
OpenStdin bool `form:"openStdin" json:"open_stdin"`
|
||||
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
|
||||
Tty bool `form:"tty" json:"tty"`
|
||||
CPUShares int64 `form:"cpu_shares" json:"cpu_shares"`
|
||||
CPUs int64 `form:"cpus" json:"cpus"`
|
||||
Memory int64 `form:"memory" json:"memory"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Image string `form:"image" json:"image" validate:"required"`
|
||||
Ports []types.ContainerPort `form:"ports" json:"ports"`
|
||||
Network string `form:"network" json:"network"`
|
||||
Volumes []types.ContainerContainerVolume `form:"volumes" json:"volumes"`
|
||||
Labels []types.KV `form:"labels" json:"labels"`
|
||||
Env []types.KV `form:"env" json:"env"`
|
||||
Entrypoint []string `form:"entrypoint" json:"entrypoint"`
|
||||
Command []string `form:"command" json:"command"`
|
||||
RestartPolicy string `form:"restart_policy" json:"restart_policy"`
|
||||
AutoRemove bool `form:"auto_remove" json:"auto_remove"`
|
||||
Privileged bool `form:"privileged" json:"privileged"`
|
||||
OpenStdin bool `form:"openStdin" json:"open_stdin"`
|
||||
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
|
||||
Tty bool `form:"tty" json:"tty"`
|
||||
CPUShares int64 `form:"cpu_shares" json:"cpu_shares"`
|
||||
CPUs int64 `form:"cpus" json:"cpus"`
|
||||
Memory int64 `form:"memory" json:"memory"`
|
||||
}
|
||||
|
||||
@@ -7,15 +7,10 @@ type ContainerNetworkID struct {
|
||||
}
|
||||
|
||||
type ContainerNetworkCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Driver string `form:"driver" json:"driver"`
|
||||
Ipv4 types.ContainerNetwork `form:"ipv4" json:"ipv4"`
|
||||
Ipv6 types.ContainerNetwork `form:"ipv6" json:"ipv6"`
|
||||
Labels []types.KV `form:"labels" json:"labels"`
|
||||
Options []types.KV `form:"options" json:"options"`
|
||||
}
|
||||
|
||||
type ContainerNetworkConnect struct {
|
||||
Network string `form:"network" json:"network" validate:"required"`
|
||||
Container string `form:"container" json:"container" validate:"required"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Driver string `form:"driver" json:"driver"`
|
||||
Ipv4 types.ContainerContainerNetwork `form:"ipv4" json:"ipv4"`
|
||||
Ipv6 types.ContainerContainerNetwork `form:"ipv6" json:"ipv6"`
|
||||
Labels []types.KV `form:"labels" json:"labels"`
|
||||
Options []types.KV `form:"options" json:"options"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package request
|
||||
|
||||
type DatabaseCreate struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required,exists=database_servers id"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
CreateUser bool `form:"create_user" json:"create_user"`
|
||||
Username string `form:"username" json:"username" validate:"required_if=CreateUser true"`
|
||||
Password string `form:"password" json:"password" validate:"required_if=CreateUser true"`
|
||||
Host string `form:"host" json:"host"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type DatabaseDelete struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required,exists=database_servers id"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package request
|
||||
|
||||
type DatabaseServerCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required,not_exists=database_servers name"`
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=mysql postgresql redis"`
|
||||
Host string `form:"host" json:"host" validate:"required"`
|
||||
Port uint `form:"port" json:"port" validate:"required,number,gte=1,lte=65535"`
|
||||
Username string `form:"username" json:"username"`
|
||||
Password string `form:"password" json:"password"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type DatabaseServerUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required,exists=database_servers id"`
|
||||
Name string `form:"name" json:"name" validate:"required,not_exists=database_servers name"`
|
||||
Host string `form:"host" json:"host" validate:"required"`
|
||||
Port uint `form:"port" json:"port" validate:"required,number,gte=1,lte=65535"`
|
||||
Username string `form:"username" json:"username"`
|
||||
Password string `form:"password" json:"password"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package request
|
||||
|
||||
type DatabaseUserCreate struct {
|
||||
ServerID uint `form:"server_id" json:"server_id" validate:"required,exists=database_servers id"`
|
||||
Username string `form:"username" json:"username" validate:"required"`
|
||||
Password string `form:"password" json:"password" validate:"required"`
|
||||
Host string `form:"host" json:"host"`
|
||||
Privileges []string `form:"privileges" json:"privileges"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type DatabaseUserUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required,exists=database_users id"`
|
||||
Password string `form:"password" json:"password"`
|
||||
Privileges []string `form:"privileges" json:"privileges"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
@@ -25,13 +25,7 @@ type FileSave struct {
|
||||
Content string `form:"content" json:"content"`
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
Source string `form:"source" json:"source" validate:"required"`
|
||||
Target string `form:"target" json:"target" validate:"required"`
|
||||
Force bool `form:"force" json:"force"`
|
||||
}
|
||||
|
||||
type FileCopy struct {
|
||||
type FileControl struct {
|
||||
Source string `form:"source" json:"source" validate:"required"`
|
||||
Target string `form:"target" json:"target" validate:"required"`
|
||||
Force bool `form:"force" json:"force"`
|
||||
@@ -50,6 +44,7 @@ type FilePermission struct {
|
||||
}
|
||||
|
||||
type FileCompress struct {
|
||||
Dir string `form:"dir" json:"dir" validate:"required"`
|
||||
Paths []string `form:"paths" json:"paths" validate:"min=1,dive,required"`
|
||||
File string `form:"file" json:"file" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ type PanelSetting struct {
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"password"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Port int `json:"port" validate:"required,number,gte=1,lte=65535"`
|
||||
Port uint `json:"port" validate:"required,number,gte=1,lte=65535"`
|
||||
HTTPS bool `json:"https"`
|
||||
Cert string `json:"cert" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
|
||||
@@ -18,6 +18,7 @@ type WebsiteCreate struct {
|
||||
DBName string `form:"db_name" json:"db_name"`
|
||||
DBUser string `form:"db_user" json:"db_user"`
|
||||
DBPassword string `form:"db_password" json:"db_password" validate:"password"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
type WebsiteDelete struct {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
@@ -29,12 +28,12 @@ func (r *CertRenew) Run() {
|
||||
|
||||
var certs []biz.Cert
|
||||
if err := app.Orm.Preload("Website").Preload("Account").Preload("DNS").Find(&certs).Error; err != nil {
|
||||
app.Logger.Error("获取证书失败", zap.Error(err))
|
||||
app.Logger.Warn("获取证书失败", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, cert := range certs {
|
||||
if !cert.AutoRenew {
|
||||
if cert.Type == "upload" || !cert.AutoRenew {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -51,7 +50,7 @@ func (r *CertRenew) Run() {
|
||||
|
||||
_, err = r.certRepo.Renew(cert.ID)
|
||||
if err != nil {
|
||||
app.Logger.Error("续签证书失败", zap.Error(err))
|
||||
app.Logger.Warn("续签证书失败", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
@@ -48,7 +48,7 @@ func (r *Monitoring) Run() {
|
||||
}
|
||||
|
||||
if err = app.Orm.Create(&biz.Monitor{Info: info}).Error; err != nil {
|
||||
app.Logger.Error("记录系统监控失败", zap.Error(err))
|
||||
app.Logger.Warn("记录系统监控失败", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@ func (r *Monitoring) Run() {
|
||||
if day <= 0 || app.Status != app.StatusNormal {
|
||||
return
|
||||
}
|
||||
if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format("2006-01-02 15:04:05")).Delete(&biz.Monitor{}).Error; err != nil {
|
||||
app.Logger.Error("删除过期系统监控失败", zap.Error(err))
|
||||
if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format(time.DateTime)).Delete(&biz.Monitor{}).Error; err != nil {
|
||||
app.Logger.Warn("删除过期系统监控失败", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+13
-10
@@ -1,10 +1,11 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
@@ -32,32 +33,34 @@ func (r *PanelTask) Run() {
|
||||
// 优化数据库
|
||||
if err := app.Orm.Exec("VACUUM").Error; err != nil {
|
||||
app.Status = app.StatusFailed
|
||||
app.Logger.Error("优化面板数据库失败", zap.Error(err))
|
||||
app.Logger.Warn("优化面板数据库失败", slog.Any("err", err))
|
||||
}
|
||||
if err := app.Orm.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
||||
app.Status = app.StatusFailed
|
||||
app.Logger.Error("优化面板数据库失败", zap.Error(err))
|
||||
app.Logger.Warn("优化面板数据库失败", slog.Any("err", err))
|
||||
}
|
||||
|
||||
// 备份面板
|
||||
if err := r.backupRepo.Create(biz.BackupTypePanel, ""); err != nil {
|
||||
app.Logger.Error("备份面板失败", zap.Error(err))
|
||||
app.Logger.Warn("备份面板失败", slog.Any("err", err))
|
||||
}
|
||||
|
||||
// 清理备份
|
||||
path, err := r.backupRepo.GetPath("panel")
|
||||
if err == nil {
|
||||
if err = r.backupRepo.ClearExpired(path, "panel_", 10); err != nil {
|
||||
app.Logger.Error("清理面板备份失败", zap.Error(err))
|
||||
app.Logger.Warn("清理面板备份失败", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 更新商店缓存
|
||||
if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline {
|
||||
if err = r.appRepo.UpdateCache(); err != nil {
|
||||
app.Logger.Error("更新商店缓存失败", zap.Error(err))
|
||||
time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() {
|
||||
if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline {
|
||||
if err = r.appRepo.UpdateCache(); err != nil {
|
||||
app.Logger.Warn("更新商店缓存失败", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 回收内存
|
||||
runtime.GC()
|
||||
|
||||
@@ -17,7 +17,6 @@ func init() {
|
||||
&biz.CertDNS{},
|
||||
&biz.CertAccount{},
|
||||
&biz.Cron{},
|
||||
&biz.Database{},
|
||||
&biz.Monitor{},
|
||||
&biz.App{},
|
||||
&biz.Setting{},
|
||||
@@ -32,7 +31,6 @@ func init() {
|
||||
&biz.CertDNS{},
|
||||
&biz.CertAccount{},
|
||||
&biz.Cron{},
|
||||
&biz.Database{},
|
||||
&biz.Monitor{},
|
||||
&biz.App{},
|
||||
&biz.Setting{},
|
||||
@@ -55,4 +53,29 @@ func init() {
|
||||
)
|
||||
},
|
||||
})
|
||||
Migrations = append(Migrations, &gormigrate.Migration{
|
||||
ID: "20241107-database",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
Migrations = append(Migrations, &gormigrate.Migration{
|
||||
ID: "20241124-database",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
_ = tx.Migrator().DropTable("databases")
|
||||
return tx.AutoMigrate(
|
||||
&biz.DatabaseServer{},
|
||||
&biz.DatabaseUser{},
|
||||
)
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return tx.Migrator().DropTable(
|
||||
&biz.DatabaseServer{},
|
||||
&biz.DatabaseUser{},
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package queuejob
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
)
|
||||
@@ -36,7 +38,7 @@ func (r *ProcessTask) Handle(args ...any) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(task.Shell); err != nil { // nolint: govet
|
||||
if _, err = shell.Exec(task.Shell); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -48,5 +50,6 @@ func (r *ProcessTask) Handle(args ...any) error {
|
||||
}
|
||||
|
||||
func (r *ProcessTask) ErrHandle(err error) {
|
||||
app.Logger.Warn("background task failed", slog.Any("task_id", r.taskID), slog.Any("err", err))
|
||||
_ = r.taskRepo.UpdateStatus(r.taskID, biz.TaskStatusFailed)
|
||||
}
|
||||
|
||||
@@ -74,6 +74,11 @@ func Cli() []*cli.Command {
|
||||
Usage: "关闭HTTPS",
|
||||
Action: cliService.HTTPSOff,
|
||||
},
|
||||
{
|
||||
Name: "generate",
|
||||
Usage: "生成HTTPS证书",
|
||||
Action: cliService.HTTPSGenerate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -168,6 +173,51 @@ func Cli() []*cli.Command {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "database",
|
||||
Usage: "数据库管理",
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "add-server",
|
||||
Usage: "添加数据库服务器",
|
||||
Action: cliService.DatabaseAddServer,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Usage: "服务器类型",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "服务器名称",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "服务器地址",
|
||||
Required: true,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "port",
|
||||
Usage: "服务器端口",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "服务器用户名",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "服务器密码",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "remark",
|
||||
Usage: "服务器备注",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "backup",
|
||||
Usage: "数据备份",
|
||||
|
||||
+44
-36
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/internal/apps"
|
||||
"github.com/TheTNB/panel/internal/embed"
|
||||
"github.com/TheTNB/panel/internal/http/middleware"
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
@@ -20,25 +21,24 @@ func Http(r chi.Router) {
|
||||
r.With(middleware.Throttle(5, time.Minute)).Post("/login", user.Login)
|
||||
r.Post("/logout", user.Logout)
|
||||
r.Get("/isLogin", user.IsLogin)
|
||||
r.With(middleware.MustLogin).Get("/info", user.Info)
|
||||
r.Get("/info", user.Info)
|
||||
})
|
||||
|
||||
r.Route("/dashboard", func(r chi.Router) {
|
||||
dashboard := service.NewDashboardService()
|
||||
r.Get("/panel", dashboard.Panel)
|
||||
r.With(middleware.MustLogin).Get("/homeApps", dashboard.HomeApps)
|
||||
r.With(middleware.MustLogin).Post("/current", dashboard.Current)
|
||||
r.With(middleware.MustLogin).Get("/systemInfo", dashboard.SystemInfo)
|
||||
r.With(middleware.MustLogin).Get("/countInfo", dashboard.CountInfo)
|
||||
r.With(middleware.MustLogin).Get("/installedDbAndPhp", dashboard.InstalledDbAndPhp)
|
||||
r.With(middleware.MustLogin).Get("/checkUpdate", dashboard.CheckUpdate)
|
||||
r.With(middleware.MustLogin).Get("/updateInfo", dashboard.UpdateInfo)
|
||||
r.With(middleware.MustLogin).Post("/update", dashboard.Update)
|
||||
r.With(middleware.MustLogin).Post("/restart", dashboard.Restart)
|
||||
r.Get("/homeApps", dashboard.HomeApps)
|
||||
r.Post("/current", dashboard.Current)
|
||||
r.Get("/systemInfo", dashboard.SystemInfo)
|
||||
r.Get("/countInfo", dashboard.CountInfo)
|
||||
r.Get("/installedDbAndPhp", dashboard.InstalledDbAndPhp)
|
||||
r.Get("/checkUpdate", dashboard.CheckUpdate)
|
||||
r.Get("/updateInfo", dashboard.UpdateInfo)
|
||||
r.Post("/update", dashboard.Update)
|
||||
r.Post("/restart", dashboard.Restart)
|
||||
})
|
||||
|
||||
r.Route("/task", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
task := service.NewTaskService()
|
||||
r.Get("/status", task.Status)
|
||||
r.Get("/", task.List)
|
||||
@@ -47,7 +47,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/website", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
website := service.NewWebsiteService()
|
||||
r.Get("/defaultConfig", website.GetDefaultConfig)
|
||||
r.Post("/defaultConfig", website.UpdateDefaultConfig)
|
||||
@@ -63,8 +62,31 @@ func Http(r chi.Router) {
|
||||
r.Post("/{id}/obtainCert", website.ObtainCert)
|
||||
})
|
||||
|
||||
r.Route("/database", func(r chi.Router) {
|
||||
database := service.NewDatabaseService()
|
||||
r.Get("/", database.List)
|
||||
r.Post("/", database.Create)
|
||||
r.Delete("/", database.Delete)
|
||||
})
|
||||
|
||||
r.Route("/databaseServer", func(r chi.Router) {
|
||||
server := service.NewDatabaseServerService()
|
||||
r.Get("/", server.List)
|
||||
r.Post("/", server.Create)
|
||||
r.Put("/{id}", server.Update)
|
||||
r.Delete("/{id}", server.Delete)
|
||||
r.Post("/{id}/sync", server.Sync)
|
||||
})
|
||||
|
||||
r.Route("/databaseUser", func(r chi.Router) {
|
||||
user := service.NewDatabaseUserService()
|
||||
r.Get("/", user.List)
|
||||
r.Post("/", user.Create)
|
||||
r.Put("/{id}", user.Update)
|
||||
r.Delete("/{id}", user.Delete)
|
||||
})
|
||||
|
||||
r.Route("/backup", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
backup := service.NewBackupService()
|
||||
r.Get("/{type}", backup.List)
|
||||
r.Post("/{type}", backup.Create)
|
||||
@@ -74,7 +96,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/cert", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
cert := service.NewCertService()
|
||||
r.Get("/caProviders", cert.CAProviders)
|
||||
r.Get("/dnsProviders", cert.DNSProviders)
|
||||
@@ -82,10 +103,13 @@ func Http(r chi.Router) {
|
||||
r.Route("/cert", func(r chi.Router) {
|
||||
r.Get("/", cert.List)
|
||||
r.Post("/", cert.Create)
|
||||
r.Post("/upload", cert.Upload)
|
||||
r.Put("/{id}", cert.Update)
|
||||
r.Get("/{id}", cert.Get)
|
||||
r.Delete("/{id}", cert.Delete)
|
||||
r.Post("/{id}/obtain", cert.Obtain)
|
||||
r.Post("/{id}/obtainAuto", cert.ObtainAuto)
|
||||
r.Post("/{id}/obtainManual", cert.ObtainManual)
|
||||
r.Post("/{id}/obtainSelfSigned", cert.ObtainSelfSigned)
|
||||
r.Post("/{id}/renew", cert.Renew)
|
||||
r.Post("/{id}/manualDNS", cert.ManualDNS)
|
||||
r.Post("/{id}/deploy", cert.Deploy)
|
||||
@@ -109,7 +133,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/app", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
app := service.NewAppService()
|
||||
r.Get("/list", app.List)
|
||||
r.Post("/install", app.Install)
|
||||
@@ -121,7 +144,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/cron", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
cron := service.NewCronService()
|
||||
r.Get("/", cron.List)
|
||||
r.Post("/", cron.Create)
|
||||
@@ -132,7 +154,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/safe", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
safe := service.NewSafeService()
|
||||
r.Get("/ssh", safe.GetSSH)
|
||||
r.Post("/ssh", safe.UpdateSSH)
|
||||
@@ -141,7 +162,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/firewall", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
firewall := service.NewFirewallService()
|
||||
r.Get("/status", firewall.GetStatus)
|
||||
r.Post("/status", firewall.UpdateStatus)
|
||||
@@ -157,7 +177,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/ssh", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
ssh := service.NewSSHService()
|
||||
r.Get("/", ssh.List)
|
||||
r.Post("/", ssh.Create)
|
||||
@@ -167,7 +186,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/container", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
r.Route("/container", func(r chi.Router) {
|
||||
container := service.NewContainerService()
|
||||
r.Get("/", container.List)
|
||||
@@ -179,11 +197,8 @@ func Http(r chi.Router) {
|
||||
r.Post("/{id}/restart", container.Restart)
|
||||
r.Post("/{id}/pause", container.Pause)
|
||||
r.Post("/{id}/unpause", container.Unpause)
|
||||
r.Get("/{id}/inspect", container.Inspect)
|
||||
r.Post("/{id}/kill", container.Kill)
|
||||
r.Post("/{id}/rename", container.Rename)
|
||||
r.Get("/{id}/stats", container.Stats)
|
||||
r.Get("/{id}/exist", container.Exist)
|
||||
r.Get("/{id}/logs", container.Logs)
|
||||
r.Post("/prune", container.Prune)
|
||||
})
|
||||
@@ -192,40 +207,32 @@ func Http(r chi.Router) {
|
||||
r.Get("/", containerNetwork.List)
|
||||
r.Post("/", containerNetwork.Create)
|
||||
r.Delete("/{id}", containerNetwork.Remove)
|
||||
r.Get("/{id}/exist", containerNetwork.Exist)
|
||||
r.Get("/{id}/inspect", containerNetwork.Inspect)
|
||||
r.Post("/{network}/connect", containerNetwork.Connect)
|
||||
r.Post("/{network}/disconnect", containerNetwork.Disconnect)
|
||||
r.Post("/prune", containerNetwork.Prune)
|
||||
})
|
||||
r.Route("/image", func(r chi.Router) {
|
||||
containerImage := service.NewContainerImageService()
|
||||
r.Get("/", containerImage.List)
|
||||
r.Get("/{id}/exist", containerImage.Exist)
|
||||
r.Post("/", containerImage.Pull)
|
||||
r.Delete("/{id}", containerImage.Remove)
|
||||
r.Get("/{id}", containerImage.Inspect)
|
||||
r.Post("/prune", containerImage.Prune)
|
||||
})
|
||||
r.Route("/volume", func(r chi.Router) {
|
||||
containerVolume := service.NewContainerVolumeService()
|
||||
r.Get("/", containerVolume.List)
|
||||
r.Post("/", containerVolume.Create)
|
||||
r.Get("/{id}/exist", containerVolume.Exist)
|
||||
r.Delete("/{id}", containerVolume.Remove)
|
||||
r.Get("/{id}", containerVolume.Inspect)
|
||||
r.Post("/prune", containerVolume.Prune)
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/file", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
file := service.NewFileService()
|
||||
r.Post("/create", file.Create)
|
||||
r.Get("/content", file.Content)
|
||||
r.Post("/save", file.Save)
|
||||
r.Post("/delete", file.Delete)
|
||||
r.Post("/upload", file.Upload)
|
||||
r.Post("/exist", file.Exist)
|
||||
r.Post("/move", file.Move)
|
||||
r.Post("/copy", file.Copy)
|
||||
r.Get("/download", file.Download)
|
||||
@@ -239,7 +246,6 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/monitor", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
monitor := service.NewMonitorService()
|
||||
r.Get("/setting", monitor.GetSetting)
|
||||
r.Post("/setting", monitor.UpdateSetting)
|
||||
@@ -248,14 +254,12 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/setting", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
setting := service.NewSettingService()
|
||||
r.Get("/", setting.Get)
|
||||
r.Post("/", setting.Update)
|
||||
})
|
||||
|
||||
r.Route("/systemctl", func(r chi.Router) {
|
||||
r.Use(middleware.MustLogin)
|
||||
systemctl := service.NewSystemctlService()
|
||||
r.Get("/status", systemctl.Status)
|
||||
r.Get("/isEnabled", systemctl.IsEnabled)
|
||||
@@ -266,6 +270,10 @@ func Http(r chi.Router) {
|
||||
r.Post("/start", systemctl.Start)
|
||||
r.Post("/stop", systemctl.Stop)
|
||||
})
|
||||
|
||||
r.Route("/apps", func(r chi.Router) {
|
||||
apps.Boot(r)
|
||||
})
|
||||
})
|
||||
|
||||
r.NotFound(func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
@@ -115,6 +115,22 @@ func (s *CertService) List(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CertService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.CertUpload](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cert, err := s.certRepo.Upload(req)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, cert)
|
||||
}
|
||||
|
||||
func (s *CertService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.CertCreate](r)
|
||||
if err != nil {
|
||||
@@ -178,26 +194,44 @@ func (s *CertService) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *CertService) Obtain(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *CertService) ObtainAuto(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cert, err := s.certRepo.Get(req.ID)
|
||||
if err != nil {
|
||||
if _, err = s.certRepo.ObtainAuto(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if cert.DNS != nil || cert.Website != nil {
|
||||
_, err = s.certRepo.ObtainAuto(req.ID)
|
||||
} else {
|
||||
_, err = s.certRepo.ObtainManual(req.ID)
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *CertService) ObtainManual(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = s.certRepo.ObtainManual(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *CertService) ObtainSelfSigned(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.certRepo.ObtainSelfSigned(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
+123
-50
@@ -7,10 +7,12 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/collect"
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/urfave/cli/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
@@ -18,35 +20,39 @@ import (
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/api"
|
||||
"github.com/TheTNB/panel/pkg/cert"
|
||||
"github.com/TheTNB/panel/pkg/firewall"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/ntp"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type CliService struct {
|
||||
hr string
|
||||
api *api.API
|
||||
appRepo biz.AppRepo
|
||||
userRepo biz.UserRepo
|
||||
settingRepo biz.SettingRepo
|
||||
backupRepo biz.BackupRepo
|
||||
websiteRepo biz.WebsiteRepo
|
||||
hash hash.Hasher
|
||||
hr string
|
||||
api *api.API
|
||||
appRepo biz.AppRepo
|
||||
userRepo biz.UserRepo
|
||||
settingRepo biz.SettingRepo
|
||||
backupRepo biz.BackupRepo
|
||||
websiteRepo biz.WebsiteRepo
|
||||
databaseServerRepo biz.DatabaseServerRepo
|
||||
hash hash.Hasher
|
||||
}
|
||||
|
||||
func NewCliService() *CliService {
|
||||
return &CliService{
|
||||
hr: `+----------------------------------------------------`,
|
||||
api: api.NewAPI(app.Version),
|
||||
appRepo: data.NewAppRepo(),
|
||||
userRepo: data.NewUserRepo(),
|
||||
settingRepo: data.NewSettingRepo(),
|
||||
backupRepo: data.NewBackupRepo(),
|
||||
websiteRepo: data.NewWebsiteRepo(),
|
||||
hash: hash.NewArgon2id(),
|
||||
hr: `+----------------------------------------------------`,
|
||||
api: api.NewAPI(app.Version),
|
||||
appRepo: data.NewAppRepo(),
|
||||
userRepo: data.NewUserRepo(),
|
||||
settingRepo: data.NewSettingRepo(),
|
||||
backupRepo: data.NewBackupRepo(),
|
||||
websiteRepo: data.NewWebsiteRepo(),
|
||||
databaseServerRepo: data.NewDatabaseServerRepo(),
|
||||
hash: hash.NewArgon2id(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +89,7 @@ func (s *CliService) Update(ctx context.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("获取最新版本失败:%v", err)
|
||||
}
|
||||
|
||||
download := str.FirstElement(panel.Downloads)
|
||||
download := collect.First(panel.Downloads)
|
||||
if download == nil {
|
||||
return fmt.Errorf("下载地址为空")
|
||||
}
|
||||
@@ -102,15 +108,15 @@ func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("获取管理员信息失败:%v", err)
|
||||
}
|
||||
|
||||
password := str.RandomString(16)
|
||||
password := str.Random(16)
|
||||
hashed, err := s.hash.Make(password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码生成失败:%v", err)
|
||||
}
|
||||
user.Username = str.RandomString(8)
|
||||
user.Username = str.Random(8)
|
||||
user.Password = hashed
|
||||
if user.Email == "" {
|
||||
user.Email = str.RandomString(8) + "@example.com"
|
||||
user.Email = str.Random(8) + "@example.com"
|
||||
}
|
||||
|
||||
if err = app.Orm.Save(user).Error; err != nil {
|
||||
@@ -168,7 +174,7 @@ func (s *CliService) UserList(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
fmt.Printf("ID: %d, 用户名: %s, 邮箱: %s, 创建日期: %s\n", user.ID, user.Username, user.Email, user.CreatedAt.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("ID: %d, 用户名: %s, 邮箱: %s, 创建日期: %s\n", user.ID, user.Username, user.Email, user.CreatedAt.Format(time.DateTime))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -236,18 +242,17 @@ func (s *CliService) UserPassword(ctx context.Context, cmd *cli.Command) error {
|
||||
|
||||
func (s *CliService) HTTPSOn(ctx context.Context, cmd *cli.Command) error {
|
||||
config := new(types.PanelConfig)
|
||||
cm := yaml.CommentMap{}
|
||||
raw, err := io.Read("/usr/local/etc/panel/config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
|
||||
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.HTTP.TLS = true
|
||||
|
||||
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -262,18 +267,17 @@ func (s *CliService) HTTPSOn(ctx context.Context, cmd *cli.Command) error {
|
||||
|
||||
func (s *CliService) HTTPSOff(ctx context.Context, cmd *cli.Command) error {
|
||||
config := new(types.PanelConfig)
|
||||
cm := yaml.CommentMap{}
|
||||
raw, err := io.Read("/usr/local/etc/panel/config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
|
||||
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.HTTP.TLS = false
|
||||
|
||||
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -286,20 +290,50 @@ func (s *CliService) HTTPSOff(ctx context.Context, cmd *cli.Command) error {
|
||||
return s.Restart(ctx, cmd)
|
||||
}
|
||||
|
||||
func (s *CliService) HTTPSGenerate(ctx context.Context, cmd *cli.Command) error {
|
||||
var names []string
|
||||
if lv4, err := tools.GetLocalIPv4(); err == nil {
|
||||
names = append(names, lv4)
|
||||
}
|
||||
if lv6, err := tools.GetLocalIPv6(); err == nil {
|
||||
names = append(names, lv6)
|
||||
}
|
||||
if rv4, err := tools.GetPublicIPv4(); err == nil {
|
||||
names = append(names, rv4)
|
||||
}
|
||||
if rv6, err := tools.GetPublicIPv6(); err == nil {
|
||||
names = append(names, rv6)
|
||||
}
|
||||
|
||||
crt, key, err := cert.GenerateSelfSigned(names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), string(crt), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Write(filepath.Join(app.Root, "panel/storage/cert.key"), string(key), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("已生成HTTPS证书")
|
||||
return s.Restart(ctx, cmd)
|
||||
}
|
||||
|
||||
func (s *CliService) EntranceOn(ctx context.Context, cmd *cli.Command) error {
|
||||
config := new(types.PanelConfig)
|
||||
cm := yaml.CommentMap{}
|
||||
raw, err := io.Read("/usr/local/etc/panel/config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
|
||||
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.HTTP.Entrance = "/" + str.RandomString(6)
|
||||
config.HTTP.Entrance = "/" + str.Random(6)
|
||||
|
||||
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -315,18 +349,17 @@ func (s *CliService) EntranceOn(ctx context.Context, cmd *cli.Command) error {
|
||||
|
||||
func (s *CliService) EntranceOff(ctx context.Context, cmd *cli.Command) error {
|
||||
config := new(types.PanelConfig)
|
||||
cm := yaml.CommentMap{}
|
||||
raw, err := io.Read("/usr/local/etc/panel/config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
|
||||
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.HTTP.Entrance = "/"
|
||||
|
||||
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -340,24 +373,42 @@ func (s *CliService) EntranceOff(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func (s *CliService) Port(ctx context.Context, cmd *cli.Command) error {
|
||||
port := cast.ToInt(cmd.Args().First())
|
||||
port := cast.ToUint(cmd.Args().First())
|
||||
if port < 1 || port > 65535 {
|
||||
return fmt.Errorf("端口范围错误")
|
||||
}
|
||||
|
||||
config := new(types.PanelConfig)
|
||||
cm := yaml.CommentMap{}
|
||||
raw, err := io.Read("/usr/local/etc/panel/config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
|
||||
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if port != config.HTTP.Port {
|
||||
if os.TCPPortInUse(port) {
|
||||
return errors.New("端口已被占用")
|
||||
}
|
||||
}
|
||||
|
||||
config.HTTP.Port = port
|
||||
|
||||
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 放行端口
|
||||
fw := firewall.NewFirewall()
|
||||
err = fw.Port(firewall.FireInfo{
|
||||
Type: firewall.TypeNormal,
|
||||
PortStart: uint(config.HTTP.Port),
|
||||
PortEnd: uint(config.HTTP.Port),
|
||||
Direction: firewall.DirectionIn,
|
||||
Strategy: firewall.StrategyAccept,
|
||||
}, firewall.OperationAdd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -430,6 +481,25 @@ func (s *CliService) WebsiteWrite(ctx context.Context, cmd *cli.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CliService) DatabaseAddServer(ctx context.Context, cmd *cli.Command) error {
|
||||
req := &request.DatabaseServerCreate{
|
||||
Type: cmd.String("type"),
|
||||
Name: cmd.String("name"),
|
||||
Host: cmd.String("host"),
|
||||
Port: uint(cmd.Uint("port")),
|
||||
Username: cmd.String("username"),
|
||||
Password: cmd.String("password"),
|
||||
Remark: cmd.String("remark"),
|
||||
}
|
||||
|
||||
if err := s.databaseServerRepo.Create(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("数据库服务器 %s 添加成功\n", cmd.String("name"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error {
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
|
||||
@@ -437,7 +507,7 @@ func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Println("|-备份类型:网站")
|
||||
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
|
||||
if err := s.backupRepo.Create(biz.BackupTypeWebsite, cmd.String("name"), cmd.String("path")); err != nil {
|
||||
return fmt.Errorf("|-备份失败:%v", err)
|
||||
return fmt.Errorf("备份失败:%v", err)
|
||||
}
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("☆ 备份成功 [%s]\n", time.Now().Format(time.DateTime))
|
||||
@@ -453,7 +523,7 @@ func (s *CliService) BackupDatabase(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Printf("|-数据库:%s\n", cmd.String("type"))
|
||||
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
|
||||
if err := s.backupRepo.Create(biz.BackupType(cmd.String("type")), cmd.String("name"), cmd.String("path")); err != nil {
|
||||
return fmt.Errorf("|-备份失败:%v", err)
|
||||
return fmt.Errorf("备份失败:%v", err)
|
||||
}
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("☆ 备份成功 [%s]\n", time.Now().Format(time.DateTime))
|
||||
@@ -467,7 +537,7 @@ func (s *CliService) BackupPanel(ctx context.Context, cmd *cli.Command) error {
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-备份类型:面板")
|
||||
if err := s.backupRepo.Create(biz.BackupTypePanel, "", cmd.String("path")); err != nil {
|
||||
return fmt.Errorf("|-备份失败:%v", err)
|
||||
return fmt.Errorf("备份失败:%v", err)
|
||||
}
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("☆ 备份成功 [%s]\n", time.Now().Format(time.DateTime))
|
||||
@@ -491,7 +561,7 @@ func (s *CliService) BackupClear(ctx context.Context, cmd *cli.Command) error {
|
||||
fmt.Printf("|-清理目标:%s\n", cmd.String("file"))
|
||||
fmt.Printf("|-保留份数:%d\n", cmd.Int("save"))
|
||||
if err = s.backupRepo.ClearExpired(path, cmd.String("file"), int(cmd.Int("save"))); err != nil {
|
||||
return fmt.Errorf("|-清理失败:%v", err)
|
||||
return fmt.Errorf("清理失败:%v", err)
|
||||
}
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("☆ 清理成功 [%s]\n", time.Now().Format(time.DateTime))
|
||||
@@ -728,7 +798,7 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
}
|
||||
|
||||
value, err := hash.NewArgon2id().Make(str.RandomString(32))
|
||||
value, err := hash.NewArgon2id().Make(str.Random(32))
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
}
|
||||
@@ -739,20 +809,23 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
}
|
||||
|
||||
if err = s.HTTPSGenerate(ctx, cmd); err != nil {
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
}
|
||||
|
||||
config := new(types.PanelConfig)
|
||||
cm := yaml.CommentMap{}
|
||||
raw, err := io.Read("/usr/local/etc/panel/config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
|
||||
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.App.Key = str.RandomString(32)
|
||||
config.HTTP.Entrance = "/" + str.RandomString(6)
|
||||
config.App.Key = str.Random(32)
|
||||
config.HTTP.Entrance = "/" + str.Random(6)
|
||||
|
||||
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
@@ -29,35 +27,15 @@ func (s *ContainerService) List(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
paged, total := Paginate(r, containers)
|
||||
items := make([]any, 0)
|
||||
for _, item := range paged {
|
||||
var name string
|
||||
if len(item.Names) > 0 {
|
||||
name = item.Names[0]
|
||||
}
|
||||
items = append(items, map[string]any{
|
||||
"id": item.ID,
|
||||
"name": strings.TrimLeft(name, "/"),
|
||||
"image": item.Image,
|
||||
"image_id": item.ImageID,
|
||||
"command": item.Command,
|
||||
"created": time.Unix(item.Created, 0).Format(time.DateTime),
|
||||
"ports": item.Ports,
|
||||
"labels": item.Labels,
|
||||
"state": item.State,
|
||||
"status": item.Status,
|
||||
})
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": items,
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ContainerService) Search(w http.ResponseWriter, r *http.Request) {
|
||||
name := strings.Fields(r.FormValue("name"))
|
||||
containers, err := s.containerRepo.ListByNames(name)
|
||||
containers, err := s.containerRepo.ListByName(r.FormValue("name"))
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -175,22 +153,6 @@ func (s *ContainerService) Unpause(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerService) Inspect(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
container, err := s.containerRepo.Inspect(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, container)
|
||||
}
|
||||
|
||||
func (s *ContainerService) Kill(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerID](r)
|
||||
if err != nil {
|
||||
@@ -221,38 +183,6 @@ func (s *ContainerService) Rename(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerService) Stats(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := s.containerRepo.Stats(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, stats)
|
||||
}
|
||||
|
||||
func (s *ContainerService) Exist(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
exist, err := s.containerRepo.Exist(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, exist)
|
||||
}
|
||||
|
||||
func (s *ContainerService) Logs(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerID](r)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,14 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
)
|
||||
|
||||
type ContainerImageService struct {
|
||||
@@ -31,41 +29,12 @@ func (s *ContainerImageService) List(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
paged, total := Paginate(r, images)
|
||||
|
||||
items := make([]any, 0)
|
||||
for _, item := range paged {
|
||||
items = append(items, map[string]any{
|
||||
"id": item.ID,
|
||||
"created": time.Unix(item.Created, 0).Format(time.DateTime),
|
||||
"containers": item.Containers,
|
||||
"size": str.FormatBytes(float64(item.Size)),
|
||||
"labels": item.Labels,
|
||||
"repo_tags": item.RepoTags,
|
||||
"repo_digests": item.RepoDigests,
|
||||
})
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": items,
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ContainerImageService) Exist(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerImageID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
exist, err := s.containerImageRepo.Exist(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, exist)
|
||||
}
|
||||
|
||||
func (s *ContainerImageService) Pull(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerImagePull](r)
|
||||
if err != nil {
|
||||
@@ -96,22 +65,6 @@ func (s *ContainerImageService) Remove(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerImageService) Inspect(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerImageID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
inspect, err := s.containerImageRepo.Inspect(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, inspect)
|
||||
}
|
||||
|
||||
func (s *ContainerImageService) Prune(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.containerImageRepo.Prune(); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
@@ -30,40 +29,9 @@ func (s *ContainerNetworkService) List(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
paged, total := Paginate(r, networks)
|
||||
|
||||
items := make([]any, 0)
|
||||
for _, item := range paged {
|
||||
var ipamConfig []any
|
||||
for _, v := range item.IPAM.Config {
|
||||
ipamConfig = append(ipamConfig, map[string]any{
|
||||
"subnet": v.Subnet,
|
||||
"gateway": v.Gateway,
|
||||
"ip_range": v.IPRange,
|
||||
"aux_address": v.AuxAddress,
|
||||
})
|
||||
}
|
||||
items = append(items, map[string]any{
|
||||
"id": item.ID,
|
||||
"name": item.Name,
|
||||
"driver": item.Driver,
|
||||
"ipv6": item.EnableIPv6,
|
||||
"scope": item.Scope,
|
||||
"internal": item.Internal,
|
||||
"attachable": item.Attachable,
|
||||
"ingress": item.Ingress,
|
||||
"labels": item.Labels,
|
||||
"options": item.Options,
|
||||
"ipam": map[string]any{
|
||||
"config": ipamConfig,
|
||||
"driver": item.IPAM.Driver,
|
||||
"options": item.IPAM.Options,
|
||||
},
|
||||
"created": item.Created.Format(time.DateTime),
|
||||
})
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": items,
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,68 +66,6 @@ func (s *ContainerNetworkService) Remove(w http.ResponseWriter, r *http.Request)
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerNetworkService) Exist(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerNetworkID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
exist, err := s.containerNetworkRepo.Exist(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, exist)
|
||||
}
|
||||
|
||||
func (s *ContainerNetworkService) Inspect(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerNetworkID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
network, err := s.containerNetworkRepo.Inspect(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, network)
|
||||
}
|
||||
|
||||
func (s *ContainerNetworkService) Connect(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerNetworkConnect](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerNetworkRepo.Connect(req.Network, req.Container); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerNetworkService) Disconnect(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerNetworkConnect](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerNetworkRepo.Disconnect(req.Network, req.Container); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerNetworkService) Prune(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.containerNetworkRepo.Prune(); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
)
|
||||
|
||||
type ContainerVolumeService struct {
|
||||
@@ -30,31 +29,9 @@ func (s *ContainerVolumeService) List(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
paged, total := Paginate(r, volumes)
|
||||
|
||||
items := make([]any, 0)
|
||||
for _, item := range paged {
|
||||
var usage any
|
||||
if item.UsageData != nil {
|
||||
usage = map[string]any{
|
||||
"ref_count": item.UsageData.RefCount,
|
||||
"size": str.FormatBytes(float64(item.UsageData.Size)),
|
||||
}
|
||||
}
|
||||
items = append(items, map[string]any{
|
||||
"id": item.Name,
|
||||
"created": item.CreatedAt,
|
||||
"driver": item.Driver,
|
||||
"mount": item.Mountpoint,
|
||||
"labels": item.Labels,
|
||||
"options": item.Options,
|
||||
"scope": item.Scope,
|
||||
"status": item.Status,
|
||||
"usage": usage,
|
||||
})
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": items,
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,32 +42,16 @@ func (s *ContainerVolumeService) Create(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
volume, err := s.containerVolumeRepo.Create(req)
|
||||
name, err := s.containerVolumeRepo.Create(req)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, volume.Name)
|
||||
Success(w, name)
|
||||
|
||||
}
|
||||
|
||||
func (s *ContainerVolumeService) Exist(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerVolumeID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
exist, err := s.containerVolumeRepo.Exist(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, exist)
|
||||
}
|
||||
|
||||
func (s *ContainerVolumeService) Remove(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerVolumeID](r)
|
||||
if err != nil {
|
||||
@@ -106,22 +67,6 @@ func (s *ContainerVolumeService) Remove(w http.ResponseWriter, r *http.Request)
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerVolumeService) Inspect(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerVolumeID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
volume, err := s.containerVolumeRepo.Inspect(req.ID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, volume)
|
||||
}
|
||||
|
||||
func (s *ContainerVolumeService) Prune(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.containerVolumeRepo.Prune(); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/utils/collect"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
@@ -20,7 +21,6 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/api"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
@@ -52,8 +52,8 @@ func (s *DashboardService) Panel(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"name": name,
|
||||
"language": app.Conf.MustString("app.locale"),
|
||||
"name": name,
|
||||
"locale": app.Conf.MustString("app.locale"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ func (s *DashboardService) CountInfo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if postgresqlInstalled {
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), 5432)
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err == nil {
|
||||
defer postgres.Close()
|
||||
databases, err := postgres.Databases()
|
||||
@@ -295,7 +295,7 @@ func (s *DashboardService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
download := str.FirstElement(panel.Downloads)
|
||||
download := collect.First(panel.Downloads)
|
||||
if download == nil {
|
||||
Error(w, http.StatusInternalServerError, "获取下载链接失败")
|
||||
return
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
databaseRepo biz.DatabaseRepo
|
||||
}
|
||||
|
||||
func NewDatabaseService() *Database {
|
||||
return &Database{
|
||||
databaseRepo: data.NewDatabaseRepo(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Database) List(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.Paginate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
databases, total, err := s.databaseRepo.List(req.Page, req.Limit)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": databases,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Database) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseCreate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseRepo.Create(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *Database) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseDelete](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseRepo.Delete(req.ServerID, req.Name); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type DatabaseServer struct {
|
||||
databaseServerRepo biz.DatabaseServerRepo
|
||||
}
|
||||
|
||||
func NewDatabaseServerService() *DatabaseServer {
|
||||
return &DatabaseServer{
|
||||
databaseServerRepo: data.NewDatabaseServerRepo(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DatabaseServer) List(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.Paginate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
servers, total, err := s.databaseServerRepo.List(req.Page, req.Limit)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": servers,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DatabaseServer) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseServerCreate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseServerRepo.Create(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseServer) Update(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseServerUpdate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseServerRepo.Update(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseServer) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseServerRepo.Delete(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseServer) Sync(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseServerRepo.Sync(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type DatabaseUser struct {
|
||||
databaseUserRepo biz.DatabaseUserRepo
|
||||
}
|
||||
|
||||
func NewDatabaseUserService() *DatabaseUser {
|
||||
return &DatabaseUser{
|
||||
databaseUserRepo: data.NewDatabaseUserRepo(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) List(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.Paginate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
users, total, err := s.databaseUserRepo.List(req.Page, req.Limit)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": users,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseUserCreate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseUserRepo.Create(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) Update(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseUserUpdate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseUserRepo.Update(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseUserRepo.Delete(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
+83
-44
@@ -17,13 +17,14 @@ import (
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type FileService struct {
|
||||
@@ -72,11 +73,11 @@ func (s *FileService) Content(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
Error(w, http.StatusInternalServerError, "目标路径不是文件")
|
||||
Error(w, http.StatusInternalServerError, "target is a directory")
|
||||
return
|
||||
}
|
||||
if fileInfo.Size() > 10*1024*1024 {
|
||||
Error(w, http.StatusInternalServerError, "文件大小超过 10 M,不支持在线编辑")
|
||||
Error(w, http.StatusInternalServerError, "file is too large, please download it")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -107,7 +108,6 @@ func (s *FileService) Save(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.setPermission(req.Path, 0755, "www", "www")
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -118,6 +118,12 @@ func (s *FileService) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
banned := []string{"/", app.Root, filepath.Join(app.Root, "server"), filepath.Join(app.Root, "panel")}
|
||||
if slices.Contains(banned, req.Path) {
|
||||
Error(w, http.StatusForbidden, "please don't do this")
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Remove(req.Path); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -135,17 +141,17 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.FormValue("path")
|
||||
_, handler, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "上传文件失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "upload file error: %v", err)
|
||||
return
|
||||
}
|
||||
if io.Exists(path) {
|
||||
Error(w, http.StatusForbidden, "目标路径 %s 已存在", path)
|
||||
Error(w, http.StatusForbidden, "target path %s already exists", path)
|
||||
return
|
||||
}
|
||||
|
||||
if !io.Exists(filepath.Dir(path)) {
|
||||
if err = io.Mkdir(filepath.Dir(path), 0755); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "create directory error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -153,12 +159,12 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
src, _ := handler.Open()
|
||||
out, err := stdos.OpenFile(path, stdos.O_CREATE|stdos.O_RDWR|stdos.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "打开文件失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "open file error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = stdio.Copy(out, src); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "写入文件失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "write file error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -167,41 +173,77 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *FileService) Exist(w http.ResponseWriter, r *http.Request) {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
var paths []string
|
||||
if err := binder.Body(&paths); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var results []bool
|
||||
for item := range slices.Values(paths) {
|
||||
results = append(results, io.Exists(item))
|
||||
}
|
||||
|
||||
Success(w, results)
|
||||
}
|
||||
|
||||
func (s *FileService) Move(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.FileMove](r)
|
||||
if err != nil {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
var req []request.FileControl
|
||||
if err := binder.Body(&req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if io.Exists(req.Target) && !req.Force {
|
||||
Error(w, http.StatusForbidden, "目标路径 %s 已存在", req.Target)
|
||||
return
|
||||
}
|
||||
for item := range slices.Values(req) {
|
||||
if io.Exists(item.Target) && !item.Force {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = io.Mv(req.Source, req.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
|
||||
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
|
||||
return
|
||||
}
|
||||
|
||||
if err := io.Mv(item.Source, item.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *FileService) Copy(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.FileCopy](r)
|
||||
if err != nil {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
var req []request.FileControl
|
||||
if err := binder.Body(&req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if io.Exists(req.Target) && !req.Force {
|
||||
Error(w, http.StatusForbidden, "目标路径 %s 已存在", req.Target)
|
||||
return
|
||||
}
|
||||
for item := range slices.Values(req) {
|
||||
if io.Exists(item.Target) && !item.Force {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = io.Cp(req.Source, req.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
|
||||
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
|
||||
return
|
||||
}
|
||||
|
||||
if err := io.Cp(item.Source, item.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
@@ -220,7 +262,7 @@ func (s *FileService) Download(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if info.IsDir() {
|
||||
Error(w, http.StatusInternalServerError, "不能下载目录")
|
||||
Error(w, http.StatusInternalServerError, "can't download a directory")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -266,7 +308,7 @@ func (s *FileService) Info(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Success(w, chix.M{
|
||||
"name": info.Name(),
|
||||
"size": str.FormatBytes(float64(info.Size())),
|
||||
"size": tools.FormatBytes(float64(info.Size())),
|
||||
"mode_str": info.Mode().String(),
|
||||
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
|
||||
"dir": info.IsDir(),
|
||||
@@ -307,13 +349,7 @@ func (s *FileService) Compress(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
format, err := io.FormatArchiveByPath(req.File)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Compress(req.Paths, req.File, format); err != nil {
|
||||
if err = io.Compress(req.Dir, req.Paths, req.File); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -329,18 +365,21 @@ func (s *FileService) UnCompress(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
format, err := io.FormatArchiveByPath(req.File)
|
||||
if err = io.UnCompress(req.File, req.Path); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := io.ListCompress(req.File)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.UnCompress(req.File, req.Path, format); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
for item := range slices.Values(list) {
|
||||
s.setPermission(filepath.Join(req.Path, item), 0755, "www", "www")
|
||||
}
|
||||
|
||||
s.setPermission(req.Path, 0755, "www", "www")
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -409,7 +448,7 @@ func (s *FileService) List(w http.ResponseWriter, r *http.Request) {
|
||||
// formatDir 格式化目录信息
|
||||
func (s *FileService) formatDir(base string, entries []stdos.DirEntry) []any {
|
||||
var paths []any
|
||||
for _, file := range entries {
|
||||
for file := range slices.Values(entries) {
|
||||
info, err := file.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -419,7 +458,7 @@ func (s *FileService) formatDir(base string, entries []stdos.DirEntry) []any {
|
||||
paths = append(paths, map[string]any{
|
||||
"name": info.Name(),
|
||||
"full": filepath.Join(base, info.Name()),
|
||||
"size": str.FormatBytes(float64(info.Size())),
|
||||
"size": tools.FormatBytes(float64(info.Size())),
|
||||
"mode_str": info.Mode().String(),
|
||||
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
|
||||
"owner": os.GetUser(stat.Uid),
|
||||
@@ -445,7 +484,7 @@ func (s *FileService) formatInfo(infos map[string]stdos.FileInfo) []map[string]a
|
||||
paths = append(paths, map[string]any{
|
||||
"name": info.Name(),
|
||||
"full": path,
|
||||
"size": str.FormatBytes(float64(info.Size())),
|
||||
"size": tools.FormatBytes(float64(info.Size())),
|
||||
"mode_str": info.Mode().String(),
|
||||
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
|
||||
"owner": os.GetUser(stat.Uid),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user