Compare commits

...
Author SHA1 Message Date
ssongliu 0d548c7df9 fix: Fix http image registry edit exception (#9460) 2025-07-08 18:18:00 +08:00
ssongliu 8ec5a5bd4b fix: Fix menu internationalization issues (#9456) 2025-07-08 06:05:55 +00:00
ssongliu 2e8bcc09e6 fix: Fix dashboard refresh exception issue (#9455) 2025-07-08 04:15:53 +00:00
ssongliu 29a304e2c8 fix: Optimize container image repo restart detection logic (#9447) 2025-07-08 02:47:53 +00:00
ssongliu b1adc37920 fix: Modify OneDrive default connection settings (#9440) 2025-07-07 10:05:50 +00:00
CityFun 03fac3ec06 fix: Resolve the issue of lost GPU configuration after upgrade ollama (#9362) 2025-07-01 07:47:33 +00:00
蘭 6e465ac215 fix: Fix the issue of multiple selection moving folder errors (#9319)
refs: #8612
2025-06-27 06:03:24 +00:00
ssongliu 21bbadc37b fix: Resolve abnormal state after Docker restart (#9305) 2025-06-26 11:19:22 +00:00
ssongliu 3061d640ce fix: Modify the service status judgment logic (#9301) 2025-06-26 17:18:14 +08:00
ssongliu 874717cbcd fix: Remove code comments (#9297) 2025-06-26 06:39:22 +00:00
ssongliu 6850acabae fix: Optimize supervisor initialization logic (#9295) 2025-06-26 06:29:23 +00:00
ssongliu 890ea42749 fix: Fix ClamAV service name detection issue (#9292) 2025-06-26 14:27:57 +08:00
ssongliu 6aaf61cde4 fix: Increase systemctl operation timeout (#9281) 2025-06-25 08:21:22 +00:00
wanghe-fit2cloud 73930c5e29 feat: Compatible with system upgrades for v1.10.30 and earlier versions 2025-06-24 17:32:23 +08:00
ssongliu 8f8d280e90 Revert "fix: Fix the issue with upgrade failures (#9262)" (#9264)
This reverts commit b46192024f.
2025-06-24 17:24:42 +08:00
ssongliu b46192024f fix: Fix the issue with upgrade failures (#9262) 2025-06-24 08:51:13 +00:00
ssongliu 8f939ad5be fix: Make firewall operations compatible with OpenWrt (#9257) 2025-06-24 06:21:13 +00:00
ssongliu a6363ce4e5 fix: Fix the issue with cronjob report loading failure (#9254)
Refs #8684
2025-06-24 05:47:14 +00:00
ssongliu fd490b2642 fix: Modify the image repository validation rules (#9253)
Refs #8870
2025-06-24 05:45:13 +00:00
ssongliu 7624670ee0 fix: Add a restart prompt for HTTP-type mirror repositories (#9252)
Refs #8978
2025-06-24 05:43:14 +00:00
ssongliu 8c3145e1c6 fix: Fix the problem of ip changes in container upgrades for macvlan … (#9251)
Refs #8950
2025-06-24 05:41:13 +00:00
ssongliu bec7b6989f fix: Fix the issue of abnormal highlighting in the cronjob script (#9246)
Refs #8683
2025-06-23 10:40:14 +00:00
CityFun 326f2654e6 feat: add PbootCMS extensions template for php runtime (#9243) 2025-06-23 18:37:47 +08:00
CityFun 708499a335 feat: fix issue with build frontend failed (#8994) 2025-06-10 08:56:35 +00:00
CityFun bdeb842939 feat: change 1panel v1 docs url (#8993) 2025-06-10 08:16:34 +00:00
王贺 edefdce3ff docs: Update the installation script (#8979) 2025-06-10 11:03:13 +08:00
longjuan b348c36571 feat: add name attrs to login inputs for password managers to recognize (#8953) 2025-06-09 11:26:02 +08:00
玲珑芯ExquisiteCore 6ae150b64e feat: Move current status to first position in MySQL settings and set… (#8882)
* feat: Move current status to first position in MySQL and Redis settings and set as default tab
2025-06-03 10:22:00 +08:00
maninhill 3687b9bea2 chore: Update README.md 2025-05-25 16:42:01 +08:00
maninhill 711431da82 Update README.md 2025-05-25 16:40:02 +08:00
Alan e93afc33aa ini filetype support (#8628) 2025-05-14 16:52:41 +08:00
CityFun 82dd993751 fix: Fix issue with update website config (#8610) 2025-05-12 10:41:58 +00:00
Wankko Ree 9ba293e154 fix: the docker image which no tag but used be pruned (#8529) 2025-05-03 13:25:24 +08:00
igophper 8fa9f128d8 fix(cronjob): prevent unnecessary log updates and clean log formatting (#8481)
Refs #8425
2025-04-28 09:09:48 +08:00
maninhill ed9f1599e6 chore: Update README.md 2025-04-25 11:38:18 +08:00
igophper 787a29769d fix(editor): reset edit state after not saving content (#8474)
Refs #8461
2025-04-25 11:13:23 +08:00
巴山夜语andgcsong023 6b379389d3 WIP: refactor(service): Refactor OpenRC service manager (#8416)
* refactor(service): 重构 OpenRC 服务管理器

- 更新 IsEnabled 和 IsActive 检查逻辑,使用更可靠的命令
- 修复 ServiceExists 检查,直接使用文件路径判断
- 优化 FindServices 函数,扫描 /etc/init.d 目录
- 调整 BuildCommand 函数,支持 OpenRC 特定操作
- 修改 ParseStatus 函数,使用更新后的正则表达式

* feat(backend): 优化 Fail2ban 初始化配置以支持 Alpine 系统

- 增加对 Alpine 系统的特殊配置支持
- 改进防火墙类型检测逻辑,支持多种防火墙服务
- 增加 SSH 端口和认证日志路径的自动检测
- 优化配置文件模板,提高兼容性和安全性

* refactor(backend): 重构 SSH 日志解析功能

- 改进了对不同日志格式的支持,包括 secure, auth 和 messages 文件
- 优化了日志解析逻辑,提高了代码的可读性和可维护性
- 增加了对 RFC3339 时间格式的支持
- 改善了对失败登录尝试的解析,包括无效用户和连接关闭的情况
- 重构了日期解析和 IP 地址验证的逻辑

* refactor(upgrade): 优化升级服务中的初始化脚本选择逻辑

- 新增 selectInitScript 函数,根据系统初始化管理器类型选择合适的初始化脚本
- 支持 systemd、openrc 和 sysvinit 三种初始化管理器
- 对于 sysvinit,增加对 /etc/rc.common 文件存在性的判断,以区分不同的初始化脚本
- 默认情况下使用当前服务名称作为初始化脚本名称

* fix(upgrade): 修复升级时初始化脚本更新问题

- 修改了 criticalUpdates 数组中的服务脚本更新逻辑
- 在 selectInitScript 函数中增加了复制脚本文件的逻辑,以应对服务名和脚本名不一致的情况

* feat(snap): 添加初始化脚本到快照

- 在创建快照时,将服务脚本复制到 initscript 目录
- 然后将整个 initscript 目录复制到快照的目标目录
- 添加了日志输出,便于调试和记录

* refactor(backend): 重构快照恢复流程

- 移除了未使用的 import 语句
- 删除了注释掉的代码块
- 修改了 1Panel 服务恢复的逻辑,增加了对当前服务名称的获取
- 快照恢复过程中,根据宿主机类型,自动选择初始化服务脚本
- 添加了日志输出以提高可追踪性
- 优化了文件路径的处理方式

---------

Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
2025-04-23 17:04:08 +08:00
ChengPlay 5bcbf32823 Fix issue with errors when reverse proxy source text includes comments. (#8444)
Refs https://github.com/1Panel-dev/1Panel/issues/8442
2025-04-22 07:51:06 +00:00
maninhill 0aa90e1b2d chore: Update README.md 2025-04-19 09:59:23 +08:00
maninhill a7b87211b4 chore: Update README.md 2025-04-19 09:53:46 +08:00
maninhill 2c666933a5 chore: Update README.md 2025-04-19 09:43:31 +08:00
maninhill f280b533c6 Update README.md 2025-04-19 09:42:05 +08:00
wanghe-fit2cloud 47a16bc9a9 chore(build): optimize build script 2025-04-18 14:48:50 +08:00
ChengPlay 713202b6eb feat: update golang.org/x/net version (#8412) 2025-04-17 03:02:20 +00:00
巴山夜语andgcsong023 79020abb1c feat(systemctl): implement service manager initialization and command execution (#8380)
* feat(systemctl): 实现服务管理器初始化和命令执行

- 新增 systemctl 包,实现对 systemd、openrc 和 sysvinit 三种服务管理器的支持
- 添加服务状态检查、启动、停止、重启和启用/禁用功能
- 实现服务发现和智能服务名处理
- 添加配置文件查看功能
- 优化错误处理和日志记录

* refactor(system): 重构系统服务管理逻辑

- 引入 systemctl 工具包以统一处理系统服务
- 优化服务状态获取、配置文件路径解析等逻辑
- 重构 HostToolService 中的 GetToolStatus 方法
- 更新 DockerService、SettingService 等相关服务的处理方式
- 调整快照创建和恢复过程中的服务处理逻辑

* feat(utils): 添加目录复制功能并优化文件复制逻辑

- 新增 CopyDirs 函数,用于复制整个目录及其内容
- 添加对符号链接的复制支持
- 实现通用的 Copy 函数,根据文件类型自动选择 CopyFile 或 CopyDirs
- 在 CopyFile 函数中增加对源文件是目录的检查和错误提示

* refactortoolbox: 重构 Fail2ban 和 Pure-FTPd 的管理逻辑

- 优化了 Fail2ban 和 Pure-FTPd 的启动、停止、重启等操作的实现
- 改进了 Fail2ban 版本信息的获取方法
- 统一了错误处理和日志记录的格式
- 调整了部分导入的包,提高了代码的可维护性

* build: 禁用 CGO 以提高构建性能和兼容性

- 在 Linux 后端构建命令中添加 CGO_ENABLED=0 环境变量
- 此修改可以提高构建速度,并确保生成的二进制文件在没有 C 库依赖的环境中也能运行

* refactor(docker): 重构 Docker 服务的重启和操作逻辑

- 添加 isDockerSnapInstalled 函数来判断 Docker 是否通过 Snap 安装
- 在 OperateDocker 和 restartDocker 函数中增加对 Snap 安装的处理
- 移除未使用的 getDockerRestartCommand 函数

* fix(service): 优化快照恢复后的服务重启逻辑

- 在使用 systemd 管理服务时,增加 daemon-reload 操作以确保服务配置更新
- 重启 1panel 服务,以应用快照恢复的更改

* refactor(server): 支持非 systemd 系统的恢复操作

- 增加 isSystemd 函数判断系统是否为 systemd 类型
- 根据系统类型选择性地恢复服务文件
- 兼容 systemd 和非 systemd 系统的恢复流程

* fix(upgrade): 优化升级过程中的服务重启逻辑

- 移动服务重启逻辑到版本号更新之后,修复因提前重启导致的版本号未更新BUG。
- 在 systemctl 重启之前添加 daemon-reload 命令

---------

Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
2025-04-17 10:26:13 +08:00
chiichen 83ef41cf1a feat: basic support riscv64 (#8369)
* feat: add riscv64

* feat: correct arch related codes
2025-04-17 10:24:04 +08:00
dependabot[bot] 91675c69e9 chore(deps): bump golang.org/x/crypto from 0.32.0 to 0.35.0 (#8400)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.32.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.35.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 11:41:42 +08:00
ChengPlay 0e68065b40 feat: change commandPlaceHolder translate (#8391) 2025-04-14 08:56:12 +00:00
ssongliu 600b769083 fix: Fix the mysql root account permission switchover exception (#8378) 2025-04-11 07:08:02 +00:00
zhengkunwang e7670f96de feat: update mcp proxy_pass (#8374) 2025-04-10 16:45:24 +08:00
zhengkunwang 342c04b30a fix: fix issue with mcp bind website (#8356) 2025-04-09 03:55:56 +00:00
zhengkunwang 5ab8377ac1 feat: update allowPort check (#8355) 2025-04-09 02:47:54 +00:00
ssongliu 74eb2c8dd6 fix: Fix the problem that jumping to a directory that does not exist … (#8353) 2025-04-08 14:33:54 +00:00
zhengkunwang f136d97941 change mcp helper (#8352) 2025-04-08 10:59:53 +00:00
ssongliu a5629e3f42 fix: Fix the problem of failed MySQL permission modification (#8351) 2025-04-08 10:49:53 +00:00
zhengkunwang 515d6ca500 fix: fix bug with mcp-server status error (#8349) 2025-04-08 09:21:53 +00:00
zhengkunwang c0ccc0318d feat: add warn text for mcp server (#8346) 2025-04-08 05:55:52 +00:00
zhengkunwang 8196c11010 feat: import mcp server support env (#8344) 2025-04-08 03:11:52 +00:00
ssongliu 170bd22062 fix: Modify database verification rules (#8342) 2025-04-08 02:39:52 +00:00
zhengkunwang 097a33c68f style: update style for mcp server (#8338) 2025-04-07 11:09:51 +00:00
zhengkunwang 3be3bf9926 feat: remove env from mcp config (#8337) 2025-04-07 10:03:50 +00:00
zhengkunwang 038f2b4f54 feat: change mcp list style (#8336) 2025-04-07 09:35:50 +00:00
zhengkunwang f84e284c20 feat: change DNSTypes order (#8335) 2025-04-07 09:01:50 +00:00
ssongliu 48e75b5916 fix: Set bridge as the default network for container creation. (#8332) 2025-04-07 07:47:49 +00:00
zhengkunwang a12ceaee6e feat: add config for mcp server (#8331) 2025-04-07 07:41:51 +00:00
zhengkunwang 9050680115 feat: update mcp form label (#8327) 2025-04-07 02:49:49 +00:00
zhengkunwang 0022a9b508 fix: fix issue with install app no label (#8326) 2025-04-07 02:47:49 +00:00
zhengkunwang 3437d62f31 feat: Website SSL DNS Type support WestCN (#8305) 2025-04-02 10:21:54 +00:00
ssongliu dc880835d0 fix: Modify the container terminal connection mode (#8301) 2025-04-02 10:19:54 +00:00
ssongliu 1663bf8677 fix: Regenerates the interface document (#8298) 2025-04-02 09:07:54 +00:00
ssongliu aed236cbc9 fix: Ignore the scheduled task backup content failed to be uploaded (#8297)
Refs #7869
2025-04-02 09:05:54 +00:00
ssongliu 90be74504e feat: Support the retention of remote backup files when deleting sche… (#8296)
Refs #8236
2025-04-02 09:03:55 +00:00
zhengkunwang bba8aab18c feat: Add MCP management (#8299) 2025-04-02 09:01:54 +00:00
ssongliu 47d135ecca fix: Optimize database password verification and password change (#8294) 2025-04-02 16:03:44 +08:00
ssongliu d96fe49993 fix: Resolve cloudns reference error (#8293) 2025-04-02 03:41:53 +00:00
ssongliu ac396b61ce fix: Modify the panel password verification rule (#8291)
Refs #4290
2025-04-02 03:39:54 +00:00
ssongliu 6798d0c7ee fix: Fixed an update failure when the container network is bridge (#8290)
Refs #8209
2025-04-02 03:37:53 +00:00
亖 1490ed7541 dev:修复注释路由前缀 (#8292) 2025-04-02 10:10:00 +08:00
白铭骢 (Mingcong Bai) 48b3032de6 chore(deps): update modernc.org/sqlite to v1.29.7 (#8260)
With modernc.org/sqlite@v1.29.1, 1Panel fails to build on LoongArch with
the following error:

../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/defs.go:13:35: undefined: sqlite3_index_constraint
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/defs.go:14:35: undefined: sqlite3_index_orderby
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/defs.go:15:35: undefined: sqlite3_index_constraint_usage
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:19:5: undefined: Xsqlite3_threadsafe
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:29:11: undefined: Xsqlite3_config
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:29:32: undefined: SQLITE_CONFIG_MUTEX
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:29:122: undefined: SQLITE_OK
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:30:8: undefined: Xsqlite3_errstr
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:40:17: undefined: Sqlite3_mutex_methods
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:239:46: undefined: SQLITE_OK
../../go/pkg/mod/modernc.org/sqlite@v1.29.1/lib/mutex.go:239:46: too many errors

Upstream introduced LoongArch (loong64) target support in
modernc.org/sqlite@v1.29.7. Take a conservative approach and bump
modernc.org/sqlite to v1.29.7, this will allow for 1Panel to build on
LoongArch (both frontend and backend).

Signed-off-by: Mingcong Bai <jeffbai@aosc.io>
2025-03-27 13:45:10 +08:00
sunsky ed12bec460 feat: add DNS provider ClouDNS (#8207)
* feat: add DNS provider ClouDNS

* feat: ClouDNS account parameter validation
2025-03-25 10:12:14 +08:00
dependabot[bot] 3c4a1ea614 chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 (#8229)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.1 to 4.5.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.1...v4.5.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 17:38:30 +08:00
ssongliu 79eb0288b4 fix: Fix the problem that the terminal exit fails (#8189) 2025-03-19 03:43:21 +00:00
蘭 99775e1eb4 fix: Fix some issues (#8178) 2025-03-18 17:16:23 +08:00
zhengkunwang db848b41d2 feat: update buildx and containerd version (#8183) 2025-03-18 17:13:36 +08:00
david082321 b5b11dff86 Update tw.ts (#8161) 2025-03-17 10:58:19 +08:00
zhengkunwang 9cf4bfc606 feat: update golang.org/x/net version to v0.37.0 (#8146) 2025-03-13 18:30:25 +08:00
ThinkStu 221162d44f Update zh.ts (#8136)
correct typos
2025-03-12 22:43:20 +08:00
蘭 617970a1f8 fix: license setting data (#8132) 2025-03-12 16:25:32 +08:00
ssongliu 5610c73d67 fix: Fix the problem that services exit unexpectedly when the contain… (#8127) 2025-03-12 03:19:03 +00:00
ssongliu 51fe02661f fix: Fix the problem of incorrect calculation of container memory res… (#8125)
Refs #8033
2025-03-12 02:13:03 +00:00
lm379andlm379 8f491b2933 feat: allow tencent cloud cos dual stack domain (#8123)
Co-authored-by: lm379 <lm379@qq.com>
2025-03-11 23:58:23 +08:00
王贺 b7e16bd6d5 Update .goreleaser.yaml (#8118) 2025-03-11 13:50:48 +08:00
ssongliu 38ca2ddd01 fix(container): Fix cpu surge when the container terminal executes exit (#8115) 2025-03-10 23:47:50 +08:00
ssongliu 402b41100b fix(container): Fix cpu surge when the container terminal executes exit (#8096)
Refs #7574
2025-03-07 11:06:59 +00:00
ssongliu 766beae4e1 fix(container): Fix the problem that a container cannot create a read… (#8095)
Refs #8074
2025-03-07 07:44:59 +00:00
maninhill 05f72e9dcf chore: Update README.md 2025-03-06 11:40:56 +08:00
igophper 23553c68ae fix(fail2ban): fix the issue of set fail2ban log path err (#8076)
Refs #8075
2025-03-06 09:44:48 +08:00
zhengkunwang 4dd1eb7a1e feat: Modify Swagger route response for unauthenticated access (#8057) 2025-03-04 05:48:57 +00:00
蘭 db307732c6 feat: Adjust license (#8056) 2025-03-03 14:32:56 +00:00
igophperandssongliu 33d4ade1d4 refactor(container): optimize memory calculation and concurrent data handling (#8038)
* refactor(container): optimize memory calculation and concurrent data handling

Refs #8033

* Update container.go

---------

Co-authored-by: ssongliu <73214554+ssongliu@users.noreply.github.com>
2025-03-03 17:38:32 +08:00
igophper 186075ff9a style: optimize code editor file path style (#8039)
Refs #8015
2025-03-03 10:23:24 +08:00
zhengkunwang 34df745d1c style: Change wget page style (#8016) 2025-02-26 10:16:54 +00:00
zhengkunwang ec363ac157 feat: update default app.yaml (#8011) 2025-02-26 15:35:54 +08:00
蘭 193b0f3cd4 fix: Update swagger docs (#8002)
Refs #8001
2025-02-26 10:55:40 +08:00
zhengkunwang 67c1bf2924 feat: Add certificate validation when creating AI Proxy (#7994) 2025-02-25 15:21:16 +08:00
ssongliu b1373ff77e feat: Support gpu of schema v11 (#7988) 2025-02-25 14:16:34 +08:00
dependabot[bot] 55d0e27212 chore(deps): bump github.com/go-jose/go-jose/v4 from 4.0.4 to 4.0.5
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.4...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-25 09:34:14 +08:00
zhengkunwang 1c052b7c13 fix: Fix the issue with AI proxy certificate res error (#7983) 2025-02-24 22:24:51 +08:00
蘭 6c8d48c2dc feat: API interface IP whitelist supports IPv6 (#7981)
Refs #7836
2025-02-24 10:05:13 +00:00
ssongliu b61dae638b fix(forward): Fix the problem of duplicate creation of port forwardin… (#7975)
Refs #7969
2025-02-24 09:21:14 +00:00
zhengkunwang 872a93aa07 fix: Fix the issue with the homepage route refresh (#7974) 2025-02-24 15:00:20 +08:00
igophper b1f337020c fix(container/log): enable close-on-click-modal and wrap log update in nextTick (#7960)
Refs #7547
2025-02-24 13:34:51 +08:00
maninhill 56858dd74c chore: Update README.md 2025-02-24 10:54:05 +08:00
Johannes Plunien 358415f737 feat: Support freemyip.com DNS provider (#7958) 2025-02-24 10:21:37 +08:00
david082321 5e73260103 feat: Update zh-Hant translation (#7966) 2025-02-22 23:16:38 +08:00
zhengkunwang e169f46784 fix: Fixed the issue of no echo after binding the SSL to the AI. (#7953) 2025-02-21 09:29:51 +00:00
ssongliu 39416abd47 fix: Fix the issue of inconsistent deletion status for the ollama model (#7950)
Refs #7948
2025-02-21 08:51:51 +00:00
wanghe-fit2cloud 640a0ca815 chore: Optimize some command outputs 2025-02-21 16:45:56 +08:00
ssongliu 8e54cd5162 style: Adjust the width display of the model list (#7946) 2025-02-21 07:54:29 +00:00
ssongliu 0f0099accb fix: Fix the issue of loading errors for editing container port data (#7945)
Refs #7938
2025-02-21 04:01:49 +00:00
ssongliu 718ebeb203 fix: Modify help information about Ollama pull (#7935) 2025-02-20 15:21:50 +08:00
ssongliu 4101bf691d style: Fix the issue of inconsistent menu spacing. (#7932) 2025-02-20 13:45:48 +08:00
蘭 a97deb3dca feat: API timestamp verification (#7927)
* feat: API timestamp verification

* feat: API timestamp verification
2025-02-20 13:29:26 +08:00
zhengkunwang 10203d5c0c fix: Resolve the issue of local application label loss (#7926) 2025-02-20 13:29:04 +08:00
maninhill a357d6697d chore: Update README.md 2025-02-19 22:58:06 +08:00
ssongliu 555b0aa999 fix: Optimize status display. (#7920) 2025-02-19 22:54:34 +08:00
蘭 6dd0dbe2c3 feat: Improve international language (#7918) 2025-02-19 13:35:39 +00:00
zhengkunwang c31bbae768 feat: Optimized the display when no certificate is present. (#7917) 2025-02-19 11:03:39 +00:00
ssongliu 2bb0f15973 fix: Fix the loading method of slow logs in MySQL and MariaDB (#7915)
Refs #6353
2025-02-19 09:48:36 +00:00
zhengkunwang be229551e3 feat: AI tool Added quick jump functionality (#7913) 2025-02-19 08:26:31 +00:00
蘭 6283eb4c69 feat: Add language translation and icons (#7912) 2025-02-19 08:14:32 +00:00
ssongliu c329fb6599 feat: Support for terminal operation with the Ollama model (#7907) 2025-02-19 14:07:05 +08:00
zhengkunwang223 c3685f6b74 feat: Adjusted the position of the quick jump button. 2025-02-18 20:25:31 +08:00
maninhill f4f5a36475 chore: Update README.md 2025-02-18 20:25:04 +08:00
zhengkunwang ea1261b48e feat: Added partial prompt messages (#7900) 2025-02-18 10:08:52 +00:00
wanghe-fit2cloud 0c68f16a66 feat: Modify some translations 2025-02-18 17:09:13 +08:00
ssongliu 33e5c9230f fix: Optimized ollama model status display (#7899) 2025-02-18 17:06:20 +08:00
zhengkunwang 96eefab36f feat: Added underscore support for website default documents (#7898) 2025-02-18 17:06:10 +08:00
zhengkunwang b0b6bf458f feat: Added display of ollama proxy address (#7897) 2025-02-18 15:00:33 +08:00
zhengkunwang f3d85d1309 fix: Fixed the issue where local application descriptions were not di… (#7896)
Refs https://github.com/1Panel-dev/1Panel/issues/7886
2025-02-18 06:17:11 +00:00
ssongliu 39385ea0a0 fix: Adjust the synchronization method for the Ollama model (#7895) 2025-02-18 06:09:10 +00:00
zhengkunwang e492c6573c feat: Modified the logic for AI Tool (#7894) 2025-02-18 05:59:10 +00:00
wanghe-fit2cloud 10a7fb3be2 feat: Update the official website URL for the international version 2025-02-18 10:31:38 +08:00
王贺 8bc7cfade3 Update README.md (#7891) 2025-02-17 22:26:29 +08:00
ssongliu ea044d6c32 fix: change the AI routing interface (#7887) 2025-02-17 15:33:57 +08:00
zhengkunwang f6ee3dc75b feat: Added streaming transmission configuration (#7875) 2025-02-15 12:59:38 +00:00
ssongliu 367a74503f fix: Fix the problem of duplicate content display in the model list (#7872) 2025-02-14 09:10:05 +00:00
蘭 53b1a7af4c feat: Copy and move files with the same name file processing (#7871) 2025-02-14 06:28:06 +00:00
zhengkunwang 078d7fbe1e feat: Add AI-enhanced configuration (#7870) 2025-02-14 06:26:05 +00:00
ssongliu c709e48ceb feat: support load ollama model details (#7867) 2025-02-13 14:56:24 +00:00
endymx 0741f60896 fix(iptables): 复杂环境下转发删除端口错误 (#7868) 2025-02-13 22:56:09 +08:00
ssongliu af8eef4a91 feat: Support Ollama model management (#7866) 2025-02-13 15:28:29 +08:00
zhengkunwang f233ef7069 feat: Adding GPU acceleration configuration to some applications (#7865) 2025-02-13 07:02:08 +00:00
蘭 1c199bd3d7 style: Optimize app store style and layout (#7858)
Refs #7846
2025-02-12 21:56:52 +08:00
ssongliu 036c973fec fix: Fix the security entry verification error issue. (#7854)
Refs #7853
2025-02-11 10:13:49 +00:00
蘭 8bb225d272 feat: Check for duplicate files before uploading (#7849)
Refs #5893
2025-02-11 10:11:48 +00:00
zhengkunwang ab932ae154 fix: Fixed the issue where application labels were not displayed after updating. (#7834) 2025-02-10 11:17:24 +08:00
david082321 89f969ec6e feat: Update zh-Hant translation (#7830) 2025-02-09 22:01:02 +08:00
ssongliu afa8cf22b1 feat: Add function backups by default for MySQL and MariaDB databases. (#7823) 2025-02-08 22:56:46 +08:00
ssongliu 67aa8471dc fix: Remove some comments. (#7822) 2025-02-08 08:41:27 +00:00
zhengkunwang 0617b81b3b feat: The appstore has added support for the Korean language (#7818) 2025-02-07 13:07:20 +00:00
ssongliu 98c535a12c fix: improve the backend verification for security entry settings (#7810)
Refs #7657
2025-02-06 10:15:06 +00:00
ssongliu ec46518936 feat: Improve the name of the professional version (#7807) 2025-02-06 10:13:03 +00:00
ssongliu a7b034387f fix: fix some internationalization formats (#7803) 2025-02-05 14:41:59 +00:00
endymx 7905dd00a6 fix(firewall): 删除复杂转发规则时报错 (#7791) 2025-02-05 18:37:42 +08:00
zhengkunwang 74583aa1aa feat: fix anti-leech is not work on proxy website (#7801)
Refs https://github.com/1Panel-dev/1Panel/issues/7752
2025-02-05 10:07:16 +00:00
dependabot[bot] 4be9d71edf chore(deps): bump github.com/golang/glog from 1.2.2 to 1.2.4 (#7787) 2025-01-31 13:08:01 +08:00
zhengkunwang 57bfc38eb8 feat: Application details have been enhanced with tags. (#7777)
Refs https://github.com/1Panel-dev/1Panel/issues/7774
2025-01-27 11:04:26 +00:00
zhengkunwang aaaa5980b5 feat: Added password encryption for login functionality (#7764) 2025-01-23 18:09:12 +08:00
蘭 4f57dfc76e feat: Unify the response body of the API interface (#7761) 2025-01-23 08:28:04 +00:00
wanghe-fit2cloud 2c31ffc66b feat: Enhance app redirection messages 2025-01-23 15:32:11 +08:00
王贺 b104a13b71 Update README.md (#7747) 2025-01-20 22:48:29 +08:00
maninhill 28633d2fef chore: Update README.md 2025-01-20 22:33:20 +08:00
蘭 9052386c83 feat: Korean language support (#7727)
Refs #7363
2025-01-15 10:26:30 +00:00
maninhill 7bc45f2d77 chore: Update README.md 2025-01-15 15:24:20 +08:00
wanghe-fit2cloud 2ef6ecb951 chore: Update the README.md 2025-01-15 10:40:22 +08:00
zhengkunwang ca0dc71338 feat: Enable Multi-Language Support for Application Installation Forms (#7717) 2025-01-14 19:03:22 +08:00
zhengkunwang 04a1ec9a9a feat: Add Multi-Language Support for Application Labels (#7714) 2025-01-14 16:00:09 +08:00
蘭 999e5a779a feat: Update swagger docs (#7710) 2025-01-13 15:10:04 +00:00
zhengkunwang 99f62b7230 fix(website):Fix Reverse Proxy Deletion Failure in Certain Scenarios (#7709)
Refs https://github.com/1Panel-dev/1Panel/issues/7701
2025-01-13 15:08:04 +00:00
zhengkunwang 4dee4e1cc2 feat: Update Application Name Restrictions (#7706) 2025-01-13 07:56:03 +00:00
maninhill 539addc656 chore: Update README.md 2025-01-10 23:07:15 +08:00
BugKing 1a35b307fa docs: add introduction of 1Panel Pro in README (#7699) 2025-01-10 22:32:02 +08:00
蘭 a04f4a8b96 feat: Improve international language and style (#7697)
Refs #7696
2025-01-10 10:57:45 +00:00
ssongliu 6b88e1adf2 fix: Remove redundant code (#7694) 2025-01-10 15:06:18 +08:00
蘭 99bc8177d4 feat: Save custom theme color scheme (#7690) 2025-01-10 04:13:44 +00:00
ssongliu cf2a1372f2 style: Optimize some internationalization styles (#7689) 2025-01-10 04:11:44 +00:00
蘭 42c30f324a feat: Improve international language (#7687) 2025-01-10 02:21:44 +00:00
蘭 dab83fee20 feat: Modify international version functions (#7686) 2025-01-10 02:19:44 +00:00
蘭 6f3c5586fc feat: Update international address (#7685) 2025-01-10 02:17:43 +00:00
ssongliu aab8df6abc fix: Fix the issue of database cover exception display. (#7682) 2025-01-09 13:59:41 +00:00
蘭 b9be79b05a feat: Menu enables vue-router mode (#7681) 2025-01-09 12:15:42 +00:00
zhengkunwang ec3024f2c3 feat: Change AppDto to AppItem (#7680) 2025-01-09 12:13:41 +00:00
ssongliu 070f61e229 fix: Fix the issue of pagination errors in the backup list (#7673) 2025-01-09 03:07:40 +00:00
蘭 a82a4ebfc5 style: Optimize style and layout (#7671) 2025-01-08 10:19:37 +00:00
蘭 87009ddb27 feat: Modify the copyright (#7670) 2025-01-08 07:39:37 +00:00
蘭 3650ff27b3 feat: Dealing with Sass version compatibility issues and removing debugger from build (#7669) 2025-01-08 07:21:38 +00:00
ssongliu 6c08a3114a fix: Fix the issue of interface returning internationalization failure (#7668) 2025-01-08 07:19:37 +00:00
蘭 381cb61185 feat: Internationalization language order (#7666) 2025-01-08 03:33:37 +00:00
蘭 8d69da7702 feat: Break long menu items into multiple lines (#7665) 2025-01-08 03:31:36 +00:00
ssongliu 8922395fdc fix: Fix the issue of frontend packaging errors (#7661) 2025-01-07 06:47:35 +00:00
ssongliu 83db40e261 feat: Support for asynchronously obtaining the backup file size (#7660) 2025-01-07 06:35:33 +00:00
zhengkunwang 95ec6c62ef feat(system): Fix Issue Allowing Access via Old Secure Entry After Mo… (#7659)
Refs https://github.com/1Panel-dev/1Panel/issues/7658
2025-01-07 06:09:32 +00:00
蘭 56fc9ea428 feat: Improve international language proficiency (#7656) 2025-01-07 04:19:33 +00:00
蘭 88c6fc050b feat: Upgrade package version (#7655) 2025-01-07 04:03:33 +00:00
蘭 2392f91567 feat: Panel aliases support common special characters (#7654)
Refs #6909
2025-01-07 04:01:33 +00:00
蘭 5f0c740eb0 feat: Improve and format language files (#7645) 2025-01-06 05:48:21 +00:00
Lucas Pedro 120d0dd7c6 Traducao pt br (#7624) 2025-01-06 10:57:34 +08:00
liuchang_cloud 2844f6db7f feat: Support Japanese by Google Translate (#7437) 2025-01-06 10:39:49 +08:00
王贺 570074f92d Update README.md 2025-01-04 10:56:43 +08:00
Anye a13047c855 Update Default PHP Extensions (#7637) 2025-01-04 10:44:41 +08:00
蘭 ca0d24bf56 feat: Improve and format language files (#7634) 2025-01-03 08:52:09 +00:00
Pisyek K. 2b18251360 chore: add bahasa melayu translation (#7623)
* chore: add bahasa melayu translation
2025-01-03 11:42:13 +08:00
蘭 f7c875acda feat: Save the theme color scheme to the client (#7626)
Refs #7074
2025-01-03 02:30:07 +00:00
ssongliu a291a50c9f feat: Support for license import in the international version (#7625) 2025-01-02 15:00:05 +00:00
蘭 5bc7bc6aac fix: Fix missing response types in Swagger API endpoints (#7611)
Refs #7607
2024-12-31 09:57:55 +00:00
zhengkunwang 9c376300ff feat(ssl): Add Support for Rain Yun in DNS Account (#7613)
Refs https://github.com/1Panel-dev/1Panel/issues/7115
2024-12-31 09:55:54 +00:00
Ryan Wang 5187b20252 fix: correct Swagger API definitions (#7606)
Signed-off-by: Ryan Wang <i@ryanc.cc>
2024-12-31 10:32:47 +08:00
ssongliu fc401ffd8f fix: Optimize image deletion error return (#7602)
Refs #7068
2024-12-30 09:17:49 +00:00
zhengkunwang 667ee5256e fix: Fix Application Startup Failure Caused by Case-Sensitive Applica… (#7601)
Refs https://github.com/1Panel-dev/1Panel/issues/7600
2024-12-30 09:15:49 +00:00
wanghe-fit2cloud f8356de93c Modify the copyright 2024-12-30 16:38:54 +08:00
endymx 55bb997d51 feat(iptables): Support forwarding for complex networks (#7598)
* feat(iptables): 支持对复杂网络的转发

* refactor(iptables): 提供更完善的接口和报错
2024-12-30 13:55:53 +08:00
ssongliu 7c46f54e15 fix: Fix the issue of failure to retrieve firewall status (#7597)
Refs #7527
2024-12-30 05:37:49 +00:00
ssongliu 6dbb6d2247 fix: Fix the issue of backup failure due to complex passwords in the pg (#7596) 2024-12-30 13:36:51 +08:00
ssongliu 9d0ae45edc feat: Optimize the display of scheduled task execution cycles (#7595) 2024-12-30 05:35:49 +00:00
蘭 b467dfa3b2 feat: Add expiration time setting for API key (#7584) 2024-12-30 05:33:49 +00:00
蘭 c13387bc70 style: Optimize the display of status cards on the overview page (#7562)
* style: Optimize the display of status cards on the overview page
2024-12-27 14:01:02 +08:00
BugKing 41b2523750 docs: replace Discord link in readme (#7573) 2024-12-26 18:32:50 +08:00
zhengkunwang afe82bd4be feat: Remove Default Configuration for Reverse Proxy Cache (#7566) 2024-12-26 10:16:07 +08:00
JB-SelfCompany 0f296fb70b Update README for Russian language (#7560) 2024-12-25 16:37:06 +08:00
JB-SelfCompanyandBubblegum d96ccd06a3 Russian language support. (#7554)
* Add Russian language

* Use prettier

---------

Co-authored-by: Bubblegum <jack_benq@selfhosted.lan>
2024-12-25 09:34:50 +08:00
BugKing 14095affa9 docs: add discord badge in readme (#7544) 2024-12-24 14:03:05 +08:00
Anye ed60565bc9 Update cache.conf (#7516)
Remove `proxy_cache ` from the global config to prevent it from being enabled for sites that don't need caching.
2024-12-23 11:21:23 +08:00
david082321 8313912c4f fix: Update zh-Hant translation (#7515) 2024-12-21 20:34:24 +08:00
wolf 6274015d06 fix: Optimize some translations (#7495) 2024-12-20 18:45:45 +08:00
蘭 d195ce9155 feat: Login to access Swagger documentation (#7496) 2024-12-20 18:37:59 +08:00
Flyum cd0cd8a152 fix: Modify some English translations (#7494) 2024-12-20 18:06:44 +08:00
liuboF2candliubo 1f45ed6b06 fix: Correct English internationalization translations (#7493)
Co-authored-by: liubo <liubo@fit2cloud.com>
2024-12-20 18:05:58 +08:00
zhengkunwang 7f6ad89edf feat: Optimize some translations (#7492) 2024-12-20 17:48:52 +08:00
liuboF2candliubo 5cdea288e2 fix: 修正英文国际化翻译 (#7491)
Co-authored-by: liubo <liubo@fit2cloud.com>
2024-12-20 17:29:43 +08:00
wanghe-fit2cloud e56feaf826 docs: Optimize README 2024-12-20 16:35:23 +08:00
zhengkunwang 1288a09647 fix: Optimize some translations (#7490) 2024-12-20 16:33:35 +08:00
wanghe-fit2cloud 0cdaafa1c0 docs: Optimize README 2024-12-20 16:21:23 +08:00
Frist soulful 0b3334253c Update zh-Hant.yaml (#7489) 2024-12-20 16:20:31 +08:00
Yuki Guo 10f72f12d2 docs: Update tw.ts (#7487) 2024-12-20 16:08:07 +08:00
feng626 01643b2816 perf: Optimize FTP sync function for better performance and readability (#7479) 2024-12-20 15:45:05 +08:00
zrfit 56e0da4fe3 docs: Update README.tr.md (#7486) 2024-12-20 15:41:27 +08:00
song-cc-rock 144c397bf4 fix: update zh-hant spell 2024-12-20 15:34:57 +08:00
ZhaoJiSen e06541984f Add ImportMeta type definition (#7481) 2024-12-20 15:32:58 +08:00
zhengkunwang ea7e92db84 feat: Remove Redundant Code (#7484) 2024-12-20 07:32:25 +00:00
wxg 8d48640c1d fix: Update zh-Hant.yaml (#7483) 2024-12-20 15:31:25 +08:00
zrfit cfe5cb4b6b Add File README.tr.md (#7480) 2024-12-20 15:25:53 +08:00
9527 05c4d7dd38 docs: Add German README (#7482) 2024-12-20 15:20:45 +08:00
wangyicheng 13c6e0df99 docs: Update README.es.md (#7477) 2024-12-20 15:11:10 +08:00
MignonZhang dafd6ba71e fix: fix typos (#7478) 2024-12-20 15:09:26 +08:00
莫鹍 2751c37716 refactor(i18n): fix spelling error (#7476) 2024-12-20 15:08:22 +08:00
蘭 50c01762e7 feat: Open international professional features (#7472) 2024-12-20 07:04:26 +00:00
w940853815 a77ebd3eb6 fix: Typo in hostname save function and button click (#7475) 2024-12-20 15:02:30 +08:00
gerry-f2c d84f0b5597 style: Optimize the backup account style code (#7474) 2024-12-20 15:00:14 +08:00
Slooop a3cdf87ea2 style: Optimize the backup account style code (#7473) 2024-12-20 14:59:57 +08:00
Chenyang Shen 8ab3c16b3d Update zh-Hant.yaml 2024-12-20 14:54:24 +08:00
Shenguobin0102 18e943fd06 fix: Modified the RTL format to right-align the first sentence of the body (#7470) 2024-12-20 14:52:53 +08:00
zyyfit 3b7a067dda Create README.fr.md (#7469) 2024-12-20 14:24:09 +08:00
ZhaoQian1222 4636ba815b fix: Corrected some internationalization translations (#7468) 2024-12-20 14:22:30 +08:00
Shenguobin0102 317fad2a28 docs: Update README.ar.md (#7467) 2024-12-20 14:18:31 +08:00
MatataYou 0ae28a1dd9 Update README.es.md (#7466) 2024-12-20 14:05:28 +08:00
Anye bbfe5f1d96 style: Modify some styles to improve the appearance of the version button (#7464) 2024-12-20 13:59:02 +08:00
zhengkunwang b713f20b66 style: Optimize OpenResty Warnings Displayed During Zooming (#7465)
Refs https://github.com/1Panel-dev/1Panel/issues/7452
2024-12-20 05:50:25 +00:00
Shenguobin0102 3eb7a5adc5 docs: Add Arabic README (#7463) 2024-12-20 13:49:23 +08:00
MatataYou 956af78749 docs: Add Spanish Language README (#7461) 2024-12-20 13:43:55 +08:00
zhengkunwang 69ad01687e feat: Optimize Location Information Display in System Login Logs (#7459) 2024-12-20 04:16:25 +00:00
Anye e83d07e88e style: Modify some styles to improve the appearance of the version button (#7462) 2024-12-20 12:03:49 +08:00
zhengkunwang 6683696032 fix(system): Fix Cloudflare Proxy Access Issue with 1Panel When Secure Entry Is Enabled (#7458) 2024-12-20 11:03:49 +08:00
zhengkunwang 3396a5ffea go: upgraded golang.org/x/crypto v0.28.0 => v0.31.0 (#7456) 2024-12-20 02:26:24 +00:00
zhengkunwang 18a14d3b75 go: upgraded golang.org/x/net v0.30.0 => v0.33.0 (#7455) 2024-12-20 02:14:23 +00:00
王贺 2b13352004 chore: Add Indonesian language (#7445) 2024-12-19 23:20:48 +08:00
maninhill 94e5d7e642 chore: Update README.md 2024-12-19 22:42:35 +08:00
maninhill 001b4dbada docs: Create README.id.md 2024-12-19 22:41:22 +08:00
BugKing 1192dec689 docs: Add Product Hunt badge (#7434) 2024-12-19 11:55:18 +08:00
蘭 956d47f9da refactor: Demo server, prohibit operation (#7433) 2024-12-19 02:06:19 +00:00
John Niang 063a071228 Fix invalid postion of before content in home page in Firefox Linux (#7429) 2024-12-18 18:33:59 +08:00
Ryan Wang c54a7de827 docs: add Korean README documentation (#7427)
Signed-off-by: Ryan Wang <i@ryanc.cc>
2024-12-18 17:15:10 +08:00
老广 0410e48aad docs: Add README.pt-br.md (#7425) 2024-12-18 16:29:44 +08:00
liuchang_cloud a4d95737d3 docs: Update README.md (#7424) 2024-12-18 16:16:14 +08:00
jianneng-fit2cloud bc64d83ab2 style: Container style (#7422) 2024-12-18 15:32:30 +08:00
蘭 87616e388b style: Optimize style (#7414)
Refs #7419
2024-12-18 06:44:14 +00:00
taojinlongandtaojinlong 3f35f90fc2 fix: fix typos (#7413)
Co-authored-by: taojinlong <jinlong@fit2cloud.com>
2024-12-18 14:06:00 +08:00
王嘉豪 c3ba69814c Update fa.yaml (#7411) 2024-12-18 14:05:46 +08:00
wisonic-s 66a517e58d fix: fix typos (#7407) 2024-12-18 14:05:24 +08:00
Bryan 3108c927af fix: typos (#7405) 2024-12-18 14:04:51 +08:00
Junjun 5738cceac9 fix typos (#7404) 2024-12-18 14:04:17 +08:00
dataeaseShu db2f648bce fix: fix typos (#7403) 2024-12-18 14:04:06 +08:00
fit2cloud-chenyw 6012a6cf9f fix: datas should be dataList (#7402) 2024-12-18 14:03:56 +08:00
ulleo a4dcbc90e6 fix: fix typos (#7401) 2024-12-18 14:03:40 +08:00
BugKing 955ecce952 docs: Update README.md (#7416) 2024-12-18 13:34:56 +08:00
maninhill 91fba21ed3 chore: Update README.md 2024-12-18 11:55:20 +08:00
wanghe-fit2cloud 490e2934b8 chore: Optimize the README file 2024-12-18 11:54:06 +08:00
wanghe-fit2cloud cb6d3d4fc3 chore: Optimize the README file 2024-12-18 11:52:05 +08:00
378 changed files with 38009 additions and 7709 deletions
+6 -4
View File
@@ -10,7 +10,7 @@ before:
- ./ci/script.sh
- sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl
- go mod tidy
builds:
- main: ./cmd/server/main.go
binary: 1panel
@@ -31,17 +31,19 @@ builds:
- arm
- ppc64le
- s390x
- riscv64
archives:
- format: tar.gz
- formats: [ 'tar.gz' ]
name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}"
wrap_in_directory: true
files:
- 1pctl
- 1panel.service
- install.sh
- GeoIP.mmdb
- 1panel.service
- initscript/*
- lang/*
- GeoIP.mmdb
checksum:
name_template: 'checksums.txt'
+1 -1
View File
@@ -10,7 +10,7 @@ PR are always welcome, even if they only contain small fixes like typos or a few
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot of features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
This [development guideline](https://1panel.cn/docs/dev_manual/dev_manual/) contains information about repository structure, how to set up development environment, how to run it, and more.
This [development guideline](https://docs.1panel.pro/dev_manual/dev_manual/) contains information about repository structure, how to set up development environment, how to run it, and more.
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
+1 -1
View File
@@ -23,7 +23,7 @@ build_frontend:
build_backend_on_linux:
cd $(SERVER_PATH) \
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
&& CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_darwin:
cd $(SERVER_PATH) \
+57 -40
View File
@@ -1,70 +1,87 @@
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>Top-Rated Web-based Linux Server Management Tool</b></p>
<p align="center"><b>现代化、开源的 Linux 服务器运维管理面板</b></p>
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>Simplify Linux Server Management</b></p>
<p align="center">
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 180px; height: auto;" /></a>
<a href="https://hellogithub.com/repository/71791baf930149ac9b84e1acf186573f" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=71791baf930149ac9b84e1acf186573f&claim_uid=p8vB3kP5CMrthiL&theme=dark&theme=neutral" alt="Featured|HelloGitHub" style="width: 180px; height: auto;" /></a>
<a href="https://market.aliyun.com/products/53690006/cmjj00062740.html?userCode=kmemb8jp" target="_blank"><img src="https://img.alicdn.com/imgextra/i2/O1CN01H5JIwY1rZ0OobDjnJ_!!6000000005644-2-tps-1000-216.png" alt="1Panel-dev%2F1Panel | Aliyun" style="width: 180px; height: auto;" /></a>
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 240px; height: auto;" /></a>
</p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel?color=%231890FF" alt="License: GPL v3"></a>
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="https://discord.gg/bUpUqWqdRr" target="_blank">
<img src="https://img.shields.io/discord/1318846410149335080?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb"
alt="chat on Discord"></a>
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="GitHub Stars"></a>
<a href="https://gitee.com/fit2cloud-feizhiyun/1Panel"><img src="https://gitee.com/fit2cloud-feizhiyun/1Panel/badge/star.svg?theme=gvp" alt="Gitee Stars"></a><br>
[<a href="docs/README_EN.md">English</a>] | [<a href="docs/README_TW.md">中文(繁體)</a>] | [<a href="docs/README_JP.md">日本語</a>]
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a><br>
</p>
<p align="center">
<a href="/README.md"><img alt="English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="/docs/README.zh-Hans.md"><img alt="中文(简体)" src="https://img.shields.io/badge/中文(简体)-d9d9d9"></a>
<a href="/docs/README.ja.md"><img alt="日本語" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="/docs/README.pt-br.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-d9d9d9"></a>
<a href="/docs/README.ar.md"><img alt="العربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="/docs/README.de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-d9d9d9"></a>
<a href="/docs/README.es.md"><img alt="Español" src="https://img.shields.io/badge/Español-d9d9d9"></a><br>
<a href="/docs/README.fr.md"><img alt="français" src="https://img.shields.io/badge/français-d9d9d9"></a>
<a href="/docs/README.ko.md"><img alt="한국어" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="/docs/README.id.md"><img alt="Bahasa Indonesia" src="https://img.shields.io/badge/Bahasa Indonesia-d9d9d9"></a>
<a href="/docs/README.zh-Hant.md"><img alt="中文(繁體)" src="https://img.shields.io/badge/中文(繁體)-d9d9d9"></a>
<a href="/docs/README.tr.md"><img alt="Türkçe" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="/docs/README.ru.md"><img alt="Русский" src="https://img.shields.io/badge/%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9-d9d9d9"></a>
<a href="/docs/README.ms.md"><img alt="Bahasa Melayu" src="https://img.shields.io/badge/Bahasa Melayu-d9d9d9"></a>
</p>
[![Watch the video](https://resource.fit2cloud.com/1panel/img/overview_video.png)](https://www.bilibili.com/video/BV1Mt421n7LZ/)
------------------------------
## 什么是 1Panel?
1Panel 是新一代的 Linux 服务器运维管理面板。
1Panel provides an intuitive web interface and MCP Server to manage websites, files, containers, databases, and LLMs on a Linux server.
- **高效管理**:用户可以通过 Web 界面轻松管理 Linux 服务器,如主机监控、文件管理、数据库管理、容器管理等;
- **快速建站**:深度集成开源建站软件 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等操作一键搞定;
- **应用商店**:精选上架各类高质量的开源工具和应用软件,协助用户轻松安装并升级;
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供病毒防护、防火墙和日志审计等功能;
- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, container management, LLMs management.
- **Rapid Website Deployment**: With deep integration of the popular open-source website building software WordPress, 1Panel streamlines the process of domain binding and SSL certificate configuration, all achievable with just one click.
- **Application Store**: 1Panel curates a wide range of high-quality open-source tools and applications, facilitating easy installation and updates for its users.
- **Security and Reliability**: By leveraging containerization and secure application deployment practices, 1Panel minimizes vulnerability exposure. It further enhances security through integrated firewall management and log auditing capabilities.
- **One-Click Backup & Restore**: Data protection is made simple with 1Panel's one-click backup and restore functionality, supporting various cloud storage solutions to ensure data integrity and availability.
- **MCP Server**: [1Panel MCP Server](https://github.com/1Panel-dev/mcp-1panel) allow user to execute server operations via natural language.
## 快速开始
## Quick Start
**一键安装**
Execute the script below and follow the prompts to install 1Panel:
执行如下命令一键安装 1Panel:
```sh
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
```bash
curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh
```
如果是用于离线环境,推荐使用 [安装包方式](https://1panel.cn/docs/installation/package_installation/) 进行安装部署。
Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for more details.
**学习资料**
中国用户请使用此 [安装脚本](https://1panel.cn/docs/v2/installation/online_installation/),默认安装 V2 版本,应用数量相较国际版本更丰富。
- [在线文档](https://1panel.cn/docs/)
- [社区论坛](https://bbs.fit2cloud.com/c/1p/7)
- [如何加入微信交流群?](https://bbs.fit2cloud.com/t/topic/2147)
## Screenshot
## 专业版
![UI Display](https://resource.1panel.pro/img/1panel.png)
相比于社区版,1Panel 专业版为用户提供了大量增强功能及技术支持服务,增强功能包括 WAF 增强、网站防篡改、网站监控、GPU 监控、黑金主题及自定义界面等。 [点击查看专业版详细介绍](https://www.lxware.cn/1panel)。
## Star History
## 飞致云的其他明星项目
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date)
- [MaxKB](https://github.com/1Panel-dev/MaxKB/) - 基于 LLM 大语言模型的开源知识库问答系统
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源持续测试工具
- [KubePi](https://github.com/1Panel-dev/KubePi/) - 现代化、开源的 K8s 面板
## Pro Edition
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, Website monitoring, Mobile APP, custom logo and theme, etc.
[Click to see Pro Edition details](https://1panel.pro/pricing).
## Security Information
If you discover any security issues, please refer to [SECURITY.md](/SECURITY.md).
## GitAds Sponsored
[![Sponsored by GitAds](https://gitads.dev/v1/ad-serve?source=1panel-dev/1panel@github)](https://gitads.dev/v1/ad-track?source=1panel-dev/1panel@github)
## License
Copyright (c) 2014-2024 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
<https://www.gnu.org/licenses/gpl-3.0.html>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
<!-- GitAds-Verify: 9M8AMBB11GYJ3RUNXGB5R1MR71KPATU9 -->
+257
View File
@@ -0,0 +1,257 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/common"
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/xpu"
"github.com/gin-gonic/gin"
)
// @Tags AI
// @Summary Create Ollama model
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型 [name]","formatEN":"add Ollama model [name]"}
func (b *BaseApi) CreateOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := AIToolService.Create(req.Name); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Rereate Ollama model
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/recreate [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型重试 [name]","formatEN":"re-add Ollama model [name]"}
func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := AIToolService.Recreate(req.Name); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Close Ollama model conn
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/close [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"}
func (b *BaseApi) CloseOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := AIToolService.Close(req.Name); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Sync Ollama model list
// @Success 200 {array} dto.OllamaModelDropList
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Ollama 模型列表","formatEN":"sync Ollama model list"}
func (b *BaseApi) SyncOllamaModel(c *gin.Context) {
list, err := AIToolService.Sync()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags AI
// @Summary Page Ollama models
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/search [post]
func (b *BaseApi) SearchOllamaModel(c *gin.Context) {
var req dto.SearchWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := AIToolService.Search(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags AI
// @Summary Page Ollama models
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200 {string} details
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/load [post]
func (b *BaseApi) LoadOllamaModelDetail(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
detail, err := AIToolService.LoadDetail(req.Name)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, detail)
}
// @Tags AI
// @Summary Delete Ollama model
// @Accept json
// @Param request body dto.ForceDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ollama_models","output_column":"name","output_value":"names"}],"formatZH":"删除 Ollama 模型 [names]","formatEN":"remove Ollama model [names]"}
func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
var req dto.ForceDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := AIToolService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags AI
// @Summary Load gpu / xpu info
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/gpu/load [get]
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
ok, client := gpu.New()
if ok {
info, err := client.LoadGpuInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, info)
return
}
xpuOK, xpuClient := xpu.New()
if xpuOK {
info, err := xpuClient.LoadGpuInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, info)
return
}
helper.SuccessWithData(c, &common.GpuInfo{})
}
// @Tags AI
// @Summary Bind domain
// @Accept json
// @Param request body dto.OllamaBindDomain true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/domain/bind [post]
func (b *BaseApi) BindDomain(c *gin.Context) {
var req dto.OllamaBindDomain
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := AIToolService.BindDomain(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags AI
// @Summary Get bind domain
// @Accept json
// @Param request body dto.OllamaBindDomainReq true "request"
// @Success 200 {object} dto.OllamaBindDomainRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/domain/get [post]
func (b *BaseApi) GetBindDomain(c *gin.Context) {
var req dto.OllamaBindDomainReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := AIToolService.GetBindDomain(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// Tags AI
// Summary Update bind domain
// Accept json
// Param request body dto.OllamaBindDomain true "request"
// Success 200
// Security ApiKeyAuth
// Security Timestamp
// Router /ai/domain/update [post]
func (b *BaseApi) UpdateBindDomain(c *gin.Context) {
var req dto.OllamaBindDomain
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := AIToolService.UpdateBindDomain(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+10 -10
View File
@@ -13,7 +13,7 @@ import (
// @Summary List apps
// @Accept json
// @Param request body request.AppSearch true "request"
// @Success 200
// @Success 200 {object} response.AppRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/search [post]
@@ -22,7 +22,7 @@ func (b *BaseApi) SearchApp(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
list, err := appService.PageApp(req)
list, err := appService.PageApp(c, req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -68,14 +68,14 @@ func (b *BaseApi) SyncApp(c *gin.Context) {
// @Success 200 {object} response.AppDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/:key [get]
// @Router /apps/{key} [get]
func (b *BaseApi) GetApp(c *gin.Context) {
appKey, err := helper.GetStrParamByKey(c, "key")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
appDTO, err := appService.GetApp(appKey)
appDTO, err := appService.GetApp(c, appKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -88,11 +88,11 @@ func (b *BaseApi) GetApp(c *gin.Context) {
// @Accept json
// @Param appId path integer true "app id"
// @Param version path string true "app version"
// @Param version path string true "app type"
// @Param type path string true "app type"
// @Success 200 {object} response.AppDetailDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/detail/:appId/:version/:type [get]
// @Router /apps/detail/{appId}/{version}/{type} [get]
func (b *BaseApi) GetAppDetail(c *gin.Context) {
appID, err := helper.GetIntParamByKey(c, "appId")
if err != nil {
@@ -112,11 +112,11 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
// @Tags App
// @Summary Get app detail by id
// @Accept json
// @Param appId path integer true "id"
// @Param id path integer true "id"
// @Success 200 {object} response.AppDetailDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/details/:id [get]
// @Router /apps/details/{id} [get]
func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
appDetailID, err := helper.GetIntParamByKey(c, "id")
if err != nil {
@@ -173,7 +173,7 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
}
func (b *BaseApi) GetAppTags(c *gin.Context) {
tags, err := appService.GetAppTags()
tags, err := appService.GetAppTags(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -183,7 +183,7 @@ func (b *BaseApi) GetAppTags(c *gin.Context) {
// @Tags App
// @Summary Get app list update
// @Success 200
// @Success 200 {object} response.AppUpdateRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/checkupdate [get]
+13 -10
View File
@@ -13,7 +13,7 @@ import (
// @Summary Page app installed
// @Accept json
// @Param request body request.AppInstalledSearch true "request"
// @Success 200
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/search [post]
@@ -28,7 +28,10 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: int64(len(list)),
})
} else {
total, list, err := appInstallService.Page(req)
if err != nil {
@@ -45,7 +48,7 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
// @Tags App
// @Summary List app installed
// @Accept json
// @Success 200 array dto.AppInstallInfo
// @Success 200 {array} dto.AppInstallInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/list [get]
@@ -104,10 +107,11 @@ func (b *BaseApi) LoadPort(c *gin.Context) {
// @Summary Search app password by key
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {string} response.DatabaseConn
// @Param key path string true "key"
// @Success 200 {object} response.DatabaseConn
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/conninfo/:key [get]
// @Router /apps/installed/conninfo/{key} [get]
func (b *BaseApi) LoadConnInfo(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -128,7 +132,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
// @Success 200 {array} dto.AppResource
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/delete/check/:appInstallId [get]
// @Router /apps/installed/delete/check/{appInstallId} [get]
func (b *BaseApi) DeleteCheck(c *gin.Context) {
appInstallId, err := helper.GetIntParamByKey(c, "appInstallId")
if err != nil {
@@ -187,7 +191,7 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) {
// @Success 200 {array} response.AppService
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/services/:key [get]
// @Router /apps/services/{key} [get]
func (b *BaseApi) GetServices(c *gin.Context) {
key := c.Param("key")
services, err := appInstallService.GetServices(key)
@@ -201,7 +205,6 @@ func (b *BaseApi) GetServices(c *gin.Context) {
// @Tags App
// @Summary Search app update version by install id
// @Accept json
// @Param appInstallId path integer true "request"
// @Success 200 {array} dto.AppVersion
// @Security ApiKeyAuth
// @Security Timestamp
@@ -266,10 +269,10 @@ func (b *BaseApi) GetDefaultConfig(c *gin.Context) {
// @Summary Search params by appInstallId
// @Accept json
// @Param appInstallId path string true "request"
// @Success 200 {object} response.AppParam
// @Success 200 {object} response.AppConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/params/:appInstallId [get]
// @Router /apps/installed/params/{appInstallId} [get]
func (b *BaseApi) GetParams(c *gin.Context) {
appInstallId, err := helper.GetIntParamByKey(c, "appInstallId")
if err != nil {
+3 -10
View File
@@ -8,8 +8,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/captcha"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/geo"
"github.com/gin-gonic/gin"
)
@@ -121,7 +119,7 @@ func (b *BaseApi) GetResponsePage(c *gin.Context) {
// @Tags Auth
// @Summary Check System isDemo
// @Success 200
// @Success 200 {boolean} isDemo
// @Router /auth/demo [get]
func (b *BaseApi) CheckIsDemo(c *gin.Context) {
helper.SuccessWithData(c, global.CONF.System.IsDemo)
@@ -129,7 +127,7 @@ func (b *BaseApi) CheckIsDemo(c *gin.Context) {
// @Tags Auth
// @Summary Check System isIntl
// @Success 200
// @Success 200 {boolean} isIntl
// @Router /auth/intl [get]
func (b *BaseApi) CheckIsIntl(c *gin.Context) {
helper.SuccessWithData(c, global.CONF.System.IsIntl)
@@ -137,7 +135,7 @@ func (b *BaseApi) CheckIsIntl(c *gin.Context) {
// @Tags Auth
// @Summary Load System Language
// @Success 200
// @Success 200 {string} language
// @Router /auth/language [get]
func (b *BaseApi) GetLanguage(c *gin.Context) {
settingInfo, err := settingService.GetSettingInfo()
@@ -157,11 +155,6 @@ func saveLoginLogs(c *gin.Context, err error) {
logs.Status = constant.StatusSuccess
}
logs.IP = c.ClientIP()
address, err := geo.GetIPLocation(logs.IP, common.GetLang(c))
if err != nil {
global.LOG.Errorf("get ip location failed: %s", err)
}
logs.Agent = c.GetHeader("User-Agent")
logs.Address = address
_ = logService.CreateLoginLog(logs)
}
+50 -4
View File
@@ -64,7 +64,7 @@ func (b *BaseApi) RefreshOneDriveToken(c *gin.Context) {
// @Summary List buckets
// @Accept json
// @Param request body dto.ForBuckets true "request"
// @Success 200 {array} string
// @Success 200 {array} object
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/backup/search [post]
@@ -140,7 +140,7 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) {
// @Summary Page backup records
// @Accept json
// @Param request body dto.RecordSearch true "request"
// @Success 200
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/backup/record/search [post]
@@ -162,11 +162,34 @@ func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
})
}
// @Tags Backup Account
// @Summary Load backup records size
// @Accept json
// @Param request body dto.RecordSearch true "request"
// @Success 200 {array} dto.BackupFile
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/backup/record/size [post]
func (b *BaseApi) LoadBackupSize(c *gin.Context) {
var req dto.RecordSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
list, err := backupService.LoadSize(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Backup Account
// @Summary Page backup records by cronjob
// @Accept json
// @Param request body dto.RecordSearchByCronjob true "request"
// @Success 200
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/backup/record/search/bycronjob [post]
@@ -188,11 +211,34 @@ func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
})
}
// @Tags Backup Account
// @Summary Load backup records size for cronjob
// @Accept json
// @Param request body dto.RecordSearchByCronjob true "request"
// @Success 200 {array} dto.BackupFile
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/backup/record/size/bycronjob [post]
func (b *BaseApi) LoadBackupSizeByCronjob(c *gin.Context) {
var req dto.RecordSearchByCronjob
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
list, err := backupService.LoadSizeByCronjob(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Backup Account
// @Summary Download backup record
// @Accept json
// @Param request body dto.DownloadRecord true "request"
// @Success 200
// @Success 200 {string} filePath
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/backup/record/download [post]
+4 -2
View File
@@ -120,6 +120,7 @@ func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
// @Summary Operate Clam
// @Accept json
// @Param request body dto.Operate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/clam/operate [post]
@@ -142,6 +143,7 @@ func (b *BaseApi) OperateClam(c *gin.Context) {
// @Summary Clean clam record
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/clam/record/clean [post]
@@ -189,7 +191,7 @@ func (b *BaseApi) SearchClamRecord(c *gin.Context) {
// @Summary Load clam record detail
// @Accept json
// @Param request body dto.ClamLogReq true "request"
// @Success 200
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/clam/record/log [post]
@@ -212,7 +214,7 @@ func (b *BaseApi) LoadClamRecordLog(c *gin.Context) {
// @Summary Load clam file
// @Accept json
// @Param request body dto.ClamFileReq true "request"
// @Success 200 {object} dto.PageResult
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/clam/file/search [post]
+2 -2
View File
@@ -106,7 +106,7 @@ func (b *BaseApi) SearchRedisCommand(c *gin.Context) {
// @Tags Command
// @Summary Tree commands
// @Accept json
// @Success 200 {Array} dto.CommandTree
// @Success 200 {array} dto.CommandTree
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/command/tree [get]
@@ -122,7 +122,7 @@ func (b *BaseApi) SearchCommandTree(c *gin.Context) {
// @Tags Redis Command
// @Summary List redis commands
// @Success 200 {Array} dto.RedisCommand
// @Success 200 {array} dto.RedisCommand
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/command/redis [get]
+11 -7
View File
@@ -1,13 +1,14 @@
package v1
import (
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"strconv"
)
// @Tags Container
@@ -40,7 +41,7 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
// @Summary List containers
// @Accept json
// @Produce json
// @Success 200
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/list [post]
@@ -82,7 +83,7 @@ func (b *BaseApi) SearchCompose(c *gin.Context) {
// @Summary Test compose
// @Accept json
// @Param request body dto.ComposeCreate true "request"
// @Success 200
// @Success 200 {boolean} isOK
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/compose/test [post]
@@ -105,7 +106,7 @@ func (b *BaseApi) TestCompose(c *gin.Context) {
// @Summary Create compose
// @Accept json
// @Param request body dto.ComposeCreate true "request"
// @Success 200
// @Success 200 {string} log
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/compose [post]
@@ -311,7 +312,7 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) {
// @Summary Load container log
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/load/log [post]
@@ -390,11 +391,11 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) {
// @Tags Container
// @Summary Container stats
// @Param id path integer true "container id"
// @Param id path string true "container id"
// @Success 200 {object} dto.ContainerStats
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/stats/:id [get]
// @Router /containers/stats/{id} [get]
func (b *BaseApi) ContainerStats(c *gin.Context) {
containerID, ok := c.Params.Get("id")
if !ok {
@@ -438,6 +439,7 @@ func (b *BaseApi) Inspect(c *gin.Context) {
// @Param since query string false "since"
// @Param follow query string false "follow"
// @Param tail query string false "tail"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/search/log [post]
@@ -464,6 +466,7 @@ func (b *BaseApi) ContainerLogs(c *gin.Context) {
// @Summary Download Container logs
// @Accept json
// @Param request body dto.ContainerLog true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/download/log [post]
@@ -680,6 +683,7 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
// @Param since query string false "date"
// @Param follow query string false "follow"
// @Param tail query string false "tail"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/compose/search/log [get]
+1 -1
View File
@@ -92,7 +92,7 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
// @Summary Load Cronjob record log
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/records/log [post]
+2 -2
View File
@@ -34,7 +34,7 @@ func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
// @Success 200 {object} dto.DashboardBase
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/base/:ioOption/:netOption [get]
// @Router /dashboard/base/{ioOption}/{netOption} [get]
func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
ioOption, ok := c.Params.Get("ioOption")
if !ok {
@@ -78,7 +78,7 @@ func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/system/restart/:operation [post]
// @Router /dashboard/system/restart/{operation} [post]
func (b *BaseApi) SystemRestart(c *gin.Context) {
operation, ok := c.Params.Get("operation")
if !ok {
+7 -4
View File
@@ -43,7 +43,7 @@ func (b *BaseApi) CreateDatabase(c *gin.Context) {
// @Summary Check database
// @Accept json
// @Param request body dto.DatabaseCreate true "request"
// @Success 200
// @Success 200 {boolean} isOk
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/db/check [post]
@@ -93,10 +93,11 @@ func (b *BaseApi) SearchDatabase(c *gin.Context) {
// @Tags Database
// @Summary List databases
// @Param type path string true "type"
// @Success 200 {array} dto.DatabaseOption
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/db/list/:type [get]
// @Router /databases/db/list/{type} [get]
func (b *BaseApi) ListDatabase(c *gin.Context) {
dbType, err := helper.GetStrParamByKey(c, "type")
if err != nil {
@@ -114,10 +115,11 @@ func (b *BaseApi) ListDatabase(c *gin.Context) {
// @Tags Database
// @Summary Retrieve database list based on type
// @Param type path string true "type"
// @Success 200 {array} dto.DatabaseItem
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/db/item/:type [get]
// @Router /databases/db/item/{type} [get]
func (b *BaseApi) LoadDatabaseItems(c *gin.Context) {
dbType, err := helper.GetStrParamByKey(c, "type")
if err != nil {
@@ -135,10 +137,11 @@ func (b *BaseApi) LoadDatabaseItems(c *gin.Context) {
// @Tags Database
// @Summary Get databases
// @Param name path string true "name"
// @Success 200 {object} dto.DatabaseInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/db/:name [get]
// @Router /databases/db/{name} [get]
func (b *BaseApi) GetDatabase(c *gin.Context) {
name, err := helper.GetStrParamByKey(c, "name")
if err != nil {
+1 -1
View File
@@ -34,7 +34,7 @@ func (b *BaseApi) LoadDBBaseInfo(c *gin.Context) {
// @Summary Load Database conf
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/common/load/file [post]
+1
View File
@@ -217,6 +217,7 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
// @Summary Load mysql database from remote
// @Accept json
// @Param request body dto.MysqlLoadDB true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/load [post]
+3 -1
View File
@@ -168,9 +168,11 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
// @Summary Load postgresql database from remote
// @Accept json
// @Param request body dto.PostgresqlLoadDB true "request"
// @Param database path string true "database"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/pg/:database/load [post]
// @Router /databases/pg/{database}/load [post]
func (b *BaseApi) LoadPostgresqlDBFromRemote(c *gin.Context) {
database, err := helper.GetStrParamByKey(c, "database")
if err != nil {
+4 -4
View File
@@ -28,7 +28,7 @@ func (b *BaseApi) LoadDeviceBaseInfo(c *gin.Context) {
// @Tags Device
// @Summary list time zone options
// @Accept json
// @Success 200 {Array} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/device/zone/options [get]
@@ -46,7 +46,7 @@ func (b *BaseApi) LoadTimeOption(c *gin.Context) {
// @Summary load conf
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/device/conf [post]
@@ -185,7 +185,7 @@ func (b *BaseApi) UpdateDeviceSwap(c *gin.Context) {
// @Summary Check device DNS conf
// @Accept json
// @Param request body dto.SettingUpdate true "request"
// @Success 200
// @Success 200 {boolean} data
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/device/check/dns [post]
@@ -206,7 +206,7 @@ func (b *BaseApi) CheckDNS(c *gin.Context) {
// @Tags Device
// @Summary Scan system
// @Success 200
// @Success 200 {object} dto.CleanData
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/scan [post]
+4
View File
@@ -15,6 +15,8 @@ var (
appService = service.NewIAppService()
appInstallService = service.NewIAppInstalledService()
AIToolService = service.NewIAIToolService()
containerService = service.NewIContainerService()
composeTemplateService = service.NewIComposeTemplateService()
imageRepoService = service.NewIImageRepoService()
@@ -66,4 +68,6 @@ var (
favoriteService = service.NewIFavoriteService()
websiteCAService = service.NewIWebsiteCAService()
mcpServerService = service.NewIMcpServerService()
)
+4 -2
View File
@@ -29,7 +29,7 @@ func (b *BaseApi) LoadFail2BanBaseInfo(c *gin.Context) {
// @Summary Page fail2ban ip list
// @Accept json
// @Param request body dto.Fail2BanSearch true "request"
// @Success 200 {Array} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/fail2ban/search [post]
@@ -52,6 +52,7 @@ func (b *BaseApi) SearchFail2Ban(c *gin.Context) {
// @Summary Operate fail2ban
// @Accept json
// @Param request body dto.Operate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/fail2ban/operate [post]
@@ -74,6 +75,7 @@ func (b *BaseApi) OperateFail2Ban(c *gin.Context) {
// @Summary Operate sshd of fail2ban
// @Accept json
// @Param request body dto.Operate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/fail2ban/operate/sshd [post]
@@ -116,7 +118,7 @@ func (b *BaseApi) UpdateFail2BanConf(c *gin.Context) {
// @Tags Fail2ban
// @Summary Load fail2ban conf
// @Accept json
// @Success 200
// @Success 200 {string} file
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/fail2ban/load/conf [get]
+2 -2
View File
@@ -12,7 +12,7 @@ import (
// @Summary List favorites
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/favorite/search [post]
@@ -36,7 +36,7 @@ func (b *BaseApi) SearchFavorite(c *gin.Context) {
// @Summary Create favorite
// @Accept json
// @Param request body request.FavoriteCreate true "request"
// @Success 200
// @Success 200 {object} model.Favorite
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/favorite [post]
+38 -10
View File
@@ -39,19 +39,19 @@ func (b *BaseApi) ListFiles(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
files, err := fileService.GetFileList(req)
fileList, err := fileService.GetFileList(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, files)
helper.SuccessWithData(c, fileList)
}
// @Tags File
// @Summary Page file
// @Accept json
// @Param request body request.SearchUploadWithPage true "request"
// @Success 200 {array} response.FileInfo
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/upload/search [post]
@@ -290,6 +290,7 @@ func (b *BaseApi) SaveContent(c *gin.Context) {
// @Tags File
// @Summary Upload file
// @Accept multipart/form-data
// @Param file formData file true "request"
// @Success 200
// @Security ApiKeyAuth
@@ -400,7 +401,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
// @Summary Check file exist
// @Accept json
// @Param request body request.FilePathCheck true "request"
// @Success 200
// @Success 200 {boolean} isOk
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/check [post]
@@ -409,11 +410,37 @@ func (b *BaseApi) CheckFile(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if _, err := os.Stat(req.Path); err != nil {
helper.SuccessWithData(c, false)
fileOp := files.NewFileOp()
if fileOp.Stat(req.Path) {
helper.SuccessWithData(c, true)
return
}
helper.SuccessWithData(c, true)
if req.WithInit {
if err := fileOp.CreateDir(req.Path, 0644); err != nil {
helper.SuccessWithData(c, false)
return
}
helper.SuccessWithData(c, true)
return
}
helper.SuccessWithData(c, false)
}
// @Tags File
// @Summary Batch check file exist
// @Accept json
// @Param request body request.FilePathsCheck true "request"
// @Success 200 {array} response.ExistFileInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/batch/check [post]
func (b *BaseApi) BatchCheckFiles(c *gin.Context) {
var req request.FilePathsCheck
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
fileList := fileService.BatchCheckFiles(req)
helper.SuccessWithData(c, fileList)
}
// @Tags File
@@ -441,7 +468,7 @@ func (b *BaseApi) ChangeFileName(c *gin.Context) {
// @Summary Wget file
// @Accept json
// @Param request body request.FileWget true "request"
// @Success 200
// @Success 200 {object} response.FileWgetRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/wget [post]
@@ -583,7 +610,7 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
// @Summary Load file size
// @Accept json
// @Param request body request.DirSizeReq true "request"
// @Success 200
// @Success 200 {object} response.DirSizeRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/size [post]
@@ -662,6 +689,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
// @Tags File
// @Summary Chunk upload file
// @Accept multipart/form-data
// @Param file formData file true "request"
// @Success 200
// @Security ApiKeyAuth
@@ -784,7 +812,7 @@ func (b *BaseApi) Keys(c *gin.Context) {
// @Tags File
// @Summary Read file by Line
// @Param request body request.FileReadByLineReq true "request"
// @Success 200
// @Success 200 {object} response.FileLineContent
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/read [post]
+2 -2
View File
@@ -53,7 +53,7 @@ func (b *BaseApi) SearchFirewallRule(c *gin.Context) {
// @Summary Page firewall status
// @Accept json
// @Param request body dto.FirewallOperation true "request"
// @Success 200 {object} dto.PageResult
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/firewall/operate [post]
@@ -203,7 +203,7 @@ func (b *BaseApi) UpdatePortRule(c *gin.Context) {
}
// @Tags Firewall
// @Summary Uodate address group
// @Summary Update address group
// @Accept json
// @Param request body dto.AddrRuleUpdate true "request"
// @Success 200
+1
View File
@@ -55,6 +55,7 @@ func (b *BaseApi) LoadFtpLogInfo(c *gin.Context) {
// @Summary Operate FTP
// @Accept json
// @Param request body dto.Operate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/ftp/operate [post]
+4 -4
View File
@@ -12,7 +12,7 @@ import (
// @Summary Create host
// @Accept json
// @Param request body dto.HostOperate true "request"
// @Success 200
// @Success 200 {object} dto.HostInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts [post]
@@ -35,7 +35,7 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
// @Summary Test host conn by info
// @Accept json
// @Param request body dto.HostConnTest true "request"
// @Success 200
// @Success 200 {boolean} connStatus
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/test/byinfo [post]
@@ -56,7 +56,7 @@ func (b *BaseApi) TestByInfo(c *gin.Context) {
// @Success 200 {boolean} connStatus
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/test/byid/:id [post]
// @Router /hosts/test/byid/{id} [post]
func (b *BaseApi) TestByID(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
@@ -95,7 +95,7 @@ func (b *BaseApi) HostTree(c *gin.Context) {
// @Summary Page host
// @Accept json
// @Param request body dto.SearchHostWithPage true "request"
// @Success 200 {array} dto.HostTree
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/search [post]
+13 -13
View File
@@ -11,10 +11,10 @@ import (
// @Summary Get tool status
// @Accept json
// @Param request body request.HostToolReq true "request"
// @Success 200
// @Success 200 {object} response.HostToolRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool [post]
// @Router /hosts/tool [post]
func (b *BaseApi) GetToolStatus(c *gin.Context) {
var req request.HostToolReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -36,7 +36,7 @@ func (b *BaseApi) GetToolStatus(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool/create [post]
// @Router /hosts/tool/create [post]
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
func (b *BaseApi) InitToolConfig(c *gin.Context) {
var req request.HostToolCreate
@@ -58,7 +58,7 @@ func (b *BaseApi) InitToolConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool/operate [post]
// @Router /hosts/tool/operate [post]
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
func (b *BaseApi) OperateTool(c *gin.Context) {
var req request.HostToolReq
@@ -77,10 +77,10 @@ func (b *BaseApi) OperateTool(c *gin.Context) {
// @Summary Get tool config
// @Accept json
// @Param request body request.HostToolConfig true "request"
// @Success 200
// @Success 200 {object} response.HostToolConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool/config [post]
// @Router /hosts/tool/config [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
var req request.HostToolConfig
@@ -100,10 +100,10 @@ func (b *BaseApi) OperateToolConfig(c *gin.Context) {
// @Summary Get tool logs
// @Accept json
// @Param request body request.HostToolLogReq true "request"
// @Success 200
// @Success 200 {string} logContent
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool/log [post]
// @Router /hosts/tool/log [post]
func (b *BaseApi) GetToolLog(c *gin.Context) {
var req request.HostToolLogReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -125,7 +125,7 @@ func (b *BaseApi) GetToolLog(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool/supervisor/process [post]
// @Router /hosts/tool/supervisor/process [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
func (b *BaseApi) OperateProcess(c *gin.Context) {
var req request.SupervisorProcessConfig
@@ -144,10 +144,10 @@ func (b *BaseApi) OperateProcess(c *gin.Context) {
// @Tags Host tool
// @Summary Get Supervisor process config
// @Accept json
// @Success 200
// @Success 200 {object} response.SupervisorProcessConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool/supervisor/process [get]
// @Router /hosts/tool/supervisor/process [get]
func (b *BaseApi) GetProcess(c *gin.Context) {
configs, err := hostToolService.GetSupervisorProcessConfig()
if err != nil {
@@ -161,10 +161,10 @@ func (b *BaseApi) GetProcess(c *gin.Context) {
// @Summary Get Supervisor process config
// @Accept json
// @Param request body request.SupervisorProcessFileReq true "request"
// @Success 200
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/tool/supervisor/process/file [post]
// @Router /hosts/tool/supervisor/process/file [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
func (b *BaseApi) GetProcessFile(c *gin.Context) {
var req request.SupervisorProcessFileReq
+2 -2
View File
@@ -94,7 +94,7 @@ func (b *BaseApi) ImageBuild(c *gin.Context) {
// @Summary Pull image
// @Accept json
// @Param request body dto.ImagePull true "request"
// @Success 200 {string} log
// @Success 200 {string} logPath
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/image/pull [post]
@@ -118,7 +118,7 @@ func (b *BaseApi) ImagePull(c *gin.Context) {
// @Summary Push image
// @Accept json
// @Param request body dto.ImagePush true "request"
// @Success 200 {string} log
// @Success 200 {string} logPath
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/image/push [post]
+5 -5
View File
@@ -76,7 +76,7 @@ func (b *BaseApi) CheckRepoStatus(c *gin.Context) {
// @Tags Container Image-repo
// @Summary Create image repo
// @Accept json
// @Param request body dto.ImageRepoDelete true "request"
// @Param request body dto.ImageRepoCreate true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
@@ -99,20 +99,20 @@ func (b *BaseApi) CreateRepo(c *gin.Context) {
// @Tags Container Image-repo
// @Summary Delete image repo
// @Accept json
// @Param request body dto.ImageRepoDelete true "request"
// @Param request body dto.OperateByID true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/repo/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"image_repos","output_column":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"删除镜像仓库 [name]","formatEN":"delete image repo [name]"}
func (b *BaseApi) DeleteRepo(c *gin.Context) {
var req dto.ImageRepoDelete
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := imageRepoService.BatchDelete(req); err != nil {
if err := imageRepoService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
+4 -4
View File
@@ -21,7 +21,7 @@ func (b *BaseApi) GetLoginLogs(c *gin.Context) {
return
}
total, list, err := logService.PageLoginLog(req)
total, list, err := logService.PageLoginLog(c, req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -63,7 +63,7 @@ func (b *BaseApi) GetOperationLogs(c *gin.Context) {
// @Summary Clean operation logs
// @Accept json
// @Param request body dto.CleanLog true "request"
// @Success 200 {object} dto.PageResult
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /logs/clean [post]
@@ -84,7 +84,7 @@ func (b *BaseApi) CleanLogs(c *gin.Context) {
// @Tags Logs
// @Summary Load system log files
// @Success 200
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /logs/system/files [get]
@@ -100,7 +100,7 @@ func (b *BaseApi) GetSystemFiles(c *gin.Context) {
// @Tags Logs
// @Summary Load system logs
// @Success 200
// @Success 200 {string} data
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /logs/system [post]
+167
View File
@@ -0,0 +1,167 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags McpServer
// @Summary List mcp servers
// @Accept json
// @Param request body request.McpServerSearch true "request"
// @Success 200 {object} response.McpServersRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/search [post]
func (b *BaseApi) PageMcpServers(c *gin.Context) {
var req request.McpServerSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
list := mcpServerService.Page(req)
helper.SuccessWithData(c, list)
}
// @Tags McpServer
// @Summary Create mcp server
// @Accept json
// @Param request body request.McpServerCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server [post]
func (b *BaseApi) CreateMcpServer(c *gin.Context) {
var req request.McpServerCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := mcpServerService.Create(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags McpServer
// @Summary Update mcp server
// @Accept json
// @Param request body request.McpServerUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server/update [post]
func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
var req request.McpServerUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := mcpServerService.Update(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags McpServer
// @Summary Delete mcp server
// @Accept json
// @Param request body request.McpServerDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server/del [post]
func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
var req request.McpServerDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := mcpServerService.Delete(req.ID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags McpServer
// @Summary Operate mcp server
// @Accept json
// @Param request body request.McpServerOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server/op [post]
func (b *BaseApi) OperateMcpServer(c *gin.Context) {
var req request.McpServerOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := mcpServerService.Operate(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags McpServer
// @Summary Bind Domain for mcp server
// @Accept json
// @Param request body request.McpBindDomain true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/domain/bind [post]
func (b *BaseApi) BindMcpDomain(c *gin.Context) {
var req request.McpBindDomain
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := mcpServerService.BindDomain(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags McpServer
// @Summary Update bind Domain for mcp server
// @Accept json
// @Param request body request.McpBindDomainUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/domain/update [post]
func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
var req request.McpBindDomainUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := mcpServerService.UpdateBindDomain(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags McpServer
// @Summary Get bin Domain for mcp server
// @Accept json
// @Success 200 {object} response.McpBindDomainRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/domain/get [get]
func (b *BaseApi) GetMcpBindDomain(c *gin.Context) {
res, err := mcpServerService.GetBindDomain()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
+1 -1
View File
@@ -18,7 +18,7 @@ import (
// @Tags Monitor
// @Summary Load monitor datas
// @Param request body dto.MonitorSearch true "request"
// @Success 200
// @Success 200 {array} dto.MonitorData
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/monitor/search [post]
+1 -1
View File
@@ -9,7 +9,7 @@ import (
// @Tags OpenResty
// @Summary Load OpenResty conf
// @Success 200 {object} response.FileInfo
// @Success 200 {object} response.NginxFile
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /openresty [get]
+5 -2
View File
@@ -12,7 +12,7 @@ import (
// @Summary Page Extensions
// @Accept json
// @Param request body request.PHPExtensionsSearch true "request"
// @Success 200 {array} response.PHPExtensionsDTO
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/php/extensions/search [post]
@@ -27,7 +27,10 @@ func (b *BaseApi) PagePHPExtensions(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
helper.SuccessWithData(c, dto.PageResult{
Total: int64(len(list)),
Items: list,
})
} else {
total, list, err := phpExtensionsService.Page(req)
if err != nil {
+2 -2
View File
@@ -12,7 +12,7 @@ import (
// @Summary List Recycle Bin files
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/recycle/search [post]
@@ -72,7 +72,7 @@ func (b *BaseApi) ClearRecycleBinFile(c *gin.Context) {
// @Tags File
// @Summary Get Recycle Bin status
// @Accept json
// @Success 200
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/recycle/status [get]
+6 -6
View File
@@ -12,7 +12,7 @@ import (
// @Summary List runtimes
// @Accept json
// @Param request body request.RuntimeSearch true "request"
// @Success 200
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/search [post]
@@ -36,7 +36,7 @@ func (b *BaseApi) SearchRuntimes(c *gin.Context) {
// @Summary Create runtime
// @Accept json
// @Param request body request.RuntimeCreate true "request"
// @Success 200
// @Success 200 {object} model.Runtime
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes [post]
@@ -115,10 +115,10 @@ func (b *BaseApi) UpdateRuntime(c *gin.Context) {
// @Summary Get runtime
// @Accept json
// @Param id path string true "request"
// @Success 200
// @Success 200 {object} response.RuntimeDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/:id [get]
// @Router /runtimes/{id} [get]
func (b *BaseApi) GetRuntime(c *gin.Context) {
id, err := helper.GetIntParamByKey(c, "id")
if err != nil {
@@ -137,7 +137,7 @@ func (b *BaseApi) GetRuntime(c *gin.Context) {
// @Summary Get Node package scripts
// @Accept json
// @Param request body request.NodePackageReq true "request"
// @Success 200
// @Success 200 {array} response.PackageScripts
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/node/package [post]
@@ -180,7 +180,7 @@ func (b *BaseApi) OperateRuntime(c *gin.Context) {
// @Summary Get Node modules
// @Accept json
// @Param request body request.NodeModuleReq true "request"
// @Success 200
// @Success 200 {array} response.NodeModule
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/node/modules [post]
+23 -3
View File
@@ -3,8 +3,10 @@ package v1
import (
"encoding/base64"
"errors"
"fmt"
"os"
"path"
"regexp"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -53,11 +55,21 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if req.Key == "SecurityEntrance" {
if !checkEntrancePattern(req.Value) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("the format of the security entrance %s is incorrect.", req.Value))
return
}
}
if err := settingService.Update(req.Key, req.Value); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if req.Key == "SecurityEntrance" {
entranceValue := base64.StdEncoding.EncodeToString([]byte(req.Value))
c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true)
}
helper.SuccessWithData(c, nil)
}
@@ -160,7 +172,7 @@ func (b *BaseApi) UpdateSSL(c *gin.Context) {
// @Tags System Setting
// @Summary Load system cert info
// @Success 200 {object} dto.SettingInfo
// @Success 200 {object} dto.SSLInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/ssl/info [get]
@@ -192,7 +204,7 @@ func (b *BaseApi) DownloadSSL(c *gin.Context) {
// @Tags System Setting
// @Summary Load system address
// @Accept json
// @Success 200
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/interface [get]
@@ -346,7 +358,7 @@ func (b *BaseApi) MFABind(c *gin.Context) {
// @Tags System Setting
// @Summary Generate api key
// @Accept json
// @Success 200
// @Success 200 {string} apiKey
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/api/config/generate/key [post]
@@ -391,3 +403,11 @@ func (b *BaseApi) UpdateApiConfig(c *gin.Context) {
}
helper.SuccessWithData(c, nil)
}
func checkEntrancePattern(val string) bool {
if len(val) == 0 {
return true
}
result, _ := regexp.MatchString("^[a-zA-Z0-9]{5,116}$", val)
return result
}
+2 -2
View File
@@ -55,7 +55,7 @@ func (b *BaseApi) ImportSnapshot(c *gin.Context) {
// @Summary Load Snapshot status
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Success 200 {object} dto.SnapshotStatus
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/snapshot/status [post]
@@ -124,7 +124,7 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) {
// @Summary Load system snapshot size
// @Accept json
// @Param request body dto.PageSnapshot true "request"
// @Success 200
// @Success 200 {array} dto.SnapshotFile
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/snapshot/size [post]
+11 -10
View File
@@ -12,7 +12,7 @@ import (
// @Success 200 {object} dto.SSHInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/ssh/search [post]
// @Router /hosts/ssh/search [post]
func (b *BaseApi) GetSSHInfo(c *gin.Context) {
info, err := sshService.GetSSHInfo()
if err != nil {
@@ -26,9 +26,10 @@ func (b *BaseApi) GetSSHInfo(c *gin.Context) {
// @Summary Operate SSH
// @Accept json
// @Param request body dto.Operate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/ssh/operate [post]
// @Router /hosts/ssh/operate [post]
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"}
func (b *BaseApi) OperateSSH(c *gin.Context) {
var req dto.Operate
@@ -50,7 +51,7 @@ func (b *BaseApi) OperateSSH(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/ssh/update [post]
// @Router /hosts/ssh/update [post]
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置 [key] => [value]","formatEN":"update SSH setting [key] => [value]"}
func (b *BaseApi) UpdateSSH(c *gin.Context) {
var req dto.SSHUpdate
@@ -72,7 +73,7 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/conffile/update [post]
// @Router /hosts/conffile/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
var req dto.SSHConf
@@ -94,7 +95,7 @@ func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/ssh/generate [post]
// @Router /hosts/ssh/generate [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
func (b *BaseApi) GenerateSSH(c *gin.Context) {
var req dto.GenerateSSH
@@ -113,10 +114,10 @@ func (b *BaseApi) GenerateSSH(c *gin.Context) {
// @Summary Load host SSH secret
// @Accept json
// @Param request body dto.GenerateLoad true "request"
// @Success 200
// @Success 200 {string} data
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/ssh/secret [post]
// @Router /hosts/ssh/secret [post]
func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
var req dto.GenerateLoad
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -138,7 +139,7 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
// @Success 200 {object} dto.SSHLog
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/ssh/log [post]
// @Router /hosts/ssh/log [post]
func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
var req dto.SearchSSHLog
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -155,10 +156,10 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
// @Tags SSH
// @Summary Load host SSH conf
// @Success 200
// @Success 200 {string} data
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /host/ssh/conf [get]
// @Router /hosts/ssh/conf [get]
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
data, err := sshService.LoadSSHConf()
if err != nil {
+65 -89
View File
@@ -68,12 +68,11 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
<-quitChan
if wshandleError(wsConn, err) {
return
}
dt := time.Now().Add(time.Second)
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}
func (b *BaseApi) RedisWsSsh(c *gin.Context) {
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
@@ -95,38 +94,30 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
name := c.Query("name")
from := c.Query("from")
commands := []string{"redis-cli"}
database, err := databaseService.Get(name)
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
return
}
if from == "local" {
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
source := c.Query("source")
var containerID string
var initCmd []string
switch source {
case "redis":
containerID, initCmd, err = loadRedisInitCmd(c)
case "ollama":
containerID, initCmd, err = loadOllamaInitCmd(c)
case "container":
containerID, initCmd, err = loadContainerInitCmd(c)
default:
if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) {
return
}
name = redisInfo.ContainerName
if len(database.Password) != 0 {
commands = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"}
}
} else {
itemPort := fmt.Sprintf("%v", database.Port)
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort}
if len(database.Password) != 0 {
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort, "-a", database.Password, "--no-auth-warning"}
}
name = "1Panel-redis-cli-tools"
}
pidMap := loadMapFromDockerTop(name)
itemCmds := append([]string{"exec", "-it", name}, commands...)
slave, err := terminal.NewCommand(itemCmds)
if wshandleError(wsConn, err) {
return
}
defer killBash(name, strings.Join(commands, " "), pidMap)
pidMap := loadMapFromDockerTop(containerID)
slave, err := terminal.NewCommand(initCmd)
if wshandleError(wsConn, err) {
return
}
defer killBash(containerID, strings.ReplaceAll(strings.Join(initCmd, " "), fmt.Sprintf("exec -it %s ", containerID), ""), pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
@@ -141,83 +132,68 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
<-quitChan
global.LOG.Info("websocket finished")
if wshandleError(wsConn, err) {
return
}
dt := time.Now().Add(time.Second)
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}
func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
name := c.Query("name")
from := c.Query("from")
commands := []string{"exec", "-it"}
database, err := databaseService.Get(name)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
}
defer wsConn.Close()
if global.CONF.System.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
if from == "local" {
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
if err != nil {
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
}
name = redisInfo.ContainerName
commands = append(commands, []string{name, "redis-cli"}...)
if len(database.Password) != 0 {
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
}
} else {
name = "1Panel-redis-cli-tools"
commands = append(commands, []string{name, "redis-cli", "-h", database.Address, "-p", fmt.Sprintf("%v", database.Port)}...)
if len(database.Password) != 0 {
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
}
}
return name, commands, nil
}
func loadOllamaInitCmd(c *gin.Context) (string, []string, error) {
name := c.Query("name")
if cmd.CheckIllegal(name) {
return "", nil, fmt.Errorf("ollama model %s contains illegal characters", name)
}
ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"})
if err != nil {
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
}
containerName := ollamaInfo.ContainerName
return containerName, []string{"exec", "-it", containerName, "ollama", "run", name}, nil
}
func loadContainerInitCmd(c *gin.Context) (string, []string, error) {
containerID := c.Query("containerid")
command := c.Query("command")
user := c.Query("user")
if len(command) == 0 || len(containerID) == 0 {
if wshandleError(wsConn, errors.New("error param of command or containerID")) {
return
}
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
cmds := []string{"exec", containerID, command}
if len(user) != 0 {
cmds = []string{"exec", "-u", user, containerID, command}
}
if cmd.CheckIllegal(user, containerID, command) {
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
return
}
return "", nil, fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
}
stdout, err := cmd.ExecWithCheck("docker", cmds...)
if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
return
if len(command) == 0 || len(containerID) == 0 {
return "", nil, fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
}
commands := []string{"exec", "-it", containerID, command}
if len(user) != 0 {
commands = []string{"exec", "-it", "-u", user, containerID, command}
}
pidMap := loadMapFromDockerTop(containerID)
slave, err := terminal.NewCommand(commands)
if wshandleError(wsConn, err) {
return
}
defer killBash(containerID, command, pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
if wshandleError(wsConn, err) {
return
}
quitChan := make(chan bool, 3)
tty.Start(quitChan)
go slave.Wait(quitChan)
<-quitChan
global.LOG.Info("websocket finished")
if wshandleError(wsConn, err) {
return
}
return containerID, commands, nil
}
func wshandleError(ws *websocket.Conn, err error) bool {
+1 -1
View File
@@ -26,7 +26,7 @@ func (b *BaseApi) GetUpgradeInfo(c *gin.Context) {
// @Summary Load release notes by version
// @Accept json
// @Param request body dto.Upgrade true "request"
// @Success 200
// @Success 200 {string} notes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/upgrade [get]
+39 -14
View File
@@ -51,7 +51,7 @@ func (b *BaseApi) GetWebsites(c *gin.Context) {
// @Tags Website
// @Summary List website names
// @Success 200 {array} string
// @Success 200 {array} response.WebsiteOption
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/options [get]
@@ -166,7 +166,7 @@ func (b *BaseApi) UpdateWebsite(c *gin.Context) {
// @Success 200 {object} response.WebsiteDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/:id [get]
// @Router /websites/{id} [get]
func (b *BaseApi) GetWebsite(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
@@ -185,10 +185,11 @@ func (b *BaseApi) GetWebsite(c *gin.Context) {
// @Summary Search website nginx by id
// @Accept json
// @Param id path integer true "request"
// @Param type path string true "type"
// @Success 200 {object} response.FileInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/:id/config/:type [get]
// @Router /websites/{id}/config/{type} [get]
func (b *BaseApi) GetWebsiteNginx(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
@@ -254,7 +255,7 @@ func (b *BaseApi) UpdateNginxConfig(c *gin.Context) {
// @Success 200 {object} response.WebsiteHTTPS
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/:id/https [get]
// @Router /websites/{id}/https [get]
func (b *BaseApi) GetHTTPSConfig(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
@@ -272,11 +273,12 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) {
// @Tags Website HTTPS
// @Summary Update https conf
// @Accept json
// @Param id path integer true "request"
// @Param request body request.WebsiteHTTPSOp true "request"
// @Success 200 {object} response.WebsiteHTTPS
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/:id/https [post]
// @Router /websites/{id}/https [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] https 配置","formatEN":"Update website https [domain] conf"}
func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) {
var req request.WebsiteHTTPSOp
@@ -386,7 +388,7 @@ func (b *BaseApi) ChangeDefaultServer(c *gin.Context) {
// @Success 200 {object} response.PHPConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/php/config/:id [get]
// @Router /websites/php/config/{id} [get]
func (b *BaseApi) GetWebsitePHPConfig(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
@@ -468,7 +470,7 @@ func (b *BaseApi) ChangePHPVersion(c *gin.Context) {
// @Summary Get rewrite conf
// @Accept json
// @Param request body request.NginxRewriteReq true "request"
// @Success 200
// @Success 200 {object} response.NginxRewriteRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/rewrite [post]
@@ -552,7 +554,7 @@ func (b *BaseApi) UpdateSiteDirPermission(c *gin.Context) {
// @Summary Get proxy conf
// @Accept json
// @Param request body request.WebsiteProxyReq true "request"
// @Success 200
// @Success 200 {array} request.WebsiteProxyConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/proxies [post]
@@ -569,6 +571,28 @@ func (b *BaseApi) GetProxyConfig(c *gin.Context) {
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Delete proxy conf
// @Accept json
// @Param request body request.WebsiteProxyDel true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/proxies/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain] 反向代理配置","formatEN":"Delete domain [domain] proxy config"}
func (b *BaseApi) DeleteProxyConfig(c *gin.Context) {
var req request.WebsiteProxyDel
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := websiteService.DeleteProxy(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Update proxy conf
// @Accept json
@@ -616,7 +640,7 @@ func (b *BaseApi) UpdateProxyConfigFile(c *gin.Context) {
// @Summary Get AuthBasic conf
// @Accept json
// @Param request body request.NginxAuthReq true "request"
// @Success 200
// @Success 200 {object} response.NginxAuthRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/auths [post]
@@ -657,7 +681,7 @@ func (b *BaseApi) UpdateAuthConfig(c *gin.Context) {
// @Summary Get AntiLeech conf
// @Accept json
// @Param request body request.NginxCommonReq true "request"
// @Success 200
// @Success 200 {object} response.NginxAntiLeechRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/leech [post]
@@ -720,7 +744,7 @@ func (b *BaseApi) UpdateRedirectConfig(c *gin.Context) {
// @Summary Get redirect conf
// @Accept json
// @Param request body request.WebsiteProxyReq true "request"
// @Success 200
// @Success 200 {array} response.NginxRedirectConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/redirect [post]
@@ -762,7 +786,7 @@ func (b *BaseApi) UpdateRedirectConfigFile(c *gin.Context) {
// @Summary Get website dir
// @Accept json
// @Param request body request.WebsiteCommonReq true "request"
// @Success 200
// @Success 200 {object} response.WebsiteDirConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/dir [post]
@@ -782,10 +806,11 @@ func (b *BaseApi) GetDirConfig(c *gin.Context) {
// @Tags Website
// @Summary Get default html
// @Accept json
// @Success 200 {object} response.FileInfo
// @Param type path string true "type"
// @Success 200 {object} response.WebsiteHtmlRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/default/html/:type [get]
// @Router /websites/default/html/{type} [get]
func (b *BaseApi) GetDefaultHtml(c *gin.Context) {
resourceType, err := helper.GetStrParamByKey(c, "type")
if err != nil {
+2 -2
View File
@@ -32,7 +32,7 @@ func (b *BaseApi) DeleteWebDomain(c *gin.Context) {
// @Summary Create website domain
// @Accept json
// @Param request body request.WebsiteDomainCreate true "request"
// @Success 200 {object} model.WebsiteDomain
// @Success 200 {array} model.WebsiteDomain
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/domains [post]
@@ -57,7 +57,7 @@ func (b *BaseApi) CreateWebDomain(c *gin.Context) {
// @Success 200 {array} model.WebsiteDomain
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/domains/:websiteId [get]
// @Router /websites/domains/{websiteId} [get]
func (b *BaseApi) GetWebDomains(c *gin.Context) {
websiteId, err := helper.GetIntParamByKey(c, "websiteId")
if err != nil {
+5 -5
View File
@@ -17,7 +17,7 @@ import (
// @Summary Page website ssl
// @Accept json
// @Param request body request.WebsiteSSLSearch true "request"
// @Success 200
// @Success 200 {array} response.WebsiteSSLDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/ssl/search [post]
@@ -135,10 +135,10 @@ func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) {
// @Summary Search website ssl by website id
// @Accept json
// @Param websiteId path integer true "request"
// @Success 200
// @Success 200 {object} response.WebsiteSSLDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/ssl/website/:websiteId [get]
// @Router /websites/ssl/website/{websiteId} [get]
func (b *BaseApi) GetWebsiteSSLByWebsiteId(c *gin.Context) {
websiteId, err := helper.GetIntParamByKey(c, "websiteId")
if err != nil {
@@ -157,10 +157,10 @@ func (b *BaseApi) GetWebsiteSSLByWebsiteId(c *gin.Context) {
// @Summary Search website ssl by id
// @Accept json
// @Param id path integer true "request"
// @Success 200
// @Success 200 {object} response.WebsiteSSLDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/ssl/:id [get]
// @Router /websites/ssl/{id} [get]
func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
+45
View File
@@ -0,0 +1,45 @@
package dto
import "time"
type OllamaModelInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Size string `json:"size"`
From string `json:"from"`
LogFileExist bool `json:"logFileExist"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
}
type OllamaModelDropList struct {
ID uint `json:"id"`
Name string `json:"name"`
}
type OllamaModelName struct {
Name string `json:"name"`
}
type OllamaBindDomain struct {
Domain string `json:"domain" validate:"required"`
AppInstallID uint `json:"appInstallID" validate:"required"`
SSLID uint `json:"sslID"`
WebsiteID uint `json:"websiteID"`
IPList string `json:"ipList"`
}
type OllamaBindDomainReq struct {
AppInstallID uint `json:"appInstallID" validate:"required"`
}
type OllamaBindDomainRes struct {
Domain string `json:"domain"`
SSLID uint `json:"sslID"`
AcmeAccountID uint `json:"acmeAccountID"`
AllowIPs []string `json:"allowIPs"`
WebsiteID uint `json:"websiteID"`
ConnUrl string `json:"connUrl"`
}
+21 -10
View File
@@ -1,9 +1,5 @@
package dto
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type AppDatabase struct {
ServiceName string `json:"PANEL_DB_HOST"`
DbName string `json:"PANEL_DB_NAME"`
@@ -62,7 +58,7 @@ type AppDefine struct {
}
type LocalAppAppDefine struct {
AppProperty model.App `json:"additionalProperties" yaml:"additionalProperties"`
AppProperty AppProperty `json:"additionalProperties" yaml:"additionalProperties"`
}
type LocalAppParam struct {
@@ -82,8 +78,9 @@ type AppProperty struct {
Name string `json:"name"`
Type string `json:"type"`
Tags []string `json:"tags"`
ShortDescZh string `json:"shortDescZh"`
ShortDescEn string `json:"shortDescEn"`
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
Description Locale `json:"description"`
Key string `json:"key"`
Required []string `json:"Required"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
@@ -93,6 +90,7 @@ type AppProperty struct {
Github string `json:"github"`
Document string `json:"document"`
Version float64 `json:"version"`
GpuSupport bool `json:"gpuSupport"`
}
type AppConfigVersion struct {
@@ -104,9 +102,21 @@ type AppConfigVersion struct {
}
type Tag struct {
Key string `json:"key"`
Name string `json:"name"`
Sort int `json:"sort"`
Key string `json:"key"`
Name string `json:"name"`
Sort int `json:"sort"`
Locales Locale `json:"locales"`
}
type Locale struct {
En string `json:"en"`
Ja string `json:"ja"`
Ms string `json:"ms"`
PtBr string `json:"pt-br" yaml:"pt-br"`
Ru string `json:"ru"`
ZhHant string `json:"zh-hant" yaml:"zh-hant"`
Zh string `json:"zh"`
Ko string `json:"ko"`
}
type AppForm struct {
@@ -118,6 +128,7 @@ type AppFormFields struct {
Type string `json:"type"`
LabelZh string `json:"labelZh"`
LabelEn string `json:"labelEn"`
Label Locale `json:"label"`
Required bool `json:"required"`
Default interface{} `json:"default"`
EnvKey string `json:"envKey"`
+1 -1
View File
@@ -29,7 +29,7 @@ type Login struct {
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
Language string `json:"language" validate:"required,oneof=zh en tw"`
Language string `json:"language" validate:"required,oneof=zh en tw ja ko ru ms 'pt-BR'"`
}
type MFALogin struct {
+6 -1
View File
@@ -21,6 +21,12 @@ type BackupInfo struct {
Vars string `json:"vars"`
}
type BackupFile struct {
ID uint `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
}
type OneDriveInfo struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
@@ -66,7 +72,6 @@ type BackupRecords struct {
BackupType string `json:"backupType"`
FileDir string `json:"fileDir"`
FileName string `json:"fileName"`
Size int64 `json:"size"`
}
type DownloadRecord struct {
+5
View File
@@ -52,3 +52,8 @@ type OperationWithNameAndType struct {
Name string `json:"name"`
Type string `json:"type" validate:"required"`
}
type ForceDelete struct {
IDs []uint `json:"ids"`
ForceDelete bool `json:"forceDelete"`
}
+7 -5
View File
@@ -71,14 +71,16 @@ type CronjobDownload struct {
}
type CronjobClean struct {
IsDelete bool `json:"isDelete"`
CleanData bool `json:"cleanData"`
CronjobID uint `json:"cronjobID" validate:"required"`
IsDelete bool `json:"isDelete"`
CleanData bool `json:"cleanData"`
CleanRemoteData bool `json:"cleanRemoteData"`
CronjobID uint `json:"cronjobID" validate:"required"`
}
type CronjobBatchDelete struct {
CleanData bool `json:"cleanData"`
IDs []uint `json:"ids" validate:"required"`
CleanData bool `json:"cleanData"`
CleanRemoteData bool `json:"cleanRemoteData"`
IDs []uint `json:"ids" validate:"required"`
}
type CronjobInfo struct {
+1 -1
View File
@@ -19,7 +19,7 @@ type Fail2BanSearch struct {
}
type Fail2BanUpdate struct {
Key string `json:"key" validate:"required,oneof=port bantime findtime maxretry banaction logpath port"`
Key string `json:"key" validate:"required,oneof=port bantime findtime maxretry banaction logpath"`
Value string `json:"value"`
}
+2 -1
View File
@@ -30,7 +30,8 @@ type PortRuleOperate struct {
}
type ForwardRuleOperate struct {
Rules []struct {
ForceDelete bool `json:"forceDelete"`
Rules []struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Num string `json:"num"`
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
+4 -4
View File
@@ -6,8 +6,8 @@ type ImageRepoCreate struct {
Name string `json:"name" validate:"required"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Username string `json:"username"`
Password string `json:"password"`
Auth bool `json:"auth"`
}
@@ -15,8 +15,8 @@ type ImageRepoUpdate struct {
ID uint `json:"id"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Username string `json:"username"`
Password string `json:"password"`
Auth bool `json:"auth"`
}
+10
View File
@@ -0,0 +1,10 @@
package dto
type DockerComposeService struct {
Image string `yaml:"image"`
ContainerName string `yaml:"container_name"`
Restart string `yaml:"restart"`
Ports []string `yaml:"ports"`
Environment []string `yaml:"environment"`
Command []string `yaml:"command"`
}
+1
View File
@@ -33,6 +33,7 @@ type AppContainerConfig struct {
DockerCompose string `json:"dockerCompose"`
HostMode bool `json:"hostMode"`
PullImage bool `json:"pullImage"`
GpuConfig bool `json:"gpuConfig"`
}
type AppInstalledSearch struct {
+12 -6
View File
@@ -76,7 +76,12 @@ type FileRename struct {
}
type FilePathCheck struct {
Path string `json:"path" validate:"required"`
Path string `json:"path" validate:"required"`
WithInit bool `json:"withInit"`
}
type FilePathsCheck struct {
Paths []string `json:"paths" validate:"required"`
}
type FileWget struct {
@@ -87,11 +92,12 @@ type FileWget struct {
}
type FileMove struct {
Type string `json:"type" validate:"required"`
OldPaths []string `json:"oldPaths" validate:"required"`
NewPath string `json:"newPath" validate:"required"`
Name string `json:"name"`
Cover bool `json:"cover"`
Type string `json:"type" validate:"required"`
OldPaths []string `json:"oldPaths" validate:"required"`
NewPath string `json:"newPath" validate:"required"`
Name string `json:"name"`
Cover bool `json:"cover"`
CoverPaths []string `json:"coverPaths"`
}
type FileDownload struct {
+57
View File
@@ -0,0 +1,57 @@
package request
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type McpServerSearch struct {
dto.PageInfo
Name string `json:"name"`
Sync bool `json:"sync"`
}
type McpServerCreate struct {
Name string `json:"name" validate:"required"`
Command string `json:"command" validate:"required"`
Environments []Environment `json:"environments"`
Volumes []Volume `json:"volumes"`
Port int `json:"port" validate:"required"`
ContainerName string `json:"containerName"`
BaseURL string `json:"baseUrl"`
SsePath string `json:"ssePath"`
HostIP string `json:"hostIP"`
}
type McpServerUpdate struct {
ID uint `json:"id" validate:"required"`
McpServerCreate
}
type Environment struct {
Key string `json:"key"`
Value string `json:"value"`
}
type Volume struct {
Source string `json:"source"`
Target string `json:"target"`
}
type McpServerDelete struct {
ID uint `json:"id" validate:"required"`
}
type McpServerOperate struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
}
type McpBindDomain struct {
Domain string `json:"domain" validate:"required"`
SSLID uint `json:"sslID"`
IPList string `json:"ipList"`
}
type McpBindDomainUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
SSLID uint `json:"sslID"`
IPList string `json:"ipList"`
}
+5
View File
@@ -193,6 +193,11 @@ type WebsiteProxyConfig struct {
ProxySSLName string `json:"proxySSLName"`
}
type WebsiteProxyDel struct {
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
}
type WebsiteProxyReq struct {
ID uint `json:"id" validate:"required"`
}
+31 -27
View File
@@ -10,8 +10,8 @@ import (
)
type AppRes struct {
Items []*AppDto `json:"items"`
Total int64 `json:"total"`
Items []*AppItem `json:"items"`
Total int64 `json:"total"`
}
type AppUpdateRes struct {
@@ -23,29 +23,31 @@ type AppUpdateRes struct {
type AppDTO struct {
model.App
Installed bool `json:"installed"`
Versions []string `json:"versions"`
Tags []model.Tag `json:"tags"`
Installed bool `json:"installed"`
Versions []string `json:"versions"`
Tags []TagDTO `json:"tags"`
}
type AppDto struct {
Name string `json:"name"`
Key string `json:"key"`
ID uint `json:"id"`
ShortDescZh string `json:"shortDescZh"`
ShortDescEn string `json:"shortDescEn"`
Icon string `json:"icon"`
Type string `json:"type"`
Status string `json:"status"`
Resource string `json:"resource"`
Installed bool `json:"installed"`
Versions []string `json:"versions"`
Limit int `json:"limit"`
Tags []model.Tag `json:"tags"`
type AppItem struct {
Name string `json:"name"`
Key string `json:"key"`
ID uint `json:"id"`
Description string `json:"description"`
Icon string `json:"icon"`
Type string `json:"type"`
Status string `json:"status"`
Resource string `json:"resource"`
Installed bool `json:"installed"`
Versions []string `json:"versions"`
Limit int `json:"limit"`
Tags []TagDTO `json:"tags"`
GpuSupport bool `json:"gpuSupport"`
}
type TagDTO struct {
model.Tag
ID uint `json:"id"`
Key string `json:"key"`
Name string `json:"name"`
}
type AppInstalledCheck struct {
@@ -65,10 +67,11 @@ type AppInstalledCheck struct {
type AppDetailDTO struct {
model.AppDetail
Enable bool `json:"enable"`
Params interface{} `json:"params"`
Image string `json:"image"`
HostMode bool `json:"hostMode"`
Enable bool `json:"enable"`
Params interface{} `json:"params"`
Image string `json:"image"`
HostMode bool `json:"hostMode"`
GpuSupport bool `json:"gpuSupport"`
}
type IgnoredApp struct {
@@ -89,9 +92,10 @@ type AppInstalledDTO struct {
}
type AppDetail struct {
Website string `json:"website"`
Document string `json:"document"`
Github string `json:"github"`
Website string `json:"website"`
Document string `json:"document"`
Github string `json:"github"`
GpuSupport bool `json:"gpuSupport"`
}
type AppInstallDTO struct {
+9
View File
@@ -2,6 +2,7 @@ package response
import (
"github.com/1Panel-dev/1Panel/backend/utils/files"
"time"
)
type FileInfo struct {
@@ -46,3 +47,11 @@ type FileLineContent struct {
type FileExist struct {
Exist bool `json:"exist"`
}
type ExistFileInfo struct {
Name string `json:"name"`
Path string `json:"path"`
Size float64 `json:"size"`
ModTime time.Time `json:"modTime"`
IsDir bool `json:"isDir"`
}
+26
View File
@@ -0,0 +1,26 @@
package response
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type McpServersRes struct {
Items []McpServerDTO `json:"items"`
Total int64 `json:"total"`
}
type McpServerDTO struct {
model.McpServer
Environments []request.Environment `json:"environments"`
Volumes []request.Volume `json:"volumes"`
}
type McpBindDomainRes struct {
Domain string `json:"domain"`
SSLID uint `json:"sslID"`
AcmeAccountID uint `json:"acmeAccountID"`
AllowIPs []string `json:"allowIPs"`
WebsiteID uint `json:"websiteID"`
ConnUrl string `json:"connUrl"`
}
+3
View File
@@ -70,6 +70,8 @@ type SettingInfo struct {
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
LicenseVerify string `json:"licenseVerify"`
}
type SettingUpdate struct {
@@ -240,4 +242,5 @@ type ApiInterfaceConfig struct {
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
}
+11
View File
@@ -0,0 +1,11 @@
package model
type OllamaModel struct {
BaseModel
Name string `json:"name"`
Size string `json:"size"`
From string `json:"from"`
Status string `json:"status"`
Message string `json:"message"`
}
+18 -1
View File
@@ -1,7 +1,10 @@
package model
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/gin-gonic/gin"
"path/filepath"
"strings"
)
@@ -12,10 +15,12 @@ type App struct {
Key string `json:"key" gorm:"type:varchar(64);not null;"`
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh" gorm:"type:longtext;"`
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn" gorm:"type:longtext;"`
Description string `json:"description"`
Icon string `json:"icon" gorm:"type:longtext;"`
Type string `json:"type" gorm:"type:varchar(64);not null"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
Required string `json:"required" gorm:"type:varchar(64);"`
GpuSupport bool `json:"gpuSupport"`
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
Limit int `json:"limit" gorm:"type:Integer;not null"`
Website string `json:"website" gorm:"type:varchar(64);not null"`
@@ -36,8 +41,20 @@ func (i *App) IsLocalApp() bool {
}
func (i *App) GetAppResourcePath() string {
if i.IsLocalApp() {
//这里要去掉本地应用的local前缀
return filepath.Join(constant.LocalAppResourceDir, strings.TrimPrefix(i.Key, "local"))
}
return filepath.Join(constant.RemoteAppResourceDir, i.Key)
}
func (i *App) GetDescription(ctx *gin.Context) string {
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(i.Description), &translations)
lang := strings.ToLower(common.GetLang(ctx))
if desc, ok := translations[lang]; ok && desc != "" {
return desc
}
if lang == "zh" {
return i.ShortDescZh
}
return i.ShortDescEn
}
+18
View File
@@ -0,0 +1,18 @@
package model
type McpServer struct {
BaseModel
Name string `json:"name"`
DockerCompose string `json:"dockerCompose"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
Message string `json:"message"`
Port int `json:"port"`
Status string `json:"status"`
Env string `json:"env"`
BaseURL string `json:"baseUrl"`
SsePath string `json:"ssePath"`
WebsiteID int `json:"websiteID"`
Dir string `json:"dir"`
HostIP string `json:"hostIP"`
}
+4 -3
View File
@@ -2,7 +2,8 @@ package model
type Tag struct {
BaseModel
Key string `json:"key" gorm:"type:varchar(64);not null"`
Name string `json:"name" gorm:"type:varchar(64);not null"`
Sort int `json:"sort" gorm:"type:int;not null;default:1"`
Key string `json:"key" gorm:"type:varchar(64);not null"`
Name string `json:"name" gorm:"type:varchar(64);not null"`
Translations string `json:"translations"`
Sort int `json:"sort" gorm:"type:int;not null;default:1"`
}
+69
View File
@@ -0,0 +1,69 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
)
type AiRepo struct{}
type IAiRepo interface {
Get(opts ...DBOption) (model.OllamaModel, error)
List(opts ...DBOption) ([]model.OllamaModel, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.OllamaModel, error)
Create(cronjob *model.OllamaModel) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
}
func NewIAiRepo() IAiRepo {
return &AiRepo{}
}
func (u *AiRepo) Get(opts ...DBOption) (model.OllamaModel, error) {
var item model.OllamaModel
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&item).Error
return item, err
}
func (u *AiRepo) List(opts ...DBOption) ([]model.OllamaModel, error) {
var list []model.OllamaModel
db := global.DB.Model(&model.OllamaModel{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&list).Error
return list, err
}
func (u *AiRepo) Page(page, size int, opts ...DBOption) (int64, []model.OllamaModel, error) {
var list []model.OllamaModel
db := global.DB.Model(&model.OllamaModel{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&list).Error
return count, list, err
}
func (u *AiRepo) Create(item *model.OllamaModel) error {
return global.DB.Create(item).Error
}
func (u *AiRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.OllamaModel{}).Where("id = ?", id).Updates(vars).Error
}
func (u *AiRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.OllamaModel{}).Error
}
+7
View File
@@ -15,6 +15,7 @@ type DBOption func(*gorm.DB) *gorm.DB
type ICommonRepo interface {
WithByID(id uint) DBOption
WithByName(name string) DBOption
WithByLowerName(name string) DBOption
WithByType(tp string) DBOption
WithOrderBy(orderStr string) DBOption
WithOrderRuleBy(orderBy, order string) DBOption
@@ -45,6 +46,12 @@ func (c *CommonRepo) WithByName(name string) DBOption {
}
}
func (c *CommonRepo) WithByLowerName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("LOWER(name) = LOWER(?)", name)
}
}
func (c *CommonRepo) WithByDate(startTime, endTime time.Time) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("start_time > ? AND start_time < ?", startTime, endTime)
+7
View File
@@ -16,6 +16,7 @@ type IGroupRepo interface {
Delete(opts ...DBOption) error
CancelDefault(groupType string) error
WithByHostDefault() DBOption
WithByWebsiteDefault() DBOption
}
func NewIGroupRepo() IGroupRepo {
@@ -56,6 +57,12 @@ func (u *GroupRepo) WithByHostDefault() DBOption {
}
}
func (u *GroupRepo) WithByWebsiteDefault() DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("is_default = ? AND type = ?", 1, "website")
}
}
func (u *GroupRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
+56
View File
@@ -0,0 +1,56 @@
package repo
import "github.com/1Panel-dev/1Panel/backend/app/model"
type McpServerRepo struct {
}
type IMcpServerRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error)
GetFirst(opts ...DBOption) (*model.McpServer, error)
Create(mcpServer *model.McpServer) error
Save(mcpServer *model.McpServer) error
DeleteBy(opts ...DBOption) error
List(opts ...DBOption) ([]model.McpServer, error)
}
func NewIMcpServerRepo() IMcpServerRepo {
return &McpServerRepo{}
}
func (m McpServerRepo) Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error) {
var servers []model.McpServer
db := getDb(opts...).Model(&model.McpServer{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&servers).Error
return count, servers, err
}
func (m McpServerRepo) GetFirst(opts ...DBOption) (*model.McpServer, error) {
var mcpServer model.McpServer
if err := getDb(opts...).First(&mcpServer).Error; err != nil {
return nil, err
}
return &mcpServer, nil
}
func (m McpServerRepo) List(opts ...DBOption) ([]model.McpServer, error) {
var mcpServers []model.McpServer
if err := getDb(opts...).Find(&mcpServers).Error; err != nil {
return nil, err
}
return mcpServers, nil
}
func (m McpServerRepo) Create(mcpServer *model.McpServer) error {
return getDb().Create(mcpServer).Error
}
func (m McpServerRepo) Save(mcpServer *model.McpServer) error {
return getDb().Save(mcpServer).Error
}
func (m McpServerRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.McpServer{}).Error
}
+5
View File
@@ -16,6 +16,7 @@ type ISettingRepo interface {
Create(key, value string) error
Update(key, value string) error
WithByKey(key string) DBOption
UpdateOrCreate(key, value string) error
CreateMonitorBase(model model.MonitorBase) error
BatchCreateMonitorIO(ioList []model.MonitorIO) error
@@ -85,3 +86,7 @@ func (u *SettingRepo) DelMonitorIO(timeForDelete time.Time) error {
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
}
func (u *SettingRepo) UpdateOrCreate(key, value string) error {
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Assign(model.Setting{Key: key, Value: value}).FirstOrCreate(&model.Setting{}).Error
}
+415
View File
@@ -0,0 +1,415 @@
package service
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
type AIToolService struct{}
type IAIToolService interface {
Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error)
Create(name string) error
Close(name string) error
Recreate(name string) error
Delete(req dto.ForceDelete) error
Sync() ([]dto.OllamaModelDropList, error)
LoadDetail(name string) (string, error)
BindDomain(req dto.OllamaBindDomain) error
GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error)
UpdateBindDomain(req dto.OllamaBindDomain) error
}
func NewIAIToolService() IAIToolService {
return &AIToolService{}
}
func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) {
var options []repo.DBOption
if len(req.Info) != 0 {
options = append(options, commonRepo.WithLikeName(req.Info))
}
total, list, err := aiRepo.Page(req.Page, req.PageSize, options...)
if err != nil {
return 0, nil, err
}
var dtoLists []dto.OllamaModelInfo
for _, itemModel := range list {
var item dto.OllamaModelInfo
if err := copier.Copy(&item, &itemModel); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
logPath := path.Join(global.CONF.System.DataDir, "log", "AITools", itemModel.Name)
if _, err := os.Stat(logPath); err == nil {
item.LogFileExist = true
}
dtoLists = append(dtoLists, item)
}
return int64(total), dtoLists, err
}
func (u *AIToolService) LoadDetail(name string) (string, error) {
if cmd.CheckIllegal(name) {
return "", buserr.New(constant.ErrCmdIllegal)
}
containerName, err := LoadContainerName()
if err != nil {
return "", err
}
stdout, err := cmd.Execf("docker exec %s ollama show %s", containerName, name)
if err != nil {
return "", err
}
return stdout, err
}
func (u *AIToolService) Create(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New(constant.ErrCmdIllegal)
}
modelInfo, _ := aiRepo.Get(commonRepo.WithByName(name))
if modelInfo.ID != 0 {
return constant.ErrRecordExist
}
containerName, err := LoadContainerName()
if err != nil {
return err
}
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", name)
if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil {
return err
}
}
info := model.OllamaModel{
Name: name,
From: "local",
Status: constant.StatusWaiting,
}
if err := aiRepo.Create(&info); err != nil {
return err
}
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
go pullOllamaModel(file, containerName, info)
return nil
}
func (u *AIToolService) Close(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New(constant.ErrCmdIllegal)
}
containerName, err := LoadContainerName()
if err != nil {
return err
}
stdout, err := cmd.Execf("docker exec %s ollama stop %s", containerName, name)
if err != nil {
return fmt.Errorf("handle ollama stop %s failed, stdout: %s, err: %v", name, stdout, err)
}
return nil
}
func (u *AIToolService) Recreate(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New(constant.ErrCmdIllegal)
}
modelInfo, _ := aiRepo.Get(commonRepo.WithByName(name))
if modelInfo.ID == 0 {
return constant.ErrRecordNotFound
}
containerName, err := LoadContainerName()
if err != nil {
return err
}
if err := aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusWaiting, "from": "local"}); err != nil {
return err
}
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", name)
if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil {
return err
}
}
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
go pullOllamaModel(file, containerName, modelInfo)
return nil
}
func (u *AIToolService) Delete(req dto.ForceDelete) error {
ollamaList, _ := aiRepo.List(commonRepo.WithIdsIn(req.IDs))
if len(ollamaList) == 0 {
return constant.ErrRecordNotFound
}
containerName, err := LoadContainerName()
if err != nil && !req.ForceDelete {
return err
}
for _, item := range ollamaList {
if item.Status != constant.StatusDeleted {
stdout, err := cmd.Execf("docker exec %s ollama rm %s", containerName, item.Name)
if err != nil && !req.ForceDelete {
return fmt.Errorf("handle ollama rm %s failed, stdout: %s, err: %v", item.Name, stdout, err)
}
}
_ = aiRepo.Delete(commonRepo.WithByID(item.ID))
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", item.Name)
_ = os.Remove(logItem)
}
return nil
}
func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
containerName, err := LoadContainerName()
if err != nil {
return nil, err
}
stdout, err := cmd.Execf("docker exec %s ollama list", containerName)
if err != nil {
return nil, err
}
var list []model.OllamaModel
lines := strings.Split(stdout, "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 5 {
continue
}
if parts[0] == "NAME" {
continue
}
list = append(list, model.OllamaModel{Name: parts[0], Size: parts[2] + " " + parts[3]})
}
listInDB, _ := aiRepo.List()
var dropList []dto.OllamaModelDropList
for _, itemModel := range listInDB {
isExit := false
for i := 0; i < len(list); i++ {
if list[i].Name == itemModel.Name {
_ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusSuccess, "message": "", "size": list[i].Size})
list = append(list[:i], list[(i+1):]...)
isExit = true
break
}
}
if !isExit && itemModel.Status != constant.StatusWaiting {
_ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusDeleted, "message": "not exist", "size": ""})
dropList = append(dropList, dto.OllamaModelDropList{ID: itemModel.ID, Name: itemModel.Name})
continue
}
}
for _, item := range list {
item.Status = constant.StatusSuccess
item.From = "remote"
_ = aiRepo.Create(&item)
}
return dropList, nil
}
func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
if nginxInstall.ID == 0 {
return buserr.New("ErrOpenrestyInstall")
}
var (
ipList []string
err error
)
if len(req.IPList) > 0 {
ipList, err = common.HandleIPList(req.IPList)
if err != nil {
return err
}
}
if req.SSLID > 0 {
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
if err != nil {
return err
}
if ssl.Pem == "" {
return buserr.New("ErrSSL")
}
}
createWebsiteReq := request.WebsiteCreate{
PrimaryDomain: req.Domain,
Alias: strings.ToLower(req.Domain),
Type: constant.Deployment,
AppType: constant.InstalledApp,
AppInstallID: req.AppInstallID,
}
websiteService := NewIWebsiteService()
if err := websiteService.CreateWebsite(createWebsiteReq); err != nil {
return err
}
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain)))
if err != nil {
return err
}
if len(ipList) > 0 {
if err = ConfigAllowIPs(ipList, website); err != nil {
return err
}
}
if req.SSLID > 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: true,
Type: "existed",
WebsiteSSLID: req.SSLID,
HttpConfig: "HTTPSOnly",
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
}
if err = ConfigAIProxy(website); err != nil {
return err
}
return nil
}
func (u *AIToolService) GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.AppInstallID))
if err != nil {
return nil, err
}
res := &dto.OllamaBindDomainRes{}
website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(install.ID))
if website.ID == 0 {
return res, nil
}
res.WebsiteID = website.ID
res.Domain = website.PrimaryDomain
if website.WebsiteSSLID > 0 {
res.SSLID = website.WebsiteSSLID
ssl, _ := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID))
res.AcmeAccountID = ssl.AcmeAccountID
}
res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain)
res.AllowIPs = GetAllowIps(website)
return res, nil
}
func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
if nginxInstall.ID == 0 {
return buserr.New("ErrOpenrestyInstall")
}
var (
ipList []string
err error
)
if len(req.IPList) > 0 {
ipList, err = common.HandleIPList(req.IPList)
if err != nil {
return err
}
}
if req.SSLID > 0 {
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
if err != nil {
return err
}
if ssl.Pem == "" {
return buserr.New("ErrSSL")
}
}
websiteService := NewIWebsiteService()
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
if err = ConfigAllowIPs(ipList, website); err != nil {
return err
}
if req.SSLID > 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: true,
Type: "existed",
WebsiteSSLID: req.SSLID,
HttpConfig: "HTTPSOnly",
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
return nil
}
if website.WebsiteSSLID > 0 && req.SSLID == 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: false,
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
}
return nil
}
func LoadContainerName() (string, error) {
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
if err != nil {
return "", fmt.Errorf("ollama service is not found, err: %v", err)
}
if ollamaBaseInfo.Status != constant.Running {
return "", fmt.Errorf("container %s of ollama is not running, please check and retry!", ollamaBaseInfo.ContainerName)
}
return ollamaBaseInfo.ContainerName, nil
}
func pullOllamaModel(file *os.File, containerName string, info model.OllamaModel) {
defer file.Close()
cmd := exec.Command("docker", "exec", containerName, "ollama", "pull", info.Name)
multiWriter := io.MultiWriter(os.Stdout, file)
cmd.Stdout = multiWriter
cmd.Stderr = multiWriter
_ = cmd.Run()
itemSize, err := loadModelSize(info.Name, containerName)
if len(itemSize) != 0 {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
} else {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
}
_, _ = file.WriteString("ollama pull completed!")
}
func loadModelSize(name string, containerName string) (string, error) {
stdout, err := cmd.Execf("docker exec %s ollama list | grep %s", containerName, name)
if err != nil {
return "", err
}
lines := strings.Split(string(stdout), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 5 {
continue
}
return parts[2] + " " + parts[3], nil
}
return "", fmt.Errorf("no such model %s in ollama list, std: %s", name, string(stdout))
}
+53 -35
View File
@@ -5,6 +5,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"path/filepath"
@@ -34,9 +35,9 @@ type AppService struct {
}
type IAppService interface {
PageApp(req request.AppSearch) (interface{}, error)
GetAppTags() ([]response.TagDTO, error)
GetApp(key string) (*response.AppDTO, error)
PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error)
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
SyncAppListFromRemote() error
@@ -50,7 +51,7 @@ func NewIAppService() IAppService {
return &AppService{}
}
func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error) {
var opts []repo.DBOption
opts = append(opts, appRepo.OrderByRecommend())
if req.Name != "" {
@@ -89,31 +90,24 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
if err != nil {
return nil, err
}
var appDTOs []*response.AppDto
var appDTOs []*response.AppItem
lang := strings.ToLower(common.GetLang(ctx))
for _, ap := range apps {
appDTO := &response.AppDto{
ID: ap.ID,
Name: ap.Name,
Key: ap.Key,
Type: ap.Type,
Icon: ap.Icon,
ShortDescZh: ap.ShortDescZh,
ShortDescEn: ap.ShortDescEn,
Resource: ap.Resource,
Limit: ap.Limit,
appDTO := &response.AppItem{
ID: ap.ID,
Name: ap.Name,
Key: ap.Key,
Type: ap.Type,
Icon: ap.Icon,
Resource: ap.Resource,
Limit: ap.Limit,
GpuSupport: ap.GpuSupport,
}
appDTO.Description = ap.GetDescription(ctx)
appDTOs = append(appDTOs, appDTO)
appTags, err := appTagRepo.GetByAppId(ap.ID)
tags, err := getAppTags(ap.ID, lang)
if err != nil {
continue
}
var tagIds []uint
for _, at := range appTags {
tagIds = append(tagIds, at.TagId)
}
tags, err := tagRepo.GetByIds(tagIds)
if err != nil {
continue
return nil, err
}
appDTO.Tags = tags
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID))
@@ -125,18 +119,32 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
return res, nil
}
func (a AppService) GetAppTags() ([]response.TagDTO, error) {
func (a AppService) GetAppTags(ctx *gin.Context) ([]response.TagDTO, error) {
tags, _ := tagRepo.All()
res := make([]response.TagDTO, 0)
lang := strings.ToLower(common.GetLang(ctx))
for _, tag := range tags {
res = append(res, response.TagDTO{
Tag: tag,
})
tagDTO := response.TagDTO{
ID: tag.ID,
Key: tag.Key,
}
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(tag.Translations), &translations)
if name, ok := translations[lang]; ok && name != "" {
tagDTO.Name = name
} else {
if lang == "zh" || lang == "zh-Hant" {
tagDTO.Name = tag.Name
} else {
tagDTO.Name = tag.Key
}
}
res = append(res, tagDTO)
}
return res, nil
}
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, error) {
var appDTO response.AppDTO
if key == "postgres" {
key = "postgresql"
@@ -146,6 +154,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
return nil, err
}
appDTO.App = app
appDTO.App.Description = app.GetDescription(ctx)
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
if err != nil {
return nil, err
@@ -155,7 +164,12 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
versionsRaw = append(versionsRaw, detail.Version)
}
appDTO.Versions = common.GetSortedVersions(versionsRaw)
tags, err := getAppTags(app.ID, strings.ToLower(common.GetLang(ctx)))
if err != nil {
return nil, err
}
appDTO.GpuSupport = app.GpuSupport
appDTO.Tags = tags
return &appDTO, nil
}
@@ -252,6 +266,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
if err := checkLimit(app); err != nil {
appDetailDTO.Enable = false
}
appDetailDTO.GpuSupport = app.GpuSupport
return appDetailDTO, nil
}
func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
@@ -296,7 +311,8 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
return
}
if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 {
if list, _ := appInstallRepo.ListBy(commonRepo.WithByLowerName(req.Name)); len(list) > 0 {
err = buserr.New(constant.ErrAppNameExist)
return
}
@@ -827,10 +843,11 @@ func (a AppService) SyncAppListFromRemote() (err error) {
oldAppIds []uint
)
for _, t := range list.Extra.Tags {
translations, _ := json.Marshal(t.Locales)
tags = append(tags, &model.Tag{
Key: t.Key,
Name: t.Name,
Sort: t.Sort,
Key: t.Key,
Translations: string(translations),
Sort: t.Sort,
})
}
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
@@ -848,6 +865,7 @@ func (a AppService) SyncAppListFromRemote() (err error) {
global.LOG.Infof("Starting synchronization of application details...")
for _, l := range list.Apps {
app := appsMap[l.AppProperty.Key]
app.GpuSupport = l.AppProperty.GpuSupport
if l.AppProperty.Version > 0 && common.CompareVersion(strconv.FormatFloat(l.AppProperty.Version, 'f', -1, 64), setting.SystemVersion) {
delete(appsMap, l.AppProperty.Key)
+4
View File
@@ -750,6 +750,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
}
res.AppContainerConfig = config
res.HostMode = isHostModel(install.DockerCompose)
res.GpuConfig = isGpuConfig(install.DockerCompose)
return &res, nil
}
@@ -838,6 +839,9 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
}, commonRepo.WithByID(appInstall.ID))
if appKey == "mysql" || appKey == "postgresql" {
return nil
}
}
if param == "user-password" {
oldVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", appInstall.UserPassword)
+138 -14
View File
@@ -442,17 +442,14 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
if detail.DockerCompose == "" {
return "", nil
}
func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map[string]interface{}, error) {
composeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); err != nil {
return "", err
return nil, err
}
value, ok := composeMap["services"]
if !ok || value == nil {
return "", buserr.New(constant.ErrFileParse)
return nil, buserr.New("ErrFileParse")
}
servicesMap := value.(map[string]interface{})
if len(servicesMap) == 1 {
@@ -470,6 +467,34 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
delete(servicesMap, oldServiceName)
}
}
serviceValue := servicesMap[install.ServiceName].(map[string]interface{})
oldComposeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(install.DockerCompose), &oldComposeMap); err != nil {
return nil, err
}
oldValue, ok := oldComposeMap["services"]
if !ok || oldValue == nil {
return nil, buserr.New("ErrFileParse")
}
oldValueMap := oldValue.(map[string]interface{})
oldServiceValue := oldValueMap[install.ServiceName].(map[string]interface{})
if oldServiceValue["deploy"] != nil {
serviceValue["deploy"] = oldServiceValue["deploy"]
}
servicesMap[install.ServiceName] = serviceValue
composeMap["services"] = servicesMap
return composeMap, nil
}
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
if detail.DockerCompose == "" {
return "", nil
}
composeMap, err := handleUpgradeCompose(install, detail)
if err != nil {
return "", err
}
envs := make(map[string]interface{})
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
return "", err
@@ -1051,6 +1076,8 @@ func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App {
app.Key = key
app.ShortDescZh = config.ShortDescZh
app.ShortDescEn = config.ShortDescEn
description, _ := json.Marshal(config.Description)
app.Description = string(description)
app.Website = config.Website
app.Document = config.Document
app.Github = config.Github
@@ -1150,14 +1177,31 @@ func handleLocalApp(appDir string) (app *model.App, err error) {
err = buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
return
}
app = &localAppDefine.AppProperty
appDefine := localAppDefine.AppProperty
app = &model.App{}
app.Name = appDefine.Name
app.TagsKey = append(appDefine.Tags, "Local")
app.Type = appDefine.Type
app.CrossVersionUpdate = appDefine.CrossVersionUpdate
app.Limit = appDefine.Limit
app.Recommend = appDefine.Recommend
app.Website = appDefine.Website
app.Github = appDefine.Github
app.Document = appDefine.Document
if appDefine.ShortDescZh != "" {
app.ShortDescZh = appDefine.ShortDescZh
}
if appDefine.ShortDescEn != "" {
app.ShortDescEn = appDefine.ShortDescEn
}
desc, _ := json.Marshal(appDefine.Description)
app.Description = string(desc)
app.Key = "local" + appDefine.Key
app.Resource = constant.AppResourceLocal
app.Status = constant.AppNormal
app.Recommend = 9999
app.TagsKey = append(app.TagsKey, "Local")
app.Key = "local" + app.Key
readMePath := path.Join(appDir, "README.md")
readMeByte, err := fileOp.GetContent(readMePath)
readMeByte, err := fileOp.GetContent(path.Join(appDir, "README.md"))
if err == nil {
app.ReadMe = string(readMeByte)
}
@@ -1319,9 +1363,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
Path: installed.GetPath(),
CreatedAt: installed.CreatedAt,
App: response.AppDetail{
Github: installed.App.Github,
Website: installed.App.Website,
Document: installed.App.Document,
Github: installed.App.Github,
Website: installed.App.Website,
Document: installed.App.Document,
GpuSupport: installed.App.GpuSupport,
},
}
if updated {
@@ -1480,6 +1525,20 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
deploy["resources"] = resource
serviceValue["deploy"] = deploy
if req.GpuConfig {
resource["reservations"] = map[string]interface{}{
"devices": []map[string]interface{}{
{
"driver": "nvidia",
"count": "all",
"capabilities": []string{"gpu"},
},
},
}
} else {
delete(resource, "reservations")
}
ports, ok := serviceValue["ports"].([]interface{})
if ok {
for i, port := range ports {
@@ -1581,9 +1640,74 @@ func isHostModel(dockerCompose string) bool {
return false
}
func isGpuConfig(dockerCompose string) bool {
composeMap := make(map[string]interface{})
_ = yaml.Unmarshal([]byte(dockerCompose), &composeMap)
services, serviceValid := composeMap["services"].(map[string]interface{})
if !serviceValid {
return false
}
for _, service := range services {
serviceValue := service.(map[string]interface{})
deploy := map[string]interface{}{}
if de, ok := serviceValue["deploy"]; ok {
deploy = de.(map[string]interface{})
}
resource := map[string]interface{}{}
if res, ok := deploy["resources"]; ok {
resource = res.(map[string]interface{})
}
if reservations, ok := resource["reservations"]; ok {
reservationsMap := reservations.(map[string]interface{})
if _, dOk := reservationsMap["devices"]; dOk {
return true
}
}
}
return false
}
func RequestDownloadCallBack(downloadCallBackUrl string) {
if downloadCallBackUrl == "" {
return
}
_, _, _ = httpUtil.HandleGet(downloadCallBackUrl, http.MethodGet, constant.TimeOut5s)
}
func getAppTags(appID uint, lang string) ([]response.TagDTO, error) {
appTags, err := appTagRepo.GetByAppId(appID)
if err != nil {
return nil, err
}
var tagIds []uint
for _, at := range appTags {
tagIds = append(tagIds, at.TagId)
}
tags, err := tagRepo.GetByIds(tagIds)
if err != nil {
return nil, err
}
var res []response.TagDTO
for _, t := range tags {
if t.Name != "" {
tagDTO := response.TagDTO{
ID: t.ID,
Key: t.Key,
Name: t.Name,
}
res = append(res, tagDTO)
} else {
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(t.Translations), &translations)
if name, ok := translations[lang]; ok {
tagDTO := response.TagDTO{
ID: t.ID,
Key: t.Key,
Name: name,
}
res = append(res, tagDTO)
}
}
}
return res, nil
}
+31 -19
View File
@@ -38,16 +38,11 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
if nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
if err = checkPassword(info.Password); err != nil {
return nil, err
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
@@ -83,17 +78,12 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
return nil, err
}
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
if nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
if err = checkPassword(info.Password); err != nil {
return nil, err
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
return nil, err
@@ -214,8 +204,30 @@ func (u *AuthService) GetSecurityEntrance() string {
func (u *AuthService) IsLogin(c *gin.Context) bool {
sID, _ := c.Cookie(constant.SessionName)
_, err := global.SESSION.Get(sID)
return err == nil
}
func checkPassword(password string) error {
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))
privateKey, err := encrypt.ParseRSAPrivateKey(priKey.Value)
if err != nil {
return false
return err
}
return true
loginPassword, err := encrypt.DecryptPassword(password, privateKey)
if err != nil {
return err
}
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
existPassword, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
return err
}
if !hmac.Equal([]byte(loginPassword), []byte(existPassword)) {
return constant.ErrAuth
}
return nil
}
+53 -26
View File
@@ -8,7 +8,6 @@ import (
"fmt"
"os"
"path"
"sort"
"strings"
"sync"
"time"
@@ -30,7 +29,9 @@ type BackupService struct{}
type IBackupService interface {
List() ([]dto.BackupInfo, error)
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
LoadSize(req dto.RecordSearch) ([]dto.BackupFile, error)
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
LoadSizeByCronjob(req dto.RecordSearchByCronjob) ([]dto.BackupFile, error)
LoadOneDriveInfo() (dto.OneDriveInfo, error)
DownloadRecord(info dto.DownloadRecord) (string, error)
Create(backupDto dto.BackupOperate) error
@@ -94,11 +95,29 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
return 0, nil, err
}
datas, err := u.loadRecordSize(records)
sort.Slice(datas, func(i, j int) bool {
return datas[i].CreatedAt.After(datas[j].CreatedAt)
})
return total, datas, err
var list []dto.BackupRecords
for _, item := range records {
var itemRecord dto.BackupRecords
if err := copier.Copy(&itemRecord, &item); err != nil {
continue
}
list = append(list, itemRecord)
}
return total, list, err
}
func (u *BackupService) LoadSize(req dto.RecordSearch) ([]dto.BackupFile, error) {
_, records, err := backupRepo.PageRecord(
req.Page, req.PageSize,
commonRepo.WithOrderBy("created_at desc"),
commonRepo.WithByName(req.Name),
commonRepo.WithByType(req.Type),
backupRepo.WithByDetailName(req.DetailName),
)
if err != nil {
return nil, err
}
return u.loadRecordSize(records)
}
func (u *BackupService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
@@ -125,11 +144,27 @@ func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchBy
return 0, nil, err
}
datas, err := u.loadRecordSize(records)
sort.Slice(datas, func(i, j int) bool {
return datas[i].CreatedAt.After(datas[j].CreatedAt)
})
return total, datas, err
var list []dto.BackupRecords
for _, item := range records {
var itemRecord dto.BackupRecords
if err := copier.Copy(&itemRecord, &item); err != nil {
continue
}
list = append(list, itemRecord)
}
return total, list, err
}
func (u *BackupService) LoadSizeByCronjob(req dto.RecordSearchByCronjob) ([]dto.BackupFile, error) {
_, records, err := backupRepo.PageRecord(
req.Page, req.PageSize,
commonRepo.WithOrderBy("created_at desc"),
backupRepo.WithByCronID(req.CronjobID),
)
if err != nil {
return nil, err
}
return u.loadRecordSize(records)
}
type loadSizeHelper struct {
@@ -482,49 +517,41 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
return nil
}
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupRecords, error) {
var datas []dto.BackupRecords
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupFile, error) {
data := make([]dto.BackupFile, len(records))
clientMap := make(map[string]loadSizeHelper)
var wg sync.WaitGroup
for i := 0; i < len(records); i++ {
var item dto.BackupRecords
if err := copier.Copy(&item, &records[i]); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
data[i].ID = records[i].ID
data[i].Name = records[i].FileName
itemPath := path.Join(records[i].FileDir, records[i].FileName)
if _, ok := clientMap[records[i].Source]; !ok {
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].Source))
if err != nil {
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].Source, err)
clientMap[records[i].Source] = loadSizeHelper{}
datas = append(datas, item)
continue
}
client, err := u.NewClient(&backup)
if err != nil {
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].Source, err)
clientMap[records[i].Source] = loadSizeHelper{}
datas = append(datas, item)
continue
}
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
datas = append(datas, item)
data[i].Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
clientMap[records[i].Source] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true}
continue
}
if clientMap[records[i].Source].isOk {
wg.Add(1)
go func(index int) {
item.Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
datas = append(datas, item)
data[i].Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
return datas, nil
return data, nil
}
func loadLocalDir() (string, error) {
+5 -2
View File
@@ -2,13 +2,14 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -169,8 +170,9 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
Timeout: 300,
}); err != nil {
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
} else {
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
}
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
_ = os.RemoveAll(rollbackFile)
} else {
_ = os.RemoveAll(rollbackFile)
@@ -186,6 +188,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
Timeout: 300,
}); err != nil {
global.LOG.Errorf("recover mysql db %s from %s failed, err: %v", req.DetailName, req.File, err)
return err
}
isOk = true
+5 -2
View File
@@ -2,13 +2,14 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/common"
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
@@ -157,8 +158,9 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
Timeout: 300,
}); err != nil {
global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
} else {
global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile)
}
global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile)
_ = os.RemoveAll(rollbackFile)
} else {
_ = os.RemoveAll(rollbackFile)
@@ -171,6 +173,7 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
Username: dbInfo.Username,
Timeout: 300,
}); err != nil {
global.LOG.Errorf("recover postgresql db %s from %s failed, err: %v", req.DetailName, req.File, err)
return err
}
isOk = true
+103 -129
View File
@@ -1,7 +1,6 @@
package service
import (
"bufio"
"fmt"
"os"
"os/exec"
@@ -28,14 +27,14 @@ import (
)
const (
clamServiceNameCentOs = "clamd@scan.service"
clamServiceNameUbuntu = "clamav-daemon.service"
freshClamService = "clamav-freshclam.service"
resultDir = "clamav"
clamServiceKey = "clam"
freshClamServiceKey = "freshclam"
resultDir = "clamav"
)
type ClamService struct {
serviceName string
serviceName string
freshClamService string
}
type IClamService interface {
@@ -63,23 +62,32 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
var baseInfo dto.ClamBaseInfo
baseInfo.Version = "-"
baseInfo.FreshVersion = "-"
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
c.serviceName = clamServiceNameCentOs
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
clamSvc, err := systemctl.GetServiceName(clamServiceKey)
if err != nil {
baseInfo.IsExist = false
return baseInfo, nil
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
c.serviceName = clamServiceNameUbuntu
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
c.serviceName = clamSvc
isExist, err := systemctl.IsExist(c.serviceName)
if err != nil {
baseInfo.IsExist = false
}
freshExist, _ := systemctl.IsExist(freshClamService)
if freshExist {
baseInfo.FreshIsExist = true
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
baseInfo.IsExist = isExist
baseInfo.IsActive, _ = systemctl.IsActive(clamSvc)
freshSvc, err := systemctl.GetServiceName(freshClamServiceKey)
if err != nil {
baseInfo.FreshIsExist = false
return baseInfo, nil
}
c.freshClamService = freshSvc
freshisExist, err := systemctl.IsExist(c.freshClamService)
if err != nil {
baseInfo.FreshIsExist = false
}
baseInfo.FreshIsExist = freshisExist
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshSvc)
if !cmd.Which("clamdscan") {
baseInfo.IsActive = false
}
@@ -110,22 +118,27 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
}
func (c *ClamService) Operate(operate string) error {
var err error
switch operate {
case "start", "restart", "stop":
stdout, err := cmd.Execf("systemctl %s %s", operate, c.serviceName)
if err != nil {
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
}
return nil
case "fresh-start", "fresh-restart", "fresh-stop":
stdout, err := cmd.Execf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
if err != nil {
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
}
return nil
case "start":
err = systemctl.Start(c.serviceName)
case "stop":
err = systemctl.Stop(c.serviceName)
case "restart":
err = systemctl.Restart(c.serviceName)
case "fresh-start":
err = systemctl.Start(c.freshClamService)
case "fresh-stop":
err = systemctl.Stop(c.freshClamService)
case "fresh-restart":
err = systemctl.Restart(c.freshClamService)
default:
return fmt.Errorf("not support such operation: %v", operate)
return fmt.Errorf("unsupported operation: %s", operate)
}
if err != nil {
return fmt.Errorf("%s %s failed: %v", operate, c.serviceName, err)
}
return nil
}
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
@@ -432,102 +445,80 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
filePath := ""
switch req.Name {
case "clamd":
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/clamd.conf"
} else {
filePath = "/etc/clamd.d/scan.conf"
}
filePath = c.getConfigPath("clamd")
case "clamd-log":
filePath = c.loadLogPath("clamd-log")
if len(filePath) != 0 {
break
}
if c.serviceName == clamServiceNameUbuntu {
filePath = "/var/log/clamav/clamav.log"
} else {
filePath = "/var/log/clamd.scan"
}
case "freshclam":
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
}
filePath = c.getConfigPath("freshclam")
case "freshclam-log":
filePath = c.loadLogPath("freshclam-log")
if len(filePath) != 0 {
break
}
if c.serviceName == clamServiceNameUbuntu {
filePath = "/var/log/clamav/freshclam.log"
} else {
filePath = "/var/log/freshclam.log"
}
default:
return "", fmt.Errorf("not support such type")
return "", fmt.Errorf("unsupported file type")
}
if _, err := os.Stat(filePath); err != nil {
content, err := systemctl.ViewConfig(filePath, systemctl.ConfigOption{TailLines: req.Tail})
if err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
var tail string
if req.Tail != "0" {
tail = req.Tail
} else {
tail = "+1"
}
cmd := exec.Command("tail", "-n", tail, filePath)
stdout, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
}
return string(stdout), nil
return content, nil
}
func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
filePath := ""
service := ""
var (
filePath string
service string
)
switch req.Name {
case "clamd":
if c.serviceName == clamServiceNameUbuntu {
service = clamServiceNameUbuntu
filePath = "/etc/clamav/clamd.conf"
} else {
service = clamServiceNameCentOs
filePath = "/etc/clamd.d/scan.conf"
}
filePath = c.getConfigPath("clamd")
service = c.serviceName
case "freshclam":
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
}
service = "clamav-freshclam.service"
filePath = c.getConfigPath("freshclam")
service = c.freshClamService
default:
return fmt.Errorf("not support such type")
return fmt.Errorf("unsupported file type")
}
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.File)
write.Flush()
_ = systemctl.Restart(service)
if _, err := file.WriteString(req.File); err != nil {
return err
}
if err := systemctl.Restart(service); err != nil {
return fmt.Errorf("restart %s failed: %v", service, err)
}
return nil
}
func (c *ClamService) getConfigPath(confType string) string {
switch confType {
case "clamd":
if _, err := os.Stat("/etc/clamav/clamd.conf"); err == nil {
return "/etc/clamav/clamd.conf"
}
return "/etc/clamd.d/scan.conf"
case "freshclam":
if _, err := os.Stat("/etc/clamav/freshclam.conf"); err == nil {
return "/etc/clamav/freshclam.conf"
}
return "/etc/freshclam.conf"
default:
return ""
}
}
func StopAllCronJob(withCheck bool) bool {
if withCheck {
isActive := false
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
isexist, _ := systemctl.IsExist(clamServiceKey)
if isexist {
isActive, _ = systemctl.IsActive(clamServiceKey)
}
if isActive {
return false
@@ -590,42 +581,25 @@ func loadResultFromLog(pathItem string) dto.ClamLog {
return data
}
func (c *ClamService) loadLogPath(name string) string {
confPath := ""
if name == "clamd-log" {
if c.serviceName == clamServiceNameUbuntu {
confPath = "/etc/clamav/clamd.conf"
} else {
confPath = "/etc/clamd.d/scan.conf"
}
} else {
if c.serviceName == clamServiceNameUbuntu {
confPath = "/etc/clamav/freshclam.conf"
} else {
confPath = "/etc/freshclam.conf"
}
}
if _, err := os.Stat(confPath); err != nil {
return ""
configKey := "clamd"
searchPrefix := "LogFile "
if name != "clamd-log" {
configKey = "freshclam"
searchPrefix = "UpdateLogFile "
}
confPath := c.getConfigPath(configKey)
content, err := os.ReadFile(confPath)
if err != nil {
global.LOG.Debugf("Failed to read %s config: %v", configKey, err)
return ""
}
lines := strings.Split(string(content), "\n")
if name == "clamd-log" {
for _, line := range lines {
if strings.HasPrefix(line, "LogFile ") {
return strings.Trim(strings.ReplaceAll(line, "LogFile ", ""), " ")
}
}
} else {
for _, line := range lines {
if strings.HasPrefix(line, "UpdateLogFile ") {
return strings.Trim(strings.ReplaceAll(line, "UpdateLogFile ", ""), " ")
}
for _, line := range strings.Split(string(content), "\n") {
if strings.HasPrefix(line, searchPrefix) {
return strings.TrimSpace(strings.TrimPrefix(line, searchPrefix))
}
}
return ""
}
+34 -25
View File
@@ -255,14 +255,14 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
if err != nil {
return nil, err
}
var datas []dto.ContainerListStats
datas := make([]dto.ContainerListStats, len(list))
var wg sync.WaitGroup
wg.Add(len(list))
for i := 0; i < len(list); i++ {
go func(item types.Container) {
datas = append(datas, loadCpuAndMem(client, item.ID))
go func(index int, item types.Container) {
datas[index] = loadCpuAndMem(client, item.ID)
wg.Done()
}(list[i])
}(i, list[i])
}
wg.Wait()
return datas, nil
@@ -450,12 +450,14 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
networkSettings := oldContainer.NetworkSettings
bridgeNetworkSettings := networkSettings.Networks[data.Network]
if bridgeNetworkSettings.IPAMConfig != nil {
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address
data.Ipv4 = ipv4Address
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address
data.Ipv6 = ipv6Address
if data.Network != "bridge" {
data.Ipv4 = bridgeNetworkSettings.IPAMConfig.IPv4Address
data.Ipv6 = bridgeNetworkSettings.IPAMConfig.IPv6Address
}
} else {
data.Ipv4 = bridgeNetworkSettings.IPAddress
if data.Network != "bridge" {
data.Ipv4 = bridgeNetworkSettings.IPAddress
}
}
data.Cmd = oldContainer.Config.Cmd
@@ -556,10 +558,7 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
hostConf := oldContainer.HostConfig
var networkConf network.NetworkingConfig
if oldContainer.NetworkSettings != nil {
for networkKey := range oldContainer.NetworkSettings.Networks {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
networkConf.EndpointsConfig = oldContainer.NetworkSettings.Networks
}
if err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}); err != nil {
return err
@@ -937,15 +936,27 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
}
return cpuPercent
}
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
memPercent := 0.0
memUsage := float64(memStats.Usage)
memLimit := float64(memStats.Limit)
memUsage := calculateMemUsageUnixNoCache(memStats)
memLimit := memStats.Limit
if memUsage > 0.0 && memLimit > 0.0 {
memPercent = (memUsage / memLimit) * 100.0
memPercent = (float64(memUsage) / float64(memLimit)) * 100.0
}
return memPercent
}
func calculateMemUsageUnixNoCache(mem types.MemoryStats) uint64 {
if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
return mem.Usage - v
}
if v := mem.Stats["inactive_file"]; v < mem.Usage {
return mem.Usage - v
}
return mem.Usage
}
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
for _, bioEntry := range blkio.IoServiceBytesRecursive {
switch strings.ToLower(bioEntry.Op) {
@@ -1063,7 +1074,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage)
data.MemoryCache = stats.MemoryStats.Stats["cache"]
data.MemoryUsage = stats.MemoryStats.Usage
data.MemoryUsage = calculateMemUsageUnixNoCache(stats.MemoryStats)
data.MemoryLimit = stats.MemoryStats.Limit
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
@@ -1183,9 +1194,10 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
for _, volume := range req.Volumes {
if volume.Type == "volume" {
hostConf.Mounts = append(hostConf.Mounts, mount.Mount{
Type: mount.Type(volume.Type),
Source: volume.SourceDir,
Target: volume.ContainerDir,
Type: mount.Type(volume.Type),
Source: volume.SourceDir,
Target: volume.ContainerDir,
ReadOnly: volume.Mode == "ro",
})
config.Volumes[volume.ContainerDir] = struct{}{}
} else {
@@ -1200,10 +1212,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
var oldNetworkConf network.NetworkingConfig
if networkConf != nil {
for networkKey := range networkConf.Networks {
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
oldNetworkConf.EndpointsConfig = networkConf.Networks
}
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
@@ -1262,12 +1271,12 @@ func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper {
var exposedPorts []dto.PortHelper
samePortMap := make(map[string]dto.PortHelper)
ports := transPortToStr(itemPorts)
var itemPort dto.PortHelper
for _, item := range ports {
itemStr := strings.Split(item, "->")
if len(itemStr) < 2 {
continue
}
var itemPort dto.PortHelper
lastIndex := strings.LastIndex(itemStr[0], ":")
if lastIndex == -1 {
itemPort.HostPort = itemStr[0]
+11 -2
View File
@@ -114,8 +114,17 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
if err != nil {
return err
}
if !req.CleanRemoteData {
for key := range accountMap {
if key != constant.Local {
delete(accountMap, key)
}
}
}
cronjob.RetainCopies = 0
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
if len(accountMap) != 0 {
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
}
} else {
u.removeExpiredLog(cronjob)
}
@@ -249,7 +258,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
global.Cron.Remove(cron.EntryID(idItem))
}
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, IsDelete: true}); err != nil {
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, CleanRemoteData: req.CleanRemoteData, IsDelete: true}); err != nil {
return err
}
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
+8 -1
View File
@@ -322,7 +322,8 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
if len(account) != 0 {
global.LOG.Debugf("start upload file to %s, dir: %s", account, path.Join(accountMap[account].backupPath, cloudSrc))
if _, err := accountMap[account].client.Upload(file, path.Join(accountMap[account].backupPath, cloudSrc)); err != nil {
return "", err
global.LOG.Errorf("upload file to %s failed, err: %v", account, err)
continue
}
global.LOG.Debugf("upload successful!")
}
@@ -349,12 +350,18 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
if cronjob.Type == "snapshot" {
for _, account := range accounts {
if len(account) != 0 {
if _, ok := accountMap[account]; !ok {
continue
}
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
}
}
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz")))
} else {
for _, account := range accounts {
if _, ok := accountMap[account]; !ok {
continue
}
if len(account) != 0 {
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
}
+28 -8
View File
@@ -10,19 +10,19 @@ import (
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/xpu"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
)
type DashboardService struct{}
@@ -341,7 +341,17 @@ func loadDiskInfo() []dto.DiskInfo {
}
func loadGPUInfo() []dto.GPUInfo {
list := xpack.LoadGpuInfo()
ok, client := gpu.New()
var list []interface{}
if ok {
info, err := client.LoadGpuInfo()
if err != nil || len(info.GPUs) == 0 {
return nil
}
for _, item := range info.GPUs {
list = append(list, item)
}
}
if len(list) == 0 {
return nil
}
@@ -359,7 +369,17 @@ func loadGPUInfo() []dto.GPUInfo {
}
func loadXpuInfo() []dto.XPUInfo {
list := xpack.LoadXpuInfo()
var list []interface{}
ok, xpuClient := xpu.New()
if ok {
xpus, err := xpuClient.LoadDashData()
if err != nil || len(xpus) == 0 {
return nil
}
for _, item := range xpus {
list = append(list, item)
}
}
if len(list) == 0 {
return nil
}
-4
View File
@@ -50,10 +50,6 @@ func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (st
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
case "redis-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "mysql-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
case "mariadb-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
+2 -1
View File
@@ -18,6 +18,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/shirou/gopsutil/v3/mem"
)
@@ -121,7 +122,7 @@ func (u *DeviceService) Update(key, value string) error {
return err
}
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
err := systemctl.Restart("1panel")
if err != nil {
global.LOG.Errorf("restart system for new time zone failed, err: %v", err)
}
+2 -2
View File
@@ -11,12 +11,12 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/google/uuid"
@@ -285,7 +285,7 @@ func (u *DeviceService) Clean(req []dto.Clean) {
if restart {
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
err := systemctl.Restart("1panel")
if err != nil {
global.LOG.Errorf("restart system port failed, err: %v", err)
}
+30 -26
View File
@@ -365,17 +365,18 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker"
sudo := cmd.SudoHandleCmd()
dockerCmd, err := getDockerRestartCommand()
h, err := systemctl.DefaultHandler(service)
if err != nil {
return err
}
if req.Operation == "stop" {
isSocketActive, _ := systemctl.IsActive("docker.socket")
if isSocketActive {
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
if err != nil {
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
socketHandle, err := systemctl.DefaultHandler("docker.socket")
if err == nil {
status, err := socketHandle.IsActive()
if err == nil && status.IsActive {
if std, err := socketHandle.ExecuteAction("stop"); err != nil {
global.LOG.Errorf("handle stop docker.socket failed, err: %v", std)
}
}
}
}
@@ -386,13 +387,20 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
}
}
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
if isDockerSnapInstalled() {
command := fmt.Sprintf("snap %s docker", req.Operation)
stdout, err := cmd.Exec(command)
if err != nil {
return fmt.Errorf("failed to restart docker: %v", stdout)
}
return nil
}
result, err := h.ExecuteAction(req.Operation)
if err != nil {
return errors.New(stdout)
return errors.New(result.Output)
}
return nil
}
func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize string) {
if opts, ok := daemonMap["log-opts"]; ok {
if len(logMaxFile) != 0 || len(logMaxSize) != 0 {
@@ -451,31 +459,27 @@ func validateDockerConfig() error {
return nil
}
if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") {
return fmt.Errorf("Docker configuration validation failed, err: %v", stdout)
return fmt.Errorf("docker configuration validation failed, err: %v", stdout)
}
return nil
}
func getDockerRestartCommand() (string, error) {
func isDockerSnapInstalled() bool {
stdout, err := cmd.Exec("which docker")
if err != nil {
return "", fmt.Errorf("failed to find docker: %v", err)
return false
}
dockerPath := stdout
if strings.Contains(dockerPath, "snap") {
return "snap", nil
}
return "systemctl", nil
stdout = strings.TrimSpace(stdout)
return strings.Contains(stdout, "snap")
}
func restartDocker() error {
restartCmd, err := getDockerRestartCommand()
if err != nil {
return err
if isDockerSnapInstalled() {
stdout, err := cmd.Exec("snap restart docker")
if err != nil {
return fmt.Errorf("failed to restart docker: %v", stdout)
}
return nil
}
stdout, err := cmd.Execf("%s restart docker", restartCmd)
if err != nil {
return fmt.Errorf("failed to restart Docker: %s", stdout)
}
return nil
return systemctl.Restart("docker")
}
+4
View File
@@ -12,6 +12,8 @@ var (
appInstallRepo = repo.NewIAppInstallRepo()
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
aiRepo = repo.NewIAiRepo()
mysqlRepo = repo.NewIMysqlRepo()
postgresqlRepo = repo.NewIPostgresqlRepo()
databaseRepo = repo.NewIDatabaseRepo()
@@ -44,4 +46,6 @@ var (
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
favoriteRepo = repo.NewIFavoriteRepo()
mcpServerRepo = repo.NewIMcpServerRepo()
)
+32 -1
View File
@@ -50,6 +50,7 @@ type IFileService interface {
ChangeMode(op request.FileCreate) error
BatchChangeModeAndOwner(op request.FileRoleReq) error
ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error)
BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo
}
var filteredPaths = []string{
@@ -365,10 +366,18 @@ func (f *FileService) MvFile(m request.FileMove) error {
return buserr.New(constant.ErrMovePathFailed)
}
}
var errs []error
if m.Type == "cut" {
if len(m.CoverPaths) > 0 {
for _, src := range m.CoverPaths {
if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil {
errs = append(errs, err)
global.LOG.Errorf("cut copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
}
}
}
return fo.Cut(m.OldPaths, m.NewPath, m.Name, m.Cover)
}
var errs []error
if m.Type == "copy" {
for _, src := range m.OldPaths {
if err := fo.CopyAndReName(src, m.NewPath, m.Name, m.Cover); err != nil {
@@ -478,6 +487,12 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
}
case "image-pull", "image-push", "image-build", "compose-create":
logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
case "ollama-model":
logFilePath = path.Join(global.CONF.System.DataDir, "log", "AITools", req.Name)
case "mysql-slow-logs":
logFilePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
case "mariadb-slow-logs":
logFilePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
}
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
@@ -501,3 +516,19 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
}
return res, nil
}
func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo {
fileList := make([]response.ExistFileInfo, 0, len(req.Paths))
for _, filePath := range req.Paths {
if info, err := os.Stat(filePath); err == nil {
fileList = append(fileList, response.ExistFileInfo{
Size: float64(info.Size()),
Name: info.Name(),
Path: filePath,
ModTime: info.ModTime(),
IsDir: info.IsDir(),
})
}
}
return fileList
}
+4
View File
@@ -381,6 +381,10 @@ func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error {
TargetIP: r.TargetIP,
TargetPort: r.TargetPort,
}, r.Operation); err != nil {
if req.ForceDelete {
global.LOG.Error(err)
continue
}
return err
}
}
+16 -11
View File
@@ -103,23 +103,28 @@ func (f *FtpService) Sync() error {
if err != nil {
return err
}
currentData := make(map[string]model.Ftp)
for _, item := range listsInDB {
currentData[item.User] = item
}
sameData := make(map[string]struct{})
for _, item := range lists {
for _, itemInDB := range listsInDB {
if item.User == itemInDB.User {
sameData[item.User] = struct{}{}
if item.Path != itemInDB.Path || item.Status != itemInDB.Status {
_ = ftpRepo.Update(itemInDB.ID, map[string]interface{}{"path": item.Path, "status": item.Status})
if itemInDB, ok := currentData[item.User]; ok {
sameData[item.User] = struct{}{}
if item.Path != itemInDB.Path || item.Status != itemInDB.Status {
if err := ftpRepo.Update(itemInDB.ID, map[string]interface{}{"path": item.Path, "status": item.Status}); err != nil {
return err
}
break
}
} else {
if err := ftpRepo.Create(&model.Ftp{User: item.User, Path: item.Path, Status: item.Status}); err != nil {
return err
}
}
}
for _, item := range lists {
if _, ok := sameData[item.User]; !ok {
_ = ftpRepo.Create(&model.Ftp{User: item.User, Path: item.Path, Status: item.Status})
}
}
for _, item := range listsInDB {
if _, ok := sameData[item.User]; !ok {
_ = ftpRepo.Update(item.ID, map[string]interface{}{"status": "deleted"})
+119 -89
View File
@@ -3,6 +3,12 @@ package service
import (
"bytes"
"fmt"
"os/exec"
"os/user"
"path"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/buserr"
@@ -14,11 +20,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors"
"gopkg.in/ini.v1"
"os/exec"
"os/user"
"path"
"strconv"
"strings"
)
type HostToolService struct{}
@@ -39,93 +40,108 @@ func NewIHostToolService() IHostToolService {
}
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
res := &response.HostToolRes{}
res.Type = req.Type
res := &response.HostToolRes{Type: req.Type}
switch req.Type {
case constant.Supervisord:
supervisorConfig := &response.Supervisor{}
if !cmd.Which(constant.Supervisord) {
supervisorConfig.IsExist = false
res.Config = supervisorConfig
return res, nil
}
supervisorConfig.IsExist = true
serviceExist, _ := systemctl.IsExist(constant.Supervisord)
if !serviceExist {
serviceExist, _ = systemctl.IsExist(constant.Supervisor)
if !serviceExist {
supervisorConfig.IsExist = false
res.Config = supervisorConfig
return res, nil
} else {
supervisorConfig.ServiceName = constant.Supervisor
}
} else {
supervisorConfig.ServiceName = constant.Supervisord
}
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
supervisorConfig.ServiceName = serviceNameSet.Value
}
versionRes, _ := cmd.Exec("supervisord -v")
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
_, ctlRrr := exec.LookPath("supervisorctl")
supervisorConfig.CtlExist = ctlRrr == nil
active, _ := systemctl.IsActive(supervisorConfig.ServiceName)
if active {
supervisorConfig.Status = "running"
} else {
supervisorConfig.Status = "stopped"
}
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
if pathSet.ID != 0 || pathSet.Value != "" {
supervisorConfig.ConfigPath = pathSet.Value
res.Config = supervisorConfig
return res, nil
} else {
supervisorConfig.Init = true
}
servicePath := "/usr/lib/systemd/system/supervisor.service"
fileOp := files.NewFileOp()
if !fileOp.Stat(servicePath) {
servicePath = "/usr/lib/systemd/system/supervisord.service"
}
if fileOp.Stat(servicePath) {
startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart")
if startCmd != "" {
args := strings.Fields(startCmd)
cIndex := -1
for i, arg := range args {
if arg == "-c" {
cIndex = i
break
}
}
if cIndex != -1 && cIndex+1 < len(args) {
supervisorConfig.ConfigPath = args[cIndex+1]
}
}
}
if supervisorConfig.ConfigPath == "" {
configPath := "/etc/supervisord.conf"
if !fileOp.Stat(configPath) {
configPath = "/etc/supervisor/supervisord.conf"
if fileOp.Stat(configPath) {
supervisorConfig.ConfigPath = configPath
}
}
}
res.Config = supervisorConfig
return h.getSupervisorStatus(res)
}
return res, nil
}
func (h *HostToolService) getSupervisorStatus(res *response.HostToolRes) (*response.HostToolRes, error) {
supervisorConfig := &response.Supervisor{}
if !cmd.Which(constant.Supervisord) {
supervisorConfig.IsExist = false
res.Config = supervisorConfig
return res, nil
}
supervisorConfig.IsExist = true
serviceName, err := h.determineServiceName()
if err != nil || serviceName == "" {
supervisorConfig.IsExist = false
res.Config = supervisorConfig
return res, nil
}
supervisorConfig.ServiceName = serviceName
if nameSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); nameSetting.Value != "" {
supervisorConfig.ServiceName = nameSetting.Value
}
if version, err := cmd.Exec("supervisord -v"); err == nil {
supervisorConfig.Version = strings.TrimSpace(version)
}
_, errCtl := exec.LookPath("supervisorctl")
supervisorConfig.CtlExist = errCtl == nil
if active, err := systemctl.IsActive(supervisorConfig.ServiceName); err == nil && active {
supervisorConfig.Status = "running"
} else {
supervisorConfig.Status = "stopped"
}
h.resolveConfigPath(supervisorConfig)
res.Config = supervisorConfig
return res, nil
}
func (h *HostToolService) determineServiceName() (string, error) {
if setting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); setting.Value != "" {
serviceName, err := systemctl.GetServiceName(setting.Value)
if err != nil {
return "", fmt.Errorf("get service name error: %v", err)
}
return serviceName, nil
}
if serviceName, err := systemctl.GetServiceName(constant.Supervisord); err == nil {
return serviceName, nil
}
return systemctl.GetServiceName(constant.Supervisor)
}
func (h *HostToolService) resolveConfigPath(config *response.Supervisor) {
if pathSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)); pathSetting.Value != "" {
config.ConfigPath = pathSetting.Value
return
}
config.Init = true
if servicePath, err := systemctl.GetServicePath(config.ServiceName); err == nil {
if startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart"); startCmd != "" {
if path := parseConfigPathFromCommand(startCmd); path != "" {
config.ConfigPath = path
return
}
}
}
defaultPaths := []string{
"/etc/supervisord.conf",
"/etc/supervisor/supervisord.conf",
"/usr/local/etc/supervisord.conf",
}
for _, path := range defaultPaths {
if systemctl.FileExist(path) {
config.ConfigPath = path
return
}
}
}
func parseConfigPathFromCommand(cmd string) string {
parts := strings.Fields(cmd)
for i, part := range parts {
if part == "-c" && i+1 < len(parts) {
return parts[i+1]
}
}
return ""
}
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
switch req.Type {
case constant.Supervisord:
@@ -200,14 +216,28 @@ func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
}
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
serviceName := req.Type
if req.Type == constant.Supervisord {
var serviceName string
var err error
switch req.Type {
case constant.Supervisord:
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
serviceName = serviceNameSet.Value
} else {
serviceName = req.Type
}
default:
serviceName = req.Type
}
return systemctl.Operate(req.Operate, serviceName)
serviceName, err = systemctl.GetServiceName(serviceName)
if err != nil {
if errors.Is(err, systemctl.ErrServiceNotFound) {
return fmt.Errorf("%s service unavailable. Please ensure supervisor server is installed and configured", serviceName)
}
return err
}
_, err = systemctl.CustomAction(req.Operate, serviceName)
return err
}
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
+3
View File
@@ -439,6 +439,9 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
}
return buserr.WithDetail(constant.ErrInUsed, id, nil)
}
if strings.Contains(err.Error(), "image has dependent") {
return buserr.New(constant.ErrObjectBeDependent)
}
return err
}
}
+73 -47
View File
@@ -26,7 +26,7 @@ type IImageRepoService interface {
Login(req dto.OperateByID) error
Create(req dto.ImageRepoCreate) error
Update(req dto.ImageRepoUpdate) error
BatchDelete(req dto.ImageRepoDelete) error
Delete(req dto.OperateByID) error
}
func NewIImageRepoService() IImageRepoService {
@@ -89,31 +89,7 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
if err := func() error {
for range ticker.C {
select {
case <-ctx.Done():
cancel()
return errors.New("the docker service cannot be restarted")
default:
stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) == "active\n" && err == nil {
global.LOG.Info("docker restart with new conf successful!")
return nil
}
}
}
return nil
}(); err != nil {
if err := stopBeforeUpdateRepo(); err != nil {
return err
}
}
@@ -131,15 +107,32 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
return imageRepoRepo.Create(&imageRepo)
}
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
for _, id := range req.Ids {
if id == 1 {
return errors.New("The default value cannot be edit !")
}
func (u *ImageRepoService) Delete(req dto.OperateByID) error {
if req.ID == 1 {
return errors.New("The default value cannot be edit !")
}
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
itemRepo, _ := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
if itemRepo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if itemRepo.Auth {
_, _ = cmd.Execf("docker logout -i %s", itemRepo.DownloadUrl)
}
if itemRepo.Protocol == "https" {
return imageRepoRepo.Delete(commonRepo.WithByID(req.ID))
}
if err := u.handleRegistries("", itemRepo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", itemRepo.DownloadUrl, err)
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := imageRepoRepo.Delete(commonRepo.WithByID(req.ID)); err != nil {
return err
}
go func() {
_ = restartDocker()
}()
return nil
}
@@ -154,37 +147,39 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if err != nil {
return err
}
needRestart := false
if repo.Protocol == "http" && req.Protocol == "https" {
if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err)
}
needRestart = true
}
if repo.Protocol == "http" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
if repo.DownloadUrl != req.DownloadUrl {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
needRestart = true
}
}
if repo.Protocol == "https" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
needRestart = true
}
if repo.Auth != req.Auth || repo.DownloadUrl != req.DownloadUrl {
if repo.Auth {
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
if needRestart {
if err := stopBeforeUpdateRepo(); err != nil {
return err
}
}
if err := validateDockerConfig(); err != nil {
return err
if repo.Auth {
_, _ = cmd.Execf("docker logout -i %s", repo.DownloadUrl)
}
if err := restartDocker(); err != nil {
return err
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
}
upMap := make(map[string]interface{})
@@ -256,3 +251,34 @@ func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) err
}
return nil
}
func stopBeforeUpdateRepo() error {
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
if err := func() error {
for range ticker.C {
select {
case <-ctx.Done():
cancel()
return errors.New("the docker service cannot be restarted")
default:
stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) == "active\n" && err == nil {
global.LOG.Info("docker restart with new conf successful!")
return nil
}
}
}
return nil
}(); err != nil {
return err
}
return nil
}
+6 -2
View File
@@ -2,6 +2,9 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/geo"
"github.com/gin-gonic/gin"
"os"
"path"
"path/filepath"
@@ -26,7 +29,7 @@ const logs = "https://resource.fit2cloud.com/installation-log.sh"
type ILogService interface {
ListSystemLogFile() ([]string, error)
CreateLoginLog(operation model.LoginLog) error
PageLoginLog(search dto.SearchLgLogWithPage) (int64, interface{}, error)
PageLoginLog(c *gin.Context, search dto.SearchLgLogWithPage) (int64, interface{}, error)
CreateOperationLog(operation model.OperationLog) error
PageOperationLog(search dto.SearchOpLogWithPage) (int64, interface{}, error)
@@ -77,7 +80,7 @@ func (u *LogService) ListSystemLogFile() ([]string, error) {
return files, nil
}
func (u *LogService) PageLoginLog(req dto.SearchLgLogWithPage) (int64, interface{}, error) {
func (u *LogService) PageLoginLog(c *gin.Context, req dto.SearchLgLogWithPage) (int64, interface{}, error) {
total, ops, err := logRepo.PageLoginLog(
req.Page,
req.PageSize,
@@ -91,6 +94,7 @@ func (u *LogService) PageLoginLog(req dto.SearchLgLogWithPage) (int64, interface
if err := copier.Copy(&item, &op); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.Address, _ = geo.GetIPLocation(item.IP, common.GetLang(c))
dtoOps = append(dtoOps, item)
}
return total, dtoOps, err
+643
View File
@@ -0,0 +1,643 @@
package service
import (
"context"
"errors"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
"github.com/1Panel-dev/1Panel/cmd/server/mcp"
"github.com/1Panel-dev/1Panel/cmd/server/nginx_conf"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"path"
"strconv"
"strings"
)
type McpServerService struct{}
type IMcpServerService interface {
Page(req request.McpServerSearch) response.McpServersRes
Create(create request.McpServerCreate) error
Update(req request.McpServerUpdate) error
Delete(id uint) error
Operate(req request.McpServerOperate) error
GetBindDomain() (response.McpBindDomainRes, error)
BindDomain(req request.McpBindDomain) error
UpdateBindDomain(req request.McpBindDomainUpdate) error
}
func NewIMcpServerService() IMcpServerService {
return &McpServerService{}
}
func (m McpServerService) Page(req request.McpServerSearch) response.McpServersRes {
var (
res response.McpServersRes
items []response.McpServerDTO
)
total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
for _, item := range data {
_ = syncMcpServerContainerStatus(&item)
serverDTO := response.McpServerDTO{
McpServer: item,
Environments: make([]request.Environment, 0),
Volumes: make([]request.Volume, 0),
}
project, _ := docker.GetComposeProject(item.Name, path.Join(constant.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
for _, service := range project.Services {
if service.Environment != nil {
for key, value := range service.Environment {
serverDTO.Environments = append(serverDTO.Environments, request.Environment{
Key: key,
Value: *value,
})
}
}
if service.Volumes != nil {
for _, volume := range service.Volumes {
serverDTO.Volumes = append(serverDTO.Volumes, request.Volume{
Source: volume.Source,
Target: volume.Target,
})
}
}
}
items = append(items, serverDTO)
}
res.Total = total
res.Items = items
return res
}
func (m McpServerService) Update(req request.McpServerUpdate) error {
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
if mcpServer.Port != req.Port {
if err := checkPortExist(req.Port); err != nil {
return err
}
}
if mcpServer.ContainerName != req.ContainerName {
if err := checkContainerName(req.ContainerName); err != nil {
return err
}
}
mcpServer.Name = req.Name
mcpServer.ContainerName = req.ContainerName
mcpServer.Port = req.Port
mcpServer.Command = req.Command
mcpServer.BaseURL = req.BaseURL
mcpServer.SsePath = req.SsePath
mcpServer.HostIP = req.HostIP
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
return err
}
env := handleEnv(mcpServer)
mcpDir := path.Join(constant.McpDir, mcpServer.Name)
envPath := path.Join(mcpDir, ".env")
if err := gotenv.Write(env, envPath); err != nil {
return err
}
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
if err := files.NewFileOp().SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
return err
}
mcpServer.Status = constant.RuntimeStarting
if err := mcpServerRepo.Save(mcpServer); err != nil {
return err
}
go startMcp(mcpServer)
return nil
}
func (m McpServerService) Create(create request.McpServerCreate) error {
servers, _ := mcpServerRepo.List()
for _, server := range servers {
if server.Port == create.Port {
return buserr.New("ErrPortInUsed")
}
if server.ContainerName == create.ContainerName {
return buserr.New("ErrContainerName")
}
if server.Name == create.Name {
return buserr.New(constant.ErrNameIsExist)
}
if server.SsePath == create.SsePath {
return buserr.New("ErrSsePath")
}
}
if err := checkPortExist(create.Port); err != nil {
return err
}
if err := checkContainerName(create.ContainerName); err != nil {
return err
}
mcpDir := path.Join(constant.McpDir, create.Name)
mcpServer := &model.McpServer{
Name: create.Name,
ContainerName: create.ContainerName,
Port: create.Port,
Command: create.Command,
Status: constant.RuntimeStarting,
BaseURL: create.BaseURL,
SsePath: create.SsePath,
Dir: mcpDir,
HostIP: create.HostIP,
}
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
return err
}
env := handleEnv(mcpServer)
filesOP := files.NewFileOp()
if !filesOP.Stat(mcpDir) {
_ = filesOP.CreateDir(mcpDir, 0644)
}
envPath := path.Join(mcpDir, ".env")
if err := gotenv.Write(env, envPath); err != nil {
return err
}
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
if err := filesOP.SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
return err
}
if err := mcpServerRepo.Create(mcpServer); err != nil {
return err
}
addProxy(mcpServer)
go startMcp(mcpServer)
return nil
}
func (m McpServerService) Delete(id uint) error {
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return err
}
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
_, _ = compose.Down(composePath)
_ = files.NewFileOp().DeleteDir(path.Join(constant.McpDir, mcpServer.Name))
websiteID := GetWebsiteID()
if websiteID > 0 {
websiteService := NewIWebsiteService()
delProxyReq := request.WebsiteProxyDel{
ID: websiteID,
Name: mcpServer.Name,
}
_ = websiteService.DeleteProxy(delProxyReq)
}
return mcpServerRepo.DeleteBy(commonRepo.WithByID(id))
}
func (m McpServerService) Operate(req request.McpServerOperate) error {
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
composePath := path.Join(mcpServer.Dir, "docker-compose.yml")
var out string
switch req.Operate {
case "start":
out, err = compose.Up(composePath)
mcpServer.Status = constant.RuntimeRunning
case "stop":
out, err = compose.Down(composePath)
mcpServer.Status = constant.RuntimeStopped
case "restart":
out, err = compose.Restart(composePath)
mcpServer.Status = constant.RuntimeRunning
}
if err != nil {
mcpServer.Status = constant.RuntimeError
mcpServer.Message = out
}
return mcpServerRepo.Save(mcpServer)
}
func (m McpServerService) GetBindDomain() (response.McpBindDomainRes, error) {
var res response.McpBindDomainRes
websiteID := GetWebsiteID()
if websiteID == 0 {
return res, nil
}
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
if err != nil {
return res, nil
}
res.WebsiteID = website.ID
res.Domain = website.PrimaryDomain
if website.WebsiteSSLID > 0 {
res.SSLID = website.WebsiteSSLID
ssl, _ := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID))
res.AcmeAccountID = ssl.AcmeAccountID
}
res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain)
res.AllowIPs = GetAllowIps(website)
return res, nil
}
func (m McpServerService) BindDomain(req request.McpBindDomain) error {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
if nginxInstall.ID == 0 {
return buserr.New("ErrOpenrestyInstall")
}
var (
ipList []string
err error
)
if len(req.IPList) > 0 {
ipList, err = common.HandleIPList(req.IPList)
if err != nil {
return err
}
}
if req.SSLID > 0 {
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
if err != nil {
return err
}
if ssl.Pem == "" {
return buserr.New("ErrSSL")
}
}
group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault())
createWebsiteReq := request.WebsiteCreate{
PrimaryDomain: req.Domain,
Alias: strings.ToLower(req.Domain),
Type: constant.Static,
WebsiteGroupID: group.ID,
}
websiteService := NewIWebsiteService()
if err := websiteService.CreateWebsite(createWebsiteReq); err != nil {
return err
}
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain)))
if err != nil {
return err
}
_ = settingRepo.UpdateOrCreate("MCP_WEBSITE_ID", fmt.Sprintf("%d", website.ID))
if len(ipList) > 0 {
if err = ConfigAllowIPs(ipList, website); err != nil {
return err
}
}
if req.SSLID > 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: true,
Type: "existed",
WebsiteSSLID: req.SSLID,
HttpConfig: "HTTPSOnly",
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
}
if err = addMCPProxy(website.ID); err != nil {
return err
}
return nil
}
func (m McpServerService) UpdateBindDomain(req request.McpBindDomainUpdate) error {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
if nginxInstall.ID == 0 {
return buserr.New("ErrOpenrestyInstall")
}
var (
ipList []string
err error
)
if len(req.IPList) > 0 {
ipList, err = common.HandleIPList(req.IPList)
if err != nil {
return err
}
}
if req.SSLID > 0 {
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
if err != nil {
return err
}
if ssl.Pem == "" {
return buserr.New("ErrSSL")
}
}
websiteService := NewIWebsiteService()
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
if err = ConfigAllowIPs(ipList, website); err != nil {
return err
}
if req.SSLID > 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: true,
Type: "existed",
WebsiteSSLID: req.SSLID,
HttpConfig: "HTTPSOnly",
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
}
if website.WebsiteSSLID > 0 && req.SSLID == 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: false,
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
}
go updateMcpConfig(website.ID)
return nil
}
func updateMcpConfig(websiteID uint) {
servers, _ := mcpServerRepo.List()
if len(servers) == 0 {
return
}
website, _ := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
websiteDomain := website.Domains[0]
var baseUrl string
if website.Protocol == constant.ProtocolHTTP {
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
} else {
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
}
go func() {
for _, server := range servers {
if server.BaseURL != baseUrl {
server.BaseURL = baseUrl
server.HostIP = "127.0.0.1"
_ = updateMcpServer(&server)
}
}
}()
}
func addProxy(server *model.McpServer) {
websiteID := GetWebsiteID()
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
if err != nil {
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
return
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
return
}
fileOp := files.NewFileOp()
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
if !fileOp.Stat(includeDir) {
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
return
}
}
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
if err != nil {
return
}
includePath := path.Join(includeDir, server.Name+".conf")
config.FilePath = includePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
if !ok {
return
}
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
location.ChangePath("^~", server.SsePath)
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr(constant.ErrUpdateBuWebsite, err))
return
}
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
global.LOG.Errorf("update nginx config failed, err: %v", err)
return
}
}
func addMCPProxy(websiteID uint) error {
servers, _ := mcpServerRepo.List()
if len(servers) == 0 {
return nil
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
if err != nil {
return err
}
fileOp := files.NewFileOp()
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
if !fileOp.Stat(includeDir) {
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
return err
}
}
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
if err != nil {
return err
}
websiteDomain := website.Domains[0]
var baseUrl string
if website.Protocol == constant.ProtocolHTTP {
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
} else {
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
}
if websiteDomain.Port != 80 && websiteDomain.Port != 443 {
baseUrl = fmt.Sprintf("%s:%d", baseUrl, websiteDomain.Port)
}
for _, server := range servers {
includePath := path.Join(includeDir, server.Name+".conf")
config.FilePath = includePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
if !ok {
err = errors.New("error")
return err
}
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
location.ChangePath("^~", server.SsePath)
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
server.BaseURL = baseUrl
server.HostIP = "127.0.0.1"
go updateMcpServer(&server)
}
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
return err
}
return nil
}
func updateMcpServer(mcpServer *model.McpServer) error {
env := handleEnv(mcpServer)
if err := gotenv.Write(env, path.Join(mcpServer.Dir, ".env")); err != nil {
return err
}
_ = mcpServerRepo.Save(mcpServer)
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
_, _ = compose.Down(composePath)
if _, err := compose.Up(composePath); err != nil {
mcpServer.Status = constant.RuntimeError
mcpServer.Message = err.Error()
}
return mcpServerRepo.Save(mcpServer)
}
func handleEnv(mcpServer *model.McpServer) gotenv.Env {
env := make(gotenv.Env)
env["CONTAINER_NAME"] = mcpServer.ContainerName
env["COMMAND"] = mcpServer.Command
env["PANEL_APP_PORT_HTTP"] = strconv.Itoa(mcpServer.Port)
env["BASE_URL"] = mcpServer.BaseURL
env["SSE_PATH"] = mcpServer.SsePath
env["HOST_IP"] = mcpServer.HostIP
envStr, _ := gotenv.Marshal(env)
mcpServer.Env = envStr
return env
}
func handleCreateParams(mcpServer *model.McpServer, environments []request.Environment, volumes []request.Volume) error {
var composeContent []byte
if mcpServer.ID == 0 {
composeContent = mcp.DefaultMcpCompose
} else {
composeContent = []byte(mcpServer.DockerCompose)
}
composeMap := make(map[string]interface{})
if err := yaml.Unmarshal(composeContent, &composeMap); err != nil {
return err
}
services, serviceValid := composeMap["services"].(map[string]interface{})
if !serviceValid {
return buserr.New(constant.ErrFileParse)
}
serviceName := ""
serviceValue := make(map[string]interface{})
if mcpServer.ID > 0 {
serviceName = mcpServer.Name
serviceValue = services[serviceName].(map[string]interface{})
} else {
for name, service := range services {
serviceName = name
serviceValue = service.(map[string]interface{})
break
}
delete(services, serviceName)
}
delete(serviceValue, "environment")
if len(environments) > 0 {
envMap := make(map[string]string)
for _, env := range environments {
envMap[env.Key] = env.Value
}
serviceValue["environment"] = envMap
}
delete(serviceValue, "volumes")
if len(volumes) > 0 {
volumeList := make([]string, 0)
for _, volume := range volumes {
volumeList = append(volumeList, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
}
serviceValue["volumes"] = volumeList
}
services[mcpServer.Name] = serviceValue
composeByte, err := yaml.Marshal(composeMap)
if err != nil {
return err
}
mcpServer.DockerCompose = string(composeByte)
return nil
}
func startMcp(mcpServer *model.McpServer) {
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
if mcpServer.Status != constant.RuntimeNormal {
_, _ = compose.Down(composePath)
}
if out, err := compose.Up(composePath); err != nil {
mcpServer.Status = constant.RuntimeError
mcpServer.Message = out
} else {
mcpServer.Status = constant.RuntimeRunning
mcpServer.Message = ""
}
_ = syncMcpServerContainerStatus(mcpServer)
}
func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
containerNames := []string{mcpServer.ContainerName}
cli, err := docker.NewClient()
if err != nil {
return err
}
defer cli.Close()
containers, err := cli.ListContainersByName(containerNames)
if err != nil {
return err
}
if len(containers) == 0 {
mcpServer.Status = constant.RuntimeStopped
return mcpServerRepo.Save(mcpServer)
}
container := containers[0]
switch container.State {
case "exited":
mcpServer.Status = constant.RuntimeError
case "running":
mcpServer.Status = constant.RuntimeRunning
case "paused":
mcpServer.Status = constant.RuntimeStopped
case "restarting":
mcpServer.Status = constant.RuntimeReStarting
default:
if mcpServer.Status != constant.RuntimeBuildIng {
mcpServer.Status = constant.RuntimeStopped
}
}
return mcpServerRepo.Save(mcpServer)
}
func GetWebsiteID() uint {
websiteID, _ := settingRepo.Get(settingRepo.WithByKey("MCP_WEBSITE_ID"))
if websiteID.Value == "" {
return 0
}
websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64)
return uint(websiteIDUint)
}
+56 -5
View File
@@ -1,6 +1,8 @@
package service
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
@@ -19,10 +21,10 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
)
@@ -42,6 +44,7 @@ type ISettingService interface {
HandlePasswordExpired(c *gin.Context, old, new string) error
GenerateApiKey() (string, error)
UpdateApiConfig(req dto.ApiInterfaceConfig) error
GenerateRSAKey() error
}
func NewISettingService() ISettingService {
@@ -163,7 +166,7 @@ func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error {
}
go func() {
time.Sleep(1 * time.Second)
_, err := cmd.Exec("systemctl restart 1panel.service")
err := systemctl.Restart("1panel")
if err != nil {
global.LOG.Errorf("restart system with new bind info failed, err: %v", err)
}
@@ -211,7 +214,7 @@ func (u *SettingService) UpdatePort(port uint) error {
}
go func() {
time.Sleep(1 * time.Second)
_, err := cmd.Exec("systemctl restart 1panel.service")
err := systemctl.Restart("1panel")
if err != nil {
global.LOG.Errorf("restart system port failed, err: %v", err)
}
@@ -234,7 +237,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
err := systemctl.Restart("1panel")
if err != nil {
global.LOG.Errorf("restart system failed, err: %v", err)
}
@@ -332,7 +335,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
go func() {
time.Sleep(1 * time.Second)
_, err := cmd.Exec("systemctl restart 1panel.service")
err := systemctl.Restart("1panel")
if err != nil {
global.LOG.Errorf("restart system failed, err: %v", err)
}
@@ -510,5 +513,53 @@ func (u *SettingService) UpdateApiConfig(req dto.ApiInterfaceConfig) error {
return err
}
global.CONF.System.IpWhiteList = req.IpWhiteList
if err := settingRepo.Update("ApiKeyValidityTime", req.ApiKeyValidityTime); err != nil {
return err
}
global.CONF.System.ApiKeyValidityTime = req.ApiKeyValidityTime
return nil
}
func exportPrivateKeyToPEM(privateKey *rsa.PrivateKey) string {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
})
return string(privateKeyPEM)
}
func exportPublicKeyToPEM(publicKey *rsa.PublicKey) (string, error) {
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", err
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
})
return string(publicKeyPEM), nil
}
func (u *SettingService) GenerateRSAKey() error {
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))
pubKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PUBLIC_KEY"))
if priKey.Value != "" && pubKey.Value != "" {
return nil
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
privateKeyPEM := exportPrivateKeyToPEM(privateKey)
publicKeyPEM, _ := exportPublicKeyToPEM(&privateKey.PublicKey)
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
if err != nil {
return err
}
err = settingRepo.UpdateOrCreate("PASSWORD_PUBLIC_KEY", publicKeyPEM)
if err != nil {
return err
}
return nil
}
+2 -1
View File
@@ -503,7 +503,8 @@ func hasOs(name string) bool {
strings.Contains(name, "arm64") ||
strings.Contains(name, "armv7") ||
strings.Contains(name, "ppc64le") ||
strings.Contains(name, "s390x")
strings.Contains(name, "s390x") ||
strings.Contains(name, "riscv64")
}
func loadOs() string {
+31 -7
View File
@@ -17,6 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
)
type snapHelper struct {
@@ -43,16 +44,39 @@ func snapPanel(snap snapHelper, targetDir string) {
defer snap.Wg.Done()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
status := constant.StatusDone
if err := common.CopyFile("/usr/local/bin/1panel", path.Join(targetDir, "1panel")); err != nil {
status = err.Error()
h, err := systemctl.DefaultHandler("1panel")
if err != nil {
status = fmt.Sprintf("initialize service handle failed: %v", err)
snap.Status.Panel = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
return
}
if err := common.CopyFile("/usr/local/bin/1pctl", targetDir); err != nil {
binDir := systemctl.BinaryPath
servicePath, err := h.GetServicePath()
if err != nil {
status = fmt.Sprintf("get service path failed: %v", err)
snap.Status.Panel = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
return
}
if err := common.CopyFile(path.Join(binDir, "1panel"), path.Join(targetDir, "1panel")); err != nil {
status = err.Error()
}
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", targetDir)
if err := common.CopyFile("/etc/systemd/system/1panel.service", targetDir); err != nil {
if err := common.CopyFile(path.Join(binDir, "1pctl"), targetDir); err != nil {
status = err.Error()
}
if _, err := cmd.Execf("cp -r %s/lang %s", binDir, targetDir); err != nil {
status = err.Error()
}
initScriptDir := path.Join(constant.DataDir, "initscript")
if err := common.CopyFile(servicePath, initScriptDir); err != nil {
status = err.Error()
}
global.LOG.Debugf("from %s copy init script to %s", initScriptDir, path.Join(targetDir, "initscript"))
if err := common.CopyDirs(initScriptDir, path.Join(targetDir, "initscript")); err != nil { // copy init script to targetDir
status = err.Error()
}
snap.Status.Panel = status
@@ -260,7 +284,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
} else {
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, "%P\n", targetDir+"/"+name, exStr, sourceDir)
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%%P\n' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, targetDir+"/"+name, exStr, sourceDir)
global.LOG.Debug(commands)
}
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)

Some files were not shown because too many files have changed in this diff Show More