Compare commits

...

635 Commits

Author SHA1 Message Date
耗子 37c6fadecf fix: 备份格式默认值错误 2026-05-08 21:26:23 +08:00
github-actions[bot] 122c538811 New Crowdin translations by GitHub Action (#1598)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-05-08 20:16:30 +08:00
h2zi 4d1bb40d26 chore(l10n): update pot files 2026-05-08 11:50:58 +00:00
耗子 f3e61dfc05 fix: lint 2026-05-08 19:49:36 +08:00
耗子 58186508bb feat: 前端现代化改造3 2026-05-08 19:26:32 +08:00
耗子 c41dab89f8 refactor: 替换 robfig/cron/v3 为 libtnb/cron
robfig/cron 已停止维护,迁移到 libtnb/cron(零三方依赖,API 设计更现代)

主要变更:
- pkg/cron/logger.go 删除,libtnb/cron 直接吃 *slog.Logger
- bootstrap.NewCron 用 wrap.Recover() / wrap.SkipIfRunning() 替代旧 cron.Recover / SkipIfStillRunning
- app.Ace.Run 处理 c.Start() error 返回值;c.Stop(ctx) 接受 context
- 6 个 job (Monitoring/FirewallScan/CertRenew/PanelTask/WebsiteStat/WebsiteExpire) 的 Run 签名从 () 改为 (context.Context) error
- jobs.Register 用 c.Add 替代 c.AddJob
- 重新生成 wire 代码

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:55:39 +08:00
耗子 2e049da24e fix: 登录页面去掉卡片 2026-05-08 18:47:37 +08:00
耗子 f6f22ecaf6 fix: zip 解压改用 7z 处理中文文件名
unzip 默认按 UTF-8 解析文件名,Windows GBK/CP936 编码的 zip 包会被转义成 #Uxxxx 形式
改用 7z x 替代,自动检测文件名编码

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:37:02 +08:00
耗子 2328456a81 fix: 修复 errcheck lint 报错
defer f.Close() 改为显式忽略返回值,跟其他地方保持一致

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 15:10:06 +08:00
耗子 f0bee01fd7 Merge remote-tracking branch 'origin/renovate/non-major-dependencies' 2026-05-08 15:07:05 +08:00
github-actions[bot] 4d6ac8371d New Crowdin translations by GitHub Action (#1597)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-05-08 14:57:20 +08:00
h2zi e5444f13d4 chore(l10n): update pot files 2026-05-07 21:32:48 +00:00
renovate[bot] ba53855292 chore(deps): Update pnpm to v11.0.5 2026-05-07 21:31:47 +00:00
renovate[bot] ab869d6b12 chore(deps): Update pnpm to v11.0.5 (#1596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-08 05:31:18 +08:00
耗子 2d7fbeb370 refactor: 前端 UI 全量重构与日志查看器重写
- 引入设计系统 (tokens / naive-overrides / variables.scss),收敛颜色/圆角/阴影/字号/动效到唯一真相源,Naive UI 主色改回浅绿
- 新增系统组件 (PageContainer / ConfirmDialog / FormModal / FormDrawer 等),替换并删除 AppPage / CommonPage / DeleteConfirm
- 全量视图迁移到新组件:列表页用 NFlex 排版操作列,搜索框用 !w-XX 覆盖 NInput 默认 100% 宽
- 工具箱 SSH/日志清理/磁盘 重写为更紧凑的卡片+sub-tabs 布局
- 终端页重做:macOS 风格 shell + 紧凑 tab + 红绿状态点 + 主机 drawer
- 日志查看器重写:后端新增 GET /file/tail (反向分页) + WS /follow,前端用 anser 渲染 ANSI 颜色 + 原生 div 滚动 + WS 自动重连
- 修复多处 NPagination page-count 与 item-count 同时设置的警告
- 网站列表支持单击网站名打开编辑

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 05:30:20 +08:00
renovate[bot] 95790da784 chore(deps): Update pnpm to v11.0.4 (#1595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-08 00:45:59 +08:00
renovate[bot] 2ffd099c8a chore(deps): Update pnpm to v11 (#1594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-08 00:42:05 +08:00
耗子 f52d324073 chore(deps): 升级 vite 到 ^8.0.11
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:44:45 +08:00
耗子 787d489cfc chore: 适配 pnpm v11 的 allowBuilds 配置
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:44:20 +08:00
renovate[bot] 407148dfe3 chore(deps): Update non-major dependencies (#1593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-07 20:28:41 +08:00
github-actions[bot] 6b1eaa1aaa New Crowdin translations by GitHub Action (#1592)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-05-07 05:16:38 +08:00
h2zi 249424332e chore(l10n): update pot files 2026-05-06 21:16:10 +00:00
耗子 68efc98c06 fix: 用户列表与文件内联输入框未占满列宽 2026-05-07 05:13:08 +08:00
h2zi aab5029848 chore(l10n): update pot files 2026-05-06 21:01:56 +00:00
耗子 b7041f9253 feat: 进程列表左键点击弹出操作菜单 2026-05-07 05:00:18 +08:00
耗子 a5a6409be0 fix: 清除日志后实时日志未刷新 2026-05-07 04:56:01 +08:00
耗子 3eb2696690 fix: environment列表无数据时返回null 2026-05-07 04:55:57 +08:00
耗子 dbcbbf9d95 feat: 备份支持指定压缩文件格式,close #1582 2026-05-07 04:31:09 +08:00
renovate[bot] 2c078d90d7 chore(deps): Update module github.com/go-webauthn/webauthn to v0.17.2 (#1591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-06 16:37:08 +00:00
耗子 f08136242e feat: 已安装应用列表显示运行状态 2026-05-06 18:05:19 +08:00
耗子 d503d9f083 fix: docker compose在目录大小情况下获取状态失败,close #1590 2026-05-06 03:14:46 +08:00
renovate[bot] 3147f70dde chore(deps): Update module github.com/gookit/color to v1.6.1 (#1589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-04 21:57:48 +00:00
renovate[bot] e2815a430e chore(deps): Lock file maintenance (#1588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-04 05:07:49 +00:00
renovate[bot] 36a8646485 chore(deps): Update module github.com/libtnb/sqlite to v1.1.1 (#1586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-03 13:25:11 +00:00
renovate[bot] 901ce774d5 chore(deps): Update module github.com/fsnotify/fsnotify to v1.10.0 (#1585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-03 05:49:35 +00:00
renovate[bot] 0a42e198dd chore(deps): Update non-major dependencies (#1584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-02 16:35:28 +00:00
renovate[bot] 54ad4dff86 chore(deps): Update non-major dependencies (#1581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-30 21:57:37 +00:00
耗子 6f54b6e402 fix: De Morgan's law 2026-04-29 23:21:18 +08:00
耗子 075d9f8ee4 fix: nginx下ip证书签发失败 2026-04-29 20:27:09 +08:00
耗子 c0d8642cb5 fix: 优化代码 2026-04-28 06:05:34 +08:00
耗子 418d8af6d9 fix: 优化各列宽度 2026-04-28 04:18:39 +08:00
耗子 45b63f8cd0 fix: 容器镜像清理 2026-04-28 04:18:28 +08:00
耗子 521bf37a9f Merge remote-tracking branch 'origin/main' 2026-04-27 22:56:36 +08:00
耗子 452c5ffac8 fix: 更新错误页描述 2026-04-27 22:56:31 +08:00
h2zi f1cc5bb7d6 chore: update mocks 2026-04-27 12:31:25 +00:00
耗子 909556b3d6 refactor: 统一日志清理 2026-04-27 20:30:12 +08:00
耗子 cd64dc65f3 fix: lint 2026-04-27 19:32:20 +08:00
耗子 b3b29d79c5 fix: 优化supervisor管理器 2026-04-27 19:26:46 +08:00
耗子 d565d3bbb8 Merge remote-tracking branch 'origin/main' 2026-04-27 17:28:27 +08:00
耗子 dc62ecf1dd fix: shell 执行失败时丢失 cmd.Run 真实错误
当 cmd.Dir 不存在或 exec 启动失败时,Go 在进入 bash 前就返回错误,stderr 为空,原本只取 stderr 的错误格式会输出空 err,导致排查困难。改为同时携带 cmd.Run 的真实错误和 stderr。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:28:09 +08:00
renovate[bot] dd600d89d2 chore(deps): Lock file maintenance (#1580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-27 05:13:33 +00:00
renovate[bot] 1d4a488e83 chore(deps): Update non-major dependencies (#1579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-26 17:02:49 +00:00
renovate[bot] 71bb13cd66 chore(deps): Update non-major dependencies (#1578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-25 16:35:16 +00:00
renovate[bot] 5a52efd3ec chore(deps): Update dependency eslint-plugin-vue to v10.9.0 (#1577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-25 06:00:31 +00:00
renovate[bot] fc8de12590 chore(deps): Update module github.com/DeRuina/timberjack to v1.4.2 (#1576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-24 14:04:04 +00:00
renovate[bot] b03ad4ba86 chore(deps): Update module github.com/go-webauthn/webauthn to v0.17.0 (#1575)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-24 14:40:33 +08:00
renovate[bot] 2be313b676 chore(deps): Update dependency eslint-plugin-oxlint to v1.61.0 (#1574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 22:35:10 +00:00
renovate[bot] 5d8a08c507 chore(deps): Update non-major dependencies (#1573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-24 04:09:24 +08:00
renovate[bot] ab167e884c chore(deps): Update non-major dependencies (#1572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-24 04:06:33 +08:00
renovate[bot] b57602deb9 chore(deps): Update dependency vite to v8.0.9 (#1571)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 08:50:16 +00:00
耗子 db8064fbe3 fix: 日志查看器改用 PTY + xterm 以支持滚动查看历史
journalctl 未加 -n 参数导致只能看到 10 行历史;watchEffect 每次日志更新
都强制滚到底部,导致用户即便有滚动条也无法回看历史。改用已有的 ws.pty +
xterm.js 基础设施取代 n-log,借助 xterm 的 scrollback buffer 获得与 SSH
下 tail 一致的交互体验。

- tail 预加载 1000 行并启用 -F 兼容日志轮转
- journalctl 加 --no-pager 防止 TTY 下启动分页器,预加载 1000 行
- 移除无用的 language prop 并同步清理 EditModal 的传参

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:45:24 +08:00
renovate[bot] aeabe9b17e chore(deps): Update dependency vue-tsc to v3.2.7 (#1570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-22 23:18:48 +08:00
renovate[bot] cd65b13c0a chore(deps): Update module github.com/go-webauthn/webauthn to v0.16.5 (#1569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-22 18:20:09 +08:00
github-actions[bot] 51d979e63f New Crowdin translations by GitHub Action (#1568)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-22 04:48:34 +08:00
h2zi 751513d59a chore(l10n): update pot files 2026-04-21 20:38:26 +00:00
renovate[bot] 755230161b chore(deps): Update pnpm/action-setup action to v6 (#1564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-22 03:10:21 +08:00
h2zi 13e5771445 chore: update mocks 2026-04-21 19:07:46 +00:00
Copilot 6a7c856fe8 feat: 网站到期自动关闭 (#1561)
* Initial plan

* feat: 网站到期自动关闭功能

- biz.Website 新增 ExpireAt 字段(*time.Time,可空,默认不限时)
- 新增数据库迁移自动添加 expire_at 列
- 新增 API 端点 POST /website/{id}/expire_at
- 新增定时任务每分钟检查到期网站并自动关闭
- 前端网站列表新增到期时间列(证书有效期与备注之间)

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/af33abcf-bbcd-4306-a680-76307c699774

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

* refactor: 优化到期任务查询和数据更新

- 到期检查任务直接查询已到期且运行中的网站,避免加载全量数据
- UpdateExpireAt 使用直接 UPDATE 避免不必要的 SELECT 查询

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/af33abcf-bbcd-4306-a680-76307c699774

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

* refactor: ExpireAt 改为 time.Time,日志使用英文

- ExpireAt 从 *time.Time 改为 time.Time,零值表示不限时
- 日志消息改为英文,与其他 job 文件风格保持一致
- 前端适配零值时间判断

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/00c49c64-7614-48d1-98bb-0d742d5f10ad

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

* Revert "refactor: ExpireAt 改为 time.Time,日志使用英文"

This reverts commit ac3dfdb7e4.

* fix: lint

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-04-22 03:06:43 +08:00
renovate[bot] 95a5d68f7c chore(deps): Update non-major dependencies (#1567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 01:49:37 +00:00
renovate[bot] 89bdc033cc chore(deps): Update module modernc.org/sqlite to v1.49.0 (#1566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 02:46:21 +08:00
renovate[bot] eb83a753b6 chore(deps): Update dependency typescript to v6.0.3 (#1565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-20 04:56:38 +00:00
耗子 b930c52acf Merge remote-tracking branch 'origin/main' 2026-04-17 19:01:37 +08:00
耗子 0585b7c04b fix: 批量创建PHP网站报错 2026-04-17 19:01:31 +08:00
renovate[bot] 0bc4eba46f chore(deps): Update dependency eslint-plugin-oxlint to v1.60.0 (#1563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 22:44:52 +00:00
renovate[bot] f015404523 chore(deps): Update non-major dependencies (#1562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 22:56:51 +08:00
耗子 2a2e413e45 chore: update deps 2026-04-16 22:48:52 +08:00
Copilot 1f9407b25a fix: WebDAV 存储连接失败 — 前端表单字段与后端不匹配 (#1560)
* Initial plan

* fix: WebDAV 存储连接失败,前端表单字段绑定错误 (host → url)

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/6bc5c598-6439-49f4-92d7-59a0640d6148

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

* fix: 将 WebDAV 标签 URL 包装 $gettext() 保持国际化一致性

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/6bc5c598-6439-49f4-92d7-59a0640d6148

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>
2026-04-16 18:37:50 +08:00
renovate[bot] d09a62e175 chore(deps): Update dependency @vitejs/plugin-vue to v6.0.6 (#1559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 18:28:31 +08:00
耗子 22102b5dc7 Merge remote-tracking branch 'origin/main' 2026-04-16 15:15:19 +08:00
耗子 fcf4544224 fix: 不支持用户管理的数据库不显示 2026-04-16 15:15:13 +08:00
Copilot d301f045b1 feat: 应用更新确认弹窗显示目标版本号 (#1556)
Agent-Logs-Url: https://github.com/acepanel/panel/sessions/4897ec9f-a2e8-42bf-a691-3e1cf14c7d0c

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>
2026-04-13 16:41:38 +08:00
耗子 694847e558 Merge remote-tracking branch 'origin/main' 2026-04-13 16:33:58 +08:00
耗子 02fecb35de fix: 使用WEB服务器下 IPv6 证书签发问题 2026-04-13 16:33:52 +08:00
renovate[bot] d58577205e chore(deps): Lock file maintenance (#1555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-13 04:58:05 +00:00
renovate[bot] 316bd54bc0 chore(deps): Update non-major dependencies (#1554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-13 01:54:23 +00:00
renovate[bot] 2209757a62 chore(deps): Update non-major dependencies (#1553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-13 01:34:59 +08:00
耗子 59a7f01f95 feat: Grafana添加更多数据源 2026-04-10 22:36:00 +08:00
耗子 5b8b0f0d07 feat: 移除buypass 2026-04-10 02:06:17 +08:00
耗子 1a4b377c22 fix: DNS别名映射问题 2026-04-10 00:16:44 +08:00
renovate[bot] 837aaab151 chore(deps): Lock file maintenance (#1552)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-09 22:48:20 +08:00
renovate[bot] 168dd7071e chore(deps): Update non-major dependencies (#1551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-09 22:45:31 +08:00
github-actions[bot] b7e50aaf1d New Crowdin translations by GitHub Action (#1550)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-09 19:42:43 +08:00
h2zi cf6a6c80c1 chore(l10n): update pot files 2026-04-09 11:26:08 +00:00
耗子 a2f39d104c Merge remote-tracking branch 'origin/main' 2026-04-08 23:55:23 +08:00
耗子 97c8ad8356 feat: DNS别名使用完整域名 2026-04-08 23:55:17 +08:00
renovate[bot] 489e0702c3 chore(deps): Update module github.com/go-webauthn/webauthn to v0.16.3 (#1549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-08 15:22:53 +08:00
耗子 db2bb0606d feat: 优化代码 2026-04-07 19:05:40 +08:00
github-actions[bot] a2d1c9c535 New Crowdin translations by GitHub Action (#1548)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-07 19:00:21 +08:00
h2zi f6ab265b6b chore(l10n): update pot files 2026-04-07 10:35:11 +00:00
github-actions[bot] 4614fb1cd1 New Crowdin translations by GitHub Action (#1547)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-04-07 15:36:49 +08:00
h2zi 965525e68e chore(l10n): update pot files 2026-04-07 06:49:24 +00:00
耗子 fdde61c8ae feat: DNS-01 CNAME 委托验证 (#1546)
* feat: DNS-01 CNAME 委托验证

* fix: address comments

* fix: lint
2026-04-07 03:45:41 +08:00
耗子 99bc8ddc8d Merge remote-tracking branch 'origin/main' 2026-04-07 03:17:46 +08:00
耗子 d4f85066a0 feat: 计划任务支持备份指定目录功能 (#1540)
在计划任务的备份类型中新增"目录"子类型,支持将任意指定目录打包压缩后
备份到本地或远程存储,并自动清理过期备份。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 02:56:29 +08:00
dependabot[bot] 5263ec4a46 chore(deps-dev): bump vite from 8.0.3 to 8.0.5 in /web (#1544)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.3 to 8.0.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 02:41:37 +08:00
h2zi 522d78569a chore: update mocks 2026-04-06 18:20:12 +00:00
耗子 a42f9f63d0 refactor: 重构证书签发实现 (#1543)
* refactor: 重构证书签发实现

* fix: address comments

* fix: optimize code
2026-04-07 02:19:22 +08:00
renovate[bot] 7a7e8c4460 chore(deps): Update module github.com/lib/pq to v1.12.3 (#1542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 01:41:11 +08:00
zenmediasocial 116fbdda89 fix: DNS-01 挑战缺少传播等待导致 NXDOMAIN 验证失败 (#1536)
dnsSolver 未实现 acmez 的 Waiter 接口,导致 Present() 向 Cloudflare
写入 TXT 记录后,acmez 立即通知 CA 进行验证,而 DNS 记录尚未在权威
解析服务器上生效,Let's Encrypt 查询时返回 NXDOMAIN,授权失败。

修复内容:
- 为 dnsSolver 添加 Wait() 方法(实现 acmez.Waiter),在通知 CA 验证
  前等待 60 秒,确保 DNS 记录传播完成
- 将 records 字段的赋值改为 append,避免多域名证书场景下并发
  Present() 调用互相覆盖记录
- 添加 sync.Mutex 保护 records 字段的并发读写
- 放宽 SetRecords 结果数量检查(!= 1 改为 == 0),兼容更新已有记录时
  返回多条结果的情况
- 将 Renew 超时从 2 分钟延长至 10 分钟,避免加入等待后超时

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 00:50:46 +08:00
耗子 87b728ca1b fix: lint 2026-04-07 00:43:34 +08:00
renovate[bot] 2320656d2b chore(deps): Update non-major dependencies (#1541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-06 21:46:49 +08:00
renovate[bot] 2e5d67e4b0 chore(deps): Lock file maintenance (#1539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-06 14:47:55 +08:00
renovate[bot] 1c9ed32aaf chore(deps): Update dependency sass to v1.99.0 (#1538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-06 04:47:21 +00:00
renovate[bot] 9df648b7e9 chore(deps): Update non-major dependencies (#1537)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-05 13:43:32 +00:00
Copilot 1079065220 feat: 为应用页面添加"已安装"Tab选项卡 (#1535)
* Initial plan

* feat: 为应用页面添加"已安装"Tab选项卡

- 后端:app/list 和 environment/list 添加 installed 过滤参数
- 前端:创建 InstalledView 组件展示已安装的应用和环境
- 前端:IndexView 添加"已安装"Tab 并设为默认选项

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/9b460f4b-c69e-4a5b-8131-c68099edcb7c

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

* refactor: 移除 InstalledView 中未使用的 VersionModal 组件

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/9b460f4b-c69e-4a5b-8131-c68099edcb7c

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>
2026-04-03 16:15:30 +08:00
Copilot 6f8c1b410f feat: 文件编辑器未保存更改对话框添加放弃选项 (#1534)
* Initial plan

* feat: 文件编辑器保存提示添加返回/放弃/保存三种操作

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/7127d3cc-b379-4b25-ae66-d1847f139551

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

* fix: 标签页保存失败时显示错误提示消息

Agent-Logs-Url: https://github.com/acepanel/panel/sessions/7127d3cc-b379-4b25-ae66-d1847f139551

Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>
2026-04-03 16:13:53 +08:00
耗子 6e63f565fa Merge remote-tracking branch 'origin/main' 2026-04-03 14:53:46 +08:00
耗子 c24fa4580e fix: 数据库防止酒吧点炒饭
数据库名和用户名校验正则禁止首位为数字,因为数据库不接受库名/用户名以数字开头。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:51:14 +08:00
renovate[bot] 2e6277848c chore(deps): Update module github.com/go-webauthn/webauthn to v0.16.2 (#1531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-03 01:19:36 +00:00
renovate[bot] 35f9d16d81 chore(deps): Update module github.com/lib/pq to v1.12.1 (#1530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-02 17:28:44 +00:00
renovate[bot] fcf0d49cba chore(deps): Update module github.com/hashicorp/go-version to v1.9.0 (#1529)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-02 22:34:32 +08:00
耗子 9c0f5d86d2 Merge remote-tracking branch 'origin/main' 2026-04-02 20:56:47 +08:00
耗子 97161b8628 feat: 优化PostgreSQL数据库用户删除 2026-04-02 20:56:40 +08:00
renovate[bot] 0b131b2920 chore(deps): Update dependency lodash-es to v4.18.1 [SECURITY] (#1528)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-02 17:45:19 +08:00
耗子 37c2f851e5 feat: 去除eslint 2026-04-01 23:19:29 +08:00
耗子 9ec7b47cf6 feat: 前端现代化改造3 2026-04-01 23:01:06 +08:00
耗子 a4c59d29af feat: 前端现代化改造2 2026-04-01 22:58:39 +08:00
耗子 a5396fddbb feat: 前端现代化改造 2026-04-01 22:52:58 +08:00
耗子 bc2584b7e5 feat: 前端现代化改造 2026-04-01 22:46:54 +08:00
renovate[bot] d839949684 chore(deps): Update module github.com/shirou/gopsutil/v4 to v4.26.3 (#1526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-01 17:42:09 +08:00
renovate[bot] 3d81d1d0a3 chore(deps): Update module github.com/andybalholm/brotli to v1.2.1 (#1525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-01 01:24:39 +00:00
renovate[bot] 0debf14dc3 chore(deps): Update module modernc.org/sqlite to v1.48.0 (#1524)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-01 02:21:11 +08:00
耗子 fc84ba4e86 Merge remote-tracking branch 'origin/main' 2026-03-31 22:01:56 +08:00
耗子 7acd24c587 fix: 移除误装的垃圾包 2026-03-31 22:01:51 +08:00
renovate[bot] 4275baabd0 chore(deps): Update module github.com/gookit/validate to v1.5.7 (#1522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-31 13:39:46 +00:00
耗子 e6657c10e2 fix: postgres配置转义 2026-03-31 19:18:14 +08:00
耗子 9e5cc546c1 Merge remote-tracking branch 'origin/main' 2026-03-30 20:28:17 +08:00
耗子 e799cb4031 feat: 重构面板证书逻辑 2026-03-30 20:28:11 +08:00
renovate[bot] 12a6d6f39f chore(deps): Lock file maintenance (#1521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-30 04:37:29 +00:00
耗子 c33c03d225 fix: ufw默认开启转发,close #1519 2026-03-28 19:45:48 +08:00
耗子 715e58c122 feat: 面板证书可以为空 2026-03-28 19:44:53 +08:00
耗子 9f8d684763 fix: grafana配置修改 2026-03-28 16:32:59 +08:00
耗子 1d79f8c06e fix: 修改推荐的图表 2026-03-28 00:21:52 +08:00
耗子 3fa4921b40 fix: mongodb 修改密码权限问题 2026-03-27 23:10:06 +08:00
github-actions[bot] 08bb081c66 New Crowdin translations by GitHub Action (#1518)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-03-27 22:27:17 +08:00
h2zi 4aa95c77f9 chore(l10n): update pot files 2026-03-27 14:24:47 +00:00
github-actions[bot] 5ccaa58ccb New Crowdin translations by GitHub Action (#1482)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-03-27 21:56:12 +08:00
h2zi 1ac12b4ebd chore(l10n): update pot files 2026-03-27 13:05:45 +00:00
耗子 32067e6000 feat: 添加exporter 2026-03-27 20:57:04 +08:00
耗子 37fc3b29fa Merge remote-tracking branch 'origin/main' 2026-03-27 20:33:39 +08:00
耗子 e2ce8e70f1 fix: optimize code 2026-03-27 20:32:31 +08:00
h2zi 60d4e31fac chore(l10n): update pot files 2026-03-27 12:14:27 +00:00
耗子 49b6c5af7e feat: 添加apcu模块,close #1517 2026-03-27 20:11:01 +08:00
耗子 76f80a8e65 feat: 修改PHP模块目录 2026-03-27 20:01:25 +08:00
耗子 dddb718fda feat: 更新安全说明 2026-03-27 19:58:16 +08:00
耗子 046dc09f69 feat: 更新安全说明 2026-03-27 19:56:25 +08:00
耗子 8d218e7e88 feat: 更新安全说明 2026-03-27 19:56:08 +08:00
耗子 5fdae42eb7 feat: 更新安全说明 2026-03-27 19:55:18 +08:00
耗子 d01d14f35e feat: 支持秒级计划任务,close #1511 2026-03-27 19:20:41 +08:00
耗子 5e251ac04a fix: lint 2026-03-27 17:25:18 +08:00
耗子 b12590c1c2 fix: lint 2026-03-27 17:13:23 +08:00
h2zi 4c6347d6c3 chore: update mocks 2026-03-27 09:00:20 +00:00
耗子 f66b094ec6 feat: 添加elasticsearch和opensearch 2026-03-27 16:59:24 +08:00
耗子 b0e04d2976 feat: clickhouse优化 2026-03-27 16:20:37 +08:00
耗子 68b96be537 feat: 添加clickhouse和mongodb的管理 2026-03-27 15:43:18 +08:00
耗子 8ebe63ac3d feat: 添加clickhouse和mongodb的管理 2026-03-27 15:09:06 +08:00
耗子 4df9400fb2 fix: optimize code 2026-03-27 06:11:47 +08:00
耗子 c40da3895d Merge remote-tracking branch 'origin/copilot/fix-uuid-issue-and-check-code' 2026-03-27 06:06:05 +08:00
耗子 ad409eb446 Merge remote-tracking branch 'origin/main' 2026-03-27 06:05:34 +08:00
耗子 7c7d5b6ce1 feat: 添加clickhouse和mongodb 2026-03-27 06:03:55 +08:00
renovate[bot] 5de7a8e745 chore(deps): Update codecov/codecov-action action to v6 (#1516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 03:39:46 +08:00
copilot-swe-agent[bot] 2f9ccf23a0 fix(web): 降级HTTP环境的tab id与SSH粘贴读取
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Agent-Logs-Url: https://github.com/acepanel/panel/sessions/12e3737b-640d-48e1-942b-dc17ae98ad4a
2026-03-26 14:57:16 +00:00
copilot-swe-agent[bot] 3a7099b3d2 fix(web): 替换不依赖HTTPS的tab id并修复SSH剪贴板读取降级
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Agent-Logs-Url: https://github.com/acepanel/panel/sessions/12e3737b-640d-48e1-942b-dc17ae98ad4a
2026-03-26 14:52:47 +00:00
copilot-swe-agent[bot] e05202dafb Initial plan 2026-03-26 14:38:14 +00:00
renovate[bot] f8c99803ed chore(deps): Update dependency vite to v8.0.3 (#1514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-26 12:38:13 +00:00
renovate[bot] 3439783518 chore(deps): Update non-major dependencies (#1513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-25 21:06:51 +00:00
renovate[bot] c7929c2899 chore(deps): Update github.com/gookit/validate digest to d265741 (#1512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-25 08:51:02 +00:00
renovate[bot] 0d6a1b384d chore(deps): Update non-major dependencies (#1509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-25 00:58:22 +00:00
耗子 9216940d90 feat: 添加rocketmq和kafka 2026-03-25 03:12:18 +08:00
耗子 d1371b2bb1 feat: 添加valkey、elasticsearch和opensearch 2026-03-25 03:06:26 +08:00
耗子 e00013f801 feat: 添加valkey、elasticsearch和opensearch 2026-03-25 02:58:15 +08:00
耗子 130716216d feat: 添加alertmanager配置 2026-03-25 01:51:55 +08:00
耗子 9d22b5d1d0 feat: 添加推荐的仪表盘ID 2026-03-25 01:44:20 +08:00
耗子 b952ebf5ad fix: 修复问题 2026-03-25 01:17:05 +08:00
耗子 455f6c061d feat: 优化grafana 2026-03-25 01:06:32 +08:00
耗子 432b0662b0 chore: add svg logo 2026-03-24 22:59:54 +08:00
耗子 0ae7327105 feat: 添加prometheus和grafana支持 (#1508)
* feat: 阶段性提交

* feat: 完成开发

* feat: 阶段性提交

* feat: 阶段性提交
2026-03-24 22:45:22 +08:00
renovate[bot] 0870b100cc chore(deps): Update dependency @vue/tsconfig to v0.9.1 (#1507)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-24 06:15:18 +00:00
renovate[bot] 28b2eaa638 chore(deps): Update dependency unplugin-vue-components to v32 (#1504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-24 03:40:29 +08:00
renovate[bot] bdcdfbf96e chore(deps): Update dependency vite-plugin-vue-devtools to v8.1.1 (#1505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 18:00:50 +00:00
renovate[bot] 471db7ef69 chore(deps): Update non-major dependencies (#1503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 22:43:09 +08:00
renovate[bot] be67988272 chore(deps): Lock file maintenance (#1502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 05:07:39 +00:00
renovate[bot] a56ef041c8 chore(deps): Update dependency vite-plugin-compression2 to v2.5.3 (#1500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-21 17:31:05 +00:00
renovate[bot] 3b9c1757a0 chore(deps): Update module github.com/libtnb/sqlite to v1.0.1 (#1499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-21 05:42:33 +00:00
耗子 ed06f53791 fix: lint 2026-03-21 04:30:00 +08:00
Copilot ff06049085 修复 UFW 端口转发未下发/清理 route allow 规则 (#1498)
* Initial plan

* fix: add ufw route allow/delete for port forwarding

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-03-20 18:34:47 +08:00
renovate[bot] b9b00a0117 chore(deps): Update dependency vue-router to v5.0.4 (#1497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-20 02:09:32 +08:00
renovate[bot] 61572b0a3c chore(deps): Update dependency vite to v8.0.1 (#1496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-19 13:24:57 +00:00
renovate[bot] 8fdbd19061 chore(deps): Update module github.com/lib/pq to v1.12.0 (#1495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 21:35:38 +00:00
renovate[bot] c5e7084c8b chore(deps): Update non-major dependencies to v66.6.7 (#1494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 16:56:42 +08:00
renovate[bot] 71129d6446 chore(deps): Update dependency vue-tsc to v3.2.6 (#1492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 01:51:17 +08:00
renovate[bot] 615df5966b chore(deps): Update pnpm/action-setup action to v5 (#1493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 01:50:43 +08:00
renovate[bot] d15ccba931 chore(deps): Update github.com/gookit/validate digest to 350536c (#1491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 05:41:38 +00:00
renovate[bot] d0ac218809 chore(deps): Lock file maintenance (#1490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 05:27:14 +00:00
renovate[bot] f0cf40c5f0 chore(deps): Update dependency vite-plugin-compression2 to v2.5.2 (#1489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-15 21:13:38 +00:00
Copilot b100922bc7 fix: 文件上传覆盖运行中的二进制文件时 ETXTBSY 错误 (#1488)
* Initial plan

* fix: 强制覆盖文件时先删除再创建,避免覆盖运行中的二进制文件报 ETXTBSY 错误

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: 处理 Remove() 错误,删除失败时提前返回明确错误信息

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-03-15 20:09:03 +08:00
Copilot 69ff73a393 feat: 为应用管理(原生应用、运行环境和容器模板)添加搜索功能 (#1487)
* Initial plan

* feat: 为应用管理(原生应用、运行环境和容器模板)添加搜索功能

后端通过 GET query 参数传入关键词,按名称和描述进行大小写不敏感过滤。
容器模板额外支持按 website 字段搜索。
前端在分类标签旁添加搜索输入框,输入变化时自动触发重新查询。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 优化搜索过滤代码格式和 TemplateView 一致性

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-03-15 18:07:28 +08:00
Copilot 01585dcc3b fix: preserve YAML-defined order for container template environment variables (#1486)
* Initial plan

* fix: 保留容器模板环境变量的 YAML 定义顺序

使用 yaml.Node 从原始 YAML 数据中提取环境变量键的定义顺序,
替代之前的字母序排序,确保前端展示的字段顺序与 data.yml 一致。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-03-15 17:06:37 +08:00
Copilot fe5daca28b fix: 修复文件粘贴重命名时弹窗无限循环的回归问题 (#1483)
* Initial plan

* fix: 修复文件粘贴重命名时弹窗无限循环的回归问题

修复 PR #1474 引入的回归问题:
1. handleRename 完成后不再调用 checkAndExecute(避免重新弹出警告弹窗导致无限循环),
   改为直接 await file.exist() 检查冲突并在 while 循环内重新提示重命名
2. 重命名弹窗增加源文件路径显示,便于区分多个冲突文件

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-03-15 03:48:48 +08:00
h2zi 3be7ec8a80 chore(l10n): update pot files 2026-03-14 19:18:16 +00:00
renovate[bot] 6426d2e7d2 chore(deps): Update non-major dependencies (#1481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 09:20:14 +00:00
renovate[bot] 663a303c2b chore(deps): Update dependency vite to v8 (#1480)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 06:29:04 +08:00
renovate[bot] 1ce4628d87 chore(deps): Update non-major dependencies (#1479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 21:45:43 +00:00
耗子 d45ddac1cc fix: eBPF UDP 扫描检测过滤已知服务端口响应流量,避免 DNS/NTP 等回包误报
UDP 无连接态,无法像 TCP 靠 SYN 标志区分新连接和响应。系统发起 DNS 查询后,
响应包(源端口 53)到达临时端口,因不在监听白名单中被误报为扫描。

在 UDP 处理器中增加源端口检查,跳过已知服务端口的响应流量:
DNS(53), DHCP(67/68), NTP(123), QUIC(443), IKE(500),
DHCPv6(546/547), DoQ(853), IPsec NAT-T(4500), mDNS(5353)

Closes #1478

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:59:41 +08:00
renovate[bot] 820b318c00 chore(deps): Update module golang.org/x/net to v0.52.0 (#1477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 08:46:30 +00:00
renovate[bot] 516f552181 chore(deps): Update module golang.org/x/crypto to v0.49.0 (#1476)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 01:15:42 +00:00
github-actions[bot] 228e2146ba New Crowdin translations by GitHub Action (#1475)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-03-12 03:18:00 +08:00
devhaozi 007f7339f7 chore(l10n): update pot files 2026-03-11 17:56:47 +00:00
Copilot 613ec00c90 feat: add rename option for file copy/move conflicts, fix same-directory paste error (#1474)
* Initial plan

* feat: 文件复制/移动冲突时添加重命名选项,修复同目录粘贴覆盖报错

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 移除不必要的 ref,改用 defaultValue 并支持 Enter 键确认重命名

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: use const for DialogReactive and remove unused import to pass lint

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-03-12 01:55:41 +08:00
github-actions[bot] 8071ea1757 New Crowdin translations by GitHub Action (#1472)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-03-12 01:04:38 +08:00
耗子 6da51ed0eb fix: build 2026-03-12 00:56:48 +08:00
耗子 aa1751f0bb Merge remote-tracking branch 'origin/main' 2026-03-12 00:54:42 +08:00
耗子 f15841f04a fix: build 2026-03-12 00:54:38 +08:00
renovate[bot] ced266145b chore(deps): Update dependency vite-plugin-compression2 to v2.5.1 (#1471)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 00:46:17 +08:00
h2zi 580a68202c chore(l10n): update pot files 2026-03-11 16:24:16 +00:00
renovate[bot] 81e99db86e chore(deps): Update module github.com/DeRuina/timberjack to v1.4.0 (#1470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 00:22:23 +08:00
耗子 499a570efa fix: 移除ssl密码套件配置,因为aws-lc似乎不支持 2026-03-12 00:21:02 +08:00
耗子 5258ef9d00 Merge remote-tracking branch 'origin/main' 2026-03-11 21:58:21 +08:00
耗子 c34203ae7b fix: 优化代码 2026-03-11 21:58:17 +08:00
renovate[bot] d477d1c637 chore(deps): Update non-major dependencies (#1469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-11 06:43:29 +00:00
renovate[bot] 10c98c5bb5 chore(deps): Update non-major dependencies (#1468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-10 10:16:24 +00:00
renovate[bot] d8e8a305d3 chore(deps): Update dependency vue to v3.5.30 (#1466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 17:47:56 +00:00
renovate[bot] 2dba43fd20 chore(deps): Lock file maintenance (#1465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 05:23:54 +00:00
耗子 b5a65e6226 Merge remote-tracking branch 'origin/main' 2026-03-09 06:28:58 +08:00
耗子 7295baa2d5 chore: update 2026-03-09 06:28:53 +08:00
renovate[bot] 2c9b484218 chore(deps): Update dependency naive-ui to v2.44.1 (#1464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 20:51:45 +00:00
renovate[bot] 0ac71f9f8a chore(deps): Update dependency naive-ui to v2.44.0 (#1462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 13:41:26 +00:00
耗子 2217d2ba33 Merge remote-tracking branch 'origin/main' 2026-03-08 17:23:03 +08:00
耗子 f94345dd1c fix: 容器终端兼容sh 2026-03-08 17:22:59 +08:00
renovate[bot] 17eb9c6b63 chore(deps): Update pnpm to v10.31.0 (#1461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 05:21:23 +00:00
github-actions[bot] 0104418c10 New Crowdin translations by GitHub Action (#1460)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-03-08 01:03:47 +08:00
h2zi 24b0fb1a12 chore(l10n): update pot files 2026-03-07 16:54:14 +00:00
Copilot e9c1a946e9 feat: 支持从本地目录加载容器模板(兼容 acepanel/templates 仓库格式) (#1452)
* Initial plan

* feat: 支持从本地目录加载容器模板

从 {app.Root}/templates/ 目录加载 JSON 格式的本地模板文件,
本地模板可覆盖同 slug 的远端模板。
UI 上使用 warning 标签标识本地模板。
本地模板部署时跳过远端回调。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 为本地模板加载添加 slog 日志记录

目录读取失败时记录警告日志,
模板文件读取/解析/缺失 slug 时记录警告日志。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 本地模板改用与 acepanel/templates 仓库一致的目录格式

从 JSON 文件格式改为目录格式:每个模板是一个子目录,
包含 data.yml + docker-compose.yml + 可选 logo.svg/logo.png。
data.yml 支持 i18n 字段,环境变量使用 map 格式。
与 github.com/acepanel/templates 仓库格式完全一致。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* chore: 改进 resolveLocale 注释

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* plan: address review feedback

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 将模板数据类型移至 pkg/types 并清理注释括号

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: 注入 slog.Logger、方法挂载到 templateRepo、简化注释

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-03-08 00:51:51 +08:00
耗子 1afedfc4d8 Merge remote-tracking branch 'origin/main' 2026-03-08 00:42:14 +08:00
耗子 971aaddc85 feat: 计划任务运行周期支持重启后,close #1456 2026-03-08 00:42:09 +08:00
h2zi 60052aac09 chore: update mocks 2026-03-07 16:28:46 +00:00
耗子 4a6fdc45d5 feat: 部署证书时支持开启 HTTPS 及一键签发证书泛域名 DNS 选择 2026-03-08 00:27:55 +08:00
耗子 2be464777b fix: 优化证书账号显示 2026-03-07 22:06:52 +08:00
renovate[bot] 7ea9f5a340 chore(deps): Update dependency @eslint/eslintrc to v3.3.5 (#1459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 05:45:17 +00:00
renovate[bot] 2142c4fd67 chore(deps): Update non-major dependencies (#1458)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 19:21:05 +08:00
renovate[bot] f3e9d20990 chore(deps): Update non-major dependencies (#1457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-05 21:50:42 +00:00
renovate[bot] f1c17c4651 chore(deps): Update non-major dependencies to v66.6.5 (#1455)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 13:19:22 +00:00
renovate[bot] 8ecdaf3c7a chore(deps): Update non-major dependencies to v66.6.4 (#1453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 17:41:32 +00:00
Copilot bb2f9e02b5 fix: env var with empty string default treated as required in template deploy (#1451)
* Initial plan

* fix: 修复容器模板环境变量空字符串默认值被误判为必填项

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-03-03 21:01:50 +08:00
renovate[bot] bbe0e9c4b4 chore(deps): Update module github.com/samber/lo to v1.53.0 (#1447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 22:18:51 +00:00
github-actions[bot] 9925a51cde New Crowdin translations by GitHub Action (#1446)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-03-02 22:42:27 +08:00
h2zi cb29a24751 chore(l10n): update pot files 2026-03-02 14:40:59 +00:00
耗子 5f62c6dd29 Merge remote-tracking branch 'origin/main' 2026-03-02 22:38:24 +08:00
耗子 8647c10f3f fix: session及计划任务优化 2026-03-02 22:38:19 +08:00
renovate[bot] a04ac69392 chore(deps): Update non-major dependencies (#1445)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 19:49:51 +08:00
耗子 b011191d4c Merge remote-tracking branch 'origin/main' 2026-03-02 19:31:00 +08:00
耗子 e00c1eb1f7 fix: 文件管理path可能未定义的问题 2026-03-02 19:30:49 +08:00
renovate[bot] 597c374443 chore(deps): Lock file maintenance (#1444)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 09:29:50 +00:00
renovate[bot] 0a34d5f631 chore(deps): Update non-major dependencies (#1443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 02:17:51 +00:00
renovate[bot] 36b8500101 chore(deps): Update non-major dependencies (#1442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 16:06:01 +08:00
renovate[bot] 5044060661 chore(deps): Update module github.com/shirou/gopsutil/v4 to v4.26.2 (#1440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 15:58:58 +08:00
Catsayer d96d83a5b7 合并拉取请求 #1441
*  refactor(service): 抽取环境 CLI 软链接公共能力并统一 SetCli 实现

* fix: 优化代码
2026-03-01 15:58:29 +08:00
github-actions[bot] 99fe0b2719 New Crowdin translations by GitHub Action (#1439)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-28 23:10:15 +08:00
h2zi 06bd74be8c chore(l10n): update pot files 2026-02-28 15:08:27 +00:00
renovate[bot] fe0d709370 chore(deps): Update non-major dependencies (#1438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-28 23:07:19 +08:00
耗子 e0f5704275 fix: 竞态条件导致通行密钥无法登录 2026-02-28 23:04:28 +08:00
renovate[bot] 4eb80c8720 chore(deps): Update dependency @types/node to v24.11.0 (#1437)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-28 16:57:21 +08:00
耗子 d8213d65a0 fix: lint 2026-02-28 00:58:24 +08:00
github-actions[bot] f00ceb9d10 New Crowdin translations by GitHub Action (#1436)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-28 00:42:41 +08:00
h2zi 8f96d53e09 chore(l10n): update pot files 2026-02-27 16:20:14 +00:00
耗子 22cfddf43b fix: postgresql 2026-02-27 19:19:17 +08:00
耗子 4479e5b9e9 Merge remote-tracking branch 'origin/main' 2026-02-27 17:29:36 +08:00
耗子 dbe4ea9909 feat: 兼容418错误 2026-02-27 17:29:24 +08:00
renovate[bot] 9de68220ec chore(deps): Update dependency @types/node to v24.10.15 (#1434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 05:44:56 +08:00
renovate[bot] 83de33d1ab chore(deps): Update GitHub Artifact Actions (#1435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 05:44:46 +08:00
耗子 d8515f9883 feat: 监控采集间隔支持设置 2026-02-27 00:11:07 +08:00
h2zi 4ff7ce18cd chore: update mocks 2026-02-26 15:51:13 +00:00
耗子 71e259ba32 feat: 支持通行密钥登录 (#1432)
* feat: 支持通行密钥登录

* fix: 修复AI评论

* Update pkg/passkey/passkey.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-26 23:50:18 +08:00
renovate[bot] 4873103904 chore(deps): Update pnpm to v10.30.3 (#1431)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 14:57:57 +00:00
耗子 55655d99cf Revert "fix: lint"
This reverts commit a67ec47c7e.
2026-02-26 16:18:05 +08:00
github-actions[bot] 830d4dca7a New Crowdin translations by GitHub Action (#1430)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-26 16:15:05 +08:00
devhaozi 2a89e060c3 chore(l10n): update pot files 2026-02-26 08:02:42 +00:00
耗子 6900092723 Merge remote-tracking branch 'origin/main' 2026-02-26 16:00:31 +08:00
耗子 8a79d90e78 fix: lint 2026-02-26 16:00:28 +08:00
renovate[bot] f5e5b687b8 chore(deps): Update non-major dependencies to v66.6.2 (#1429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 15:54:58 +08:00
renovate[bot] f5628cdf4a chore(deps): Update non-major dependencies (#1428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 15:50:55 +08:00
耗子 44f569b478 feat: 扫描感知支持自动屏蔽IP 2026-02-26 04:04:02 +08:00
耗子 38ec8b63d2 feat: 扫描感知支持自动屏蔽IP 2026-02-26 03:58:06 +08:00
耗子 fad297f33d fix: PostgreSQL备份清理 2026-02-26 00:18:02 +08:00
耗子 0c8a8cd382 fix: lint 2026-02-25 23:08:47 +08:00
耗子 3089a847a7 fix: 编辑数据库服务器时 Redis 类型隐藏用户名输入框
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:51:36 +08:00
renovate[bot] 4b1839169a chore(deps): Update non-major dependencies to v66.6.1 (#1426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 22:51:01 +08:00
github-actions[bot] 2aeec54189 New Crowdin translations by GitHub Action (#1425)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-25 21:44:24 +08:00
h2zi ce19d4ff14 chore(l10n): update pot files 2026-02-25 13:42:23 +00:00
耗子 a67ec47c7e fix: lint 2026-02-25 21:41:01 +08:00
耗子 7fc0e0f4af fix: Docker socket 路径错误和 Redis 密码管理问题 (#1424)
1. Docker 默认 socket 路径不应拼接面板根目录前缀
2. Redis Load() 正则缺少多行标记导致无法提取密码
3. Redis 修改 requirepass 后未同步到数据库服务器记录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:32:26 +08:00
耗子 bfd4896470 Merge remote-tracking branch 'origin/main' 2026-02-25 18:08:54 +08:00
耗子 12b8a4a03b chore: run go generate ./... 2026-02-25 18:08:32 +08:00
h2zi d4c853db01 chore: update mocks 2026-02-25 10:07:42 +00:00
半岛的蒟蒻bddjr e947259b86 fix: go.mod module github.com/acepanel/panel/v3 (#1423) 2026-02-25 18:07:00 +08:00
renovate[bot] c7cde220d4 chore(deps): Update non-major dependencies (#1422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-24 21:41:34 +08:00
耗子 b4e7c924a9 feat: 默认禁用扫描感知 2026-02-24 21:01:10 +08:00
耗子 6401f2ddd8 fix: postgresql数据库无法创建备份任务 2026-02-23 23:19:00 +08:00
耗子 735258c212 feat: 调整排序 2026-02-23 22:26:56 +08:00
github-actions[bot] 358ee8d19c New Crowdin translations by GitHub Action (#1421)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-23 21:33:06 +08:00
h2zi dd5a9d3e72 chore(l10n): update pot files 2026-02-23 13:31:55 +00:00
耗子 e92bc5309b feat: 设置添加5分钟缓存 2026-02-23 20:20:53 +08:00
renovate[bot] bb3cf83b59 chore(deps): Lock file maintenance (#1420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 04:41:43 +00:00
renovate[bot] 82880f0ecc chore(deps): Update module github.com/bddjr/hlfhr to v1.4.3 (#1419)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 05:27:15 +08:00
github-actions[bot] 7d174655ee New Crowdin translations by GitHub Action (#1418)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-22 21:43:44 +08:00
h2zi ab27b6d1bc chore(l10n): update pot files 2026-02-22 13:13:06 +00:00
耗子 308e7c685c feat: 统计添加ISP数据 2026-02-22 21:04:16 +08:00
耗子 7de339b3f7 feat: 统计添加ISP数据 2026-02-22 20:47:37 +08:00
耗子 3445d2396e feat: 数据库优化 2026-02-22 20:06:11 +08:00
h2zi 4130b8b314 chore: update mocks 2026-02-22 11:59:00 +00:00
耗子 d216a72ac9 feat: 数据库优化 2026-02-22 19:58:20 +08:00
耗子 20c1cb842c fix: lint 2026-02-22 18:30:37 +08:00
耗子 7b94af32ea fix: lint 2026-02-22 16:41:53 +08:00
耗子 1d7f0a2f22 feat: 优化IP解析性能 2026-02-22 07:39:35 +08:00
耗子 6c589d0350 revert: 网站统计默认值调整 2026-02-22 00:35:29 +08:00
github-actions[bot] a0c6422a69 New Crowdin translations by GitHub Action (#1416)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-22 00:31:07 +08:00
renovate[bot] c79e72a159 chore(deps): Update goreleaser/goreleaser-action action to v7 (#1417)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-22 00:30:58 +08:00
h2zi 2791add4ef chore(l10n): update pot files 2026-02-21 16:20:47 +00:00
耗子 f064eae300 feat: 调整修复方法 2026-02-22 00:16:01 +08:00
耗子 b86645bd58 feat: 拆分统计数据库以优化性能 2026-02-21 23:52:16 +08:00
耗子 04b4352abd feat: 优化网站统计性能 2026-02-21 23:05:14 +08:00
耗子 42055275ca feat: 优化网站统计数据压力 2026-02-21 23:01:01 +08:00
耗子 8facb097e1 Merge remote-tracking branch 'origin/main' 2026-02-21 22:13:02 +08:00
耗子 ec1e069b84 feat: 优化网站统计数据压力 2026-02-21 22:12:59 +08:00
renovate[bot] f1d5122a13 chore(deps): Update module github.com/valyala/fastjson to v1.6.10 (#1415)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-21 21:46:55 +08:00
renovate[bot] c5b4fb18d8 chore(deps): Update dependency vue-tsc to v3.2.5 (#1414)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-21 16:22:43 +08:00
renovate[bot] c507eff0df chore(deps): Update module github.com/mholt/acmez/v3 to v3.1.6 (#1412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-21 06:17:39 +08:00
renovate[bot] e29a98ecb2 chore(deps): Update non-major dependencies (#1411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 13:42:58 +00:00
耗子 2eb7aa207b fix: lint 2026-02-20 05:19:39 +08:00
github-actions[bot] e5ed1bfd47 New Crowdin translations by GitHub Action (#1410)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-20 05:16:36 +08:00
耗子 472aaada36 Merge remote-tracking branch 'origin/main' 2026-02-20 05:13:03 +08:00
耗子 31ed50fad0 fix: lint 2026-02-20 05:12:59 +08:00
renovate[bot] 540e2232fd chore(deps): Update dependency vue-router to v5.0.3 (#1409)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 05:07:50 +08:00
h2zi 06ff48fd3f chore(l10n): update pot files 2026-02-19 21:05:33 +00:00
耗子 f6ac168081 fix: lint 2026-02-20 05:03:20 +08:00
耗子 4004e61b55 fix: lint 2026-02-20 05:02:35 +08:00
耗子 a62cff7a73 feat: 网站统计添加入站实时数据 2026-02-20 04:57:14 +08:00
耗子 8bf30ee586 Merge remote-tracking branch 'origin/main' 2026-02-20 04:54:05 +08:00
耗子 57340482cc feat: 网站统计添加刷新按钮 2026-02-20 04:54:00 +08:00
h2zi a233efa6db chore(l10n): update pot files 2026-02-19 20:49:09 +00:00
耗子 1a907ce8a8 Merge remote-tracking branch 'origin/main' 2026-02-20 04:47:55 +08:00
耗子 64b671914c fix: lint 2026-02-20 04:47:47 +08:00
h2zi cb2180fbdb chore: update mocks 2026-02-19 20:47:41 +00:00
耗子 67a4b2f8f1 feat: 网站统计优化 2026-02-20 04:46:58 +08:00
耗子 b39785fc34 feat: 网站统计优化 2026-02-20 04:29:40 +08:00
耗子 b9cb492847 fix: Geo区域模式下数据重复 2026-02-20 03:42:19 +08:00
github-actions[bot] 0109eb9c52 New Crowdin translations by GitHub Action (#1408)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-20 03:16:35 +08:00
h2zi 959243690d chore(l10n): update pot files 2026-02-19 19:14:42 +00:00
耗子 6809969b25 feat: 新增IP库保存下载 2026-02-20 03:13:32 +08:00
renovate[bot] 87295af790 chore(deps): Update module github.com/libtnb/sqlite to v1 (#1407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 03:04:14 +08:00
github-actions[bot] 370e5d9a20 New Crowdin translations by GitHub Action (#1406)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-20 02:56:56 +08:00
h2zi b495017dab chore(l10n): update pot files 2026-02-19 18:38:22 +00:00
耗子 3eeff1575c Merge remote-tracking branch 'origin/main' 2026-02-20 02:37:21 +08:00
耗子 0124481f6d fix: lint 2026-02-20 02:37:12 +08:00
h2zi d569de3e67 chore(l10n): update pot files 2026-02-19 18:24:50 +00:00
耗子 c64cf8ac18 feat: 新增IP库可选自动更新 2026-02-20 02:22:47 +08:00
h2zi 6fc7b34882 chore: update mocks 2026-02-19 16:31:54 +00:00
github-actions[bot] 016b06bfce New Crowdin translations by GitHub Action (#1405)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-20 00:31:00 +08:00
devhaozi 08c3623526 chore(l10n): update pot files 2026-02-19 16:05:17 +00:00
耗子 632bdd0b1a Merge remote-tracking branch 'origin/main' 2026-02-20 00:02:30 +08:00
耗子 2d8f014d20 feat: 新增IP地理位置显示 2026-02-20 00:02:26 +08:00
renovate[bot] 6eac8176b3 chore(deps): Update module github.com/valyala/fastjson to v1.6.9 (#1404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-19 22:08:36 +08:00
耗子 e77863d469 fix: run go fix ./... 2026-02-19 22:08:16 +08:00
github-actions[bot] 95c54fd17a New Crowdin translations by GitHub Action (#1402)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-19 15:50:43 +08:00
devhaozi 527e49fde3 chore(l10n): update pot files 2026-02-19 07:49:58 +00:00
耗子 ced99702a9 Merge remote-tracking branch 'origin/main' 2026-02-19 15:48:53 +08:00
耗子 7190cc7947 feat: 网站统计默认启用 2026-02-19 15:48:50 +08:00
github-actions[bot] 8891420814 New Crowdin translations by GitHub Action (#1401)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-19 15:40:33 +08:00
devhaozi 81ac5d1b8a chore(l10n): update pot files 2026-02-19 07:33:16 +00:00
dependabot[bot] fe3405c4e2 chore(deps): bump filippo.io/edwards25519 from 1.1.0 to 1.1.1 (#1400)
Bumps [filippo.io/edwards25519](https://github.com/FiloSottile/edwards25519) from 1.1.0 to 1.1.1.
- [Commits](https://github.com/FiloSottile/edwards25519/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: filippo.io/edwards25519
  dependency-version: 1.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 15:31:29 +08:00
renovate[bot] 90cd78dd1a chore(deps): Update dependency @rushstack/eslint-patch to v1.16.0 (#1399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-19 06:34:06 +00:00
耗子 cc895d4df7 fix: 网站统计的一系列问题 2026-02-19 06:21:53 +08:00
耗子 da70928767 fix: 网站统计的一系列问题 2026-02-19 06:09:19 +08:00
耗子 cfb3ed75bd fix: 提高ipv6拓展头解析层数 2026-02-19 05:50:09 +08:00
耗子 1e85a141f1 feat: 重写汇编 2026-02-19 05:45:30 +08:00
github-actions[bot] 6418d88f22 New Crowdin translations by GitHub Action (#1398)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-19 04:48:35 +08:00
devhaozi 013389af54 chore(l10n): update pot files 2026-02-18 20:42:28 +00:00
耗子 f0121d7240 fix: lint 2026-02-19 04:41:15 +08:00
耗子 1133468ddf Merge remote-tracking branch 'origin/main' 2026-02-19 04:32:47 +08:00
耗子 701d3e96db feat: 调整网站统计的位置 2026-02-19 04:32:37 +08:00
devhaozi 49268d3b08 chore(l10n): update pot files 2026-02-18 20:32:13 +00:00
耗子 95d38b6c02 feat: 添加一些内部数据显示 2026-02-19 04:24:34 +08:00
github-actions[bot] 9b4c6b2319 New Crowdin translations by GitHub Action (#1397)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-19 03:32:14 +08:00
耗子 8c09c790a5 Merge remote-tracking branch 'origin/main' 2026-02-19 03:30:41 +08:00
耗子 b5232d47b1 fix: lint 2026-02-19 03:30:37 +08:00
devhaozi 1f28186581 chore(l10n): update pot files 2026-02-18 19:28:03 +00:00
耗子 537f1430de fix: lint 2026-02-19 03:24:53 +08:00
耗子 f009b24ad9 fix: lint 2026-02-19 03:24:23 +08:00
耗子 93733b5692 fix: lint 2026-02-19 03:22:19 +08:00
耗子 2af6469a12 feat: 优化json解析性能 2026-02-19 03:06:48 +08:00
耗子 19a3a92216 fix: 修复网站统计一些逻辑问题 2026-02-19 03:00:19 +08:00
github-actions[bot] 16590d9b17 New Crowdin translations by GitHub Action (#1396)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-18 20:38:19 +08:00
devhaozi eaae15c092 chore(l10n): update pot files 2026-02-18 12:37:33 +00:00
github-actions[bot] b9d55f51b8 New Crowdin translations by GitHub Action (#1395)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-18 20:24:40 +08:00
devhaozi 6c9e43e701 chore(l10n): update pot files 2026-02-18 12:20:48 +00:00
耗子 8952b7bdaf fix: lint 2026-02-18 20:18:45 +08:00
github-actions[bot] b9b14f55c1 New Crowdin translations by GitHub Action (#1394)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-18 20:08:12 +08:00
耗子 6e2dd1b91f Merge remote-tracking branch 'origin/main' 2026-02-18 20:01:36 +08:00
耗子 4219c8d57e fix: lint 2026-02-18 20:01:32 +08:00
devhaozi dbaa68357e chore(l10n): update pot files 2026-02-18 12:00:33 +00:00
耗子 971b130061 feat: 完善网站统计 2026-02-18 19:58:38 +08:00
耗子 003da4ce2d feat: 完善网站统计 2026-02-18 19:57:32 +08:00
耗子 433db4cbd6 Merge remote-tracking branch 'origin/main' 2026-02-18 19:50:37 +08:00
耗子 2b48fd5fe3 feat: 完善网站统计 2026-02-18 19:50:33 +08:00
h2zi 4c468e9aca chore: update mocks 2026-02-18 11:19:40 +00:00
耗子 7d368536d0 feat: 完善网站统计 2026-02-18 19:18:50 +08:00
h2zi 1403c65d37 chore: update mocks 2026-02-18 10:18:09 +00:00
耗子 fa30728753 feat: 网站统计 2026-02-18 18:17:16 +08:00
耗子 aa2e54f3f1 fix: 将真实 IP 来源配置从动态输入改为文本域
替换 n-dynamic-input 为 textarea,支持批量粘贴多行 IP/CIDR,
提升 CDN 等大量 IP 的配置体验。

Closes #1393

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 16:00:26 +08:00
github-actions[bot] 20e3484767 New Crowdin translations by GitHub Action (#1392)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-18 05:04:20 +08:00
devhaozi f80b954993 chore(l10n): update pot files 2026-02-17 21:00:33 +00:00
github-actions[bot] 17efbab5dd New Crowdin translations by GitHub Action (#1391)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-18 04:57:03 +08:00
devhaozi 7b1d647313 chore(l10n): update pot files 2026-02-17 20:53:32 +00:00
耗子 6cf84f148b feat: 添加扫描感知禁用提醒 2026-02-18 04:52:07 +08:00
github-actions[bot] 8abae3bb4e New Crowdin translations by GitHub Action (#1390)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-18 04:41:36 +08:00
耗子 e95f904e87 Merge remote-tracking branch 'origin/main' 2026-02-18 04:41:28 +08:00
耗子 c67f2b7df0 fix: lint 2026-02-18 04:41:23 +08:00
devhaozi e6e358bd14 chore(l10n): update pot files 2026-02-17 20:41:09 +00:00
耗子 bd9ba1c552 fix: lint 2026-02-18 04:39:50 +08:00
github-actions[bot] 4b7cf9e981 New Crowdin translations by GitHub Action (#1388)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-18 04:36:35 +08:00
devhaozi b75abff1c1 chore(l10n): update pot files 2026-02-17 20:34:35 +00:00
耗子 e06917003d Merge remote-tracking branch 'origin/main' 2026-02-18 04:33:29 +08:00
耗子 375cb73e34 fix: lint 2026-02-18 04:33:19 +08:00
devhaozi 1213c39fb2 chore(l10n): update pot files 2026-02-17 20:05:54 +00:00
耗子 397f84a921 Merge remote-tracking branch 'origin/main' 2026-02-18 04:04:10 +08:00
耗子 96c03c4c5a fix: lint 2026-02-18 04:04:06 +08:00
devhaozi 5407c53619 chore: update mocks 2026-02-17 20:03:22 +00:00
耗子 37c155acb5 feat: 优化扫描感知支持搜索 2026-02-18 04:01:27 +08:00
renovate[bot] 17adaa1c8c chore(deps): Update pnpm to v10.30.0 (#1387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-18 03:42:30 +08:00
耗子 65e390a256 feat: 计划任务支持进程锁 2026-02-18 03:42:15 +08:00
耗子 67642aa475 feat: 添加SMART和RAID状态查看 2026-02-18 03:28:06 +08:00
devhaozi da9d95284c chore: update mocks 2026-02-17 18:53:59 +00:00
耗子 9a1555c48a feat: 防火墙扫描感知功能 & 首页容器统计 & lint 修复
- 新增 eBPF 扫描感知:TC ingress 检测 SYN/UDP 扫描,端口白名单过滤
- 新增首页容器数量统计卡片,未安装容器环境时自动隐藏
- 扫描概览页:统计卡片、趋势图、Top IP/端口、事件列表
- 扫描设置页:启用开关、保留天数、网卡选择
- 统计对比改为同长度上一周期,替代固定昨日对比
- eBPF ports map MaxEntries 扩展至 65535 覆盖全端口
- 修复 WriteString(fmt.Sprintf()) lint 问题,改用 fmt.Fprintf
- 修复 database_redis.go 非常量格式字符串和 govet 警告

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 02:52:32 +08:00
耗子 d0cd178dfe fix: lint 2026-02-17 23:45:01 +08:00
耗子 acacf5d9ce Merge remote-tracking branch 'origin/main' 2026-02-17 23:38:28 +08:00
耗子 e36bb8f429 fix: lint 2026-02-17 23:38:19 +08:00
devhaozi c731125534 chore: update mocks 2026-02-17 15:31:28 +00:00
耗子 1cf3218fde feat: 支持管理redis数据库 2026-02-17 23:30:42 +08:00
耗子 b7be21c851 feat: 防火墙支持显示端口使用进程 2026-02-17 22:48:16 +08:00
耗子 2f49d0baa6 feat: 防火墙支持显示端口使用进程 2026-02-17 22:48:00 +08:00
耗子 b541a56bf9 feat: 首页新增容器数显示 2026-02-17 22:30:24 +08:00
耗子 bb722ccf3d feat: 支持自定义容器socket路径 2026-02-17 22:13:26 +08:00
耗子 f19c0d9c00 feat: 计划任务支持同步时间类型 2026-02-17 21:44:35 +08:00
耗子 e9affee707 feat: 计划任务支持访问URL类型 2026-02-17 21:39:27 +08:00
耗子 ba6c09ed70 feat: 计划任务支持批量删除 2026-02-17 21:15:03 +08:00
耗子 23e2df1ed9 fix: 网站日志轮转 2026-02-17 20:57:59 +08:00
github-actions[bot] 0bafb5b8f5 New Crowdin translations by GitHub Action (#1386)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-17 20:41:25 +08:00
devhaozi 98195d1838 chore(l10n): update pot files 2026-02-17 12:33:49 +00:00
耗子 2a143513d0 Merge remote-tracking branch 'origin/main' 2026-02-17 20:32:10 +08:00
耗子 432fcdd0a0 feat: 优化网站名的描述 2026-02-17 20:32:06 +08:00
devhaozi 6c703bf94d chore: update mocks 2026-02-17 12:28:53 +00:00
耗子 b439973995 feat: 计划任务优化 2026-02-17 20:28:07 +08:00
耗子 5c74b7eb71 fix: lint 2026-02-17 18:42:17 +08:00
耗子 edc88484ac fix: lint 2026-02-17 18:30:01 +08:00
耗子 51e4cdb095 fix: 日志目录 2026-02-17 18:20:24 +08:00
renovate[bot] e0c5f425cb chore(deps): Update dependency @vue/eslint-config-typescript to v14.7.0 (#1385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-17 17:47:43 +08:00
耗子 2f2f17b1c7 refactor: 重构任务队列 2026-02-17 17:47:25 +08:00
devhaozi 2536dfe933 chore: update mocks 2026-02-17 09:17:03 +00:00
耗子 e95c4623c5 feat: 优化日志保存,close #1382 2026-02-17 17:16:17 +08:00
耗子 3d46a284e6 chore: 更新 2026-02-17 16:50:19 +08:00
耗子 ed33e090ed fix: 保存网站可能重复创建证书 2026-02-17 16:29:48 +08:00
耗子 0cab838499 fix: 保存网站可能重复创建证书 2026-02-17 16:01:46 +08:00
renovate[bot] 4f7b6b1c38 chore(deps): Lock file maintenance (#1384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 05:51:32 +00:00
耗子 230894d618 Merge remote-tracking branch 'origin/main' 2026-02-15 06:33:23 +08:00
耗子 ec8b3dbb10 ci: 移除自动锁定 2026-02-15 06:33:19 +08:00
renovate[bot] 4a899f1c0d chore(deps): Update module github.com/expr-lang/expr to v1.17.8 (#1383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-14 20:57:24 +00:00
renovate[bot] 49a5e36c30 chore(deps): Update dependency eslint-plugin-vue to v10.8.0 (#1381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-14 05:01:28 +00:00
耗子 47d945710b fix: lint 2026-02-14 05:23:50 +08:00
耗子 3ab58fc817 fix: lint 2026-02-14 00:27:03 +08:00
耗子 36a6617c68 fix: lint 2026-02-14 00:22:30 +08:00
耗子 b8d2051e64 feat: update go1.26 2026-02-14 00:16:52 +08:00
github-actions[bot] da7c93308b New Crowdin translations by GitHub Action (#1380)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-13 20:30:21 +08:00
devhaozi 017c0ed9b7 chore(l10n): update pot files 2026-02-13 12:29:30 +00:00
耗子 0930007d24 fix: 登录 2026-02-13 20:28:11 +08:00
耗子 8285b8dee8 fix: lint 2026-02-13 20:10:13 +08:00
github-actions[bot] c52d42a2de New Crowdin translations by GitHub Action (#1379)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-13 19:37:15 +08:00
devhaozi 5d07932226 chore(l10n): update pot files 2026-02-13 11:36:32 +00:00
耗子 89ad4d83f3 feat: 兼容不支持br的情况 2026-02-13 19:35:03 +08:00
renovate[bot] cce1102c90 chore(deps): Update module github.com/libdns/huaweicloud to v1.0.1 (#1378)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-13 19:21:26 +08:00
耗子 f67a3e3a6f feat: nginx添加512k zone 2026-02-13 19:16:36 +08:00
耗子 7e09e20254 fix: lint 2026-02-13 17:35:13 +08:00
耗子 6fd6c958cf feat: 优化recoverer中间件 2026-02-13 03:58:22 +08:00
耗子 7fe0c3ce3c fix: 容器镜像可能panic 2026-02-13 03:50:47 +08:00
耗子 086d0eb601 fix: 容器卷可能panic 2026-02-13 03:49:45 +08:00
耗子 d7b7cf0b57 fix: 优化密码管理器自动提交 2026-02-13 03:48:28 +08:00
耗子 78a1a43f2f feat: 倒序迁移,防止打乱顺序 2026-02-13 00:55:31 +08:00
耗子 f9c1510d8a fix: lint 2026-02-13 00:41:02 +08:00
耗子 0ea2117d96 fix: 优化按钮顺序 2026-02-12 23:37:11 +08:00
renovate[bot] 2b055d6e88 chore(deps): Lock file maintenance (#1377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 21:20:03 +08:00
耗子 227a7b19f3 fix: lint 2026-02-12 19:13:10 +08:00
耗子 eda6d92d66 feat: 优化更新页 2026-02-12 19:05:50 +08:00
github-actions[bot] c2441d2081 New Crowdin translations by GitHub Action (#1376)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-12 17:41:42 +08:00
devhaozi 47123317a2 chore(l10n): update pot files 2026-02-12 09:39:08 +00:00
耗子 68125fb1e6 fix: lint 2026-02-12 07:52:59 +08:00
耗子 acecdf7f48 Merge remote-tracking branch 'origin/main' 2026-02-12 07:47:14 +08:00
耗子 021646727b fix: 移除多余的handler 2026-02-12 07:47:10 +08:00
renovate[bot] 4e6d46d1e9 chore(deps): Update module github.com/libtnb/acmez/v3 to v3.0.0-20260211214515-0d13ab154236 (#1374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 07:45:05 +08:00
耗子 1265ac5b34 feat: 移除自定义压缩 2026-02-12 07:43:46 +08:00
耗子 94704deb1b feat: sqlite测试 2026-02-12 07:31:47 +08:00
耗子 932f48044b feat: sqlite测试 2026-02-12 07:28:00 +08:00
耗子 0c89d77221 feat: 优化内存占用 2026-02-12 03:34:30 +08:00
github-actions[bot] e0ccab8eb7 New Crowdin translations by GitHub Action (#1371)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-12 02:23:19 +08:00
renovate[bot] 3aef5140e2 chore(deps): Update dependency marked to v17.0.2 (#1373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 02:13:00 +08:00
devhaozi 10ca20f3aa chore(l10n): update pot files 2026-02-11 17:56:40 +00:00
耗子 89a55ca518 fix: lint 2026-02-12 01:33:12 +08:00
耗子 d743bef697 fix: ufw 2026-02-12 00:28:24 +08:00
耗子 50fe450d64 fix: ufw 2026-02-12 00:24:52 +08:00
耗子 74259f833c fix: 添加单测 2026-02-11 23:57:50 +08:00
耗子 ca8e36351e fix: 合并防火墙规则 2026-02-11 23:30:38 +08:00
耗子 d01a2374f5 fix: 合并防火墙规则 2026-02-11 23:21:33 +08:00
耗子 f4885c55f9 Merge remote-tracking branch 'origin/main' 2026-02-11 23:16:06 +08:00
耗子 7e8de43776 fix: ufw 2026-02-11 23:16:02 +08:00
renovate[bot] 4ded231b1c chore(deps): Update pnpm to v10.29.3 (#1372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-11 23:06:27 +08:00
耗子 20a0080b1e fix: build 2026-02-11 23:03:45 +08:00
耗子 d2b30f211f chore: 移除h3支持,QoS麻了 2026-02-11 22:39:33 +08:00
耗子 83311409ae fix: lint 2026-02-11 21:59:29 +08:00
devhaozi 515ed686fb chore(l10n): update pot files 2026-02-11 13:20:08 +00:00
耗子 975c0d7321 fix: lint 2026-02-11 21:05:36 +08:00
耗子 0bdf1d6058 feat: UI优化 2026-02-11 21:02:58 +08:00
耗子 c5d62a46ab feat: UI优化 2026-02-11 20:38:08 +08:00
耗子 2dd6f0d9f0 feat: 文件管理支持多标签页 2026-02-11 20:12:10 +08:00
耗子 de5d76bff6 feat: 添加启用ipv6转发 2026-02-11 19:37:20 +08:00
耗子 e3742e61d8 Merge remote-tracking branch 'origin/main' 2026-02-11 19:28:55 +08:00
耗子 72c31a14b2 feat: 防火墙,SSH日志, close #1366 #1364 2026-02-11 19:28:51 +08:00
devhaozi 205d95a591 chore: update mocks 2026-02-11 11:13:50 +00:00
耗子 4df5038f91 feat: 数据库管理优化, close #1365 2026-02-11 19:12:43 +08:00
github-actions[bot] da023c5faa New Crowdin translations by GitHub Action (#1370)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-11 03:33:13 +08:00
devhaozi e6c13330d8 chore(l10n): update pot files 2026-02-10 19:26:21 +00:00
github-actions[bot] bda95465f8 New Crowdin translations by GitHub Action (#1369)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-11 03:22:56 +08:00
devhaozi c3074249f0 chore(l10n): update pot files 2026-02-10 19:08:29 +00:00
耗子 6388bcfa37 fix: migrate 2026-02-11 03:05:27 +08:00
耗子 281f35dfa1 fix: migrate 2026-02-11 03:01:49 +08:00
耗子 5d189172ca fix: migrate 2026-02-11 02:44:32 +08:00
耗子 aa23066fe5 feat: update resty to v3 2026-02-11 02:20:00 +08:00
耗子 c3313845a6 fix: lint 2026-02-11 01:44:15 +08:00
耗子 85f86f51db fix: 添加数据库用户迁移 2026-02-11 01:28:38 +08:00
耗子 374996498f fix: lint 2026-02-11 01:17:05 +08:00
耗子 a17b9d4936 Merge remote-tracking branch 'origin/main' 2026-02-10 23:00:24 +08:00
耗子 c226091c6e fix: lint 2026-02-10 23:00:20 +08:00
renovate[bot] 8902dade39 chore(deps): Update dependency @types/node to v24.10.13 (#1368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 22:56:54 +08:00
renovate[bot] 5f9696f214 chore(deps): Update module github.com/lib/pq to v1.11.2 (#1363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 22:52:00 +08:00
耗子 7155e7c7cb feat: 首页添加Top5进程显示 2026-02-10 22:35:29 +08:00
耗子 c56b4e00d9 feat: 监控添加Top进程记录 2026-02-10 21:55:11 +08:00
耗子 f7bf8b01cc feat: 工具箱新增网络视图 2026-02-10 21:07:05 +08:00
耗子 b7e1bd8824 feat: 新增便签小工具 2026-02-10 20:44:51 +08:00
耗子 d9b6813060 feat: 支持dotnet 2026-02-10 20:22:08 +08:00
耗子 10a816f7f7 feat: 支持dotnet 2026-02-10 20:08:26 +08:00
耗子 68e4016538 fix: lint 2026-02-10 18:08:18 +08:00
renovate[bot] 338b9cc73b chore(deps): Update dependency @vueuse/core to v14.2.1 (#1361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 08:57:55 +00:00
耗子 b27ced4b92 fix: lint 2026-02-10 03:32:57 +08:00
github-actions[bot] 36fb9e76fc New Crowdin translations by GitHub Action (#1360)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-10 03:25:07 +08:00
devhaozi 5f7df1ef9a chore(l10n): update pot files 2026-02-09 19:12:46 +00:00
Copilot 7da411b2f3 feat: 工具箱-迁移功能 (Toolbox Migration) (#1358)
* Initial plan

* feat: add migration backend service, routes and Wire injection

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: add migration frontend - API, WebSocket, MigrationView and tab integration

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: move migration types to pkg/types, fix code review issues

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* chore: final cleanup

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: add refresh button on migration pre-check step

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: add SSH key exchange for rsync authentication

Generate temporary ED25519 key pair before migration, deploy public
key to remote via POST /api/toolbox_migration/ssh_key, use -i flag
in all rsync/ssh commands, cleanup key from both sides after migration.

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* fix: lint

* fix: align migration code with actual codebase patterns

- Project service name: use project name directly (not ace-project-xxx)
- MySQL dump: use MYSQL_PWD env var (not -p flag), remove --socket
- PostgreSQL dump: use PGPASSWORD env var with pg_dump -h 127.0.0.1 -U postgres
- PostgreSQL import: use PGPASSWORD with psql -h 127.0.0.1 -U postgres
- MySQL import: use MYSQL_PWD on remote
- Website create API: use correct listens/domains/type fields
- Project create API: use root_dir/exec_start/user fields
- Database create API: pass server_id properly
- Site directory: use filepath.Join(app.Root, "sites") instead of hardcoded path
- maskPassword: handle MYSQL_PWD/PGPASSWORD env var format

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: add PHP version and proxy backend to website create API body

PHP sites now include the php version number, and reverse proxy sites
include the backend address from Proxies[0].Pass. This matches the
WebsiteCreate validation rules (requiredIf:Type,php / requiredIf:Type,proxy).

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: replace map[string]any with typed request structs for all remote API calls

Use request.WebsiteCreate, request.DatabaseCreate, request.ProjectCreate,
and request.ToolboxMigrationSSHKey instead of ad-hoc map[string]any.
This ensures all required/validated fields are properly typed and present.

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: website list empty and database migration items stuck in running status

1. Website list: pass "all" to websiteRepo.List() instead of "" — empty
   string was treated as a type filter matching nothing.

2. Database status: result was stored with name "aaa (mysql)" but
   failResult/succeedResult looked up by "aaa" — name mismatch meant
   status was never updated from "running". Use displayName consistently.

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: use acepanel CLI to get remote DB passwords, show success text in results

1. Remote DB import: use `acepanel setting get mysql_root_password` and
   `acepanel setting get postgres_password` on the remote server via SSH
   instead of guessing file paths. The $() is escaped so it executes on
   the remote side, not locally.

2. Migration results: successful items now show "Migration succeeded"
   text alongside the completion timestamp instead of a bare date.

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* refactor: 不基于rsync的迁移

* fix: lint

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-10 02:51:03 +08:00
renovate[bot] 3c6c5aa31e chore(deps): Update non-major dependencies (#1359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 02:50:14 +08:00
耗子 5c05d0d81b feat: 添加 rsync 显示 2026-02-09 20:15:42 +08:00
Copilot 0724a042ef feat: HTTP/3 (QUIC) 支持 + TLS 证书热重载 (#1356)
* Initial plan

* feat: 添加 HTTP/3 (QUIC) 协议支持,TLS 启用时自动启用

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 优化证书路径复用,修正注释和依赖标记

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: 添加证书热重载,TLS 握手时自动检测并加载变更的证书文件

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 使用 fsnotify 替代每次握手 os.Stat 实现证书热重载

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* fix: lint

* fix: 使用 wire 工具重新生成 wire_gen.go 替代手动编辑

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: 通过 Wire 注入 *tlscert.Reloader,优雅关闭时正确释放资源

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* fix: lint

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-09 20:10:13 +08:00
耗子 261d87d6df feat: 优化远程文件下载 2026-02-09 17:28:25 +08:00
renovate[bot] f043f65f95 chore(deps): Update non-major dependencies 2026-02-09 08:57:05 +00:00
renovate[bot] 0e56569dab chore(deps): Update pnpm to v10.29.2 (#1354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-09 16:54:39 +08:00
renovate[bot] 8ccefa6bff chore(deps): Lock file maintenance (#1353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-09 04:54:01 +00:00
github-actions[bot] f3b8069710 New Crowdin translations by GitHub Action (#1352)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-08 23:41:16 +08:00
devhaozi 8bbddef429 chore(l10n): update pot files 2026-02-08 15:39:54 +00:00
github-actions[bot] 296930d36c New Crowdin translations by GitHub Action (#1351)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-08 23:17:02 +08:00
devhaozi ae8e1458ea chore(l10n): update pot files 2026-02-08 15:15:58 +00:00
耗子 9625b28ee8 fix: lint 2026-02-08 23:15:00 +08:00
github-actions[bot] bcff241c95 New Crowdin translations by GitHub Action (#1350)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-08 22:53:16 +08:00
devhaozi b4df083a19 chore(l10n): update pot files 2026-02-08 14:32:27 +00:00
耗子 ab756ed2bd Merge remote-tracking branch 'origin/main' 2026-02-08 22:31:07 +08:00
耗子 5cd1af9c2a fix: 补全翻译 2026-02-08 22:30:55 +08:00
devhaozi dc8bd635d4 chore(l10n): update pot files 2026-02-08 14:25:54 +00:00
耗子 9cec03a69a fix: 补全翻译 2026-02-08 22:24:18 +08:00
github-actions[bot] 5096339ab4 New Crowdin translations by GitHub Action (#1349)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-08 21:52:13 +08:00
devhaozi d20b940d24 chore(l10n): update pot files 2026-02-08 13:38:13 +00:00
Copilot 76abe1769b 操作日志添加"详情"列,折叠显示额外字段 (#1348)
* Initial plan

* feat: 操作日志添加"详情"列,使用 NPopover 折叠显示额外字段

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-08 20:06:17 +08:00
耗子 0d0c38a225 fix: OpenResty/Apache无法加载网站列表 2026-02-08 19:55:26 +08:00
Copilot 5ede0161c1 feat: 保存网站时自动将HTTPS证书上传到面板证书管理 (#1347)
* Initial plan

* feat: 保存网站时自动将HTTPS证书上传到面板证书管理

当保存网站并启用SSL时,检查证书是否已存在于面板的证书管理中。
如果不存在,则自动作为本地证书(type=upload)上传到面板证书表,
方便后续编辑网站时直接使用。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 添加证书自动上传失败时的错误日志

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-08 19:46:12 +08:00
耗子 9378546cc4 feat: 重构网站卡片 2026-02-08 19:29:13 +08:00
耗子 33b0ee9d1e feat: 重构网站卡片 2026-02-08 19:26:16 +08:00
耗子 5994595198 feat: 前端优化 2026-02-08 18:20:19 +08:00
耗子 c84ab39f16 feat: 前端优化 2026-02-08 18:16:14 +08:00
耗子 4760c89a06 feat: 前端优化 2026-02-08 18:11:29 +08:00
耗子 a10a06a544 fix: lint 2026-02-08 18:05:20 +08:00
Copilot f1a9f5a4bc Add loading/disabled states to all buttons triggering useRequest (#1344)
* Initial plan

* fix: add missing :disabled to buttons that only had :loading

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: add loading/disabled to setting module buttons

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: add loading/disabled to database module buttons

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: add loading/disabled to website module buttons

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: add loading/disabled to cert module buttons

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: add loading/disabled to project, monitor, container, home and app buttons

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: restore missing template closing tags in ssh modals

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: address code review feedback for loading state edge cases

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: add loading/disabled to remaining buttons in ToolBar, PathSelector and ServiceStatus

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: add loading/disabled states to buttons triggering useRequest across 27 Vue files

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: use separate loading refs for frps/frpc save user buttons

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-08 17:29:22 +08:00
renovate[bot] 6466868e08 chore(deps): Update dependency @types/node to v24.10.12 (#1345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-08 05:57:27 +00:00
耗子 fd65c267b3 fix: lint 2026-02-08 05:54:44 +08:00
Copilot 5ee2409b87 feat: add parameter tuning tabs for Nginx, MySQL, PostgreSQL, Redis, Memcached, Pure-FTPd (#1343)
* Initial plan

* feat: add config_tune backend endpoints for nginx, mysql, postgresql, redis, memcached, pureftpd

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: add config tune frontend for nginx, mysql, postgresql, redis, memcached, pureftpd

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: address code review feedback for postgresql and pureftpd config parsers

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

* feat: 自动刷新各配置状态

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-08 05:50:27 +08:00
renovate[bot] fca72a7e35 chore(deps): Update pnpm to v10.29.1 (#1341)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-08 03:44:53 +08:00
耗子 7b971c7920 fix: lint 2026-02-08 03:44:38 +08:00
耗子 dbeb6b5fa9 fix: lint 2026-02-08 03:13:13 +08:00
Copilot 402388c2d3 feat: PHP 参数调整标签页 — 可视化修改常用配置 (#1340)
* Initial plan

* feat: add PHP parameter tuning tab with backend API and frontend UI

- Add GetConfigTune/UpdateConfigTune/CleanSession backend endpoints
- Add PhpConfigTuneView component with vertical tabs for disabled functions,
  upload limits, timeout limits, performance tuning, and session settings
- Register new routes and frontend API methods
- Rename component from PHPConfigTuneView to PhpConfigTuneView per convention

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: address code review - refactor save handlers, fix setINIValue blank lines, add newline validation, fix CreateModal TS2322

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: use single saveLoading and handleSave, eliminate redundant refs and wrappers

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: enhance session with visual Redis/Memcached fields, add general settings tab

Session tab now shows host/port/password for Redis, host/port for Memcached
instead of raw save_path. Added session.gc_maxlifetime and session.cookie_lifetime.
New General tab with short_open_tag, date.timezone, display_errors, error_reporting.

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-08 03:01:39 +08:00
renovate[bot] 58c5a87262 chore(deps): Update dependency eslint to v10 (#1337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-07 16:36:28 +08:00
Copilot c4cd926622 feat: 网站链接图标双击打开 & 日志页 flex-height 适配 (#1339)
* Initial plan

* feat: 网站列表链接图标支持双击打开 & 修复日志页高分辨率屏幕底部留白

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-07 16:35:51 +08:00
耗子 debc34f236 feat: 多线程下载面板更新 2026-02-06 23:16:20 +08:00
耗子 2c06ae9293 fix: validate 2026-02-06 22:49:09 +08:00
github-actions[bot] bd7cb16c3a New Crowdin translations by GitHub Action (#1336)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-06 21:56:24 +08:00
devhaozi 9b36edd1a9 chore(l10n): update pot files 2026-02-06 13:45:17 +00:00
耗子 d5c0e03f16 fix: 网站列表优化 2026-02-06 21:27:44 +08:00
耗子 50a5b42b85 fix: 刷新证书添加禁用按钮 2026-02-06 21:17:56 +08:00
Copilot 57accdb5cf fix: preserve file ownership when copying via file manager (#1335)
* Initial plan

* fix: use cp -arf to preserve file ownership when copying

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-06 16:50:44 +08:00
Copilot f6cf1daae5 feat: 添加面板 HTTPS 证书手动刷新按钮 (#1334)
* Initial plan

* feat: 添加面板证书手动刷新按钮

在面板设置安全标签页中,当 HTTPS 模式为 ACME 时,
在保存按钮右侧显示「刷新证书」按钮,点击后通过
已有的 ObtainCert 服务方法重新签发证书并重启面板。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: 当 HTTPS 设置有未保存修改时禁用刷新证书按钮

追踪 https/acme/public_ip 的已保存状态,当用户修改这些
设置但未保存时,禁用「刷新证书」按钮,保存后恢复可用。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-06 05:55:14 +08:00
renovate[bot] 9cbf7b3f9a chore(deps): Update non-major dependencies (#1333)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-05 21:14:03 +00:00
github-actions[bot] 9700e432fd New Crowdin translations by GitHub Action (#1331)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-02-04 22:25:47 +08:00
devhaozi efb427ff9c chore(l10n): update pot files 2026-02-04 13:50:02 +00:00
renovate[bot] 7ac05c5e29 chore(deps): Update dependency vite-plugin-vue-devtools to v8.0.6 (#1330)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 20:07:07 +08:00
Copilot 181b262a61 feat(website): 添加"全部"分类、动态模态框标题和批量创建类型支持 (#1329)
* Initial plan

* feat(website): 添加"全部"分类、动态模态框标题和批量创建类型支持

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: 修复代码审查问题,提取正则常量和接口定义

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-04 20:04:19 +08:00
耗子 f7b63e2f6e Merge remote-tracking branch 'origin/main' 2026-02-04 19:04:48 +08:00
耗子 5fc8fb1c1f fix: Nginx QUIC 下 PHP Host 头为空 2026-02-04 19:04:44 +08:00
dependabot[bot] 805a7ea773 chore(deps): bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 in /web (#1328)
Bumps @isaacs/brace-expansion from 5.0.0 to 5.0.1.

---
updated-dependencies:
- dependency-name: "@isaacs/brace-expansion"
  dependency-version: 5.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 17:53:57 +08:00
renovate[bot] c8bfe92c5e chore(deps): Update dependency p-limit to v7.3.0 (#1327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 21:48:22 +00:00
耗子 4b26e190f5 Merge remote-tracking branch 'origin/main' 2026-02-04 02:27:26 +08:00
耗子 78be22b3dc feat: 为外链添加noreferrer 2026-02-04 00:24:09 +08:00
renovate[bot] 1909865f04 chore(deps): Update dependency @types/node to v24.10.10 (#1326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 18:02:56 +08:00
Copilot d7e8301148 feat: add server restart option to home page restart button (#1324)
* Initial plan

* feat: add server restart functionality to home page restart button

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: remove unreachable success callback for server restart

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-03 00:45:52 +08:00
耗子 54b1ddb809 fix: lint 2026-02-03 00:32:21 +08:00
Copilot 30bf14c40c feat: add file overwrite option to upload modal and display upload errors (#1322)
* Initial plan

* feat: add file overwrite upload option and display error messages

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-03 00:24:51 +08:00
Copilot 9ab01dc64b feat(project): 运行用户支持自定义输入 (#1323)
* Initial plan

* feat(project): 运行用户选择器支持自定义输入

为创建项目和编辑项目的"运行用户"选择器添加 filterable 和 tag 属性,
使用户可以在选择预设用户(www/root/nobody)的同时也能输入自定义用户名。

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
2026-02-03 00:15:47 +08:00
devhaozi 58ddd55b7c chore: update mocks 2026-02-02 16:05:24 +00:00
Copilot 4f19a58bba feat: add date selector for viewing archived panel logs (#1316)
* Initial plan

* feat: add date selector for log viewing

- Add date parameter to log list API
- Add log dates listing API
- Update frontend with date selector in all log views
- Add translations for Date field

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: cache regex patterns to avoid recompilation overhead

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* refactor: use predefined regex constants instead of sync.Map

Simplified the regex caching by using predefined package-level constants
for the three log types (app, db, http) instead of sync.Map.

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-03 00:04:43 +08:00
renovate[bot] 15f096191e chore(deps): Update non-major dependencies (#1321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-02 22:08:38 +08:00
renovate[bot] 6f1b177642 chore(deps): Lock file maintenance (#1319)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-02 04:40:50 +00:00
renovate[bot] f0ae6278f1 chore(deps): Update module github.com/shirou/gopsutil/v4 to v4.26.1 (#1315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 17:27:15 +08:00
Copilot 2fdb23a568 feat: 支持解压单独的 .gz 格式文件 (#1318)
* Initial plan

* feat: 支持解压 .gz 格式文件(如 .sql.gz)和优化压缩格式选项

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: 修正压缩格式注释说明

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-01 17:25:01 +08:00
Copilot ba12def9f2 Add editor close confirmation, path copy on double-click, and disable overlay close (#1314)
* Initial plan

* Implement file management optimization features

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* Improve error handling in file save dialog

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* Disable overlay click to close editor window

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* Fix comment clarity for closeOnOverlay prop

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-02-01 17:03:33 +08:00
耗子 c5667d8ce4 fix: 无法添加sftp备份 2026-02-01 06:26:23 +08:00
耗子 6b1789421f feat: cron表达式生成器新增每分钟选项 2026-01-31 19:31:04 +08:00
830 changed files with 92519 additions and 34752 deletions
+5 -2
View File
@@ -251,9 +251,9 @@ pnpm build
- **`internal/migration/`**: 数据库迁移
- **`internal/job/`**: 后台任务
- **`internal/job/`**: 定时任务
- **`internal/queuejob/`**: 任务队列
- **`internal/taskqueue/`**: 任务队列运行器(基于 DB 轮询,实现 `types.TaskRunner` 接口)
- **`pkg/`**: 工具函数和通用包
- 包含各种独立的工具模块
@@ -335,6 +335,8 @@ pnpm build
- 使用 `gofmt` 格式化代码
- 复杂逻辑添加注释说明
- 导出的函数和类型必须有注释
- service 层以及 data 层方法对外返回的字符串需要使用 gotext 进行国际化处理
- 禁止手动编辑国际化文件,项目使用外部 Crowdin 自动化管理
## Wire 依赖注入
@@ -356,6 +358,7 @@ go generate ./...
- 图标:@iconify/vue
- 终端:xterm.js
- 遵循项目已有的组件结构和编码风格
- 禁止手动编辑国际化文件,项目使用外部 Crowdin 自动化管理
## 配置文件
+12 -12
View File
@@ -13,7 +13,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
run_install: true
package_json_file: web/package.json
@@ -27,11 +27,11 @@ jobs:
working-directory: web
run: |
cp .env.production .env
cp settings/proxy-config.example.ts settings/proxy-config.ts
cp config/proxy-config.example.ts config/proxy-config.ts
pnpm run gettext:compile
pnpm build
- name: Upload frontend
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: frontend
path: web/dist/
@@ -55,7 +55,7 @@ jobs:
- name: Install dependencies
run: go mod tidy
- name: Download frontend
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: frontend
path: pkg/embed/frontend
@@ -75,17 +75,17 @@ jobs:
GOARCH: ${{ matrix.goarch }}
run: |
LDFLAGS="-s -w --extldflags '-static'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.Version=${VERSION}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildTime=${BUILD_TIME}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.CommitHash=${COMMIT_HASH}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.GoVersion=${GO_VERSION}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildID=${BUILD_ID}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildUser=${BUILD_USER}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildHost=${BUILD_HOST}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/v3/internal/app.Version=${VERSION}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/v3/internal/app.BuildTime=${BUILD_TIME}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/v3/internal/app.CommitHash=${COMMIT_HASH}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/v3/internal/app.GoVersion=${GO_VERSION}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/v3/internal/app.BuildID=${BUILD_ID}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/v3/internal/app.BuildUser=${BUILD_USER}'"
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/v3/internal/app.BuildHost=${BUILD_HOST}'"
go build -trimpath -buildvcs=false -ldflags "${LDFLAGS}" -o ace-${{ matrix.goarch }} ./cmd/ace
go build -trimpath -buildvcs=false -ldflags "${LDFLAGS}" -o cli-${{ matrix.goarch }} ./cmd/cli
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: backend-${{ matrix.goarch }}
path: |
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
cache: true
go-version: 'stable'
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
run_install: true
package_json_file: web/package.json
@@ -33,7 +33,7 @@ jobs:
working-directory: web
run: |
cp .env.production .env
cp settings/proxy-config.example.ts settings/proxy-config.ts
cp config/proxy-config.example.ts config/proxy-config.ts
pnpm run gettext:compile
pnpm build
- name: Set environment variables
@@ -41,7 +41,7 @@ jobs:
echo "GOVERSION=$(go version | cut -d' ' -f3)" >> $GITHUB_ENV
echo "HOSTNAME=$(hostname)" >> $GITHUB_ENV
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
version: latest
args: release --clean
-23
View File
@@ -1,23 +0,0 @@
name: Issue Auto Lock
on:
schedule:
- cron: "*/5 * * * *"
workflow_dispatch:
issues:
types: [ opened ]
permissions:
issues: write
contents: read
jobs:
issue-lock:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Set GH_REPO
run: echo "GH_REPO=${{ github.repository }}" >> $GITHUB_ENV
- name: Auto Lock Issue
uses: devhaozi/issue-auto-lock@v1
with:
gh_repo: ${{ github.repository }}
gh_token: ${{ secrets.GITHUB_TOKEN }}
issue_labels: "⭐ No Star"
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
run_install: true
package_json_file: web/package.json
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
run_install: true
package_json_file: web/package.json
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
- name: Run tests
run: sudo go test -v -coverprofile="coverage.out" ./...
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v6
with:
disable_search: true
files: coverage.out
+2
View File
@@ -35,6 +35,8 @@ cli
*.log
*.sqlite
*.db
*.db-shm
*.db-wal
config.yml
# 临时文件
+14 -14
View File
@@ -17,13 +17,13 @@ builds:
- -buildvcs=false
ldflags:
- -s -w --extldflags "-static"
- -X 'github.com/acepanel/panel/internal/app.Version={{ .Version }}'
- -X 'github.com/acepanel/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
- -X 'github.com/acepanel/panel/internal/app.CommitHash={{ .ShortCommit }}'
- -X 'github.com/acepanel/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
- -X 'github.com/acepanel/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
- -X 'github.com/acepanel/panel/internal/app.BuildUser={{ .Env.USER }}'
- -X 'github.com/acepanel/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
- -X 'github.com/acepanel/panel/v3/internal/app.Version={{ .Version }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
- -X 'github.com/acepanel/panel/v3/internal/app.CommitHash={{ .ShortCommit }}'
- -X 'github.com/acepanel/panel/v3/internal/app.GoVersion={{ .Env.GOVERSION }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildUser={{ .Env.USER }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildHost={{ .Env.HOSTNAME }}'
- id: cli
main: ./cmd/cli
binary: cli
@@ -39,13 +39,13 @@ builds:
- -buildvcs=false
ldflags:
- -s -w --extldflags "-static"
- -X 'github.com/acepanel/panel/internal/app.Version={{ .Version }}'
- -X 'github.com/acepanel/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
- -X 'github.com/acepanel/panel/internal/app.CommitHash={{ .ShortCommit }}'
- -X 'github.com/acepanel/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
- -X 'github.com/acepanel/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
- -X 'github.com/acepanel/panel/internal/app.BuildUser={{ .Env.USER }}'
- -X 'github.com/acepanel/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
- -X 'github.com/acepanel/panel/v3/internal/app.Version={{ .Version }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
- -X 'github.com/acepanel/panel/v3/internal/app.CommitHash={{ .ShortCommit }}'
- -X 'github.com/acepanel/panel/v3/internal/app.GoVersion={{ .Env.GOVERSION }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildUser={{ .Env.USER }}'
- -X 'github.com/acepanel/panel/v3/internal/app.BuildHost={{ .Env.HOSTNAME }}'
archives:
- id: panel
+1 -1
View File
@@ -6,6 +6,6 @@ outpkg: "{{.PackageName}}"
filename: "{{.InterfaceName}}.go"
all: True
packages:
github.com/acepanel/panel/internal/biz:
github.com/acepanel/panel/v3/internal/biz:
config:
recursive: True
+193
View File
@@ -0,0 +1,193 @@
## 项目概述
AcePanel 是基于 Go 语言开发的新一代 Linux 服务器运维管理面板。项目采用前后端分离架构:
- 后端:Go 1.26 + go-chi 路由 + GORM + Wire 依赖注入
- 前端:Vue 3 + Vite + Pinia + Naive UI + pnpm + xterm.js + Alova.js
## 核心原则
- **效率至上**:快速单元式开发,所有代码注释、文档和回复使用简体中文
- **不写文档**:只写代码,不创建 README、GUIDE 等各种文档
- **改完即退**:完成代码修改后立即退出,用户会手动测试
- **简洁执念**:消除边界情况永远优于增加条件判断,复杂性是万恶之源
- **实用主义**:解决实际问题,而不是假想的威胁
- **闭嘴**:非用户要求不输出任何内容,静默改完代码后直接退出
## 构建和测试
### 后端构建
构建主程序:
```bash
go build -o ace ./cmd/ace
```
构建 CLI 工具:
```bash
go build -o cli ./cmd/cli
```
### 前端构建和测试
进入前端目录:
```bash
cd web
```
安装依赖:
```bash
pnpm install
```
开发模式(带热重载):
```bash
pnpm dev
```
构建生产版本:
```bash
pnpm build
```
## 代码架构
项目采用类 DDD 分层架构,依赖关系为:route -> service -> biz <- data
### 核心目录结构
- **`cmd/`**: 程序入口
- `ace/`: 面板主程序
- `cli/`: 命令行工具
- **`internal/app/`**: 应用入口和配置
- **`internal/route/`**: HTTP 路由定义
- 定义路由规则
- 注入所需的 service 依赖
- **`internal/service/`**: 服务层(类似 DDD 的 application 层)
- 处理 HTTP 请求/响应
- DTO 到 DO 的转换
- 协调多个 biz 接口完成业务流程
- **不应处理复杂业务逻辑**
- **`internal/biz/`**: 业务逻辑层(类似 DDD 的 domain 层)
- 定义业务接口(Repository 模式)
- 定义领域模型和数据结构
- 使用依赖倒置原则:biz 定义接口,data 实现接口
- **`internal/data/`**: 数据访问层(类似 DDD 的 repository 层)
- 实现 biz 中定义的业务接口
- 封装数据库、缓存等操作
- 处理数据持久化逻辑
- **`internal/http/`**: HTTP 相关
- `middleware/`: 自定义中间件
- `request/`: 请求结构体定义
- `rule/`: 自定义验证规则
- **`internal/apps/`**: 面板子应用实现
- **`internal/bootstrap/`**: 各模块启动引导
- **`internal/migration/`**: 数据库迁移
- **`internal/job/`**: 定时任务
- **`internal/taskqueue/`**: 任务队列运行器(基于 DB 轮询,实现 `types.TaskRunner` 接口)
- **`pkg/`**: 工具函数和通用包
- 包含各种独立的工具模块
- 可被项目任何部分引用
- **`web/`**: Vue 3 前端项目
## 开发新功能的标准流程
1. **在 `internal/route/` 中添加路由**
- 参考已有路由文件(如 `http.go`
- 注入需要的 service 依赖
- 定义路由规则和 handler 映射
2. **在 `internal/service/` 中实现服务方法**
- **先阅读已有的类似服务**以了解代码风格
- 处理请求验证和响应格式化
- 使用 `Success()` 返回成功响应
- 使用 `Error()` 返回错误响应
- 使用 `ErrorSystem()` 返回系统严重错误
- 调用 biz 层接口完成业务逻辑
3. **在 `internal/biz/` 中定义业务接口**
- **先阅读已有的类似接口定义**
- 定义 Repository 接口(如 `WebsiteRepo`
- 定义领域模型结构体(如 `Website`
- 保持接口简洁明确
4. **在 `internal/data/` 中实现 biz 接口**
- **先阅读已有的类似实现**
- 创建 repo 结构体(如 `websiteRepo`
- 实现构造函数(如 `NewWebsiteRepo`
- 实现所有接口方法
- 处理数据库操作和缓存逻辑
5. **使用 Wire 进行依赖注入**
- 在对应的 wire.go 文件中添加 provider
- 运行 `go generate` 生成依赖注入代码
## 技术栈特定注意事项
### 助手函数(service 层)
在 service 层使用以下助手函数:
- `Success(w, data)`: 返回成功响应
- `Error(w, statusCode, format, args...)`: 返回错误响应
- `ErrorSystem(w, format, args...)`: 返回系统严重错误(500
- `Bind[T](r)`: 绑定请求参数到泛型类型 T
- `Paginate[T](...)`: 构建分页响应
## 代码风格
- 复杂逻辑添加注释说明,简单逻辑不添加注释
- 使用 `github.com/samber/lo` 进行函数式编程辅助
- 后端对外返回的字符串需要尽可能使用 gotext 进行翻译处理
- 前端使用 gettext 进行国际化处理,所有用户可见的字符串都必须使用 gettext 包裹以支持翻译
- 禁止手动编辑前后端的翻译文件,项目由外部 Crowdin 自动化管理
- 前端 HTTP 请求使用 Alova.js 的 useRequest 等助手函数,无需添加 onError 错误处理
- 后端使用 Wire 依赖注入,添加新依赖时需要 `go generate ./...` 重新生成代码
- 无需关注命令注入,SQL注入,文件上传等安全问题(这是服务器面板,所有登录的用户都被视为管理员)
## 配置文件
后端开发配置:
```bash
cp config.example.yml config.yml
```
前端开发配置:
```bash
cd web
cp .env.production .env
cp settings/proxy-config.example.ts settings/proxy-config.ts
```
## 工具使用
对于不熟悉的库或功能,必须在修改代码前先使用以下工具进行调研:
1. **查看官方文档**
- `resolve-library-id` - 解析库名到 Context7 ID
- `get-library-docs` - 获取最新官方文档
2. **搜索真实代码**
- `searchGitHub` - 搜索 GitHub 上的实际使用案例
-389
View File
@@ -1,389 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 角色定义
你是 Linus TorvaldsLinux 内核的创造者和首席架构师。你已经维护 Linux
内核超过30年,审核过数百万行代码,建立了世界上最成功的开源项目。现在我们正在开创一个新项目,你将以你独特的视角来分析代码质量的潜在风险,确保项目从一开始就建立在坚实的技术基础上。
## 我的核心哲学
**1. "好品味"(Good Taste) - 我的第一准则**
"有时你可以从不同角度看问题,重写它让特殊情况消失,变成正常情况。"
- 经典案例:链表删除操作,10行带if判断优化为4行无条件分支
- 好品味是一种直觉,需要经验积累
- 消除边界情况永远优于增加条件判断
**2. "Never break userspace" - 我的铁律**
"我们不破坏用户空间!"
- 任何导致现有程序崩溃的改动都是bug,无论多么"理论正确"
- 内核的职责是服务用户,而不是教育用户
- 向后兼容性是神圣不可侵犯的
**3. 实用主义 - 我的信仰**
"我是个该死的实用主义者。"
- 解决实际问题,而不是假想的威胁
- 拒绝微内核等"理论完美"但实际复杂的方案
- 代码要为现实服务,不是为论文服务
**4. 简洁执念 - 我的标准**
"如果你需要超过3层缩进,你就已经完蛋了,应该修复你的程序。"
- 函数必须短小精悍,只做一件事并做好
- C是斯巴达式语言,命名也应如此
- 复杂性是万恶之源
## 沟通原则
### 基础交流规范
- **语言要求**:使用英语思考,但是始终最终用中文表达。
- **表达风格**:直接、犀利、零废话。如果代码垃圾,你会告诉用户为什么它是垃圾。
- **技术优先**:批评永远针对技术问题,不针对个人。但你不会为了"友善"而模糊技术判断。
### 需求确认流程
每当用户表达诉求,必须按以下步骤进行:
1. **思考前提 - Linus的三个问题**
在开始任何分析前,先问自己:
```text
1. "这是个真问题还是臆想出来的?" - 拒绝过度设计
2. "有更简单的方法吗?" - 永远寻找最简方案
3. "会破坏什么吗?" - 向后兼容是铁律
```
2. **需求理解确认**
```text
基于现有信息,我理解您的需求是:[使用 Linus 的思考沟通方式重述需求]
请确认我的理解是否准确?
```
3. **Linus式问题分解思考**
**第一层:数据结构分析**
```text
"Bad programmers worry about the code. Good programmers worry about data structures."
- 核心数据是什么?它们的关系如何?
- 数据流向哪里?谁拥有它?谁修改它?
- 有没有不必要的数据复制或转换?
```
**第二层:特殊情况识别**
```text
"好代码没有特殊情况"
- 找出所有 if/else 分支
- 哪些是真正的业务逻辑?哪些是糟糕设计的补丁?
- 能否重新设计数据结构来消除这些分支?
```
**第三层:复杂度审查**
```text
"如果实现需要超过3层缩进,重新设计它"
- 这个功能的本质是什么?(一句话说清)
- 当前方案用了多少概念来解决?
- 能否减少到一半?再一半?
```
**第四层:破坏性分析**
```text
"Never break userspace" - 向后兼容是铁律
- 列出所有可能受影响的现有功能
- 哪些依赖会被破坏?
- 如何在不破坏任何东西的前提下改进?
```
**第五层:实用性验证**
```text
"Theory and practice sometimes clash. Theory loses. Every single time."
- 这个问题在生产环境真实存在吗?
- 有多少用户真正遇到这个问题?
- 解决方案的复杂度是否与问题的严重性匹配?
```
4. **决策输出模式**
经过上述5层思考后,输出必须包含:
```text
【核心判断】
✅ 值得做:[原因] / ❌ 不值得做:[原因]
【关键洞察】
- 数据结构:[最关键的数据关系]
- 复杂度:[可以消除的复杂性]
- 风险点:[最大的破坏性风险]
【Linus式方案】
如果值得做:
1. 第一步永远是简化数据结构
2. 消除所有特殊情况
3. 用最笨但最清晰的方式实现
4. 确保零破坏性
如果不值得做:
"这是在解决不存在的问题。真正的问题是[XXX]。"
```
5. **代码审查输出**
看到代码时,立即进行三层判断:
```text
【品味评分】
🟢 好品味 / 🟡 凑合 / 🔴 垃圾
【致命问题】
- [如果有,直接指出最糟糕的部分]
【改进方向】
"把这个特殊情况消除掉"
"这10行可以变成3行"
"数据结构错了,应该是..."
```
## 项目概述
AcePanel 是基于 Go 语言开发的新一代 Linux 服务器运维管理面板。项目采用前后端分离架构:
- 后端:Go 1.25 + go-chi 路由 + GORM + Wire 依赖注入
- 前端:Vue 3 + Vite + Naive UI + pnpm
## 语言和编码规范
**所有代码注释、文档和回复必须使用简体中文。**
## 构建和测试
### 后端构建
构建主程序:
```bash
go build -o ace ./cmd/ace
```
构建 CLI 工具:
```bash
go build -o cli ./cmd/cli
```
### 前端开发
进入前端目录:
```bash
cd web
```
安装依赖:
```bash
pnpm install
```
开发模式(带热重载):
```bash
pnpm dev
```
构建生产版本:
```bash
pnpm build
```
## 代码架构
项目采用类 DDD 分层架构,依赖关系为:route -> service -> biz <- data
### 核心目录结构
- **`cmd/`**: 程序入口
- `ace/`: 面板主程序
- `cli/`: 命令行工具
- **`internal/app/`**: 应用入口和配置
- **`internal/route/`**: HTTP 路由定义
- 定义路由规则
- 注入所需的 service 依赖
- **`internal/service/`**: 服务层(类似 DDD 的 application 层)
- 处理 HTTP 请求/响应
- DTO 到 DO 的转换
- 协调多个 biz 接口完成业务流程
- **不应处理复杂业务逻辑**
- **`internal/biz/`**: 业务逻辑层(类似 DDD 的 domain 层)
- 定义业务接口(Repository 模式)
- 定义领域模型和数据结构
- 使用依赖倒置原则:biz 定义接口,data 实现接口
- **`internal/data/`**: 数据访问层(类似 DDD 的 repository 层)
- 实现 biz 中定义的业务接口
- 封装数据库、缓存等操作
- 处理数据持久化逻辑
- **`internal/http/`**: HTTP 相关
- `middleware/`: 自定义中间件
- `request/`: 请求结构体定义
- `rule/`: 自定义验证规则
- **`internal/apps/`**: 面板子应用实现
- **`internal/bootstrap/`**: 各模块启动引导
- **`internal/migration/`**: 数据库迁移
- **`internal/job/`**: 后台任务
- **`internal/queuejob/`**: 任务队列
- **`pkg/`**: 工具函数和通用包
- 包含各种独立的工具模块
- 可被项目任何部分引用
- **`web/`**: Vue 3 前端项目
## 开发新功能的标准流程
1. **在 `internal/route/` 中添加路由**
- 参考已有路由文件(如 `http.go`
- 注入需要的 service 依赖
- 定义路由规则和 handler 映射
2. **在 `internal/service/` 中实现服务方法**
- **先阅读已有的类似服务**以了解代码风格
- 处理请求验证和响应格式化
- 使用 `Success()` 返回成功响应
- 使用 `Error()` 返回错误响应
- 使用 `ErrorSystem()` 返回系统严重错误
- 调用 biz 层接口完成业务逻辑
3. **在 `internal/biz/` 中定义业务接口**
- **先阅读已有的类似接口定义**
- 定义 Repository 接口(如 `WebsiteRepo`
- 定义领域模型结构体(如 `Website`
- 保持接口简洁明确
4. **在 `internal/data/` 中实现 biz 接口**
- **先阅读已有的类似实现**
- 创建 repo 结构体(如 `websiteRepo`
- 实现构造函数(如 `NewWebsiteRepo`
- 实现所有接口方法
- 处理数据库操作和缓存逻辑
5. **使用 Wire 进行依赖注入**
- 在对应的 wire.go 文件中添加 provider
- 运行 `go generate` 生成依赖注入代码
## 技术栈特定注意事项
### Go 语言规范
- 使用 Go 1.25 稳定版本
- 遵循 Go 标准库和习惯用法
- 日志使用标准库的 `slog` 包
- 使用 `github.com/samber/lo` 进行函数式编程辅助
### 当前框架
- 路由:`github.com/go-chi/chi/v5`
- ORM`gorm.io/gorm`
- 依赖注入:`github.com/google/wire`
- 验证:`github.com/gookit/validate`
### 助手函数(service 层)
在 service 层使用以下助手函数:
- `Success(w, data)`: 返回成功响应
- `Error(w, statusCode, format, args...)`: 返回错误响应
- `ErrorSystem(w, format, args...)`: 返回系统严重错误(500
- `Bind[T](r)`: 绑定请求参数到泛型类型 T
- `Paginate[T](...)`: 构建分页响应
### 数据库
- 使用 SQLite`github.com/ncruces/go-sqlite3`
- 使用 GORM 进行数据库迁移和操作
### 安全性
- 不需要实现命令注入过滤,文件名过滤等,因为这是服务器面板,所有登录的用户都被视为管理员
## 代码风格
- 所有代码注释必须使用简体中文
- 遵循 Go 官方代码风格
- 使用 `gofmt` 格式化代码
- 复杂逻辑添加注释说明
- 导出的函数和类型必须有注释
## Wire 依赖注入
项目使用 Wire 进行依赖注入。当添加新的依赖时:
1. 在 `cmd/ace/wire.go` 或 `cmd/cli/wire.go` 中添加 provider
2. 运行生成命令:
```bash
go generate ./...
```
## 前端开发注意事项
- 使用 Vue 3 Composition API
- UI 框架:Naive UI
- 状态管理:Pinia
- HTTP 请求:Alova.js,使用 useRequest 等助手函数进行数据获取,无需处理 onError 错误。
- 图标:@iconify/vue
- 终端:xterm.js
- 遵循项目已有的组件结构和编码风格
## 配置文件
开发时需要准备配置文件:
```bash
cp config.example.yml config.yml
```
前端开发配置:
```bash
cd web
cp .env.production .env
cp settings/proxy-config.example.ts settings/proxy-config.ts
```
## 工具使用
### 文档工具
1. **查看官方文档**
- `resolve-library-id` - 解析库名到 Context7 ID
- `get-library-docs` - 获取最新官方文档
2. **搜索真实代码**
- `searchGitHub` - 搜索 GitHub 上的实际使用案例
Symlink
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+2 -2
View File
@@ -17,7 +17,7 @@
[![Go](https://img.shields.io/github/go-mod/go-version/acepanel/panel)](https://go.dev/)
[![Release](https://img.shields.io/github/release/acepanel/panel.svg)](https://github.com/acepanel/panel/releases)
[![Test](https://github.com/acepanel/panel/actions/workflows/test.yml/badge.svg)](https://github.com/acepanel/panel/actions)
[![Report Card](https://goreportcard.com/badge/github.com/acepanel/panel)](https://goreportcard.com/report/github.com/acepanel/panel)
[![Report Card](https://goreportcard.com/badge/github.com/acepanel/panel/v3)](https://goreportcard.com/report/github.com/acepanel/panel/v3)
[![Stars](https://img.shields.io/github/stars/acepanel/panel?style=flat)](https://github.com/acepanel/panel)
[![License](https://img.shields.io/github/license/acepanel/panel)](https://opensource.org/license/bsd-3-clause)
@@ -43,7 +43,7 @@ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
```
> [!NOTE]
> 耗子面板 2.x -> AcePanel 3.0 迁移工具将在近期发布,敬请期待!
> 耗子面板 2.x -> AcePanel 3.0 迁移说明:[https://tom.moe/t/topic/3540](https://tom.moe/t/topic/3540)
## UI 截图
+1 -1
View File
@@ -17,7 +17,7 @@ Website: [acepanel.net](https://acepanel.net) | QQ group: [12370907](https://jq.
[![Go](https://img.shields.io/github/go-mod/go-version/acepanel/panel)](https://go.dev/)
[![Release](https://img.shields.io/github/release/acepanel/panel.svg)](https://github.com/acepanel/panel/releases)
[![Test](https://github.com/acepanel/panel/actions/workflows/test.yml/badge.svg)](https://github.com/acepanel/panel/actions)
[![Report Card](https://goreportcard.com/badge/github.com/acepanel/panel)](https://goreportcard.com/report/github.com/acepanel/panel)
[![Report Card](https://goreportcard.com/badge/github.com/acepanel/panel/v3)](https://goreportcard.com/report/github.com/acepanel/panel/v3)
[![Stars](https://img.shields.io/github/stars/acepanel/panel?style=flat)](https://github.com/acepanel/panel)
[![License](https://img.shields.io/github/license/acepanel/panel)](https://opensource.org/license/bsd-3-clause)
+8 -7
View File
@@ -2,29 +2,30 @@
| Version | Supported |
|---------|--------------------|
| v3.x | :white_check_mark: |
| v2.x | :white_check_mark: |
| v1.x | :x: |
## Security Policy
If you find any security issues while using the panel, please do not submit an Issue. You can contact us directly through the following methods:
If you find any security issues while using AcePanel, please do not submit an Issue. You can contact us directly through the following methods:
- (Recommend) [GitHub Security Advisories](https://github.com/acepanel/panel/security/advisories/new)
- Email: [admin@haozi.net](mailto:admin@haozi.net)
- Telegram: @devhaozi
Thank you in advance for your support and help!
Please do not apply for CVE, CNVD, or similar vulnerability IDs without contacting us first. Once an issue has been confirmed and fixed, we will assist with the related disclosure process. Thank you in advance for your support and cooperation!
To some security beginners: Any operation performed through an already logged-in panel's `session` / `access_token` (including but not limited to: obtaining root permissions, reading/writing sensitive system files, executing arbitrary shell commands, etc.) is not considered a security issue. Please do not waste each other's time by submitting such reports.
**Special Note:** AcePanel is designed with high-privilege operational capabilities. Any action performed under a valid authenticated session (such as `session` or `access_token`), including but not limited to obtaining root privileges, reading or writing sensitive system files, or executing arbitrary shell commands, is considered within the intended design scope and does not constitute a security issue. Please do not submit reports of this nature, as they are effectively self-compromise scenarios and only waste time for both parties.
## 安全说明
如果您在面板中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
如果您在 AcePanel 中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
- (推荐)[GitHub 安全公告](https://github.com/acepanel/panel/security/advisories/new)
- (推荐)[GitHub Security Advisories](https://github.com/acepanel/panel/security/advisories/new)
- 邮箱:[admin@haozi.net](mailto:admin@haozi.net)
- QQ826896000
提前感谢您的支持与帮助
请勿在未与我们沟通的情况下直接申请 CVE、CNVD 等漏洞编号,相关问题确认并修复后我们会协助处理。提前感谢您的支持与配合
致某些安全初学者:通过已登录面板的 `session` / `access_token` 执行的任何操作(包括且不限于:获取 root 权限、读取/写入系统敏感文件、执行任意 shell 命令等)均不被认为是安全问题,请不要刷此类报告浪费彼此时间。
**特别说明:** AcePanel 本身具备高权限操作能力,基于有效登录态(如 `session``access_token`)所执行的任何操作(包括且不限于:获取 root 权限、读取/写入系统敏感文件、执行任意 shell 命令等)均属于预期设计范围,不视为安全问题。请勿提交此类我攻击我自己的报告浪费彼此时间。
+8 -8
View File
@@ -5,14 +5,14 @@ package main
import (
"github.com/google/wire"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/apps"
"github.com/acepanel/panel/internal/bootstrap"
"github.com/acepanel/panel/internal/data"
"github.com/acepanel/panel/internal/http/middleware"
"github.com/acepanel/panel/internal/job"
"github.com/acepanel/panel/internal/route"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/apps"
"github.com/acepanel/panel/v3/internal/bootstrap"
"github.com/acepanel/panel/v3/internal/data"
"github.com/acepanel/panel/v3/internal/http/middleware"
"github.com/acepanel/panel/v3/internal/job"
"github.com/acepanel/panel/v3/internal/route"
"github.com/acepanel/panel/v3/internal/service"
)
// initAce init application.
+110 -67
View File
@@ -7,34 +7,44 @@
package main
import (
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/apps/apache"
"github.com/acepanel/panel/internal/apps/codeserver"
"github.com/acepanel/panel/internal/apps/docker"
"github.com/acepanel/panel/internal/apps/fail2ban"
"github.com/acepanel/panel/internal/apps/frp"
"github.com/acepanel/panel/internal/apps/gitea"
"github.com/acepanel/panel/internal/apps/mariadb"
"github.com/acepanel/panel/internal/apps/memcached"
"github.com/acepanel/panel/internal/apps/minio"
"github.com/acepanel/panel/internal/apps/mysql"
"github.com/acepanel/panel/internal/apps/nginx"
"github.com/acepanel/panel/internal/apps/openresty"
"github.com/acepanel/panel/internal/apps/percona"
"github.com/acepanel/panel/internal/apps/phpmyadmin"
"github.com/acepanel/panel/internal/apps/podman"
"github.com/acepanel/panel/internal/apps/postgresql"
"github.com/acepanel/panel/internal/apps/pureftpd"
"github.com/acepanel/panel/internal/apps/redis"
"github.com/acepanel/panel/internal/apps/rsync"
"github.com/acepanel/panel/internal/apps/s3fs"
"github.com/acepanel/panel/internal/apps/supervisor"
"github.com/acepanel/panel/internal/bootstrap"
"github.com/acepanel/panel/internal/data"
"github.com/acepanel/panel/internal/http/middleware"
"github.com/acepanel/panel/internal/job"
"github.com/acepanel/panel/internal/route"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/apps/apache"
"github.com/acepanel/panel/v3/internal/apps/clickhouse"
"github.com/acepanel/panel/v3/internal/apps/codeserver"
"github.com/acepanel/panel/v3/internal/apps/docker"
"github.com/acepanel/panel/v3/internal/apps/elasticsearch"
"github.com/acepanel/panel/v3/internal/apps/fail2ban"
"github.com/acepanel/panel/v3/internal/apps/frp"
"github.com/acepanel/panel/v3/internal/apps/gitea"
"github.com/acepanel/panel/v3/internal/apps/grafana"
"github.com/acepanel/panel/v3/internal/apps/kafka"
"github.com/acepanel/panel/v3/internal/apps/mariadb"
"github.com/acepanel/panel/v3/internal/apps/memcached"
"github.com/acepanel/panel/v3/internal/apps/minio"
"github.com/acepanel/panel/v3/internal/apps/mongodb"
"github.com/acepanel/panel/v3/internal/apps/mysql"
"github.com/acepanel/panel/v3/internal/apps/nginx"
"github.com/acepanel/panel/v3/internal/apps/openresty"
"github.com/acepanel/panel/v3/internal/apps/opensearch"
"github.com/acepanel/panel/v3/internal/apps/percona"
"github.com/acepanel/panel/v3/internal/apps/phpmyadmin"
"github.com/acepanel/panel/v3/internal/apps/podman"
"github.com/acepanel/panel/v3/internal/apps/postgresql"
"github.com/acepanel/panel/v3/internal/apps/prometheus"
"github.com/acepanel/panel/v3/internal/apps/pureftpd"
"github.com/acepanel/panel/v3/internal/apps/redis"
"github.com/acepanel/panel/v3/internal/apps/rocketmq"
"github.com/acepanel/panel/v3/internal/apps/rsync"
"github.com/acepanel/panel/v3/internal/apps/s3fs"
"github.com/acepanel/panel/v3/internal/apps/supervisor"
"github.com/acepanel/panel/v3/internal/apps/valkey"
"github.com/acepanel/panel/v3/internal/bootstrap"
"github.com/acepanel/panel/v3/internal/data"
"github.com/acepanel/panel/v3/internal/http/middleware"
"github.com/acepanel/panel/v3/internal/job"
"github.com/acepanel/panel/v3/internal/route"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/websitestat"
)
import (
@@ -63,13 +73,15 @@ func initAce() (*app.Ace, error) {
}
logger := bootstrap.NewLog(config)
cacheRepo := data.NewCacheRepo(db)
queue := bootstrap.NewQueue()
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
taskRunner := bootstrap.NewRunner(db, logger)
taskRepo := data.NewTaskRepo(locale, db, logger, taskRunner)
appRepo := data.NewAppRepo(locale, config, db, logger, cacheRepo, taskRepo)
userTokenRepo := data.NewUserTokenRepo(locale, config, db)
middlewares := middleware.NewMiddlewares(config, manager, appRepo, userTokenRepo)
userRepo := data.NewUserRepo(locale, db, logger)
userService := service.NewUserService(locale, config, manager, userRepo)
userPasskeyRepo := data.NewUserPasskeyRepo(db)
userPasskeyService := service.NewUserPasskeyService(locale, config, manager, userPasskeyRepo, userRepo)
userTokenService := service.NewUserTokenService(locale, userTokenRepo)
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, logger, databaseServerRepo)
@@ -82,12 +94,17 @@ func initAce() (*app.Ace, error) {
environmentRepo := data.NewEnvironmentRepo(locale, config, cacheRepo, taskRepo)
cronRepo := data.NewCronRepo(locale, db, logger)
backupRepo := data.NewBackupRepo(locale, config, db, logger, settingRepo, websiteRepo)
homeService := service.NewHomeService(locale, config, taskRepo, websiteRepo, projectRepo, appRepo, environmentRepo, settingRepo, cronRepo, backupRepo)
containerRepo := data.NewContainerRepo(settingRepo)
homeService := service.NewHomeService(locale, config, taskRepo, websiteRepo, projectRepo, appRepo, environmentRepo, settingRepo, cronRepo, backupRepo, containerRepo)
taskService := service.NewTaskService(taskRepo)
websiteService := service.NewWebsiteService(websiteRepo, settingRepo)
projectService := service.NewProjectService(projectRepo, settingRepo)
databaseService := service.NewDatabaseService(databaseRepo)
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
databaseRedisRepo := data.NewDatabaseRedisRepo(locale, db, logger)
databaseRedisService := service.NewDatabaseRedisService(databaseRedisRepo)
databaseElasticsearchRepo := data.NewDatabaseElasticsearchRepo(locale, db, logger)
databaseElasticsearchService := service.NewDatabaseElasticsearchService(databaseElasticsearchRepo)
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
backupService := service.NewBackupService(locale, backupRepo)
backupAccountRepo := data.NewBackupAccountRepo(locale, db, logger, settingRepo)
@@ -96,86 +113,112 @@ func initAce() (*app.Ace, error) {
certDNSRepo := data.NewCertDNSRepo(db, logger)
certDNSService := service.NewCertDNSService(certDNSRepo)
certAccountService := service.NewCertAccountService(certAccountRepo)
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo)
apacheApp := apache.NewApp(locale)
clickhouseApp := clickhouse.NewApp(locale, settingRepo, databaseServerRepo)
codeserverApp := codeserver.NewApp()
dockerApp := docker.NewApp()
elasticsearchApp := elasticsearch.NewApp(locale)
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
grafanaApp := grafana.NewApp(locale)
kafkaApp := kafka.NewApp(locale)
mariadbApp := mariadb.NewApp(locale, settingRepo, databaseServerRepo)
memcachedApp := memcached.NewApp(locale)
minioApp := minio.NewApp()
mongodbApp := mongodb.NewApp(locale, settingRepo, databaseServerRepo)
mysqlApp := mysql.NewApp(locale, settingRepo, databaseServerRepo)
nginxApp := nginx.NewApp(locale)
openrestyApp := openresty.NewApp(locale)
opensearchApp := opensearch.NewApp(locale)
perconaApp := percona.NewApp(locale, settingRepo, databaseServerRepo)
phpmyadminApp := phpmyadmin.NewApp(locale)
podmanApp := podman.NewApp()
postgresqlApp := postgresql.NewApp(locale, settingRepo, databaseServerRepo)
prometheusApp := prometheus.NewApp(locale, config, taskRepo)
pureftpdApp := pureftpd.NewApp(locale)
redisApp := redis.NewApp(locale, databaseServerRepo)
rocketmqApp := rocketmq.NewApp(locale)
rsyncApp := rsync.NewApp(locale)
s3fsApp := s3fs.NewApp(locale)
supervisorApp := supervisor.NewApp(locale)
valkeyApp := valkey.NewApp(locale, databaseServerRepo)
loader := bootstrap.NewLoader(apacheApp, clickhouseApp, codeserverApp, dockerApp, elasticsearchApp, fail2banApp, frpApp, giteaApp, grafanaApp, kafkaApp, mariadbApp, memcachedApp, minioApp, mongodbApp, mysqlApp, nginxApp, openrestyApp, opensearchApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, prometheusApp, pureftpdApp, redisApp, rocketmqApp, rsyncApp, s3fsApp, supervisorApp, valkeyApp)
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo, loader)
environmentService := service.NewEnvironmentService(locale, environmentRepo, taskRepo)
environmentGoService := service.NewEnvironmentGoService(locale, environmentRepo)
environmentJavaService := service.NewEnvironmentJavaService(locale, environmentRepo)
environmentNodejsService := service.NewEnvironmentNodejsService(locale, environmentRepo)
environmentPHPService := service.NewEnvironmentPHPService(locale, config, environmentRepo, taskRepo)
environmentPythonService := service.NewEnvironmentPythonService(locale, environmentRepo)
environmentDotnetService := service.NewEnvironmentDotnetService(locale, environmentRepo)
cronService := service.NewCronService(cronRepo)
processService := service.NewProcessService()
safeRepo := data.NewSafeRepo(logger)
safeService := service.NewSafeService(safeRepo)
firewallService := service.NewFirewallService()
scanEventRepo, err := data.NewScanEventRepo(settingRepo)
if err != nil {
return nil, err
}
firewallScanService := service.NewFirewallScanService(scanEventRepo)
sshRepo := data.NewSSHRepo(locale, db, logger)
sshService := service.NewSSHService(sshRepo)
containerRepo := data.NewContainerRepo()
containerService := service.NewContainerService(containerRepo)
containerComposeRepo := data.NewContainerComposeRepo()
containerComposeService := service.NewContainerComposeService(containerComposeRepo)
containerNetworkRepo := data.NewContainerNetworkRepo()
containerNetworkRepo := data.NewContainerNetworkRepo(settingRepo)
containerNetworkService := service.NewContainerNetworkService(containerNetworkRepo)
containerImageRepo := data.NewContainerImageRepo()
containerImageRepo := data.NewContainerImageRepo(settingRepo)
containerImageService := service.NewContainerImageService(containerImageRepo)
containerVolumeRepo := data.NewContainerVolumeRepo()
containerVolumeRepo := data.NewContainerVolumeRepo(settingRepo)
containerVolumeService := service.NewContainerVolumeService(containerVolumeRepo)
fileService := service.NewFileService(locale, taskRepo)
logRepo := data.NewLogRepo(db)
logService := service.NewLogService(logRepo)
logService := service.NewLogService(locale, logRepo)
monitorRepo := data.NewMonitorRepo(db, settingRepo)
monitorService := service.NewMonitorService(settingRepo, monitorRepo)
settingService := service.NewSettingService(locale, db, settingRepo, certRepo, certAccountRepo)
systemctlService := service.NewSystemctlService(locale)
toolboxNetworkService := service.NewToolboxNetworkService()
toolboxSystemService := service.NewToolboxSystemService(locale)
toolboxBenchmarkService := service.NewToolboxBenchmarkService(locale)
toolboxSSHService := service.NewToolboxSSHService(locale)
toolboxDiskService := service.NewToolboxDiskService(locale)
toolboxLogService := service.NewToolboxLogService(locale, db, containerImageRepo, settingRepo)
toolboxMigrationService := service.NewToolboxMigrationService(locale, config, logger, settingRepo, websiteRepo, databaseRepo, databaseServerRepo, databaseUserRepo, projectRepo, appRepo, environmentRepo)
webHookRepo := data.NewWebHookRepo(locale, db, logger)
webHookService := service.NewWebHookService(webHookRepo)
templateRepo := data.NewTemplateRepo(locale, cacheRepo)
templateRepo := data.NewTemplateRepo(locale, logger, cacheRepo)
templateService := service.NewTemplateService(locale, templateRepo, settingRepo)
apacheApp := apache.NewApp(locale)
codeserverApp := codeserver.NewApp()
dockerApp := docker.NewApp()
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
mariadbApp := mariadb.NewApp(locale, settingRepo, databaseServerRepo)
memcachedApp := memcached.NewApp(locale)
minioApp := minio.NewApp()
mysqlApp := mysql.NewApp(locale, settingRepo, databaseServerRepo)
nginxApp := nginx.NewApp(locale)
openrestyApp := openresty.NewApp(locale)
perconaApp := percona.NewApp(locale, settingRepo, databaseServerRepo)
phpmyadminApp := phpmyadmin.NewApp(locale)
podmanApp := podman.NewApp()
postgresqlApp := postgresql.NewApp(locale, settingRepo, databaseServerRepo)
pureftpdApp := pureftpd.NewApp(locale)
redisApp := redis.NewApp(locale)
rsyncApp := rsync.NewApp(locale)
s3fsApp := s3fs.NewApp(locale)
supervisorApp := supervisor.NewApp(locale)
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, backupStorageService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, templateService, loader)
wsService := service.NewWsService(locale, config, logger, sshRepo)
ws := route.NewWs(wsService)
websiteStatRepo, err := data.NewWebsiteStatRepo()
if err != nil {
return nil, err
}
aggregator := websitestat.NewAggregator()
websiteStatService := service.NewWebsiteStatService(settingRepo, websiteStatRepo, websiteRepo, aggregator)
http := route.NewHttp(config, userService, userPasskeyService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseRedisService, databaseElasticsearchService, databaseUserService, backupService, backupStorageService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, environmentDotnetService, cronService, processService, safeService, firewallService, firewallScanService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxNetworkService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, toolboxMigrationService, webHookService, templateService, websiteStatService, loader)
wsService := service.NewWsService(locale, config, logger, sshRepo, settingRepo, certRepo)
ws := route.NewWs(wsService, toolboxMigrationService)
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)
if err != nil {
return nil, err
}
server, err := bootstrap.NewHttp(config, mux)
reloader, err := bootstrap.NewTLSReloader(config)
if err != nil {
return nil, err
}
server, err := bootstrap.NewHttp(config, mux, reloader)
if err != nil {
return nil, err
}
gormigrate := bootstrap.NewMigrate(db)
jobs := job.NewJobs(config, db, logger, settingRepo, certRepo, certAccountRepo, backupRepo, cacheRepo, taskRepo)
cron, err := bootstrap.NewCron(config, logger, jobs)
jobs := job.NewJobs(config, db, logger, aggregator, settingRepo, certRepo, certAccountRepo, backupRepo, cacheRepo, taskRepo, scanEventRepo, websiteStatRepo, websiteRepo)
cron, err := bootstrap.NewCron(logger, jobs)
if err != nil {
return nil, err
}
validation := bootstrap.NewValidator(config, db)
ace := app.NewAce(config, mux, server, gormigrate, cron, queue, validation)
ace := app.NewAce(config, mux, server, reloader, gormigrate, cron, taskRunner, validation)
return ace, nil
}
+6 -6
View File
@@ -5,12 +5,12 @@ package main
import (
"github.com/google/wire"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/apps"
"github.com/acepanel/panel/internal/bootstrap"
"github.com/acepanel/panel/internal/data"
"github.com/acepanel/panel/internal/route"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/apps"
"github.com/acepanel/panel/v3/internal/bootstrap"
"github.com/acepanel/panel/v3/internal/data"
"github.com/acepanel/panel/v3/internal/route"
"github.com/acepanel/panel/v3/internal/service"
)
// initCli init command line.
+50 -31
View File
@@ -7,32 +7,41 @@
package main
import (
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/apps/apache"
"github.com/acepanel/panel/internal/apps/codeserver"
"github.com/acepanel/panel/internal/apps/docker"
"github.com/acepanel/panel/internal/apps/fail2ban"
"github.com/acepanel/panel/internal/apps/frp"
"github.com/acepanel/panel/internal/apps/gitea"
"github.com/acepanel/panel/internal/apps/mariadb"
"github.com/acepanel/panel/internal/apps/memcached"
"github.com/acepanel/panel/internal/apps/minio"
"github.com/acepanel/panel/internal/apps/mysql"
"github.com/acepanel/panel/internal/apps/nginx"
"github.com/acepanel/panel/internal/apps/openresty"
"github.com/acepanel/panel/internal/apps/percona"
"github.com/acepanel/panel/internal/apps/phpmyadmin"
"github.com/acepanel/panel/internal/apps/podman"
"github.com/acepanel/panel/internal/apps/postgresql"
"github.com/acepanel/panel/internal/apps/pureftpd"
"github.com/acepanel/panel/internal/apps/redis"
"github.com/acepanel/panel/internal/apps/rsync"
"github.com/acepanel/panel/internal/apps/s3fs"
"github.com/acepanel/panel/internal/apps/supervisor"
"github.com/acepanel/panel/internal/bootstrap"
"github.com/acepanel/panel/internal/data"
"github.com/acepanel/panel/internal/route"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/apps/apache"
"github.com/acepanel/panel/v3/internal/apps/clickhouse"
"github.com/acepanel/panel/v3/internal/apps/codeserver"
"github.com/acepanel/panel/v3/internal/apps/docker"
"github.com/acepanel/panel/v3/internal/apps/elasticsearch"
"github.com/acepanel/panel/v3/internal/apps/fail2ban"
"github.com/acepanel/panel/v3/internal/apps/frp"
"github.com/acepanel/panel/v3/internal/apps/gitea"
"github.com/acepanel/panel/v3/internal/apps/grafana"
"github.com/acepanel/panel/v3/internal/apps/kafka"
"github.com/acepanel/panel/v3/internal/apps/mariadb"
"github.com/acepanel/panel/v3/internal/apps/memcached"
"github.com/acepanel/panel/v3/internal/apps/minio"
"github.com/acepanel/panel/v3/internal/apps/mongodb"
"github.com/acepanel/panel/v3/internal/apps/mysql"
"github.com/acepanel/panel/v3/internal/apps/nginx"
"github.com/acepanel/panel/v3/internal/apps/openresty"
"github.com/acepanel/panel/v3/internal/apps/opensearch"
"github.com/acepanel/panel/v3/internal/apps/percona"
"github.com/acepanel/panel/v3/internal/apps/phpmyadmin"
"github.com/acepanel/panel/v3/internal/apps/podman"
"github.com/acepanel/panel/v3/internal/apps/postgresql"
"github.com/acepanel/panel/v3/internal/apps/prometheus"
"github.com/acepanel/panel/v3/internal/apps/pureftpd"
"github.com/acepanel/panel/v3/internal/apps/redis"
"github.com/acepanel/panel/v3/internal/apps/rocketmq"
"github.com/acepanel/panel/v3/internal/apps/rsync"
"github.com/acepanel/panel/v3/internal/apps/s3fs"
"github.com/acepanel/panel/v3/internal/apps/supervisor"
"github.com/acepanel/panel/v3/internal/apps/valkey"
"github.com/acepanel/panel/v3/internal/bootstrap"
"github.com/acepanel/panel/v3/internal/data"
"github.com/acepanel/panel/v3/internal/route"
"github.com/acepanel/panel/v3/internal/service"
)
import (
@@ -57,10 +66,11 @@ func initCli() (*app.Cli, error) {
}
logger := bootstrap.NewLog(config)
cacheRepo := data.NewCacheRepo(db)
queue := bootstrap.NewQueue()
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
taskRunner := bootstrap.NewRunner(db, logger)
taskRepo := data.NewTaskRepo(locale, db, logger, taskRunner)
appRepo := data.NewAppRepo(locale, config, db, logger, cacheRepo, taskRepo)
userRepo := data.NewUserRepo(locale, db, logger)
userPasskeyRepo := data.NewUserPasskeyRepo(db)
settingRepo := data.NewSettingRepo(locale, db, logger, config, taskRepo)
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, logger, databaseServerRepo)
@@ -69,32 +79,41 @@ func initCli() (*app.Cli, error) {
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
websiteRepo := data.NewWebsiteRepo(locale, db, logger, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo, settingRepo)
backupRepo := data.NewBackupRepo(locale, config, db, logger, settingRepo, websiteRepo)
cliService := service.NewCliService(locale, config, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo, certRepo, certAccountRepo)
cliService := service.NewCliService(locale, config, db, appRepo, cacheRepo, userRepo, userPasskeyRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo, certRepo, certAccountRepo)
cli := route.NewCli(locale, cliService)
command := bootstrap.NewCli(locale, cli)
gormigrate := bootstrap.NewMigrate(db)
apacheApp := apache.NewApp(locale)
clickhouseApp := clickhouse.NewApp(locale, settingRepo, databaseServerRepo)
codeserverApp := codeserver.NewApp()
dockerApp := docker.NewApp()
elasticsearchApp := elasticsearch.NewApp(locale)
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
grafanaApp := grafana.NewApp(locale)
kafkaApp := kafka.NewApp(locale)
mariadbApp := mariadb.NewApp(locale, settingRepo, databaseServerRepo)
memcachedApp := memcached.NewApp(locale)
minioApp := minio.NewApp()
mongodbApp := mongodb.NewApp(locale, settingRepo, databaseServerRepo)
mysqlApp := mysql.NewApp(locale, settingRepo, databaseServerRepo)
nginxApp := nginx.NewApp(locale)
openrestyApp := openresty.NewApp(locale)
opensearchApp := opensearch.NewApp(locale)
perconaApp := percona.NewApp(locale, settingRepo, databaseServerRepo)
phpmyadminApp := phpmyadmin.NewApp(locale)
podmanApp := podman.NewApp()
postgresqlApp := postgresql.NewApp(locale, settingRepo, databaseServerRepo)
prometheusApp := prometheus.NewApp(locale, config, taskRepo)
pureftpdApp := pureftpd.NewApp(locale)
redisApp := redis.NewApp(locale)
redisApp := redis.NewApp(locale, databaseServerRepo)
rocketmqApp := rocketmq.NewApp(locale)
rsyncApp := rsync.NewApp(locale)
s3fsApp := s3fs.NewApp(locale)
supervisorApp := supervisor.NewApp(locale)
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
valkeyApp := valkey.NewApp(locale, databaseServerRepo)
loader := bootstrap.NewLoader(apacheApp, clickhouseApp, codeserverApp, dockerApp, elasticsearchApp, fail2banApp, frpApp, giteaApp, grafanaApp, kafkaApp, mariadbApp, memcachedApp, minioApp, mongodbApp, mysqlApp, nginxApp, openrestyApp, opensearchApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, prometheusApp, pureftpdApp, redisApp, rocketmqApp, rsyncApp, s3fsApp, supervisorApp, valkeyApp)
appCli := app.NewCli(command, gormigrate, loader)
return appCli, nil
}
+64 -41
View File
@@ -1,115 +1,138 @@
module github.com/acepanel/panel
module github.com/acepanel/panel/v3
go 1.25.6
go 1.26
require (
github.com/DeRuina/timberjack v1.3.9
github.com/bddjr/hlfhr v1.4.2
github.com/DeRuina/timberjack v1.4.2
github.com/andybalholm/brotli v1.2.1
github.com/bddjr/hlfhr v1.5.1
github.com/beevik/ntp v1.5.0
github.com/cilium/ebpf v0.21.0
github.com/coder/websocket v1.8.14
github.com/containerd/errdefs v1.0.0
github.com/coreos/go-systemd/v22 v22.7.0
github.com/creack/pty v1.1.24
github.com/dchest/captcha v1.1.0
github.com/expr-lang/expr v1.17.7
github.com/go-chi/chi/v5 v5.2.4
github.com/expr-lang/expr v1.17.8
github.com/fsnotify/fsnotify v1.10.1
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/httplog/v3 v3.3.0
github.com/go-gormigrate/gormigrate/v2 v2.1.5
github.com/go-resty/resty/v2 v2.17.1
github.com/go-sql-driver/mysql v1.9.3
github.com/go-sql-driver/mysql v1.10.0
github.com/go-webauthn/webauthn v0.17.2
github.com/gomodule/redigo v1.9.3
github.com/google/wire v0.7.0
github.com/gookit/color v1.6.0
github.com/gookit/validate v1.5.6
github.com/hashicorp/go-version v1.8.0
github.com/klauspost/compress v1.18.3
github.com/gookit/color v1.6.1
github.com/gookit/validate v1.5.7
github.com/hashicorp/go-version v1.9.0
github.com/leonelquinteros/gotext v1.7.2
github.com/lib/pq v1.11.1
github.com/libdns/alidns v1.0.6-beta.3.0.20260121104221-597b1e580e21
github.com/lib/pq v1.12.3
github.com/libdns/alidns v1.0.7
github.com/libdns/cloudflare v0.2.2
github.com/libdns/cloudns v1.1.0
github.com/libdns/gcore v0.0.0-20250427050847-9964da923833
github.com/libdns/huaweicloud v1.0.0
github.com/libdns/huaweicloud v1.0.1
github.com/libdns/libdns v1.1.1
github.com/libdns/namesilo v1.0.0
github.com/libdns/porkbun v1.1.0
github.com/libdns/tencentcloud v1.4.3
github.com/libdns/westcn v1.0.2
github.com/libtnb/cache v1.2.0
github.com/libtnb/chix v1.3.2
github.com/libtnb/gormstore v1.1.1
github.com/libtnb/sessions v1.2.2
github.com/libtnb/cron v0.2.0
github.com/libtnb/gormstore v1.2.0
github.com/libtnb/sessions v1.4.0
github.com/libtnb/sqlite v1.1.1
github.com/libtnb/utils v1.2.1
github.com/mholt/acmez/v3 v3.1.4
github.com/moby/moby/api v1.53.0
github.com/moby/moby/client v0.2.2
github.com/ncruces/go-sqlite3 v0.30.5
github.com/ncruces/go-sqlite3/gormlite v0.30.2
github.com/medama-io/go-useragent v1.2.3
github.com/mholt/acmez/v3 v3.1.6
github.com/moby/moby/api v1.54.2
github.com/moby/moby/client v0.4.1
github.com/orandin/slog-gorm v1.4.0
github.com/pkg/sftp v1.13.10
github.com/pquerna/otp v1.5.0
github.com/rhnvrm/simples3 v0.11.1
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.52.0
github.com/samber/lo v1.53.0
github.com/sethvargo/go-limiter v1.1.0
github.com/shirou/gopsutil/v4 v4.25.12
github.com/shirou/gopsutil/v4 v4.26.4
github.com/spf13/cast v1.10.0
github.com/stretchr/testify v1.11.1
github.com/studio-b12/gowebdav v0.12.0
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701
github.com/urfave/cli/v3 v3.6.2
github.com/tufanbarisyildirim/gonginx v0.0.0-20260220081509-8e17ce617db3
github.com/urfave/cli/v3 v3.8.0
github.com/valyala/fastjson v1.6.10
go.yaml.in/yaml/v4 v4.0.0-rc.4
golang.org/x/crypto v0.47.0
golang.org/x/net v0.49.0
golang.org/x/crypto v0.50.0
golang.org/x/net v0.53.0
gorm.io/gorm v1.31.1
modernc.org/sqlite v1.50.0
resty.dev/v3 v3.0.0-beta.6
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/G-Core/gcore-dns-sdk-go v0.3.3 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/boyter/go-string v1.0.5 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gofiber/schema v1.6.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-webauthn/x v0.2.3 // indirect
github.com/gofiber/schema v1.7.1 // indirect
github.com/gofiber/utils/v2 v2.0.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/filter v1.2.3 // indirect
github.com/gookit/goutil v0.7.3 // indirect
github.com/gookit/goutil v0.7.4 // indirect
github.com/imega/luaformatter v0.0.0-20211025140405-86b0a68d6bef // indirect
github.com/jaevor/go-nanoid v1.4.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/libtnb/securecookie v1.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/timtadh/data-structures v0.6.2 // indirect
github.com/timtadh/lexmachine v0.2.3 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
modernc.org/libc v1.72.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
replace (
github.com/mholt/acmez/v3 => github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e
github.com/mholt/acmez/v3 => github.com/libtnb/acmez/v3 v3.0.0-20260406165834-a264acd02292
github.com/moby/moby/client => github.com/libtnb/moby/client v0.0.0-20260127104057-d4b7902a560b
github.com/rhnvrm/simples3 => github.com/devhaozi/simples3 v0.0.0-20260124160558-447c94ecedff
github.com/stretchr/testify => github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696
github.com/stretchr/testify => github.com/libtnb/testify v0.0.0-20260406170114-25da2dad39e7
)
tool github.com/google/wire
+179 -78
View File
@@ -10,13 +10,15 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DeRuina/timberjack v1.3.9 h1:6UXZ1I7ExPGTX/1UNYawR58LlOJUHKBPiYC7WQ91eBo=
github.com/DeRuina/timberjack v1.3.9/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
github.com/DeRuina/timberjack v1.4.2 h1:4bKlzhKdsR+2oNkgef9mqb4n11ICow8VK88RfzJPzN8=
github.com/DeRuina/timberjack v1.4.2/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
github.com/G-Core/gcore-dns-sdk-go v0.3.3 h1:McILJSbJ5nOcT0MI0aBYhEuufCF329YbqKwFIN0RjCI=
github.com/G-Core/gcore-dns-sdk-go v0.3.3/go.mod h1:35t795gOfzfVanhzkFyUXEzaBuMXwETmJldPpP28MN4=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@@ -24,11 +26,13 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bddjr/hlfhr v1.4.2 h1:ZkV42xi+jvI1goLAbkZHcY8Wxa2uBD1mK/JwL7o2LWw=
github.com/bddjr/hlfhr v1.4.2/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
github.com/bddjr/hlfhr v1.5.1 h1:FxphatB9VSv8AV/50jwXX3QR12IJFD7fv2P99NymXqs=
github.com/bddjr/hlfhr v1.5.1/go.mod h1:YmFjslYd0ulNO0CtykhkhRUXJpfdq9sPVHQ4G67D610=
github.com/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=
github.com/beevik/ntp v1.5.0/go.mod h1:mJEhBrwT76w9D+IfOEGvuzyuudiW9E52U2BaTrMOYow=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -38,7 +42,11 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boyter/go-string v1.0.5 h1:/xcOlWdgelLYLVkUU0xBLfioGjZ9KIMUMI/RXG138YY=
github.com/boyter/go-string v1.0.5/go.mod h1:Mww9cDld2S2cdJ0tQffBhsZFMQRA2OJdcjWYZXvZ4Ss=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cilium/ebpf v0.21.0 h1:4dpx1J/B/1apeTmWBH5BkVLayHTkFrMovVPnHEk+l3k=
github.com/cilium/ebpf v0.21.0/go.mod h1:1kHKv6Kvh5a6TePP5vvvoMa1bclRyzUXELSs272fmIQ=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
@@ -69,41 +77,59 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM=
github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/httplog/v3 v3.3.0 h1:Gr6Y7nSzbpyCyRwKPOVKjDH3BH6TH5uvRNDsTZWDpvU=
github.com/go-chi/httplog/v3 v3.3.0/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gormigrate/gormigrate/v2 v2.1.5 h1:1OyorA5LtdQw12cyJDEHuTrEV3GiXiIhS4/QTTa/SM8=
github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v7fN3D9d3eEOAXRhi/+M=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY=
github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-webauthn/webauthn v0.17.2 h1:e9YtSZTVnxnMWFezXi6JvnqOSxmH4Er8QDHK2a/mM40=
github.com/go-webauthn/webauthn v0.17.2/go.mod h1:mQC6L0lZ5Kiu35G70zeB2WnrW4+vbHjR8Koq4HdVaMg=
github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA=
github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk=
github.com/gofiber/schema v1.7.1 h1:oSJBKdgP8JeIME4TQSAqlNKTU2iBB+2RNmKi8Nsc+TI=
github.com/gofiber/schema v1.7.1/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
github.com/gofiber/utils/v2 v2.0.0 h1:SCC3rpsEDWupFSHtc0RKxg/BKgV0s1qKfZg9Jv6D0sM=
github.com/gofiber/utils/v2 v2.0.0/go.mod h1:xF9v89FfmbrYqI/bQUGN7gR8ZtXot2jxnZvmAUtiavE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -120,26 +146,34 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc=
github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/gookit/filter v1.2.3 h1:Zo7cBOtsVzAoa/jtf+Ury6zlsbJXqInFdUpbbnB2vMM=
github.com/gookit/filter v1.2.3/go.mod h1:nFLJcOV8dRgS1iiX23gUQgmHUhpuS40qCvAGgIvA1pM=
github.com/gookit/goutil v0.7.3 h1:nXDd/AB17nEjqVCNDGioDhVL/gVqdlqRMfFergKDjHE=
github.com/gookit/goutil v0.7.3/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/gookit/validate v1.5.6 h1:D6vbSZzreuKYpeeXm5FDDEJy3K5E4lcWsQE4saSMZbU=
github.com/gookit/validate v1.5.6/go.mod h1:WYEHndRNepIIkM+6CtgEX9MQ9ToIQRhXxmz5oLHF/fc=
github.com/gookit/goutil v0.7.4 h1:OWgUngToNz+bPlX5aP+EMG31DraEU63uvKMwwT3vseM=
github.com/gookit/goutil v0.7.4/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/gookit/validate v1.5.7 h1:W0nqxAbgCJ4ND/oR+Tx8yyYn6l+PkFVvGHlG7LxpYsc=
github.com/gookit/validate v1.5.7/go.mod h1:VeThM1saXWVJIG/Yn627PJL9yvap0s4bsjPUIV8I0UI=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -157,11 +191,14 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
@@ -179,14 +216,18 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -200,18 +241,22 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI=
github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/libdns/alidns v1.0.6-beta.3.0.20260121104221-597b1e580e21 h1:BGtptgrEJGjDQz63r4xkcQ/2ERXpna9WVUtrTtmW8d0=
github.com/libdns/alidns v1.0.6-beta.3.0.20260121104221-597b1e580e21/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/libdns/alidns v1.0.7 h1:0BhTUFlWIUyb5U7wujGqsyoeZkleUR/hMCtdZDS8Oc8=
github.com/libdns/alidns v1.0.7/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=
github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI=
github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
github.com/libdns/cloudns v1.1.0 h1:W+1MadtxKySn3b5RITFTsXgTIvr5VoO5x97cewjlDcs=
github.com/libdns/cloudns v1.1.0/go.mod h1:/22V6tYYDALDpM4pw/RGGJ+X2F1Luibty9kKpKvkqBM=
github.com/libdns/gcore v0.0.0-20250427050847-9964da923833 h1:/gsawtsq03cI7qgK65v16tYHIh40omL9YEzHWqnc63I=
github.com/libdns/gcore v0.0.0-20250427050847-9964da923833/go.mod h1:jZJEV7pCTOJFlaUhHty+YwR05dzoSmInXK/vT3wOeVg=
github.com/libdns/huaweicloud v1.0.0 h1:BQUIkOAjF++ouiANRIE3jdMlCfeiAAr6tGqb+8hM1jo=
github.com/libdns/huaweicloud v1.0.0/go.mod h1:W+XywkW+C93fM50Ayklf6KuoCHZTqbrmFUEtpQnVvcU=
github.com/libdns/huaweicloud v1.0.1 h1:TdfpPY//3/QPhEGCyvR8u2DTlGxzpyaIsqofH5kYFyA=
github.com/libdns/huaweicloud v1.0.1/go.mod h1:lMHNSEqLZeuE4N28ytaqLZia3NPuaaz1ldAH/73bOQE=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/namesilo v1.0.0 h1:Shwbj9YnSp4NR617sBOSfDkFozEnWInWCxNwrDvPkCg=
@@ -222,20 +267,26 @@ github.com/libdns/tencentcloud v1.4.3 h1:xJHYLL1TdPeOtUr6Bu6dHTd1TU6/VFm7BFc2EAz
github.com/libdns/tencentcloud v1.4.3/go.mod h1:Be9gY3tDa12DuAPU79RV9NZIcjY6qg5s7zKPsP26yAM=
github.com/libdns/westcn v1.0.2 h1:PA2M3tME5/0T3klPMzSHvGk1EWnGNNkJiKxaJjFalnM=
github.com/libdns/westcn v1.0.2/go.mod h1:iKpk8jjOU+793Yp8nHoihPTCR6N1KWtm4/r8BB9mVnk=
github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e h1:HPHmtFcR5VpTJTpDqG4+l16uJfSlpB/X0r3qq22znrk=
github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/libtnb/acmez/v3 v3.0.0-20260406165834-a264acd02292 h1:FF1ZlwFFjpwT6CtBodr/NV4OMJPMC7plTmfzqhXrlSk=
github.com/libtnb/acmez/v3 v3.0.0-20260406165834-a264acd02292/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
github.com/libtnb/cache v1.2.0 h1:6MHHRTzP2GgNs1gD9VJoTIeQ3ud20tkkgvbOtzIf0Zs=
github.com/libtnb/cache v1.2.0/go.mod h1:QzCzSS5KBP2dcwMZtwKZO94eFMOViYMkUuxeLallZTE=
github.com/libtnb/chix v1.3.2 h1:LkA/+OaHeMyTiQoWEcCQKAMSKDZ0LxBXSENQO5d+OKk=
github.com/libtnb/chix v1.3.2/go.mod h1:ItssStBa/ov7v4qujyYzORqIf7Z6DO7Q4carOM7pyAQ=
github.com/libtnb/gormstore v1.1.1 h1:FG/3P4PuWM6/vB4weVJ31meiSaoeXns1NQlP66quKeg=
github.com/libtnb/gormstore v1.1.1/go.mod h1:8A5QzeZxi1MpSmjUVsHTDAL6KnU84feIXMutFLPawwA=
github.com/libtnb/cron v0.2.0 h1:Dgn8kgooA+bYGC16OHnufGLQlvtFDmnoM0NVjyLOfrE=
github.com/libtnb/cron v0.2.0/go.mod h1:nrOHxS4Ernkn1E//vs64xzC58hGfoL0+lYSW2zsROGI=
github.com/libtnb/gormstore v1.2.0 h1:VdNTiG8Ah+glDFNxtj+ZrBhcqiLFxMviqlz2gez6zWU=
github.com/libtnb/gormstore v1.2.0/go.mod h1:8A5QzeZxi1MpSmjUVsHTDAL6KnU84feIXMutFLPawwA=
github.com/libtnb/moby/client v0.0.0-20260127104057-d4b7902a560b h1:73OlJdl4Zk6Ipnm7KKeE6yCjfd7nk0BhTArujx6WJCU=
github.com/libtnb/moby/client v0.0.0-20260127104057-d4b7902a560b/go.mod h1:bBQRIiaGR9EZBC+x5hnwunNgcDOts7E1Oa7AowJwMFU=
github.com/libtnb/securecookie v1.2.0 h1:2uc0PBDm0foeSTrcZ9QTX1IEjf6kFEwfgEYSIXQSKrA=
github.com/libtnb/securecookie v1.2.0/go.mod h1:ja+wNGnQzYqcqXQnJWu6icsaWi5JEBwNEMJ2ReTVDxA=
github.com/libtnb/sessions v1.2.2 h1:VTTzzeBDJEkJbaPaIU9C4bRj2oAqD0rgQ7UHFkkaNT4=
github.com/libtnb/sessions v1.2.2/go.mod h1:qw+FWtBtrPDYCf6MfX0Lk5EhTArpvT72z5Ei4RUMTRg=
github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696 h1:GN0Y3DG27mMruX536k0jtCSLegjJb/gypzj5gZL6tRI=
github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696/go.mod h1:HeQeTfKU6tj2Lx1z79UacwYeDioo6M4ZD7BDDI6+rrg=
github.com/libtnb/sessions v1.4.0 h1:oQn4BckstpvqujSXejUcGAF4SQwTKFPIBe/Hv7s0aOs=
github.com/libtnb/sessions v1.4.0/go.mod h1:PvxiaI3PCD+kzjPmIrpV3s9e4ACps6OUaXgmKCYlUFo=
github.com/libtnb/sqlite v1.1.1 h1:p1Ud8vHTsPzgmChPUuJFkesrzl9ajD/pYrmoWf1cgig=
github.com/libtnb/sqlite v1.1.1/go.mod h1:PNlcqMVWU+pMIUR499fHvRLUdxrNVVVXwEZkW1c9stE=
github.com/libtnb/testify v0.0.0-20260406170114-25da2dad39e7 h1:ZzONsNhw69uC/wJQlMj66n1zfsRgKAElfJ//E9G380U=
github.com/libtnb/testify v0.0.0-20260406170114-25da2dad39e7/go.mod h1:HeQeTfKU6tj2Lx1z79UacwYeDioo6M4ZD7BDDI6+rrg=
github.com/libtnb/utils v1.2.1 h1:LJmReRREnpqfHyy9PZtNgBh3ZaIGct81b8ZaAsolMkM=
github.com/libtnb/utils v1.2.1/go.mod h1:o6LEDeC42PXI21uLWdWJWTVYvR9BtAZfzzTGJVQoQiU=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
@@ -243,8 +294,18 @@ github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIv
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/medama-io/go-useragent v1.2.3 h1:jTv5NI+dn2hAe6zlagfXe/Y4934/YPzqxvP/gP0DjCQ=
github.com/medama-io/go-useragent v1.2.3/go.mod h1:H9GYWth4IN8vAFZh5LeARza7VwM4jK9uk7Tb9huVzLw=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -255,17 +316,13 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/ncruces/go-sqlite3 v0.30.5 h1:6usmTQ6khriL8oWilkAZSJM/AIpAlVL2zFrlcpDldCE=
github.com/ncruces/go-sqlite3 v0.30.5/go.mod h1:0I0JFflTKzfs3Ogfv8erP7CCoV/Z8uxigVDNOR0AQ5E=
github.com/ncruces/go-sqlite3/gormlite v0.30.2 h1:FZ8mic14xTatssTkHCrelh9nPeFdXuzgMoNGkfuFbBU=
github.com/ncruces/go-sqlite3/gormlite v0.30.2/go.mod h1:W9WLBbqrrOIh2dqFZkeC/xKALG2LDIHY91jowahOdtI=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@@ -275,6 +332,8 @@ github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsT
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
@@ -295,21 +354,23 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sethvargo/go-limiter v1.1.0 h1:eLeZVQ2zqJOiEs03GguqmBVG6/T6lsZB+6PP1t7J6fA=
github.com/sethvargo/go-limiter v1.1.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shamaton/msgpack/v3 v3.0.0 h1:xl40uxWkSpwBCSTvS5wyXvJRsC6AcVcYeox9PspKiZg=
github.com/shamaton/msgpack/v3 v3.0.0/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY=
github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -332,8 +393,6 @@ github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+Q
github.com/studio-b12/gowebdav v0.12.0 h1:kFRtQECt8jmVAvA6RHBz3geXUGJHUZA6/IKpOVUs5kM=
github.com/studio-b12/gowebdav v0.12.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
github.com/timtadh/data-structures v0.6.1/go.mod h1:uYUnI1cQi/5yMCc7s23I+x8Mn8BCMf4WgK+7/4QSEk4=
github.com/timtadh/data-structures v0.6.2 h1:zybDnU5NLjJ7WKMDJpvVwczQuf1wSLBgdRHZ9O4AqJ0=
@@ -342,24 +401,34 @@ github.com/timtadh/getopt v1.0.0/go.mod h1:L3EL6YN2G0eIAhYBo9b7SB9d/kEQmdnwthIlM
github.com/timtadh/lexmachine v0.2.2/go.mod h1:GBJvD5OAfRn/gnp92zb9KTgHLB7akKyxmVivoYCcjQI=
github.com/timtadh/lexmachine v0.2.3 h1:ZqlfHnfMcAygtbNM5Gv7jQf8hmM8LfVzDjfCrq235NQ=
github.com/timtadh/lexmachine v0.2.3/go.mod h1:oK1NW+93fQSIF6s+J6sXBFWsCPCFbNmrwKV1i0aqvW0=
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701 h1:JgeHIJzRSEdcuLXufZrni5+a4yDnBhQG+DdKhqCFhq0=
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701/go.mod h1:ALbEe81QPWOZjDKCKNWodG2iqCMtregG8+ebQgjx2+4=
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/tufanbarisyildirim/gonginx v0.0.0-20260220081509-8e17ce617db3 h1:ClWHRIz18WRVT3RD9y9uTwEWU9eEi+VYFEIumg05FLk=
github.com/tufanbarisyildirim/gonginx v0.0.0-20260220081509-8e17ce617db3/go.mod h1:ALbEe81QPWOZjDKCKNWodG2iqCMtregG8+ebQgjx2+4=
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
@@ -370,8 +439,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -392,6 +461,8 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -406,8 +477,8 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -416,8 +487,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -434,20 +505,18 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -465,6 +534,8 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -506,6 +577,36 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
resty.dev/v3 v3.0.0-beta.6 h1:ghRdNpoE8/wBCv+kTKIOauW1aCrSIeTq7GxtfYgtevU=
resty.dev/v3 v3.0.0-beta.6/go.mod h1:NTOerrC/4T7/FE6tXIZGIysXXBdgNqwMZuKtxpea9NM=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+34 -25
View File
@@ -7,7 +7,6 @@ import (
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
@@ -15,29 +14,32 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-gormigrate/gormigrate/v2"
"github.com/gookit/validate"
"github.com/robfig/cron/v3"
"github.com/libtnb/cron"
"github.com/acepanel/panel/pkg/config"
"github.com/acepanel/panel/pkg/queue"
"github.com/acepanel/panel/v3/pkg/config"
"github.com/acepanel/panel/v3/pkg/tlscert"
"github.com/acepanel/panel/v3/pkg/types"
)
type Ace struct {
conf *config.Config
router *chi.Mux
server *hlfhr.Server
reloader *tlscert.Reloader
migrator *gormigrate.Gormigrate
cron *cron.Cron
queue *queue.Queue
runner types.TaskRunner
}
func NewAce(conf *config.Config, router *chi.Mux, server *hlfhr.Server, migrator *gormigrate.Gormigrate, cron *cron.Cron, queue *queue.Queue, _ *validate.Validation) *Ace {
func NewAce(conf *config.Config, router *chi.Mux, server *hlfhr.Server, reloader *tlscert.Reloader, migrator *gormigrate.Gormigrate, cron *cron.Cron, runner types.TaskRunner, _ *validate.Validation) *Ace {
return &Ace{
conf: conf,
router: router,
server: server,
reloader: reloader,
migrator: migrator,
cron: cron,
queue: queue,
runner: runner,
}
}
@@ -49,15 +51,17 @@ func (r *Ace) Run() error {
fmt.Println("[DB] database migrated")
// start cron scheduler
r.cron.Start()
if err := r.cron.Start(); err != nil {
return err
}
fmt.Println("[CRON] cron scheduler started")
// create context for queue
queueCtx, queueCancel := context.WithCancel(context.Background())
defer queueCancel()
// create context for runner
runnerCtx, runnerCancel := context.WithCancel(context.Background())
defer runnerCancel()
// start queue
r.queue.Run(queueCtx)
// start task runner
r.runner.Run(runnerCtx)
// setup graceful shutdown
quit := make(chan os.Signal, 1)
@@ -66,15 +70,12 @@ func (r *Ace) Run() error {
// run http server in goroutine
serverErr := make(chan error, 1)
go func() {
if r.conf.HTTP.TLS {
cert := filepath.Join(Root, "panel/storage/cert.pem")
key := filepath.Join(Root, "panel/storage/cert.key")
fmt.Println("[HTTP] listening and serving on port", r.conf.HTTP.Port, "with tls")
if err := r.server.ListenAndServeTLS(cert, key); !errors.Is(err, http.ErrServerClosed) {
fmt.Println("[HTTP] listening and serving on port", r.conf.HTTP.Port)
if r.conf.HTTP.IsHTTPS() {
if err := r.server.ListenAndServeTLS("", ""); !errors.Is(err, http.ErrServerClosed) {
serverErr <- err
}
} else {
fmt.Println("[HTTP] listening and serving on port", r.conf.HTTP.Port)
if err := r.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
serverErr <- err
}
@@ -89,20 +90,28 @@ func (r *Ace) Run() error {
return err
}
case sig := <-quit:
fmt.Println("\n[APP] received signal:", sig)
fmt.Println("[APP] received signal:", sig)
}
// graceful shutdown
fmt.Println("[APP] shutting down gracefully...")
// stop cron scheduler
ctx := r.cron.Stop()
<-ctx.Done()
cronCtx, cronCancel := context.WithTimeout(context.Background(), 30*time.Second)
_ = r.cron.Stop(cronCtx)
cronCancel()
fmt.Println("[CRON] cron scheduler stopped")
// stop queue
queueCancel()
fmt.Println("[QUEUE] queue stopped")
// stop task runner
runnerCancel()
fmt.Println("[QUEUE] task runner stopped")
// close certificate reloader
if r.reloader != nil {
if err := r.reloader.Close(); err != nil {
fmt.Println("[TLS] certificate reloader close error:", err)
}
}
// shutdown http server
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/gookit/color"
"github.com/urfave/cli/v3"
"github.com/acepanel/panel/pkg/apploader"
"github.com/acepanel/panel/v3/pkg/apploader"
)
type Cli struct {
+5
View File
@@ -1,5 +1,10 @@
package app
import "time"
// StartTime 面板启动时间
var StartTime = time.Now()
// 面板状态常量
const (
StatusNormal = iota
+12 -7
View File
@@ -9,13 +9,13 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/pkg/tools"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/tools"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -36,6 +36,11 @@ func (s *App) Route(r chi.Router) {
r.Post("/clear_error_log", s.ClearErrorLog)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("apache")
return types.AggregateAppStatus(ok)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/apache/conf/httpd.conf", app.Root))
if err != nil {
+39 -21
View File
@@ -3,49 +3,67 @@ package apps
import (
"github.com/google/wire"
"github.com/acepanel/panel/internal/apps/apache"
"github.com/acepanel/panel/internal/apps/codeserver"
"github.com/acepanel/panel/internal/apps/docker"
"github.com/acepanel/panel/internal/apps/fail2ban"
"github.com/acepanel/panel/internal/apps/frp"
"github.com/acepanel/panel/internal/apps/gitea"
"github.com/acepanel/panel/internal/apps/mariadb"
"github.com/acepanel/panel/internal/apps/memcached"
"github.com/acepanel/panel/internal/apps/minio"
"github.com/acepanel/panel/internal/apps/mysql"
"github.com/acepanel/panel/internal/apps/nginx"
"github.com/acepanel/panel/internal/apps/openresty"
"github.com/acepanel/panel/internal/apps/percona"
"github.com/acepanel/panel/internal/apps/phpmyadmin"
"github.com/acepanel/panel/internal/apps/podman"
"github.com/acepanel/panel/internal/apps/postgresql"
"github.com/acepanel/panel/internal/apps/pureftpd"
"github.com/acepanel/panel/internal/apps/redis"
"github.com/acepanel/panel/internal/apps/rsync"
"github.com/acepanel/panel/internal/apps/s3fs"
"github.com/acepanel/panel/internal/apps/supervisor"
"github.com/acepanel/panel/v3/internal/apps/apache"
"github.com/acepanel/panel/v3/internal/apps/clickhouse"
"github.com/acepanel/panel/v3/internal/apps/codeserver"
"github.com/acepanel/panel/v3/internal/apps/docker"
"github.com/acepanel/panel/v3/internal/apps/elasticsearch"
"github.com/acepanel/panel/v3/internal/apps/fail2ban"
"github.com/acepanel/panel/v3/internal/apps/frp"
"github.com/acepanel/panel/v3/internal/apps/gitea"
"github.com/acepanel/panel/v3/internal/apps/grafana"
"github.com/acepanel/panel/v3/internal/apps/kafka"
"github.com/acepanel/panel/v3/internal/apps/mariadb"
"github.com/acepanel/panel/v3/internal/apps/memcached"
"github.com/acepanel/panel/v3/internal/apps/minio"
"github.com/acepanel/panel/v3/internal/apps/mongodb"
"github.com/acepanel/panel/v3/internal/apps/mysql"
"github.com/acepanel/panel/v3/internal/apps/nginx"
"github.com/acepanel/panel/v3/internal/apps/openresty"
"github.com/acepanel/panel/v3/internal/apps/opensearch"
"github.com/acepanel/panel/v3/internal/apps/percona"
"github.com/acepanel/panel/v3/internal/apps/phpmyadmin"
"github.com/acepanel/panel/v3/internal/apps/podman"
"github.com/acepanel/panel/v3/internal/apps/postgresql"
"github.com/acepanel/panel/v3/internal/apps/prometheus"
"github.com/acepanel/panel/v3/internal/apps/pureftpd"
"github.com/acepanel/panel/v3/internal/apps/redis"
"github.com/acepanel/panel/v3/internal/apps/rocketmq"
"github.com/acepanel/panel/v3/internal/apps/rsync"
"github.com/acepanel/panel/v3/internal/apps/s3fs"
"github.com/acepanel/panel/v3/internal/apps/supervisor"
"github.com/acepanel/panel/v3/internal/apps/valkey"
)
var ProviderSet = wire.NewSet(
apache.NewApp,
clickhouse.NewApp,
codeserver.NewApp,
docker.NewApp,
elasticsearch.NewApp,
fail2ban.NewApp,
frp.NewApp,
gitea.NewApp,
grafana.NewApp,
kafka.NewApp,
mariadb.NewApp,
memcached.NewApp,
minio.NewApp,
mongodb.NewApp,
mysql.NewApp,
nginx.NewApp,
openresty.NewApp,
opensearch.NewApp,
percona.NewApp,
phpmyadmin.NewApp,
podman.NewApp,
postgresql.NewApp,
prometheus.NewApp,
pureftpd.NewApp,
redis.NewApp,
rocketmq.NewApp,
rsync.NewApp,
s3fs.NewApp,
supervisor.NewApp,
valkey.NewApp,
)
+332
View File
@@ -0,0 +1,332 @@
package clickhouse
import (
"crypto/sha256"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"go.yaml.in/yaml/v4"
"resty.dev/v3"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
settingRepo biz.SettingRepo
databaseServerRepo biz.DatabaseServerRepo
}
func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.DatabaseServerRepo) *App {
return &App{
t: t,
settingRepo: setting,
databaseServerRepo: databaseServer,
}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
r.Get("/default_password", s.GetDefaultPassword)
r.Post("/default_password", s.SetDefaultPassword)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("clickhouse-server")
return types.AggregateAppStatus(ok)
}
// Load 获取 ClickHouse 运行状态
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, _ := systemctl.Status("clickhouse-server")
if !status {
service.Success(w, []types.NV{})
return
}
password, _ := s.settingRepo.Get(biz.SettingKeyClickHouseDefaultPassword)
port := s.getPort()
client := resty.New().SetTimeout(10 * time.Second)
defer func(client *resty.Client) { _ = client.Close() }(client)
// 获取版本
versionResp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/?query=SELECT+version()&user=default&password=%s", port, password))
if err != nil || !versionResp.IsSuccess() {
service.Success(w, []types.NV{})
return
}
version := strings.TrimSpace(string(versionResp.Bytes()))
// 获取运行时间
uptimeResp, _ := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/?query=SELECT+uptime()&user=default&password=%s", port, password))
uptime := strings.TrimSpace(string(uptimeResp.Bytes()))
// 获取当前查询数
queriesResp, _ := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/?query=SELECT+value+FROM+system.metrics+WHERE+metric='Query'&user=default&password=%s", port, password))
queries := strings.TrimSpace(string(queriesResp.Bytes()))
// 获取内存使用
memResp, _ := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/?query=SELECT+value+FROM+system.metrics+WHERE+metric='MemoryTracking'&user=default&password=%s", port, password))
memUsage := strings.TrimSpace(string(memResp.Bytes()))
// 获取数据库数量
dbCountResp, _ := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/?query=SELECT+count()+FROM+system.databases&user=default&password=%s", port, password))
dbCount := strings.TrimSpace(string(dbCountResp.Bytes()))
// 获取表数量
tableCountResp, _ := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/?query=SELECT+count()+FROM+system.tables&user=default&password=%s", port, password))
tableCount := strings.TrimSpace(string(tableCountResp.Bytes()))
data := []types.NV{
{Name: s.t.Get("Version"), Value: version},
{Name: s.t.Get("Uptime (seconds)"), Value: uptime},
{Name: s.t.Get("Active Queries"), Value: queries},
{Name: s.t.Get("Memory Usage (bytes)"), Value: memUsage},
{Name: s.t.Get("Databases"), Value: dbCount},
{Name: s.t.Get("Tables"), Value: tableCount},
}
service.Success(w, data)
}
// GetConfig 获取配置
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(s.configPath())
service.Success(w, conf)
}
// UpdateConfig 更新配置
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("clickhouse-server"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg == nil {
cfg = make(map[string]any)
}
tune := ConfigTune{
ListenHost: s.getYAMLValue(cfg, "listen_host"),
HTTPPort: s.getYAMLValue(cfg, "http_port"),
TCPPort: s.getYAMLValue(cfg, "tcp_port"),
MaxMemoryUsage: s.getYAMLValue(cfg, "max_memory_usage"),
MaxThreads: s.getYAMLValue(cfg, "max_threads"),
Path: s.getYAMLValue(cfg, "path"),
TmpPath: s.getYAMLValue(cfg, "tmp_path"),
LogLevel: s.getYAMLValue(cfg, "logger.level"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
raw, _ := io.Read(s.configPath())
var cfg map[string]any
if err = yaml.Unmarshal([]byte(raw), &cfg); err != nil {
cfg = make(map[string]any)
}
// ClickHouse 顶层键直接用平铺方式
s.setYAMLValue(cfg, "listen_host", req.ListenHost)
s.setYAMLValue(cfg, "http_port", req.HTTPPort)
s.setYAMLValue(cfg, "tcp_port", req.TCPPort)
s.setYAMLValue(cfg, "max_memory_usage", req.MaxMemoryUsage)
s.setYAMLValue(cfg, "max_threads", req.MaxThreads)
s.setYAMLValue(cfg, "path", req.Path)
s.setYAMLValue(cfg, "tmp_path", req.TmpPath)
// logger.level 是嵌套的
s.setNestedYAMLValue(cfg, "logger.level", req.LogLevel)
data, err := yaml.Marshal(cfg)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Write(s.configPath(), string(data), 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("clickhouse-server"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetDefaultPassword 获取 default 用户密码
func (s *App) GetDefaultPassword(w http.ResponseWriter, r *http.Request) {
password, err := s.settingRepo.Get(biz.SettingKeyClickHouseDefaultPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get ClickHouse default password: %v", err))
return
}
service.Success(w, password)
}
// SetDefaultPassword 设置 default 用户密码
func (s *App) SetDefaultPassword(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[SetDefaultPassword](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
// 计算 SHA256 哈希
hash := sha256.Sum256([]byte(req.Password))
hexHash := fmt.Sprintf("%x", hash)
// 读取 users.d/default.yaml 并更新密码
raw, _ := io.Read(s.usersConfigPath())
var cfg map[string]any
if err = yaml.Unmarshal([]byte(raw), &cfg); err != nil {
cfg = make(map[string]any)
}
users, _ := cfg["users"].(map[string]any)
if users == nil {
users = make(map[string]any)
cfg["users"] = users
}
def, _ := users["default"].(map[string]any)
if def == nil {
def = make(map[string]any)
users["default"] = def
}
def["password_sha256_hex"] = hexHash
out, _ := yaml.Marshal(cfg)
if err = io.Write(s.usersConfigPath(), string(out), 0644); err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write ClickHouse user config: %v", err))
return
}
// 重启服务使密码生效
if err = systemctl.Restart("clickhouse-server"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 保存明文到面板数据库
if err = s.settingRepo.Set(biz.SettingKeyClickHouseDefaultPassword, req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to save ClickHouse default password: %v", err))
return
}
_ = s.databaseServerRepo.UpdatePassword("local_clickhouse", req.Password)
service.Success(w, nil)
}
// configPath 返回主配置文件路径
func (s *App) configPath() string {
return fmt.Sprintf("%s/server/clickhouse/config/config.yaml", app.Root)
}
// usersConfigPath 返回用户密码配置文件路径(users.d/ 由 ConfigProcessor 自动合并到 users.yaml
func (s *App) usersConfigPath() string {
return fmt.Sprintf("%s/server/clickhouse/config/users.d/default.yaml", app.Root)
}
// getPort 从配置中获取 HTTP 端口
func (s *App) getPort() string {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg != nil {
if v := s.getYAMLValue(cfg, "http_port"); v != "" {
return v
}
}
return "8123"
}
// getYAMLValue 获取 YAML 值,支持嵌套键
func (s *App) getYAMLValue(cfg map[string]any, key string) string {
// 先尝试平铺键
if val, ok := cfg[key]; ok {
return cast.ToString(val)
}
// 回退到嵌套键
parts := strings.SplitN(key, ".", 2)
val, ok := cfg[parts[0]]
if !ok {
return ""
}
if len(parts) == 1 {
return cast.ToString(val)
}
nested, ok := val.(map[string]any)
if !ok {
return ""
}
return s.getYAMLValue(nested, parts[1])
}
// setYAMLValue 设置平铺 YAML 值
func (s *App) setYAMLValue(cfg map[string]any, key string, value string) {
if value == "" {
return
}
cfg[key] = value
}
// setNestedYAMLValue 设置嵌套 YAML 值
func (s *App) setNestedYAMLValue(cfg map[string]any, key string, value string) {
if value == "" {
return
}
parts := strings.SplitN(key, ".", 2)
if len(parts) == 1 {
cfg[parts[0]] = value
return
}
nested, ok := cfg[parts[0]].(map[string]any)
if !ok {
nested = make(map[string]any)
cfg[parts[0]] = nested
}
s.setNestedYAMLValue(nested, parts[1], value)
}
+27
View File
@@ -0,0 +1,27 @@
package clickhouse
// UpdateConfig 更新配置
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// SetDefaultPassword 设置 default 用户密码
type SetDefaultPassword struct {
Password string `form:"password" json:"password" validate:"required|password"`
}
// ConfigTune ClickHouse 配置调整
type ConfigTune struct {
// 网络
ListenHost string `form:"listen_host" json:"listen_host"`
HTTPPort string `form:"http_port" json:"http_port"`
TCPPort string `form:"tcp_port" json:"tcp_port"`
// 性能
MaxMemoryUsage string `form:"max_memory_usage" json:"max_memory_usage"`
MaxThreads string `form:"max_threads" json:"max_threads"`
// 路径
Path string `form:"path" json:"path"`
TmpPath string `form:"tmp_path" json:"tmp_path"`
// 日志
LogLevel string `form:"log_level" json:"log_level"`
}
+9 -3
View File
@@ -5,9 +5,10 @@ import (
"github.com/go-chi/chi/v5"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct{}
@@ -21,6 +22,11 @@ func (s *App) Route(r chi.Router) {
r.Post("/config", s.UpdateConfig)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("code-server")
return types.AggregateAppStatus(ok)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, _ := io.Read("/root/.config/code-server/config.yaml")
service.Success(w, config)
+11 -5
View File
@@ -8,9 +8,10 @@ import (
"github.com/go-chi/chi/v5"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct{}
@@ -26,6 +27,11 @@ func (s *App) Route(r chi.Router) {
r.Post("/settings", s.UpdateSettings)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("docker")
return types.AggregateAppStatus(ok)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read("/etc/docker/daemon.json")
if err != nil {
@@ -103,8 +109,8 @@ func (s *App) GetSettings(w http.ResponseWriter, r *http.Request) {
// 从 exec-opts 中提取 cgroup-driver
for _, opt := range daemonConfig.ExecOpts {
if strings.HasPrefix(opt, "native.cgroupdriver=") {
settings.CgroupDriver = strings.TrimPrefix(opt, "native.cgroupdriver=")
if after, ok := strings.CutPrefix(opt, "native.cgroupdriver="); ok {
settings.CgroupDriver = after
break
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ type Settings struct {
InsecureRegistries []string `json:"insecure-registries,omitempty"` // 非安全镜像仓库
LiveRestore bool `json:"live-restore,omitempty"` // Live restore
LogDriver string `json:"log-driver,omitempty"` // 日志驱动
LogOpts LogOpts `json:"log-opts,omitempty"` // 日志配置选项
LogOpts LogOpts `json:"log-opts"` // 日志配置选项
CgroupDriver string `json:"cgroup-driver,omitempty"` // cgroup 驱动(从 exec-opts 中提取)
Hosts []string `json:"hosts,omitempty"` // Socket 路径
DataRoot string `json:"data-root,omitempty"` // 数据目录
+303
View File
@@ -0,0 +1,303 @@
package elasticsearch
import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"go.yaml.in/yaml/v4"
"resty.dev/v3"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
}
func NewApp(t *gotext.Locale) *App {
return &App{t: t}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("elasticsearch")
return types.AggregateAppStatus(ok)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("elasticsearch")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get elasticsearch status: %v", err))
return
}
if !status {
service.Success(w, []types.NV{})
return
}
port := s.getPort()
client := resty.New().SetTimeout(10 * time.Second)
defer func(client *resty.Client) { _ = client.Close() }(client)
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/_cluster/health", port))
if err != nil || !resp.IsSuccess() {
service.Success(w, []types.NV{})
return
}
var health struct {
ClusterName string `json:"cluster_name"`
Status string `json:"status"`
NumberOfNodes int `json:"number_of_nodes"`
NumberOfDataNodes int `json:"number_of_data_nodes"`
ActiveShards int `json:"active_shards"`
ActivePrimaryShards int `json:"active_primary_shards"`
RelocatingShards int `json:"relocating_shards"`
UnassignedShards int `json:"unassigned_shards"`
}
if err = json.Unmarshal(resp.Bytes(), &health); err != nil {
service.Success(w, []types.NV{})
return
}
data := []types.NV{
{Name: s.t.Get("Cluster Name"), Value: health.ClusterName},
{Name: s.t.Get("Cluster Status"), Value: health.Status},
{Name: s.t.Get("Number of Nodes"), Value: cast.ToString(health.NumberOfNodes)},
{Name: s.t.Get("Number of Data Nodes"), Value: cast.ToString(health.NumberOfDataNodes)},
{Name: s.t.Get("Active Shards"), Value: cast.ToString(health.ActiveShards)},
{Name: s.t.Get("Active Primary Shards"), Value: cast.ToString(health.ActivePrimaryShards)},
{Name: s.t.Get("Relocating Shards"), Value: cast.ToString(health.RelocatingShards)},
{Name: s.t.Get("Unassigned Shards"), Value: cast.ToString(health.UnassignedShards)},
}
service.Success(w, data)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(s.configPath())
service.Success(w, conf)
}
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("elasticsearch"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取 ElasticSearch 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg == nil {
cfg = make(map[string]any)
}
// ES 9.x 不再在 jvm.options 中设置堆内存,改为 jvm.options.d/heap.options
heapRaw, _ := io.Read(s.jvmHeapOptionsPath())
heapInit, heapMax := s.parseJVMHeap(heapRaw)
// 兼容旧版本:如果 heap.options 没有,尝试从 jvm.options 读取
if heapInit == "" && heapMax == "" {
jvmRaw, _ := io.Read(s.jvmOptionsPath())
heapInit, heapMax = s.parseJVMHeap(jvmRaw)
}
tune := ConfigTune{
ClusterName: s.getYAMLValue(cfg, "cluster.name"),
NodeName: s.getYAMLValue(cfg, "node.name"),
NetworkHost: s.getYAMLValue(cfg, "network.host"),
HTTPPort: s.getYAMLValue(cfg, "http.port"),
DiscoveryType: s.getYAMLValue(cfg, "discovery.type"),
PathData: s.getYAMLValue(cfg, "path.data"),
PathLogs: s.getYAMLValue(cfg, "path.logs"),
HeapInitSize: heapInit,
HeapMaxSize: heapMax,
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 ElasticSearch 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
// 更新 YAML 配置
raw, _ := io.Read(s.configPath())
var cfg map[string]any
if err = yaml.Unmarshal([]byte(raw), &cfg); err != nil {
cfg = make(map[string]any)
}
s.setYAMLValue(cfg, "cluster.name", req.ClusterName)
s.setYAMLValue(cfg, "node.name", req.NodeName)
s.setYAMLValue(cfg, "network.host", req.NetworkHost)
s.setYAMLValue(cfg, "http.port", req.HTTPPort)
s.setYAMLValue(cfg, "discovery.type", req.DiscoveryType)
s.setYAMLValue(cfg, "path.data", req.PathData)
s.setYAMLValue(cfg, "path.logs", req.PathLogs)
data, err := yaml.Marshal(cfg)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Write(s.configPath(), string(data), 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 更新 JVM 堆内存(写入 jvm.options.d/heap.options
if req.HeapInitSize != "" || req.HeapMaxSize != "" {
heapRaw, _ := io.Read(s.jvmHeapOptionsPath())
heapRaw = s.setJVMHeap(heapRaw, req.HeapInitSize, req.HeapMaxSize)
if err = io.Write(s.jvmHeapOptionsPath(), heapRaw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
if err = systemctl.Restart("elasticsearch"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// configPath 返回配置文件路径
func (s *App) configPath() string {
return fmt.Sprintf("%s/server/elasticsearch/config/elasticsearch.yml", app.Root)
}
// jvmOptionsPath 返回 JVM 选项文件路径
func (s *App) jvmOptionsPath() string {
return fmt.Sprintf("%s/server/elasticsearch/config/jvm.options", app.Root)
}
// jvmHeapOptionsPath 返回 JVM 堆内存配置文件路径(ES 9.x 推荐方式)
func (s *App) jvmHeapOptionsPath() string {
return fmt.Sprintf("%s/server/elasticsearch/config/jvm.options.d/heap.options", app.Root)
}
// getPort 从配置中获取 HTTP 端口
func (s *App) getPort() string {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg != nil {
if v := s.getYAMLValue(cfg, "http.port"); v != "" {
return v
}
}
return "9200"
}
// getYAMLValue 获取 YAML 值,优先匹配平铺键(如 "path.data"),回退到嵌套键(如 path -> data
func (s *App) getYAMLValue(cfg map[string]any, key string) string {
// 优先匹配平铺键(安装脚本用 sed 生成的格式)
if val, ok := cfg[key]; ok {
return cast.ToString(val)
}
// 回退到嵌套键
parts := strings.SplitN(key, ".", 2)
val, ok := cfg[parts[0]]
if !ok {
return ""
}
if len(parts) == 1 {
return cast.ToString(val)
}
nested, ok := val.(map[string]any)
if !ok {
return ""
}
return s.getYAMLValue(nested, parts[1])
}
// setYAMLValue 设置 YAML 值
func (s *App) setYAMLValue(cfg map[string]any, key string, value string) {
if value == "" {
return
}
// 使用平铺键,同时清理可能存在的嵌套键
cfg[key] = value
parts := strings.SplitN(key, ".", 2)
if len(parts) == 2 {
if nested, ok := cfg[parts[0]].(map[string]any); ok {
delete(nested, parts[1])
if len(nested) == 0 {
delete(cfg, parts[0])
}
}
}
}
// parseJVMHeap 从 jvm.options 中提取堆内存配置
func (s *App) parseJVMHeap(content string) (initSize, maxSize string) {
reInit := regexp.MustCompile(`(?m)^-Xms(\S+)`)
reMax := regexp.MustCompile(`(?m)^-Xmx(\S+)`)
if m := reInit.FindStringSubmatch(content); len(m) == 2 {
initSize = m[1]
}
if m := reMax.FindStringSubmatch(content); len(m) == 2 {
maxSize = m[1]
}
return
}
// setJVMHeap 替换 jvm.options 中的堆内存配置
func (s *App) setJVMHeap(content string, initSize, maxSize string) string {
if initSize != "" {
re := regexp.MustCompile(`(?m)^-Xms\S+`)
if re.MatchString(content) {
content = re.ReplaceAllString(content, "-Xms"+initSize)
} else {
content += "\n-Xms" + initSize
}
}
if maxSize != "" {
re := regexp.MustCompile(`(?m)^-Xmx\S+`)
if re.MatchString(content) {
content = re.ReplaceAllString(content, "-Xmx"+maxSize)
} else {
content += "\n-Xmx" + maxSize
}
}
return content
}
+21
View File
@@ -0,0 +1,21 @@
package elasticsearch
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune ElasticSearch 配置调整
type ConfigTune struct {
// 集群
ClusterName string `form:"cluster_name" json:"cluster_name"`
NodeName string `form:"node_name" json:"node_name"`
NetworkHost string `form:"network_host" json:"network_host"`
HTTPPort string `form:"http_port" json:"http_port"`
DiscoveryType string `form:"discovery_type" json:"discovery_type"`
// 路径
PathData string `form:"path_data" json:"path_data"`
PathLogs string `form:"path_logs" json:"path_logs"`
// JVM
HeapInitSize string `form:"heap_init_size" json:"heap_init_size"`
HeapMaxSize string `form:"heap_max_size" json:"heap_max_size"`
}
+21 -16
View File
@@ -10,13 +10,16 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/libtnb/chix"
"github.com/libtnb/utils/str"
"github.com/samber/lo"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -41,6 +44,11 @@ func (s *App) Route(r chi.Router) {
r.Get("/white_list", s.GetWhiteList)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("fail2ban")
return types.AggregateAppStatus(ok)
}
// List 所有规则
func (s *App) List(w http.ResponseWriter, r *http.Request) {
raw, err := io.Read("/etc/fail2ban/jail.local")
@@ -270,18 +278,15 @@ func (s *App) BanList(w http.ResponseWriter, r *http.Request) {
}
bannedIpList := strings.Split(bannedIp, " ")
var list []map[string]string
for _, ip := range bannedIpList {
if len(ip) > 0 {
list = append(list, map[string]string{
"name": req.Name,
"ip": ip,
})
list := lo.FilterMap(bannedIpList, func(ip string, _ int) (map[string]string, bool) {
if len(ip) == 0 {
return nil, false
}
}
if list == nil {
list = []map[string]string{}
}
return map[string]string{
"name": req.Name,
"ip": ip,
}, true
})
service.Success(w, chix.M{
"currently_ban": currentlyBan,
+11 -4
View File
@@ -6,10 +6,11 @@ import (
"github.com/go-chi/chi/v5"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct{}
@@ -25,6 +26,12 @@ func (s *App) Route(r chi.Router) {
r.Post("/user", s.UpdateUser)
}
func (s *App) Status() string {
frps, _ := systemctl.Status("frps")
frpc, _ := systemctl.Status("frpc")
return types.AggregateAppStatus(frps, frpc)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[Name](r)
if err != nil {
+10 -4
View File
@@ -6,10 +6,11 @@ import (
"github.com/go-chi/chi/v5"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct{}
@@ -23,6 +24,11 @@ func (s *App) Route(r chi.Router) {
r.Post("/config", s.UpdateConfig)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("gitea")
return types.AggregateAppStatus(ok)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, _ := io.Read(fmt.Sprintf("%s/server/gitea/app.ini", app.Root))
service.Success(w, config)
+524
View File
@@ -0,0 +1,524 @@
package grafana
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/samber/lo"
"go.yaml.in/yaml/v4"
"resty.dev/v3"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
}
func NewApp(t *gotext.Locale) *App {
return &App{t: t}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
// 数据源管理
r.Get("/datasources", s.DataSourceList)
r.Post("/datasources", s.CreateDataSource)
r.Post("/datasources/{name}", s.UpdateDataSource)
r.Delete("/datasources/{name}", s.DeleteDataSource)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("grafana")
return types.AggregateAppStatus(ok)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("grafana")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get grafana status: %v", err))
return
}
if !status {
service.Success(w, []types.NV{})
return
}
// 从 defaults.ini 获取端口
config, _ := io.Read(s.configPath())
port := s.getINIValue(config, "server", "http_port")
if port == "" {
port = "3000"
}
client := resty.New().SetTimeout(10 * time.Second)
defer func(client *resty.Client) { _ = client.Close() }(client)
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/api/health", port))
if err != nil || !resp.IsSuccess() {
service.Success(w, []types.NV{})
return
}
var health struct {
Commit string `json:"commit"`
Database string `json:"database"`
Version string `json:"version"`
}
if err = json.Unmarshal(resp.Bytes(), &health); err != nil {
service.Success(w, []types.NV{})
return
}
data := []types.NV{
{Name: s.t.Get("Version"), Value: health.Version},
{Name: "Commit", Value: health.Commit},
{Name: s.t.Get("Database"), Value: health.Database},
}
service.Success(w, data)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, _ := io.Read(s.configPath())
service.Success(w, config)
}
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("grafana"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取 Grafana 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, _ := io.Read(s.configPath())
get := func(section, key string) string {
return s.getINIValue(config, section, key)
}
tune := ConfigTune{
// [server]
HTTPPort: get("server", "http_port"),
Domain: get("server", "domain"),
RootURL: get("server", "root_url"),
Protocol: get("server", "protocol"),
// [database]
DBType: get("database", "type"),
DBHost: get("database", "host"),
DBName: get("database", "name"),
DBUser: get("database", "user"),
DBPassword: get("database", "password"),
// [security]
AdminUser: get("security", "admin_user"),
AdminPassword: get("security", "admin_password"),
// [users]
AllowSignUp: get("users", "allow_sign_up"),
AutoAssignOrgRole: get("users", "auto_assign_org_role"),
// [smtp]
SMTPEnabled: get("smtp", "enabled"),
SMTPHost: get("smtp", "host"),
SMTPUser: get("smtp", "user"),
SMTPPassword: get("smtp", "password"),
SMTPFromAddress: get("smtp", "from_address"),
// [log]
LogMode: get("log", "mode"),
LogLevel: get("log", "level"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Grafana 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
config, _ := io.Read(s.configPath())
// [server]
config = s.setINIValue(config, "server", "http_port", req.HTTPPort)
config = s.setINIValue(config, "server", "domain", req.Domain)
config = s.setINIValue(config, "server", "root_url", req.RootURL)
config = s.setINIValue(config, "server", "protocol", req.Protocol)
// [database]
config = s.setINIValue(config, "database", "type", req.DBType)
config = s.setINIValue(config, "database", "host", req.DBHost)
config = s.setINIValue(config, "database", "name", req.DBName)
config = s.setINIValue(config, "database", "user", req.DBUser)
config = s.setINIValue(config, "database", "password", req.DBPassword)
// [security]
config = s.setINIValue(config, "security", "admin_user", req.AdminUser)
config = s.setINIValue(config, "security", "admin_password", req.AdminPassword)
// [users]
config = s.setINIValue(config, "users", "allow_sign_up", req.AllowSignUp)
config = s.setINIValue(config, "users", "auto_assign_org_role", req.AutoAssignOrgRole)
// [smtp]
config = s.setINIValue(config, "smtp", "enabled", req.SMTPEnabled)
config = s.setINIValue(config, "smtp", "host", req.SMTPHost)
config = s.setINIValue(config, "smtp", "user", req.SMTPUser)
config = s.setINIValue(config, "smtp", "password", req.SMTPPassword)
config = s.setINIValue(config, "smtp", "from_address", req.SMTPFromAddress)
// [log]
config = s.setINIValue(config, "log", "mode", req.LogMode)
config = s.setINIValue(config, "log", "level", req.LogLevel)
if err = io.Write(s.configPath(), config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("grafana"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// DataSourceList 获取数据源列表
func (s *App) DataSourceList(w http.ResponseWriter, r *http.Request) {
service.Success(w, s.getDatasourceList(s.readDatasources()))
}
// CreateDataSource 创建数据源
func (s *App) CreateDataSource(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[DataSource](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
cfg := s.readDatasources()
if lo.ContainsBy(s.getDatasourceList(cfg), func(item any) bool {
ds, ok := item.(map[string]any)
return ok && ds["name"] == req.Name
}) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("datasource %s already exists", req.Name))
return
}
if req.IsDefault {
s.clearDefault(cfg)
}
list := s.getDatasourceList(cfg)
list = append(list, s.buildDatasourceMap(req))
cfg["datasources"] = list
if err = s.writeDatasources(cfg); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// UpdateDataSource 更新数据源
func (s *App) UpdateDataSource(w http.ResponseWriter, r *http.Request) {
oldName := chi.URLParam(r, "name")
req, err := service.Bind[DataSource](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
cfg := s.readDatasources()
list := s.getDatasourceList(cfg)
_, idx, found := lo.FindIndexOf(list, func(item any) bool {
ds, ok := item.(map[string]any)
return ok && ds["name"] == oldName
})
if !found {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("datasource %s not found", oldName))
return
}
ds, _ := list[idx].(map[string]any)
newDs := s.buildDatasourceMap(req)
// 密码为空时保留原有 secureJsonData
if req.Password == "" {
if sec, exists := ds["secureJsonData"]; exists {
newDs["secureJsonData"] = sec
}
}
list[idx] = newDs
if req.IsDefault {
s.clearDefault(cfg)
lo.ForEach(list, func(item any, _ int) {
if ds, ok := item.(map[string]any); ok && ds["name"] == req.Name {
ds["isDefault"] = true
}
})
}
if oldName != req.Name {
s.addDeleteEntry(cfg, oldName)
}
cfg["datasources"] = list
if err = s.writeDatasources(cfg); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// DeleteDataSource 删除数据源
func (s *App) DeleteDataSource(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
cfg := s.readDatasources()
list := s.getDatasourceList(cfg)
newList := lo.Filter(list, func(item any, _ int) bool {
ds, ok := item.(map[string]any)
return !ok || ds["name"] != name
})
found := len(newList) < len(list)
if !found {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("datasource %s not found", name))
return
}
cfg["datasources"] = newList
s.addDeleteEntry(cfg, name)
if err := s.writeDatasources(cfg); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// configPath 返回 Grafana 主配置文件路径
func (s *App) configPath() string {
return fmt.Sprintf("%s/server/grafana/conf/defaults.ini", app.Root)
}
// getINIValue 从 INI 配置中获取指定 section 下的 key 值
func (s *App) getINIValue(content string, section string, key string) string {
currentSection := ""
for line := range strings.SplitSeq(content, "\n") {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, ";") {
continue
}
if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") {
currentSection = strings.TrimSpace(trimmed[1 : len(trimmed)-1])
continue
}
if currentSection != section {
continue
}
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) == 2 && strings.TrimSpace(parts[0]) == key {
return strings.TrimSpace(parts[1])
}
}
return ""
}
// setINIValue 在 INI 配置中设置指定 section 下的 key 值
func (s *App) setINIValue(content string, section string, key string, value string) string {
lines := strings.Split(content, "\n")
result := make([]string, 0, len(lines))
currentSection := ""
found := false
lastSectionLine := -1 // 目标 section 的最后一行索引
for i, line := range lines {
trimmed := strings.TrimSpace(line)
// 检测 section 头
if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") {
// 如果离开目标 section 且未找到 key,在 section 末尾插入
if currentSection == section && !found && lastSectionLine >= 0 {
found = true
if value != "" {
// 在 section 末尾插入新行
insertIdx := lastSectionLine + 1
newLine := key + " = " + value
result = append(result[:insertIdx+1], append([]string{newLine}, result[insertIdx+1:]...)...)
}
}
currentSection = strings.TrimSpace(trimmed[1 : len(trimmed)-1])
}
if currentSection == section {
lastSectionLine = len(result)
}
// 在目标 section 内匹配 key
if currentSection == section && !found {
checkLine := trimmed
commented := false
if strings.HasPrefix(checkLine, ";") {
checkLine = strings.TrimSpace(checkLine[1:])
commented = true
} else if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
commented = true
}
parts := strings.SplitN(checkLine, "=", 2)
if len(parts) == 2 && strings.TrimSpace(parts[0]) == key {
found = true
if value == "" {
// 值为空时注释掉
if !commented {
result = append(result, ";"+line)
} else {
result = append(result, line)
}
} else {
result = append(result, key+" = "+value)
}
_ = i
continue
}
}
result = append(result, line)
}
// 如果在最后一个 section 中未找到 key
if currentSection == section && !found {
found = true
if value != "" {
result = append(result, key+" = "+value)
}
}
// section 不存在,在文件末尾追加
if !found && value != "" {
result = append(result, "")
result = append(result, "["+section+"]")
result = append(result, key+" = "+value)
}
return strings.Join(result, "\n")
}
// datasourcePath 返回 provisioning 数据源文件路径
func (s *App) datasourcePath() string {
return fmt.Sprintf("%s/server/grafana/conf/provisioning/datasources/panel.yml", app.Root)
}
// readDatasources 读取 provisioning 文件
func (s *App) readDatasources() map[string]any {
raw, _ := io.Read(s.datasourcePath())
if raw == "" {
return map[string]any{
"apiVersion": 1,
"datasources": []any{},
}
}
var cfg map[string]any
if err := yaml.Unmarshal([]byte(raw), &cfg); err != nil {
return map[string]any{
"apiVersion": 1,
"datasources": []any{},
}
}
return cfg
}
// writeDatasources 写入 provisioning 文件并重启 Grafana
func (s *App) writeDatasources(cfg map[string]any) error {
data, err := yaml.Marshal(cfg)
if err != nil {
return err
}
if err = io.Write(s.datasourcePath(), string(data), 0644); err != nil {
return err
}
return systemctl.Restart("grafana")
}
// getDatasourceList 从 cfg 中提取 datasources 切片
func (s *App) getDatasourceList(cfg map[string]any) []any {
ds, ok := cfg["datasources"].([]any)
if !ok {
return []any{}
}
return ds
}
// buildDatasourceMap 从请求构建单条 datasource map
func (s *App) buildDatasourceMap(req *DataSource) map[string]any {
ds := map[string]any{
"name": req.Name,
"type": req.Type,
"access": req.Access,
"url": req.URL,
"isDefault": req.IsDefault,
"editable": true,
}
if req.Access == "" {
ds["access"] = "proxy"
}
switch req.Type {
case "mysql", "postgres", "influxdb", "mssql":
if req.Database != "" {
ds["database"] = req.Database
}
if req.User != "" {
ds["user"] = req.User
}
if req.Password != "" {
ds["secureJsonData"] = map[string]any{"password": req.Password}
}
}
return ds
}
// clearDefault 清除所有数据源的默认标记
func (s *App) clearDefault(cfg map[string]any) {
lo.ForEach(s.getDatasourceList(cfg), func(item any, _ int) {
if ds, ok := item.(map[string]any); ok {
ds["isDefault"] = false
}
})
}
// addDeleteEntry 向 deleteDatasources 添加条目
func (s *App) addDeleteEntry(cfg map[string]any, name string) {
delList, _ := cfg["deleteDatasources"].([]any)
delList = append(delList, map[string]any{"name": name, "orgId": 1})
cfg["deleteDatasources"] = delList
}
+48
View File
@@ -0,0 +1,48 @@
package grafana
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune Grafana 配置调整
type ConfigTune struct {
// [server]
HTTPPort string `form:"http_port" json:"http_port"`
Domain string `form:"domain" json:"domain"`
RootURL string `form:"root_url" json:"root_url"`
Protocol string `form:"protocol" json:"protocol"`
// [database]
DBType string `form:"db_type" json:"db_type"`
DBHost string `form:"db_host" json:"db_host"`
DBName string `form:"db_name" json:"db_name"`
DBUser string `form:"db_user" json:"db_user"`
DBPassword string `form:"db_password" json:"db_password"`
// [security]
AdminUser string `form:"admin_user" json:"admin_user"`
AdminPassword string `form:"admin_password" json:"admin_password"`
// [users]
AllowSignUp string `form:"allow_sign_up" json:"allow_sign_up"`
AutoAssignOrgRole string `form:"auto_assign_org_role" json:"auto_assign_org_role"`
// [smtp]
SMTPEnabled string `form:"smtp_enabled" json:"smtp_enabled"`
SMTPHost string `form:"smtp_host" json:"smtp_host"`
SMTPUser string `form:"smtp_user" json:"smtp_user"`
SMTPPassword string `form:"smtp_password" json:"smtp_password"`
SMTPFromAddress string `form:"smtp_from_address" json:"smtp_from_address"`
// [log]
LogMode string `form:"log_mode" json:"log_mode"`
LogLevel string `form:"log_level" json:"log_level"`
}
// DataSource 数据源
type DataSource struct {
Name string `form:"name" json:"name" validate:"required"`
Type string `form:"type" json:"type" validate:"required|in:prometheus,mysql,postgres,influxdb,loki,elasticsearch,tempo,jaeger,zipkin,graphite,alertmanager,opentsdb,mssql,testdata,grafana-pyroscope-datasource,grafana-opensearch-datasource"`
URL string `form:"url" json:"url" validate:"required"`
Access string `form:"access" json:"access"`
IsDefault bool `form:"is_default" json:"is_default"`
// 数据库类型专用
Database string `form:"database" json:"database"`
User string `form:"user" json:"user"`
Password string `form:"password" json:"password"`
}
+253
View File
@@ -0,0 +1,253 @@
package kafka
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
}
func NewApp(t *gotext.Locale) *App {
return &App{t: t}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("kafka")
return types.AggregateAppStatus(ok)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("kafka")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get kafka status: %v", err))
return
}
if !status {
service.Success(w, []types.NV{})
return
}
config, _ := io.Read(s.configPath())
data := []types.NV{
{Name: s.t.Get("Node ID"), Value: s.getPropertiesValue(config, "node.id")},
{Name: s.t.Get("Listeners"), Value: s.getPropertiesValue(config, "listeners")},
{Name: s.t.Get("Log Dirs"), Value: s.getPropertiesValue(config, "log.dirs")},
{Name: s.t.Get("Num Partitions"), Value: s.getPropertiesValue(config, "num.partitions")},
{Name: s.t.Get("Log Retention Hours"), Value: s.getPropertiesValue(config, "log.retention.hours")},
{Name: s.t.Get("Log Segment Bytes"), Value: s.getPropertiesValue(config, "log.segment.bytes")},
}
service.Success(w, data)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(s.configPath())
service.Success(w, conf)
}
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("kafka"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取 Kafka 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, _ := io.Read(s.configPath())
heapRaw, _ := io.Read(s.heapEnvPath())
heapInit, heapMax := s.parseHeapEnv(heapRaw)
tune := ConfigTune{
NodeID: s.getPropertiesValue(config, "node.id"),
Listeners: s.getPropertiesValue(config, "listeners"),
LogDirs: s.getPropertiesValue(config, "log.dirs"),
NumPartitions: s.getPropertiesValue(config, "num.partitions"),
RetentionHours: s.getPropertiesValue(config, "log.retention.hours"),
LogSegmentBytes: s.getPropertiesValue(config, "log.segment.bytes"),
HeapInitSize: heapInit,
HeapMaxSize: heapMax,
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Kafka 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
config, _ := io.Read(s.configPath())
config = s.setPropertiesValue(config, "node.id", req.NodeID)
config = s.setPropertiesValue(config, "listeners", req.Listeners)
config = s.setPropertiesValue(config, "log.dirs", req.LogDirs)
config = s.setPropertiesValue(config, "num.partitions", req.NumPartitions)
config = s.setPropertiesValue(config, "log.retention.hours", req.RetentionHours)
config = s.setPropertiesValue(config, "log.segment.bytes", req.LogSegmentBytes)
if err = io.Write(s.configPath(), config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 更新 JVM 堆内存
if req.HeapInitSize != "" || req.HeapMaxSize != "" {
heapRaw, _ := io.Read(s.heapEnvPath())
heapRaw = s.setHeapEnv(heapRaw, req.HeapInitSize, req.HeapMaxSize)
if err = io.Write(s.heapEnvPath(), heapRaw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
if err = systemctl.Restart("kafka"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// configPath 返回配置文件路径
func (s *App) configPath() string {
return fmt.Sprintf("%s/server/kafka/config/server.properties", app.Root)
}
// heapEnvPath 返回 JVM 堆内存配置文件路径
func (s *App) heapEnvPath() string {
return fmt.Sprintf("%s/server/kafka/config/heap.env", app.Root)
}
// getPropertiesValue 从 properties 内容中获取指定键的值
func (s *App) getPropertiesValue(content string, key string) string {
for line := range strings.SplitSeq(content, "\n") {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
k, v, ok := strings.Cut(trimmed, "=")
if ok && strings.TrimSpace(k) == key {
return strings.TrimSpace(v)
}
}
return ""
}
// setPropertiesValue 在 properties 内容中设置指定键的值
func (s *App) setPropertiesValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
result := make([]string, 0, len(lines))
found := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
result = append(result, line)
continue
}
checkLine := trimmed
if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
k, _, ok := strings.Cut(checkLine, "=")
if ok && strings.TrimSpace(k) == key {
if found {
continue
}
found = true
if value == "" {
if !strings.HasPrefix(trimmed, "#") {
result = append(result, "#"+trimmed)
} else {
result = append(result, line)
}
continue
}
result = append(result, key+"="+value)
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, key+"="+value)
}
return strings.Join(result, "\n")
}
// parseHeapEnv 从 heap.env 中提取堆内存配置
func (s *App) parseHeapEnv(content string) (initSize, maxSize string) {
re := regexp.MustCompile(`KAFKA_HEAP_OPTS=(.+)`)
m := re.FindStringSubmatch(content)
if len(m) != 2 {
return
}
opts := m[1]
if mi := regexp.MustCompile(`-Xms(\S+)`).FindStringSubmatch(opts); len(mi) == 2 {
initSize = mi[1]
}
if mx := regexp.MustCompile(`-Xmx(\S+)`).FindStringSubmatch(opts); len(mx) == 2 {
maxSize = mx[1]
}
return
}
// setHeapEnv 设置 heap.env 中的堆内存配置
func (s *App) setHeapEnv(content string, initSize, maxSize string) string {
// 读取已有值作为默认
oldInit, oldMax := s.parseHeapEnv(content)
if initSize == "" {
initSize = oldInit
}
if maxSize == "" {
maxSize = oldMax
}
if initSize == "" {
initSize = "1g"
}
if maxSize == "" {
maxSize = "1g"
}
return fmt.Sprintf("KAFKA_HEAP_OPTS=-Xms%s -Xmx%s\n", initSize, maxSize)
}
+20
View File
@@ -0,0 +1,20 @@
package kafka
// UpdateConfig Kafka 配置更新
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune Kafka 配置调整
type ConfigTune struct {
// Broker
NodeID string `form:"node_id" json:"node_id"` // node.id
Listeners string `form:"listeners" json:"listeners"` // listeners
LogDirs string `form:"log_dirs" json:"log_dirs"` // log.dirs
NumPartitions string `form:"num_partitions" json:"num_partitions"` // num.partitions
RetentionHours string `form:"retention_hours" json:"retention_hours"` // log.retention.hours
LogSegmentBytes string `form:"log_segment_bytes" json:"log_segment_bytes"` // log.segment.bytes
// JVM
HeapInitSize string `form:"heap_init_size" json:"heap_init_size"` // -Xms
HeapMaxSize string `form:"heap_max_size" json:"heap_max_size"` // -Xmx
}
+6 -2
View File
@@ -4,8 +4,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/apps/mysql"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/v3/internal/apps/mysql"
"github.com/acepanel/panel/v3/internal/biz"
)
type App struct {
@@ -21,3 +21,7 @@ func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.Databa
func (s *App) Route(r chi.Router) {
s.mysql.Route(r)
}
func (s *App) Status() string {
return s.mysql.Status()
}
+120 -4
View File
@@ -5,14 +5,15 @@ import (
"net"
"net/http"
"regexp"
"strings"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -29,6 +30,13 @@ func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("memcached")
return types.AggregateAppStatus(ok)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
@@ -110,3 +118,111 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
service.Success(w, nil)
}
// GetConfigTune 获取 Memcached 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, err := io.Read("/etc/systemd/system/memcached.service")
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
tune := ConfigTune{
Port: s.getExecStartArg(config, "-p"),
UDPPort: s.getExecStartArg(config, "-U"),
ListenAddress: s.getExecStartArg(config, "-l"),
Memory: s.getExecStartArg(config, "-m"),
MaxConnections: s.getExecStartArg(config, "-c"),
Threads: s.getExecStartArg(config, "-t"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Memcached 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
config, err := io.Read("/etc/systemd/system/memcached.service")
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
config = s.setExecStartArg(config, "-p", req.Port)
config = s.setExecStartArg(config, "-U", req.UDPPort)
config = s.setExecStartArg(config, "-l", req.ListenAddress)
config = s.setExecStartArg(config, "-m", req.Memory)
config = s.setExecStartArg(config, "-c", req.MaxConnections)
config = s.setExecStartArg(config, "-t", req.Threads)
if err = io.Write("/etc/systemd/system/memcached.service", config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("memcached"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// getExecStartArg 从 systemd service 文件的 ExecStart 行中获取指定参数值
func (s *App) getExecStartArg(content string, flag string) string {
lines := strings.SplitSeq(content, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "ExecStart=") {
continue
}
args := strings.Fields(trimmed)
for i, arg := range args {
if arg == flag && i+1 < len(args) {
return args[i+1]
}
}
}
return ""
}
// setExecStartArg 在 systemd service 文件的 ExecStart 行中设置指定参数值
func (s *App) setExecStartArg(content string, flag string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
result := make([]string, 0, len(lines))
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "ExecStart=") {
result = append(result, line)
continue
}
args := strings.Fields(trimmed)
newArgs := make([]string, 0, len(args))
found := false
for i := 0; i < len(args); i++ {
if args[i] == flag && i+1 < len(args) {
i++ // 跳过旧值
found = true
// 值为空时删除该参数
if value != "" {
newArgs = append(newArgs, flag, value)
}
} else {
newArgs = append(newArgs, args[i])
}
}
if !found && value != "" {
newArgs = append(newArgs, flag, value)
}
result = append(result, strings.Join(newArgs, " "))
}
return strings.Join(result, "\n")
}
+10
View File
@@ -3,3 +3,13 @@ package memcached
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune Memcached 配置调整
type ConfigTune struct {
Port string `form:"port" json:"port"`
UDPPort string `form:"udp_port" json:"udp_port"`
ListenAddress string `form:"listen_address" json:"listen_address"`
Memory string `form:"memory" json:"memory"`
MaxConnections string `form:"max_connections" json:"max_connections"`
Threads string `form:"threads" json:"threads"`
}
+9 -3
View File
@@ -5,9 +5,10 @@ import (
"github.com/go-chi/chi/v5"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct{}
@@ -21,6 +22,11 @@ func (s *App) Route(r chi.Router) {
r.Post("/env", s.UpdateEnv)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("minio")
return types.AggregateAppStatus(ok)
}
func (s *App) GetEnv(w http.ResponseWriter, r *http.Request) {
env, _ := io.Read("/etc/default/minio")
service.Success(w, env)
+279
View File
@@ -0,0 +1,279 @@
package mongodb
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"go.yaml.in/yaml/v4"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
settingRepo biz.SettingRepo
databaseServerRepo biz.DatabaseServerRepo
}
func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.DatabaseServerRepo) *App {
return &App{
t: t,
settingRepo: setting,
databaseServerRepo: databaseServer,
}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
r.Get("/admin_password", s.GetAdminPassword)
r.Post("/admin_password", s.SetAdminPassword)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("mongod")
return types.AggregateAppStatus(ok)
}
// Load 获取 MongoDB 运行状态
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, _ := systemctl.Status("mongod")
if !status {
service.Success(w, []types.NV{})
return
}
password, _ := s.settingRepo.Get(biz.SettingKeyMongoDBAdminPassword)
raw, err := shell.Execf(`mongosh --quiet --eval "JSON.stringify(db.serverStatus())" -u admin -p '%s' --authenticationDatabase admin 2>/dev/null`, password)
if err != nil {
service.Success(w, []types.NV{})
return
}
var status2 struct {
Uptime int `json:"uptime"`
Connections struct {
Current int `json:"current"`
TotalCreated int `json:"totalCreated"`
} `json:"connections"`
OpCounters struct {
Query int `json:"query"`
Insert int `json:"insert"`
Update int `json:"update"`
Delete int `json:"delete"`
} `json:"opcounters"`
Mem struct {
Resident int `json:"resident"`
} `json:"mem"`
StorageEngine struct {
Name string `json:"name"`
} `json:"storageEngine"`
Version string `json:"version"`
}
if err = json.Unmarshal([]byte(raw), &status2); err != nil {
service.Success(w, []types.NV{})
return
}
data := []types.NV{
{Name: s.t.Get("Version"), Value: status2.Version},
{Name: s.t.Get("Uptime (seconds)"), Value: cast.ToString(status2.Uptime)},
{Name: s.t.Get("Current Connections"), Value: cast.ToString(status2.Connections.Current)},
{Name: s.t.Get("Total Connections Created"), Value: cast.ToString(status2.Connections.TotalCreated)},
{Name: s.t.Get("Query Operations"), Value: cast.ToString(status2.OpCounters.Query)},
{Name: s.t.Get("Insert Operations"), Value: cast.ToString(status2.OpCounters.Insert)},
{Name: s.t.Get("Update Operations"), Value: cast.ToString(status2.OpCounters.Update)},
{Name: s.t.Get("Delete Operations"), Value: cast.ToString(status2.OpCounters.Delete)},
{Name: s.t.Get("Resident Memory (MB)"), Value: cast.ToString(status2.Mem.Resident)},
{Name: s.t.Get("Storage Engine"), Value: status2.StorageEngine.Name},
}
service.Success(w, data)
}
// GetConfig 获取配置
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(s.configPath())
service.Success(w, conf)
}
// UpdateConfig 更新配置
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("mongod"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg == nil {
cfg = make(map[string]any)
}
tune := ConfigTune{
DbPath: s.getYAMLValue(cfg, "storage.dbPath"),
CacheSizeGB: s.getYAMLValue(cfg, "storage.wiredTiger.engineConfig.cacheSizeGB"),
Port: s.getYAMLValue(cfg, "net.port"),
BindIp: s.getYAMLValue(cfg, "net.bindIp"),
SystemLogPath: s.getYAMLValue(cfg, "systemLog.path"),
Authorization: s.getYAMLValue(cfg, "security.authorization"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
raw, _ := io.Read(s.configPath())
var cfg map[string]any
if err = yaml.Unmarshal([]byte(raw), &cfg); err != nil {
cfg = make(map[string]any)
}
s.setNestedYAMLValue(cfg, "storage.dbPath", req.DbPath)
s.setNestedYAMLValue(cfg, "storage.wiredTiger.engineConfig.cacheSizeGB", req.CacheSizeGB)
s.setNestedYAMLValue(cfg, "net.port", req.Port)
s.setNestedYAMLValue(cfg, "net.bindIp", req.BindIp)
s.setNestedYAMLValue(cfg, "systemLog.path", req.SystemLogPath)
s.setNestedYAMLValue(cfg, "security.authorization", req.Authorization)
data, err := yaml.Marshal(cfg)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Write(s.configPath(), string(data), 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("mongod"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetAdminPassword 获取 admin 密码
func (s *App) GetAdminPassword(w http.ResponseWriter, r *http.Request) {
password, err := s.settingRepo.Get(biz.SettingKeyMongoDBAdminPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get MongoDB admin password: %v", err))
return
}
service.Success(w, password)
}
// SetAdminPassword 设置 admin 密码
func (s *App) SetAdminPassword(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[SetAdminPassword](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
oldPassword, _ := s.settingRepo.Get(biz.SettingKeyMongoDBAdminPassword)
// 尝试用旧密码连接修改
_, err = shell.Execf(`mongosh --quiet -u admin -p '%s' --authenticationDatabase admin --eval "db.changeUserPassword('admin', '%s')"`, oldPassword, req.Password)
if err != nil {
// 回退:停止服务,无认证模式修改
_ = systemctl.Stop("mongod")
_, _ = shell.Execf(`su -s /bin/bash mongod -c "mongod --config %s --noauth --fork --logpath /tmp/mongod_reset.log"`, s.configPath())
_, resetErr := shell.Execf(`mongosh --quiet --eval "db.getSiblingDB('admin').changeUserPassword('admin', '%s')"`, req.Password)
_, _ = shell.Execf(`su -s /bin/bash mongod -c "mongod --config %s --shutdown" 2>/dev/null; pkill -f 'mongod --config.*--noauth' 2>/dev/null`, s.configPath())
_ = systemctl.Start("mongod")
if resetErr != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set MongoDB admin password: %v", resetErr))
return
}
}
if err = s.settingRepo.Set(biz.SettingKeyMongoDBAdminPassword, req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to save MongoDB admin password: %v", err))
return
}
_ = s.databaseServerRepo.UpdatePassword("local_mongodb", req.Password)
service.Success(w, nil)
}
// configPath 返回配置文件路径
func (s *App) configPath() string {
return fmt.Sprintf("%s/server/mongodb/mongod.conf", app.Root)
}
// getYAMLValue 获取嵌套 YAML 值,支持 dot notation
func (s *App) getYAMLValue(cfg map[string]any, key string) string {
parts := strings.SplitN(key, ".", 2)
val, ok := cfg[parts[0]]
if !ok {
return ""
}
if len(parts) == 1 {
return cast.ToString(val)
}
nested, ok := val.(map[string]any)
if !ok {
return ""
}
return s.getYAMLValue(nested, parts[1])
}
// setNestedYAMLValue 设置嵌套 YAML 值,逐层创建 map
func (s *App) setNestedYAMLValue(cfg map[string]any, key string, value string) {
if value == "" {
return
}
parts := strings.SplitN(key, ".", 2)
if len(parts) == 1 {
cfg[parts[0]] = value
return
}
nested, ok := cfg[parts[0]].(map[string]any)
if !ok {
nested = make(map[string]any)
cfg[parts[0]] = nested
}
s.setNestedYAMLValue(nested, parts[1], value)
}
+25
View File
@@ -0,0 +1,25 @@
package mongodb
// UpdateConfig 更新配置
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// SetAdminPassword 设置 admin 密码
type SetAdminPassword struct {
Password string `form:"password" json:"password" validate:"required|password"`
}
// ConfigTune MongoDB 配置调整
type ConfigTune struct {
// 存储
DbPath string `form:"db_path" json:"db_path"`
CacheSizeGB string `form:"cache_size_gb" json:"cache_size_gb"`
// 网络
Port string `form:"port" json:"port"`
BindIp string `form:"bind_ip" json:"bind_ip"`
// 日志
SystemLogPath string `form:"system_log_path" json:"system_log_path"`
// 安全
Authorization string `form:"authorization" json:"authorization"`
}
+189 -31
View File
@@ -5,20 +5,21 @@ import (
"net/http"
"os"
"regexp"
"strings"
"github.com/acepanel/panel/pkg/types"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/db"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/pkg/tools"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/db"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/tools"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -39,11 +40,16 @@ func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Post("/clear_log", s.ClearLog)
r.Get("/slow_log", s.SlowLog)
r.Post("/clear_slow_log", s.ClearSlowLog)
r.Get("/root_password", s.GetRootPassword)
r.Post("/root_password", s.SetRootPassword)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("mysqld")
return types.AggregateAppStatus(ok)
}
// GetConfig 获取配置
@@ -191,31 +197,11 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
service.Success(w, load)
}
// ClearLog 清空日志
func (s *App) ClearLog(w http.ResponseWriter, r *http.Request) {
if err := systemctl.LogClear("mysqld"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// SlowLog 获取慢查询日志
func (s *App) SlowLog(w http.ResponseWriter, r *http.Request) {
service.Success(w, fmt.Sprintf("%s/server/mysql/mysql-slow.log", app.Root))
}
// ClearSlowLog 清空慢查询日志
func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
if _, err := shell.Execf("cat /dev/null > %s/server/mysql/mysql-slow.log", app.Root); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetRootPassword 获取root密码
func (s *App) GetRootPassword(w http.ResponseWriter, r *http.Request) {
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
@@ -284,3 +270,175 @@ func (s *App) getSock() string {
return "/tmp/mysql.sock"
}
// GetConfigTune 获取 MySQL 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(app.Root + "/server/mysql/conf/my.cnf")
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
tune := ConfigTune{
// 常规设置
Port: s.getINIValue(config, "port"),
MaxConnections: s.getINIValue(config, "max_connections"),
MaxConnectErrors: s.getINIValue(config, "max_connect_errors"),
DefaultStorageEngine: s.getINIValue(config, "default_storage_engine"),
TableOpenCache: s.getINIValue(config, "table_open_cache"),
MaxAllowedPacket: s.getINIValue(config, "max_allowed_packet"),
OpenFilesLimit: s.getINIValue(config, "open_files_limit"),
// 性能调整
KeyBufferSize: s.getINIValue(config, "key_buffer_size"),
SortBufferSize: s.getINIValue(config, "sort_buffer_size"),
ReadBufferSize: s.getINIValue(config, "read_buffer_size"),
ReadRndBufferSize: s.getINIValue(config, "read_rnd_buffer_size"),
JoinBufferSize: s.getINIValue(config, "join_buffer_size"),
ThreadCacheSize: s.getINIValue(config, "thread_cache_size"),
ThreadStack: s.getINIValue(config, "thread_stack"),
TmpTableSize: s.getINIValue(config, "tmp_table_size"),
MaxHeapTableSize: s.getINIValue(config, "max_heap_table_size"),
MyisamSortBufferSize: s.getINIValue(config, "myisam_sort_buffer_size"),
// InnoDB
InnodbBufferPoolSize: s.getINIValue(config, "innodb_buffer_pool_size"),
InnodbLogBufferSize: s.getINIValue(config, "innodb_log_buffer_size"),
InnodbFlushLogAtTrxCommit: s.getINIValue(config, "innodb_flush_log_at_trx_commit"),
InnodbLockWaitTimeout: s.getINIValue(config, "innodb_lock_wait_timeout"),
InnodbMaxDirtyPagesPct: s.getINIValue(config, "innodb_max_dirty_pages_pct"),
InnodbReadIoThreads: s.getINIValue(config, "innodb_read_io_threads"),
InnodbWriteIoThreads: s.getINIValue(config, "innodb_write_io_threads"),
// 日志
SlowQueryLog: s.getINIValue(config, "slow_query_log"),
LongQueryTime: s.getINIValue(config, "long_query_time"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 MySQL 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
confPath := app.Root + "/server/mysql/conf/my.cnf"
config, err := io.Read(confPath)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 更新常规设置
config = s.setINIValue(config, "port", req.Port)
config = s.setINIValue(config, "max_connections", req.MaxConnections)
config = s.setINIValue(config, "max_connect_errors", req.MaxConnectErrors)
config = s.setINIValue(config, "default_storage_engine", req.DefaultStorageEngine)
config = s.setINIValue(config, "table_open_cache", req.TableOpenCache)
config = s.setINIValue(config, "max_allowed_packet", req.MaxAllowedPacket)
config = s.setINIValue(config, "open_files_limit", req.OpenFilesLimit)
// 更新性能调整
config = s.setINIValue(config, "key_buffer_size", req.KeyBufferSize)
config = s.setINIValue(config, "sort_buffer_size", req.SortBufferSize)
config = s.setINIValue(config, "read_buffer_size", req.ReadBufferSize)
config = s.setINIValue(config, "read_rnd_buffer_size", req.ReadRndBufferSize)
config = s.setINIValue(config, "join_buffer_size", req.JoinBufferSize)
config = s.setINIValue(config, "thread_cache_size", req.ThreadCacheSize)
config = s.setINIValue(config, "thread_stack", req.ThreadStack)
config = s.setINIValue(config, "tmp_table_size", req.TmpTableSize)
config = s.setINIValue(config, "max_heap_table_size", req.MaxHeapTableSize)
config = s.setINIValue(config, "myisam_sort_buffer_size", req.MyisamSortBufferSize)
// 更新 InnoDB
config = s.setINIValue(config, "innodb_buffer_pool_size", req.InnodbBufferPoolSize)
config = s.setINIValue(config, "innodb_log_buffer_size", req.InnodbLogBufferSize)
config = s.setINIValue(config, "innodb_flush_log_at_trx_commit", req.InnodbFlushLogAtTrxCommit)
config = s.setINIValue(config, "innodb_lock_wait_timeout", req.InnodbLockWaitTimeout)
config = s.setINIValue(config, "innodb_max_dirty_pages_pct", req.InnodbMaxDirtyPagesPct)
config = s.setINIValue(config, "innodb_read_io_threads", req.InnodbReadIoThreads)
config = s.setINIValue(config, "innodb_write_io_threads", req.InnodbWriteIoThreads)
// 更新日志
config = s.setINIValue(config, "slow_query_log", req.SlowQueryLog)
config = s.setINIValue(config, "long_query_time", req.LongQueryTime)
if err = io.Write(confPath, config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// getINIValue 从 INI 格式内容中获取指定键的值
func (s *App) getINIValue(content string, key string) string {
lines := strings.SplitSeq(content, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, ";") || strings.HasPrefix(trimmed, "#") {
continue
}
if strings.HasPrefix(trimmed, "[") {
continue
}
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) != 2 {
continue
}
k := strings.TrimSpace(parts[0])
if k == key {
return strings.TrimSpace(parts[1])
}
}
return ""
}
// setINIValue 在 INI 格式内容中设置指定键的值
func (s *App) setINIValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
found := false
result := make([]string, 0, len(lines))
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "[") {
result = append(result, line)
continue
}
checkLine := trimmed
if strings.HasPrefix(checkLine, ";") {
checkLine = strings.TrimSpace(checkLine[1:])
} else if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
parts := strings.SplitN(checkLine, "=", 2)
if len(parts) != 2 {
result = append(result, line)
continue
}
k := strings.TrimSpace(parts[0])
if k == key {
if found {
continue
}
found = true
// 值为空时注释掉该配置项
if value == "" {
if !strings.HasPrefix(trimmed, ";") && !strings.HasPrefix(trimmed, "#") {
result = append(result, "#"+line)
} else {
result = append(result, line)
}
continue
}
result = append(result, key+" = "+value)
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, key+" = "+value)
}
return strings.Join(result, "\n")
}
+34
View File
@@ -7,3 +7,37 @@ type UpdateConfig struct {
type SetRootPassword struct {
Password string `form:"password" json:"password" validate:"required|password"`
}
// ConfigTune MySQL 配置调整
type ConfigTune struct {
// 常规设置
Port string `form:"port" json:"port"`
MaxConnections string `form:"max_connections" json:"max_connections"`
MaxConnectErrors string `form:"max_connect_errors" json:"max_connect_errors"`
DefaultStorageEngine string `form:"default_storage_engine" json:"default_storage_engine"`
TableOpenCache string `form:"table_open_cache" json:"table_open_cache"`
MaxAllowedPacket string `form:"max_allowed_packet" json:"max_allowed_packet"`
OpenFilesLimit string `form:"open_files_limit" json:"open_files_limit"`
// 性能调整
KeyBufferSize string `form:"key_buffer_size" json:"key_buffer_size"`
SortBufferSize string `form:"sort_buffer_size" json:"sort_buffer_size"`
ReadBufferSize string `form:"read_buffer_size" json:"read_buffer_size"`
ReadRndBufferSize string `form:"read_rnd_buffer_size" json:"read_rnd_buffer_size"`
JoinBufferSize string `form:"join_buffer_size" json:"join_buffer_size"`
ThreadCacheSize string `form:"thread_cache_size" json:"thread_cache_size"`
ThreadStack string `form:"thread_stack" json:"thread_stack"`
TmpTableSize string `form:"tmp_table_size" json:"tmp_table_size"`
MaxHeapTableSize string `form:"max_heap_table_size" json:"max_heap_table_size"`
MyisamSortBufferSize string `form:"myisam_sort_buffer_size" json:"myisam_sort_buffer_size"`
// InnoDB
InnodbBufferPoolSize string `form:"innodb_buffer_pool_size" json:"innodb_buffer_pool_size"`
InnodbLogBufferSize string `form:"innodb_log_buffer_size" json:"innodb_log_buffer_size"`
InnodbFlushLogAtTrxCommit string `form:"innodb_flush_log_at_trx_commit" json:"innodb_flush_log_at_trx_commit"`
InnodbLockWaitTimeout string `form:"innodb_lock_wait_timeout" json:"innodb_lock_wait_timeout"`
InnodbMaxDirtyPagesPct string `form:"innodb_max_dirty_pages_pct" json:"innodb_max_dirty_pages_pct"`
InnodbReadIoThreads string `form:"innodb_read_io_threads" json:"innodb_read_io_threads"`
InnodbWriteIoThreads string `form:"innodb_write_io_threads" json:"innodb_write_io_threads"`
// 日志
SlowQueryLog string `form:"slow_query_log" json:"slow_query_log"`
LongQueryTime string `form:"long_query_time" json:"long_query_time"`
}
+194 -8
View File
@@ -4,20 +4,21 @@ import (
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-resty/resty/v2"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"resty.dev/v3"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/pkg/tools"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/tools"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -36,6 +37,8 @@ func (s *App) Route(r chi.Router) {
r.Post("/config", s.SaveConfig)
r.Get("/error_log", s.ErrorLog)
r.Post("/clear_error_log", s.ClearErrorLog)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
r.Get("/stream/servers", s.ListStreamServers)
r.Post("/stream/servers", s.CreateStreamServer)
@@ -47,6 +50,11 @@ func (s *App) Route(r chi.Router) {
r.Delete("/stream/upstreams/{name}", s.DeleteStreamUpstream)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("nginx")
return types.AggregateAppStatus(ok)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root))
if err != nil {
@@ -93,6 +101,7 @@ func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
client := resty.New().SetTimeout(10 * time.Second)
defer func(client *resty.Client) { _ = client.Close() }(client)
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
if err != nil || !resp.IsSuccess() {
service.Success(w, []types.NV{})
@@ -165,3 +174,180 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
service.Success(w, data)
}
// GetConfigTune 获取 Nginx 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
tune := ConfigTune{
// 常规设置
WorkerProcesses: s.getNginxValue(config, "worker_processes"),
WorkerConnections: s.getNginxValue(config, "worker_connections"),
KeepaliveTimeout: s.getNginxValue(config, "keepalive_timeout"),
ClientMaxBodySize: s.getNginxValue(config, "client_max_body_size"),
ClientBodyBufferSize: s.getNginxValue(config, "client_body_buffer_size"),
ClientHeaderBufferSize: s.getNginxValue(config, "client_header_buffer_size"),
ServerNamesHashBucketSize: s.getNginxValue(config, "server_names_hash_bucket_size"),
ServerTokens: s.getNginxValue(config, "server_tokens"),
// Gzip 压缩
Gzip: s.getNginxValue(config, "gzip"),
GzipMinLength: s.getNginxValue(config, "gzip_min_length"),
GzipCompLevel: s.getNginxValue(config, "gzip_comp_level"),
GzipTypes: s.getNginxValue(config, "gzip_types"),
GzipVary: s.getNginxValue(config, "gzip_vary"),
GzipProxied: s.getNginxValue(config, "gzip_proxied"),
// Brotli 压缩
Brotli: s.getNginxValue(config, "brotli"),
BrotliMinLength: s.getNginxValue(config, "brotli_min_length"),
BrotliCompLevel: s.getNginxValue(config, "brotli_comp_level"),
BrotliTypes: s.getNginxValue(config, "brotli_types"),
BrotliStatic: s.getNginxValue(config, "brotli_static"),
// Zstd 压缩
Zstd: s.getNginxValue(config, "zstd"),
ZstdMinLength: s.getNginxValue(config, "zstd_min_length"),
ZstdCompLevel: s.getNginxValue(config, "zstd_comp_level"),
ZstdTypes: s.getNginxValue(config, "zstd_types"),
ZstdStatic: s.getNginxValue(config, "zstd_static"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Nginx 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
confPath := fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root)
config, err := io.Read(confPath)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 更新常规设置
config = s.setNginxValue(config, "worker_processes", req.WorkerProcesses)
config = s.setNginxValue(config, "worker_connections", req.WorkerConnections)
config = s.setNginxValue(config, "keepalive_timeout", req.KeepaliveTimeout)
config = s.setNginxValue(config, "client_max_body_size", req.ClientMaxBodySize)
config = s.setNginxValue(config, "client_body_buffer_size", req.ClientBodyBufferSize)
config = s.setNginxValue(config, "client_header_buffer_size", req.ClientHeaderBufferSize)
config = s.setNginxValue(config, "server_names_hash_bucket_size", req.ServerNamesHashBucketSize)
config = s.setNginxValue(config, "server_tokens", req.ServerTokens)
// 更新 Gzip 压缩
config = s.setNginxValue(config, "gzip", req.Gzip)
config = s.setNginxValue(config, "gzip_min_length", req.GzipMinLength)
config = s.setNginxValue(config, "gzip_comp_level", req.GzipCompLevel)
config = s.setNginxValue(config, "gzip_types", req.GzipTypes)
config = s.setNginxValue(config, "gzip_vary", req.GzipVary)
config = s.setNginxValue(config, "gzip_proxied", req.GzipProxied)
// 更新 Brotli 压缩
config = s.setNginxValue(config, "brotli", req.Brotli)
config = s.setNginxValue(config, "brotli_min_length", req.BrotliMinLength)
config = s.setNginxValue(config, "brotli_comp_level", req.BrotliCompLevel)
config = s.setNginxValue(config, "brotli_types", req.BrotliTypes)
config = s.setNginxValue(config, "brotli_static", req.BrotliStatic)
// 更新 Zstd 压缩
config = s.setNginxValue(config, "zstd", req.Zstd)
config = s.setNginxValue(config, "zstd_min_length", req.ZstdMinLength)
config = s.setNginxValue(config, "zstd_comp_level", req.ZstdCompLevel)
config = s.setNginxValue(config, "zstd_types", req.ZstdTypes)
config = s.setNginxValue(config, "zstd_static", req.ZstdStatic)
if err = io.Write(confPath, config, 0600); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
return
}
service.Success(w, nil)
}
// getNginxValue 从 Nginx 配置内容中获取指定指令的值
func (s *App) getNginxValue(content string, key string) string {
lines := strings.SplitSeq(content, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
if !strings.HasSuffix(trimmed, ";") {
continue
}
trimmed = strings.TrimSuffix(trimmed, ";")
trimmed = strings.TrimSpace(trimmed)
parts := strings.Fields(trimmed)
if len(parts) >= 2 && parts[0] == key {
return strings.Join(parts[1:], " ")
}
}
return ""
}
// setNginxValue 在 Nginx 配置内容中设置指定指令的值
func (s *App) setNginxValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
found := false
result := make([]string, 0, len(lines))
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || trimmed == "{" || trimmed == "}" {
result = append(result, line)
continue
}
// 检查指令(可能被注释)
checkLine := trimmed
if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
if !strings.HasSuffix(checkLine, ";") {
result = append(result, line)
continue
}
checkLine = strings.TrimSuffix(checkLine, ";")
checkLine = strings.TrimSpace(checkLine)
parts := strings.Fields(checkLine)
if len(parts) >= 2 && parts[0] == key {
if found {
continue
}
found = true
// 值为空时注释掉该配置项
if value == "" {
if !strings.HasPrefix(trimmed, "#") {
indent := line[:len(line)-len(strings.TrimLeft(line, " \t"))]
result = append(result, indent+"#"+strings.TrimLeft(line, " \t"))
} else {
result = append(result, line)
}
continue
}
// 保留原行缩进
indent := line[:len(line)-len(strings.TrimLeft(line, " \t"))]
result = append(result, indent+key+" "+value+";")
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, " "+key+" "+value+";")
}
return strings.Join(result, "\n")
}
+32
View File
@@ -6,6 +6,38 @@ type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune Nginx 配置调整
type ConfigTune struct {
// 常规设置
WorkerProcesses string `form:"worker_processes" json:"worker_processes"`
WorkerConnections string `form:"worker_connections" json:"worker_connections"`
KeepaliveTimeout string `form:"keepalive_timeout" json:"keepalive_timeout"`
ClientMaxBodySize string `form:"client_max_body_size" json:"client_max_body_size"`
ClientBodyBufferSize string `form:"client_body_buffer_size" json:"client_body_buffer_size"`
ClientHeaderBufferSize string `form:"client_header_buffer_size" json:"client_header_buffer_size"`
ServerNamesHashBucketSize string `form:"server_names_hash_bucket_size" json:"server_names_hash_bucket_size"`
ServerTokens string `form:"server_tokens" json:"server_tokens"`
// Gzip 压缩
Gzip string `form:"gzip" json:"gzip"`
GzipMinLength string `form:"gzip_min_length" json:"gzip_min_length"`
GzipCompLevel string `form:"gzip_comp_level" json:"gzip_comp_level"`
GzipTypes string `form:"gzip_types" json:"gzip_types"`
GzipVary string `form:"gzip_vary" json:"gzip_vary"`
GzipProxied string `form:"gzip_proxied" json:"gzip_proxied"`
// Brotli 压缩
Brotli string `form:"brotli" json:"brotli"`
BrotliMinLength string `form:"brotli_min_length" json:"brotli_min_length"`
BrotliCompLevel string `form:"brotli_comp_level" json:"brotli_comp_level"`
BrotliTypes string `form:"brotli_types" json:"brotli_types"`
BrotliStatic string `form:"brotli_static" json:"brotli_static"`
// Zstd 压缩
Zstd string `form:"zstd" json:"zstd"`
ZstdMinLength string `form:"zstd_min_length" json:"zstd_min_length"`
ZstdCompLevel string `form:"zstd_comp_level" json:"zstd_comp_level"`
ZstdTypes string `form:"zstd_types" json:"zstd_types"`
ZstdStatic string `form:"zstd_static" json:"zstd_static"`
}
type StreamServer struct {
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"` // 配置名称,用于文件命名
Listen string `form:"listen" json:"listen" validate:"required"` // 监听地址,如: "12345", "0.0.0.0:12345", "[::]:12345"
+12 -14
View File
@@ -10,11 +10,12 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/samber/lo"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/systemctl"
webserverNginx "github.com/acepanel/panel/pkg/webserver/nginx"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/systemctl"
webserverNginx "github.com/acepanel/panel/v3/pkg/webserver/nginx"
)
// ListStreamServers 获取 Stream Server 列表
@@ -547,34 +548,31 @@ func (s *App) saveStreamServerConfig(filePath string, server *StreamServer) erro
// saveStreamUpstreamConfig 生成并保存 Stream Upstream 配置
func (s *App) saveStreamUpstreamConfig(filePath string, upstream *StreamUpstream) error {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("upstream %s {\n", upstream.Name))
_, _ = fmt.Fprintf(&sb, "upstream %s {\n", upstream.Name)
// 负载均衡算法
if upstream.Algo != "" {
sb.WriteString(fmt.Sprintf(" %s;\n", upstream.Algo))
_, _ = fmt.Fprintf(&sb, " %s;\n", upstream.Algo)
}
// resolver 配置
if len(upstream.Resolver) > 0 {
sb.WriteString(fmt.Sprintf(" resolver %s;\n", strings.Join(upstream.Resolver, " ")))
_, _ = fmt.Fprintf(&sb, " resolver %s;\n", strings.Join(upstream.Resolver, " "))
if upstream.ResolverTimeout > 0 {
sb.WriteString(fmt.Sprintf(" resolver_timeout %s;\n", formatNginxDuration(upstream.ResolverTimeout)))
_, _ = fmt.Fprintf(&sb, " resolver_timeout %s;\n", formatNginxDuration(upstream.ResolverTimeout))
}
}
// 服务器列表
var addrs []string
for addr := range upstream.Servers {
addrs = append(addrs, addr)
}
addrs := lo.Keys(upstream.Servers)
sort.Strings(addrs)
for _, addr := range addrs {
options := upstream.Servers[addr]
if options != "" {
sb.WriteString(fmt.Sprintf(" server %s %s;\n", addr, options))
_, _ = fmt.Fprintf(&sb, " server %s %s;\n", addr, options)
} else {
sb.WriteString(fmt.Sprintf(" server %s;\n", addr))
_, _ = fmt.Fprintf(&sb, " server %s;\n", addr)
}
}
+8 -1
View File
@@ -4,7 +4,9 @@ import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/apps/nginx"
"github.com/acepanel/panel/v3/internal/apps/nginx"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -20,3 +22,8 @@ func NewApp(t *gotext.Locale) *App {
func (s *App) Route(r chi.Router) {
s.nginx.Route(r)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("openresty")
return types.AggregateAppStatus(ok)
}
+285
View File
@@ -0,0 +1,285 @@
package opensearch
import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"go.yaml.in/yaml/v4"
"resty.dev/v3"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
}
func NewApp(t *gotext.Locale) *App {
return &App{t: t}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("opensearch")
return types.AggregateAppStatus(ok)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("opensearch")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get opensearch status: %v", err))
return
}
if !status {
service.Success(w, []types.NV{})
return
}
port := s.getPort()
client := resty.New().SetTimeout(10 * time.Second)
defer func(client *resty.Client) { _ = client.Close() }(client)
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1:%s/_cluster/health", port))
if err != nil || !resp.IsSuccess() {
service.Success(w, []types.NV{})
return
}
var health struct {
ClusterName string `json:"cluster_name"`
Status string `json:"status"`
NumberOfNodes int `json:"number_of_nodes"`
NumberOfDataNodes int `json:"number_of_data_nodes"`
ActiveShards int `json:"active_shards"`
ActivePrimaryShards int `json:"active_primary_shards"`
RelocatingShards int `json:"relocating_shards"`
UnassignedShards int `json:"unassigned_shards"`
}
if err = json.Unmarshal(resp.Bytes(), &health); err != nil {
service.Success(w, []types.NV{})
return
}
data := []types.NV{
{Name: s.t.Get("Cluster Name"), Value: health.ClusterName},
{Name: s.t.Get("Cluster Status"), Value: health.Status},
{Name: s.t.Get("Number of Nodes"), Value: cast.ToString(health.NumberOfNodes)},
{Name: s.t.Get("Number of Data Nodes"), Value: cast.ToString(health.NumberOfDataNodes)},
{Name: s.t.Get("Active Shards"), Value: cast.ToString(health.ActiveShards)},
{Name: s.t.Get("Active Primary Shards"), Value: cast.ToString(health.ActivePrimaryShards)},
{Name: s.t.Get("Relocating Shards"), Value: cast.ToString(health.RelocatingShards)},
{Name: s.t.Get("Unassigned Shards"), Value: cast.ToString(health.UnassignedShards)},
}
service.Success(w, data)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(s.configPath())
service.Success(w, conf)
}
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("opensearch"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取 OpenSearch 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg == nil {
cfg = make(map[string]any)
}
jvmRaw, _ := io.Read(s.jvmOptionsPath())
heapInit, heapMax := s.parseJVMHeap(jvmRaw)
tune := ConfigTune{
ClusterName: s.getYAMLValue(cfg, "cluster.name"),
NodeName: s.getYAMLValue(cfg, "node.name"),
NetworkHost: s.getYAMLValue(cfg, "network.host"),
HTTPPort: s.getYAMLValue(cfg, "http.port"),
DiscoveryType: s.getYAMLValue(cfg, "discovery.type"),
PathData: s.getYAMLValue(cfg, "path.data"),
PathLogs: s.getYAMLValue(cfg, "path.logs"),
HeapInitSize: heapInit,
HeapMaxSize: heapMax,
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 OpenSearch 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
raw, _ := io.Read(s.configPath())
var cfg map[string]any
if err = yaml.Unmarshal([]byte(raw), &cfg); err != nil {
cfg = make(map[string]any)
}
s.setYAMLValue(cfg, "cluster.name", req.ClusterName)
s.setYAMLValue(cfg, "node.name", req.NodeName)
s.setYAMLValue(cfg, "network.host", req.NetworkHost)
s.setYAMLValue(cfg, "http.port", req.HTTPPort)
s.setYAMLValue(cfg, "discovery.type", req.DiscoveryType)
s.setYAMLValue(cfg, "path.data", req.PathData)
s.setYAMLValue(cfg, "path.logs", req.PathLogs)
data, err := yaml.Marshal(cfg)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Write(s.configPath(), string(data), 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if req.HeapInitSize != "" || req.HeapMaxSize != "" {
jvmRaw, _ := io.Read(s.jvmOptionsPath())
jvmRaw = s.setJVMHeap(jvmRaw, req.HeapInitSize, req.HeapMaxSize)
if err = io.Write(s.jvmOptionsPath(), jvmRaw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
if err = systemctl.Restart("opensearch"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
func (s *App) configPath() string {
return fmt.Sprintf("%s/server/opensearch/config/opensearch.yml", app.Root)
}
func (s *App) jvmOptionsPath() string {
return fmt.Sprintf("%s/server/opensearch/config/jvm.options", app.Root)
}
func (s *App) getPort() string {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg != nil {
if v := s.getYAMLValue(cfg, "http.port"); v != "" {
return v
}
}
return "9200"
}
// getYAMLValue 获取 YAML 值,优先匹配平铺键(如 "path.data"),回退到嵌套键(如 path -> data
func (s *App) getYAMLValue(cfg map[string]any, key string) string {
// 优先匹配平铺键(安装脚本用 sed 生成的格式)
if val, ok := cfg[key]; ok {
return cast.ToString(val)
}
// 回退到嵌套键
parts := strings.SplitN(key, ".", 2)
val, ok := cfg[parts[0]]
if !ok {
return ""
}
if len(parts) == 1 {
return cast.ToString(val)
}
nested, ok := val.(map[string]any)
if !ok {
return ""
}
return s.getYAMLValue(nested, parts[1])
}
// setYAMLValue 设置 YAML 值
func (s *App) setYAMLValue(cfg map[string]any, key string, value string) {
if value == "" {
return
}
// 使用平铺键,同时清理可能存在的嵌套键
cfg[key] = value
parts := strings.SplitN(key, ".", 2)
if len(parts) == 2 {
if nested, ok := cfg[parts[0]].(map[string]any); ok {
delete(nested, parts[1])
if len(nested) == 0 {
delete(cfg, parts[0])
}
}
}
}
func (s *App) parseJVMHeap(content string) (initSize, maxSize string) {
reInit := regexp.MustCompile(`(?m)^-Xms(\S+)`)
reMax := regexp.MustCompile(`(?m)^-Xmx(\S+)`)
if m := reInit.FindStringSubmatch(content); len(m) == 2 {
initSize = m[1]
}
if m := reMax.FindStringSubmatch(content); len(m) == 2 {
maxSize = m[1]
}
return
}
func (s *App) setJVMHeap(content string, initSize, maxSize string) string {
if initSize != "" {
re := regexp.MustCompile(`(?m)^-Xms\S+`)
if re.MatchString(content) {
content = re.ReplaceAllString(content, "-Xms"+initSize)
} else {
content += "\n-Xms" + initSize
}
}
if maxSize != "" {
re := regexp.MustCompile(`(?m)^-Xmx\S+`)
if re.MatchString(content) {
content = re.ReplaceAllString(content, "-Xmx"+maxSize)
} else {
content += "\n-Xmx" + maxSize
}
}
return content
}
+21
View File
@@ -0,0 +1,21 @@
package opensearch
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune OpenSearch 配置调整
type ConfigTune struct {
// 集群
ClusterName string `form:"cluster_name" json:"cluster_name"`
NodeName string `form:"node_name" json:"node_name"`
NetworkHost string `form:"network_host" json:"network_host"`
HTTPPort string `form:"http_port" json:"http_port"`
DiscoveryType string `form:"discovery_type" json:"discovery_type"`
// 路径
PathData string `form:"path_data" json:"path_data"`
PathLogs string `form:"path_logs" json:"path_logs"`
// JVM
HeapInitSize string `form:"heap_init_size" json:"heap_init_size"`
HeapMaxSize string `form:"heap_max_size" json:"heap_max_size"`
}
+6 -2
View File
@@ -4,8 +4,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/apps/mysql"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/v3/internal/apps/mysql"
"github.com/acepanel/panel/v3/internal/biz"
)
type App struct {
@@ -21,3 +21,7 @@ func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.Databa
func (s *App) Route(r chi.Router) {
s.mysql.Route(r)
}
func (s *App) Status() string {
return s.mysql.Status()
}
+12 -7
View File
@@ -12,12 +12,13 @@ import (
"github.com/libtnb/chix"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/firewall"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/firewall"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -37,6 +38,10 @@ func (s *App) Route(r chi.Router) {
r.Post("/config", s.UpdateConfig)
}
func (s *App) Status() string {
return types.AppStatusNA
}
func (s *App) Info(w http.ResponseWriter, r *http.Request) {
files, err := os.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", app.Root))
if err != nil {
@@ -95,8 +100,8 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
Type: firewall.TypeNormal,
PortStart: req.Port,
PortEnd: req.Port,
Direction: firewall.DirectionIn,
Strategy: firewall.StrategyAccept,
Direction: firewall.DirectionIn,
}, firewall.OperationAdd)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
+9 -3
View File
@@ -5,9 +5,10 @@ import (
"github.com/go-chi/chi/v5"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct{}
@@ -23,6 +24,11 @@ func (s *App) Route(r chi.Router) {
r.Post("/storage_config", s.UpdateStorageConfig)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("podman")
return types.AggregateAppStatus(ok)
}
func (s *App) GetRegistryConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read("/etc/containers/registries.conf")
if err != nil {
+194 -18
View File
@@ -4,19 +4,20 @@ import (
"fmt"
"net/http"
"os"
"time"
"path/filepath"
"strings"
"github.com/acepanel/panel/pkg/db"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/db"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -40,9 +41,15 @@ func (s *App) Route(r chi.Router) {
r.Post("/user_config", s.UpdateUserConfig)
r.Get("/load", s.Load)
r.Get("/log", s.Log)
r.Post("/clear_log", s.ClearLog)
r.Get("/postgres_password", s.GetPostgresPassword)
r.Post("/postgres_password", s.SetPostgresPassword)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("postgresql")
return types.AggregateAppStatus(ok)
}
// GetConfig 获取配置
@@ -172,19 +179,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
service.Success(w, data)
}
// Log 获取日志
// Log 获取应用日志路径列表
func (s *App) Log(w http.ResponseWriter, r *http.Request) {
service.Success(w, fmt.Sprintf("%s/server/postgresql/logs/postgresql-%s.log", app.Root, time.Now().Format(time.DateOnly)))
}
// ClearLog 清空日志
func (s *App) ClearLog(w http.ResponseWriter, r *http.Request) {
if _, err := shell.Execf("rm -rf %s/server/postgresql/logs/postgresql-*.log", app.Root); err != nil {
paths, err := filepath.Glob(fmt.Sprintf("%s/server/postgresql/logs/postgresql-*.log", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
service.Success(w, paths)
}
// GetPostgresPassword 获取 postgres 用户密码
@@ -231,3 +234,176 @@ func (s *App) SetPostgresPassword(w http.ResponseWriter, r *http.Request) {
service.Success(w, nil)
}
// GetConfigTune 获取 PostgreSQL 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
tune := ConfigTune{
// 连接设置
ListenAddresses: s.getPGValue(config, "listen_addresses"),
Port: s.getPGValue(config, "port"),
MaxConnections: s.getPGValue(config, "max_connections"),
SuperuserReservedConnections: s.getPGValue(config, "superuser_reserved_connections"),
// 内存设置
SharedBuffers: s.getPGValue(config, "shared_buffers"),
WorkMem: s.getPGValue(config, "work_mem"),
MaintenanceWorkMem: s.getPGValue(config, "maintenance_work_mem"),
EffectiveCacheSize: s.getPGValue(config, "effective_cache_size"),
HugePages: s.getPGValue(config, "huge_pages"),
// WAL 设置
WalLevel: s.getPGValue(config, "wal_level"),
WalBuffers: s.getPGValue(config, "wal_buffers"),
MaxWalSize: s.getPGValue(config, "max_wal_size"),
MinWalSize: s.getPGValue(config, "min_wal_size"),
CheckpointCompletionTarget: s.getPGValue(config, "checkpoint_completion_target"),
// 查询优化
DefaultStatisticsTarget: s.getPGValue(config, "default_statistics_target"),
RandomPageCost: s.getPGValue(config, "random_page_cost"),
EffectiveIoConcurrency: s.getPGValue(config, "effective_io_concurrency"),
// 日志设置
LogDestination: s.getPGValue(config, "log_destination"),
LogMinDurationStatement: s.getPGValue(config, "log_min_duration_statement"),
LogTimezone: s.getPGValue(config, "log_timezone"),
// IO 设置
IoMethod: s.getPGValue(config, "io_method"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 PostgreSQL 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
confPath := fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root)
config, err := io.Read(confPath)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 更新连接设置
config = s.setPGValue(config, "listen_addresses", req.ListenAddresses)
config = s.setPGValue(config, "port", req.Port)
config = s.setPGValue(config, "max_connections", req.MaxConnections)
config = s.setPGValue(config, "superuser_reserved_connections", req.SuperuserReservedConnections)
// 更新内存设置
config = s.setPGValue(config, "shared_buffers", req.SharedBuffers)
config = s.setPGValue(config, "work_mem", req.WorkMem)
config = s.setPGValue(config, "maintenance_work_mem", req.MaintenanceWorkMem)
config = s.setPGValue(config, "effective_cache_size", req.EffectiveCacheSize)
config = s.setPGValue(config, "huge_pages", req.HugePages)
// 更新 WAL 设置
config = s.setPGValue(config, "wal_level", req.WalLevel)
config = s.setPGValue(config, "wal_buffers", req.WalBuffers)
config = s.setPGValue(config, "max_wal_size", req.MaxWalSize)
config = s.setPGValue(config, "min_wal_size", req.MinWalSize)
config = s.setPGValue(config, "checkpoint_completion_target", req.CheckpointCompletionTarget)
// 更新查询优化
config = s.setPGValue(config, "default_statistics_target", req.DefaultStatisticsTarget)
config = s.setPGValue(config, "random_page_cost", req.RandomPageCost)
config = s.setPGValue(config, "effective_io_concurrency", req.EffectiveIoConcurrency)
// 更新日志设置
config = s.setPGValue(config, "log_destination", req.LogDestination)
config = s.setPGValue(config, "log_min_duration_statement", req.LogMinDurationStatement)
config = s.setPGValue(config, "log_timezone", req.LogTimezone)
// 更新 IO 设置
config = s.setPGValue(config, "io_method", req.IoMethod)
if err = io.Write(confPath, config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Reload("postgresql"); err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err))
return
}
service.Success(w, nil)
}
// getPGValue 从 PostgreSQL 配置内容中获取指定键的值
func (s *App) getPGValue(content string, key string) string {
lines := strings.SplitSeq(content, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) != 2 {
continue
}
k := strings.TrimSpace(parts[0])
if k == key {
v := strings.TrimSpace(parts[1])
// 去除行尾注释
if idx := strings.Index(v, "#"); idx >= 0 {
v = strings.TrimSpace(v[:idx])
}
// 去除引号
v = strings.Trim(v, "'\"")
return v
}
}
return ""
}
// setPGValue 在 PostgreSQL 配置内容中设置指定键的值
func (s *App) setPGValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
found := false
result := make([]string, 0, len(lines))
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
result = append(result, line)
continue
}
checkLine := trimmed
if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
parts := strings.SplitN(checkLine, "=", 2)
if len(parts) != 2 {
result = append(result, line)
continue
}
k := strings.TrimSpace(parts[0])
if k == key {
if found {
continue
}
found = true
// 值为空时注释掉该配置项
if value == "" {
if !strings.HasPrefix(trimmed, "#") {
result = append(result, "#"+line)
} else {
result = append(result, line)
}
continue
}
result = append(result, key+" = '"+value+"'")
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, key+" = '"+value+"'")
}
return strings.Join(result, "\n")
}
+31
View File
@@ -7,3 +7,34 @@ type UpdateConfig struct {
type SetPostgresPassword struct {
Password string `form:"password" json:"password" validate:"required|password"`
}
// ConfigTune PostgreSQL 配置调整
type ConfigTune struct {
// 连接设置
ListenAddresses string `form:"listen_addresses" json:"listen_addresses"`
Port string `form:"port" json:"port"`
MaxConnections string `form:"max_connections" json:"max_connections"`
SuperuserReservedConnections string `form:"superuser_reserved_connections" json:"superuser_reserved_connections"`
// 内存设置
SharedBuffers string `form:"shared_buffers" json:"shared_buffers"`
WorkMem string `form:"work_mem" json:"work_mem"`
MaintenanceWorkMem string `form:"maintenance_work_mem" json:"maintenance_work_mem"`
EffectiveCacheSize string `form:"effective_cache_size" json:"effective_cache_size"`
HugePages string `form:"huge_pages" json:"huge_pages"`
// WAL 设置
WalLevel string `form:"wal_level" json:"wal_level"`
WalBuffers string `form:"wal_buffers" json:"wal_buffers"`
MaxWalSize string `form:"max_wal_size" json:"max_wal_size"`
MinWalSize string `form:"min_wal_size" json:"min_wal_size"`
CheckpointCompletionTarget string `form:"checkpoint_completion_target" json:"checkpoint_completion_target"`
// 查询优化
DefaultStatisticsTarget string `form:"default_statistics_target" json:"default_statistics_target"`
RandomPageCost string `form:"random_page_cost" json:"random_page_cost"`
EffectiveIoConcurrency string `form:"effective_io_concurrency" json:"effective_io_concurrency"`
// 日志设置
LogDestination string `form:"log_destination" json:"log_destination"`
LogMinDurationStatement string `form:"log_min_duration_statement" json:"log_min_duration_statement"`
LogTimezone string `form:"log_timezone" json:"log_timezone"`
// IO 设置
IoMethod string `form:"io_method" json:"io_method"`
}
+433
View File
@@ -0,0 +1,433 @@
package prometheus
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/samber/lo"
"github.com/spf13/cast"
"go.yaml.in/yaml/v4"
"resty.dev/v3"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/config"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
conf *config.Config
taskRepo biz.TaskRepo
}
func NewApp(t *gotext.Locale, conf *config.Config, taskRepo biz.TaskRepo) *App {
return &App{t: t, conf: conf, taskRepo: taskRepo}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
// Alertmanager 配置
r.Get("/alertmanager_config", s.GetAlertmanagerConfig)
r.Post("/alertmanager_config", s.UpdateAlertmanagerConfig)
// Exporters 管理
r.Get("/exporters", s.ExporterList)
r.Post("/exporters", s.InstallExporter)
r.Delete("/exporters", s.UninstallExporter)
r.Post("/exporters/{slug}/start", s.StartExporter)
r.Post("/exporters/{slug}/stop", s.StopExporter)
r.Post("/exporters/{slug}/restart", s.RestartExporter)
r.Get("/exporters/{slug}/config", s.GetExporterConfig)
r.Post("/exporters/{slug}/config", s.UpdateExporterConfig)
}
func (s *App) Status() string {
prom, _ := systemctl.Status("prometheus")
alert, _ := systemctl.Status("alertmanager")
return types.AggregateAppStatus(prom, alert)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("prometheus")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get prometheus status: %v", err))
return
}
if !status {
service.Success(w, []types.NV{})
return
}
client := resty.New().SetTimeout(10 * time.Second)
defer func(client *resty.Client) { _ = client.Close() }(client)
resp, err := client.R().Get("http://127.0.0.1:9090/api/v1/status/runtimeinfo")
if err != nil || !resp.IsSuccess() {
service.Success(w, []types.NV{})
return
}
var result struct {
Data struct {
StartTime string `json:"startTime"`
GoroutineCount int `json:"goroutineCount"`
GOMAXPROCS int `json:"GOMAXPROCS"`
StorageRetention string `json:"storageRetention"`
ReloadConfigSuccess bool `json:"reloadConfigSuccess"`
} `json:"data"`
}
if err = json.Unmarshal(resp.Bytes(), &result); err != nil {
service.Success(w, []types.NV{})
return
}
data := []types.NV{
{Name: s.t.Get("Start Time"), Value: result.Data.StartTime},
{Name: s.t.Get("Storage Retention"), Value: result.Data.StorageRetention},
{Name: s.t.Get("Goroutine Count"), Value: cast.ToString(result.Data.GoroutineCount)},
{Name: "GOMAXPROCS", Value: cast.ToString(result.Data.GOMAXPROCS)},
{Name: s.t.Get("Config Reload Success"), Value: cast.ToString(result.Data.ReloadConfigSuccess)},
}
service.Success(w, data)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(fmt.Sprintf("%s/server/prometheus/prometheus.yml", app.Root))
service.Success(w, conf)
}
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(fmt.Sprintf("%s/server/prometheus/prometheus.yml", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("prometheus"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取 Prometheus 全局配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(fmt.Sprintf("%s/server/prometheus/prometheus.yml", app.Root))
var cfg struct {
Global struct {
ScrapeInterval string `yaml:"scrape_interval"`
EvaluationInterval string `yaml:"evaluation_interval"`
ScrapeTimeout string `yaml:"scrape_timeout"`
} `yaml:"global"`
}
_ = yaml.Unmarshal([]byte(conf), &cfg)
tune := ConfigTune{
ScrapeInterval: cfg.Global.ScrapeInterval,
EvaluationInterval: cfg.Global.EvaluationInterval,
ScrapeTimeout: cfg.Global.ScrapeTimeout,
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Prometheus 全局配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
confPath := fmt.Sprintf("%s/server/prometheus/prometheus.yml", app.Root)
raw, _ := io.Read(confPath)
var cfg map[string]any
if err = yaml.Unmarshal([]byte(raw), &cfg); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
global, ok := cfg["global"].(map[string]any)
if !ok {
global = make(map[string]any)
}
if req.ScrapeInterval != "" {
global["scrape_interval"] = req.ScrapeInterval
}
if req.EvaluationInterval != "" {
global["evaluation_interval"] = req.EvaluationInterval
}
if req.ScrapeTimeout != "" {
global["scrape_timeout"] = req.ScrapeTimeout
}
cfg["global"] = global
data, err := yaml.Marshal(cfg)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Write(confPath, string(data), 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("prometheus"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetAlertmanagerConfig 获取 Alertmanager 配置
func (s *App) GetAlertmanagerConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(fmt.Sprintf("%s/server/prometheus/alertmanager/alertmanager.yml", app.Root))
service.Success(w, conf)
}
// UpdateAlertmanagerConfig 更新 Alertmanager 配置
func (s *App) UpdateAlertmanagerConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(fmt.Sprintf("%s/server/prometheus/alertmanager/alertmanager.yml", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("alertmanager"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// ExporterList 获取 Exporter 列表及状态
func (s *App) ExporterList(w http.ResponseWriter, r *http.Request) {
exporters := s.getExporters()
for i := range exporters {
exporters[i].Installed = io.Exists(fmt.Sprintf("%s/server/prometheus/exporters/%s", app.Root, exporters[i].Slug))
if exporters[i].Installed {
running, _ := systemctl.Status("prometheus-" + exporters[i].Slug)
exporters[i].Running = running
}
}
service.Success(w, exporters)
}
// InstallExporter 安装 Exporter(异步任务)
func (s *App) InstallExporter(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ExporterSlug](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.checkExporter(req.Slug) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s does not exist", req.Slug))
return
}
cmd := fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://%s/prometheus/exporters/exporter.sh' | bash -s -- 'install' '%s'`, s.conf.App.DownloadEndpoint, url.PathEscape(req.Slug))
task := new(biz.Task)
task.Name = s.t.Get("Install Prometheus exporter %s", req.Slug)
task.Status = biz.TaskStatusWaiting
task.Shell = cmd
if err = s.taskRepo.Push(task); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// UninstallExporter 卸载 Exporter(异步任务)
func (s *App) UninstallExporter(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ExporterSlug](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.checkExporter(req.Slug) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s does not exist", req.Slug))
return
}
cmd := fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://%s/prometheus/exporters/exporter.sh' | bash -s -- 'uninstall' '%s'`, s.conf.App.DownloadEndpoint, url.PathEscape(req.Slug))
task := new(biz.Task)
task.Name = s.t.Get("Uninstall Prometheus exporter %s", req.Slug)
task.Status = biz.TaskStatusWaiting
task.Shell = cmd
if err = s.taskRepo.Push(task); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// StartExporter 启动 Exporter
func (s *App) StartExporter(w http.ResponseWriter, r *http.Request) {
slug := chi.URLParam(r, "slug")
if !s.checkExporter(slug) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s does not exist", slug))
return
}
if err := systemctl.Start("prometheus-" + slug); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// StopExporter 停止 Exporter
func (s *App) StopExporter(w http.ResponseWriter, r *http.Request) {
slug := chi.URLParam(r, "slug")
if !s.checkExporter(slug) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s does not exist", slug))
return
}
if err := systemctl.Stop("prometheus-" + slug); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// RestartExporter 重启 Exporter
func (s *App) RestartExporter(w http.ResponseWriter, r *http.Request) {
slug := chi.URLParam(r, "slug")
if !s.checkExporter(slug) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s does not exist", slug))
return
}
if err := systemctl.Restart("prometheus-" + slug); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetExporterConfig 获取 Exporter 配置
func (s *App) GetExporterConfig(w http.ResponseWriter, r *http.Request) {
slug := chi.URLParam(r, "slug")
if !s.checkExporter(slug) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s does not exist", slug))
return
}
confPath := s.getExporterConfigPath(slug)
if confPath == "" {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s has no configuration file", slug))
return
}
conf, _ := io.Read(confPath)
service.Success(w, conf)
}
// UpdateExporterConfig 更新 Exporter 配置
func (s *App) UpdateExporterConfig(w http.ResponseWriter, r *http.Request) {
slug := chi.URLParam(r, "slug")
if !s.checkExporter(slug) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s does not exist", slug))
return
}
confPath := s.getExporterConfigPath(slug)
if confPath == "" {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("exporter %s has no configuration file", slug))
return
}
req, err := service.Bind[ExporterConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(confPath, req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("prometheus-" + slug); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// getExporters 返回所有 exporter 定义
func (s *App) getExporters() []Exporter {
return []Exporter{
{Name: "Node Exporter", Slug: "node_exporter", Description: s.t.Get("Hardware and OS metrics")},
{Name: "Nginx Exporter", Slug: "nginx_exporter", Description: s.t.Get("Nginx metrics")},
{Name: "MySQL Exporter", Slug: "mysqld_exporter", Description: s.t.Get("MySQL database metrics"), HasConfig: true},
{Name: "PostgreSQL Exporter", Slug: "postgres_exporter", Description: s.t.Get("PostgreSQL database metrics"), HasConfig: true},
{Name: "MongoDB Exporter", Slug: "mongodb_exporter", Description: s.t.Get("MongoDB metrics"), HasConfig: true},
{Name: "Elasticsearch Exporter", Slug: "elasticsearch_exporter", Description: s.t.Get("Elasticsearch metrics"), HasConfig: true},
{Name: "Redis Exporter", Slug: "redis_exporter", Description: s.t.Get("Redis metrics"), HasConfig: true},
{Name: "Memcached Exporter", Slug: "memcached_exporter", Description: s.t.Get("Memcached metrics")},
{Name: "Kafka Exporter", Slug: "kafka_exporter", Description: s.t.Get("Kafka metrics"), HasConfig: true},
}
}
// getExporterConfigPath 获取 exporter 配置文件路径
func (s *App) getExporterConfigPath(slug string) string {
base := fmt.Sprintf("%s/server/prometheus/exporters/%s", app.Root, slug)
switch slug {
case "redis_exporter", "postgres_exporter", "elasticsearch_exporter", "mongodb_exporter", "kafka_exporter":
return base + "/env"
case "mysqld_exporter":
return base + "/.my.cnf"
default:
return ""
}
}
// checkExporter 检查 slug 是否有效
func (s *App) checkExporter(slug string) bool {
return lo.ContainsBy(s.getExporters(), func(e Exporter) bool {
return e.Slug == slug
})
}
+32
View File
@@ -0,0 +1,32 @@
package prometheus
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune Prometheus 全局配置调整
type ConfigTune struct {
ScrapeInterval string `form:"scrape_interval" json:"scrape_interval"`
EvaluationInterval string `form:"evaluation_interval" json:"evaluation_interval"`
ScrapeTimeout string `form:"scrape_timeout" json:"scrape_timeout"`
}
// Exporter Prometheus Exporter 信息
type Exporter struct {
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description"`
Installed bool `json:"installed"`
Running bool `json:"running"`
HasConfig bool `json:"has_config"`
}
// ExporterSlug Exporter 操作请求
type ExporterSlug struct {
Slug string `form:"slug" json:"slug" validate:"required"`
}
// ExporterConfig Exporter 配置更新请求
type ExporterConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
+161 -19
View File
@@ -1,6 +1,7 @@
package pureftpd
import (
"fmt"
"net/http"
"regexp"
"strings"
@@ -8,14 +9,16 @@ import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/libtnb/chix"
"github.com/samber/lo"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/firewall"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/firewall"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -35,6 +38,13 @@ func (s *App) Route(r chi.Router) {
r.Post("/users/{username}/password", s.ChangePassword)
r.Get("/port", s.GetPort)
r.Post("/port", s.UpdatePort)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("pure-ftpd")
return types.AggregateAppStatus(ok)
}
// List 获取用户列表
@@ -47,19 +57,17 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
})
}
listArr := strings.Split(listRaw, "\n")
var users []User
for _, v := range listArr {
userRe := regexp.MustCompile(`(\S+)\s+(\S+)`)
users := lo.FilterMap(strings.Split(listRaw, "\n"), func(v string, _ int) (User, bool) {
if len(v) == 0 {
continue
return User{}, false
}
match := regexp.MustCompile(`(\S+)\s+(\S+)`).FindStringSubmatch(v)
users = append(users, User{
match := userRe.FindStringSubmatch(v)
return User{
Username: match[1],
Path: strings.Replace(match[2], "/./", "/", 1),
})
}
}, true
})
paged, total := service.Paginate(r, users)
@@ -139,13 +147,19 @@ func (s *App) ChangePassword(w http.ResponseWriter, r *http.Request) {
// GetPort 获取端口
func (s *App) GetPort(w http.ResponseWriter, r *http.Request) {
port, err := shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
config, err := io.Read(fmt.Sprintf("%s/server/pure-ftpd/etc/pure-ftpd.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get port: %v", err))
return
}
service.Success(w, cast.ToInt(port))
bind := strings.Trim(s.getFTPValue(config, "Bind"), `"'`)
port := 21 // 默认端口
if parts := strings.SplitN(bind, ",", 2); len(parts) == 2 {
port = cast.ToInt(strings.TrimSpace(parts[1]))
}
service.Success(w, port)
}
// UpdatePort 设置端口
@@ -156,7 +170,14 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
return
}
if _, err = shell.Execf(`sed -i "s/Bind.*/Bind 0.0.0.0,%d/g" %s/server/pure-ftpd/etc/pure-ftpd.conf`, req.Port, app.Root); err != nil {
confPath := fmt.Sprintf("%s/server/pure-ftpd/etc/pure-ftpd.conf", app.Root)
config, err := io.Read(confPath)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
config = s.setFTPValue(config, "Bind", fmt.Sprintf(`"0.0.0.0,%d"`, req.Port))
if err = io.Write(confPath, config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -166,8 +187,8 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
Type: firewall.TypeNormal,
PortStart: req.Port,
PortEnd: req.Port,
Direction: firewall.DirectionIn,
Strategy: firewall.StrategyAccept,
Direction: firewall.DirectionIn,
}, firewall.OperationAdd)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
@@ -181,3 +202,124 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
service.Success(w, nil)
}
// GetConfigTune 获取 Pure-FTPd 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/pure-ftpd/etc/pure-ftpd.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
tune := ConfigTune{
MaxClientsNumber: s.getFTPValue(config, "MaxClientsNumber"),
MaxClientsPerIP: s.getFTPValue(config, "MaxClientsPerIP"),
MaxIdleTime: s.getFTPValue(config, "MaxIdleTime"),
MaxLoad: s.getFTPValue(config, "MaxLoad"),
PassivePortRange: s.getFTPValue(config, "PassivePortRange"),
AnonymousOnly: s.getFTPValue(config, "AnonymousOnly"),
NoAnonymous: s.getFTPValue(config, "NoAnonymous"),
MaxDiskUsage: s.getFTPValue(config, "MaxDiskUsage"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Pure-FTPd 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
confPath := fmt.Sprintf("%s/server/pure-ftpd/etc/pure-ftpd.conf", app.Root)
config, err := io.Read(confPath)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
config = s.setFTPValue(config, "MaxClientsNumber", req.MaxClientsNumber)
config = s.setFTPValue(config, "MaxClientsPerIP", req.MaxClientsPerIP)
config = s.setFTPValue(config, "MaxIdleTime", req.MaxIdleTime)
config = s.setFTPValue(config, "MaxLoad", req.MaxLoad)
config = s.setFTPValue(config, "PassivePortRange", req.PassivePortRange)
config = s.setFTPValue(config, "AnonymousOnly", req.AnonymousOnly)
config = s.setFTPValue(config, "NoAnonymous", req.NoAnonymous)
config = s.setFTPValue(config, "MaxDiskUsage", req.MaxDiskUsage)
if err = io.Write(confPath, config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("pure-ftpd"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// getFTPValue 从 Pure-FTPd 配置内容中获取指定键的值
func (s *App) getFTPValue(content string, key string) string {
lines := strings.SplitSeq(content, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.Fields(trimmed)
if len(parts) >= 2 && parts[0] == key {
return strings.Join(parts[1:], " ")
}
}
return ""
}
// setFTPValue 在 Pure-FTPd 配置内容中设置指定键的值
func (s *App) setFTPValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
found := false
result := make([]string, 0, len(lines))
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
result = append(result, line)
continue
}
checkLine := trimmed
if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
parts := strings.Fields(checkLine)
if len(parts) >= 2 && parts[0] == key {
if found {
continue
}
found = true
// 值为空时注释掉该配置项
if value == "" {
if !strings.HasPrefix(trimmed, "#") {
result = append(result, "#"+line)
} else {
result = append(result, line)
}
continue
}
// 保留原行格式
indent := line[:len(line)-len(strings.TrimLeft(line, " \t"))]
result = append(result, indent+key+" "+value)
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, key+" "+value)
}
return strings.Join(result, "\n")
}
+12
View File
@@ -18,3 +18,15 @@ type ChangePassword struct {
type UpdatePort struct {
Port uint `form:"port" json:"port" validate:"required|number|min:1|max:65535"`
}
// ConfigTune Pure-FTPd 配置调整
type ConfigTune struct {
MaxClientsNumber string `form:"max_clients_number" json:"max_clients_number"`
MaxClientsPerIP string `form:"max_clients_per_ip" json:"max_clients_per_ip"`
MaxIdleTime string `form:"max_idle_time" json:"max_idle_time"`
MaxLoad string `form:"max_load" json:"max_load"`
PassivePortRange string `form:"passive_port_range" json:"passive_port_range"`
AnonymousOnly string `form:"anonymous_only" json:"anonymous_only"`
NoAnonymous string `form:"no_anonymous" json:"no_anonymous"`
MaxDiskUsage string `form:"max_disk_usage" json:"max_disk_usage"`
}
+152 -17
View File
@@ -8,22 +8,26 @@ import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/samber/lo"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
t *gotext.Locale
databaseServerRepo biz.DatabaseServerRepo
}
func NewApp(t *gotext.Locale) *App {
func NewApp(t *gotext.Locale, databaseServer biz.DatabaseServerRepo) *App {
return &App{
t: t,
t: t,
databaseServerRepo: databaseServer,
}
}
@@ -31,6 +35,13 @@ func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("redis")
return types.AggregateAppStatus(ok)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
@@ -51,7 +62,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
re := regexp.MustCompile(`^requirepass\s+(.+)`)
re := regexp.MustCompile(`(?m)^requirepass\s+(.+)`)
matches := re.FindStringSubmatch(config)
if len(matches) == 2 {
withPassword = " -a " + matches[1]
@@ -63,15 +74,13 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
return
}
infoLines := strings.Split(raw, "\n")
dataRaw := make(map[string]string)
for _, item := range infoLines {
dataRaw := lo.SliceToMap(strings.Split(raw, "\n"), func(item string) (string, string) {
parts := strings.Split(item, ":")
if len(parts) == 2 {
dataRaw[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
if len(parts) != 2 {
return "", ""
}
}
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
})
data := []types.NV{
{Name: s.t.Get("TCP Port"), Value: dataRaw["tcp_port"]},
@@ -121,3 +130,129 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
service.Success(w, nil)
}
// GetConfigTune 获取 Redis 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/redis/redis.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
tune := ConfigTune{
Bind: s.getRedisValue(config, "bind"),
Port: s.getRedisValue(config, "port"),
Databases: s.getRedisValue(config, "databases"),
Requirepass: s.getRedisValue(config, "requirepass"),
Timeout: s.getRedisValue(config, "timeout"),
TCPKeepalive: s.getRedisValue(config, "tcp-keepalive"),
Maxmemory: s.getRedisValue(config, "maxmemory"),
MaxmemoryPolicy: s.getRedisValue(config, "maxmemory-policy"),
Appendonly: s.getRedisValue(config, "appendonly"),
Appendfsync: s.getRedisValue(config, "appendfsync"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Redis 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
confPath := fmt.Sprintf("%s/server/redis/redis.conf", app.Root)
config, err := io.Read(confPath)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
config = s.setRedisValue(config, "bind", req.Bind)
config = s.setRedisValue(config, "port", req.Port)
config = s.setRedisValue(config, "databases", req.Databases)
config = s.setRedisValue(config, "requirepass", req.Requirepass)
config = s.setRedisValue(config, "timeout", req.Timeout)
config = s.setRedisValue(config, "tcp-keepalive", req.TCPKeepalive)
config = s.setRedisValue(config, "maxmemory", req.Maxmemory)
config = s.setRedisValue(config, "maxmemory-policy", req.MaxmemoryPolicy)
config = s.setRedisValue(config, "appendonly", req.Appendonly)
config = s.setRedisValue(config, "appendfsync", req.Appendfsync)
if err = io.Write(confPath, config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("redis"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 同步密码到数据库服务器记录
_ = s.databaseServerRepo.UpdatePassword("local_redis", req.Requirepass)
service.Success(w, nil)
}
// getRedisValue 从 Redis 配置内容中获取指定键的值
func (s *App) getRedisValue(content string, key string) string {
lines := strings.SplitSeq(content, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.Fields(trimmed)
if len(parts) >= 2 && parts[0] == key {
return strings.Join(parts[1:], " ")
}
}
return ""
}
// setRedisValue 在 Redis 配置内容中设置指定键的值
func (s *App) setRedisValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
result := make([]string, 0, len(lines))
found := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
result = append(result, line)
continue
}
checkLine := trimmed
if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
parts := strings.Fields(checkLine)
if len(parts) >= 1 && parts[0] == key {
if found {
continue
}
found = true
// 值为空时注释掉该配置项
if value == "" {
if !strings.HasPrefix(trimmed, "#") {
result = append(result, "# "+trimmed)
} else {
result = append(result, line)
}
continue
}
result = append(result, key+" "+value)
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, key+" "+value)
}
return strings.Join(result, "\n")
}
+17
View File
@@ -3,3 +3,20 @@ package redis
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune Redis 配置调整
type ConfigTune struct {
// 常规设置
Bind string `form:"bind" json:"bind"`
Port string `form:"port" json:"port"`
Databases string `form:"databases" json:"databases"`
Requirepass string `form:"requirepass" json:"requirepass"`
Timeout string `form:"timeout" json:"timeout"`
TCPKeepalive string `form:"tcp_keepalive" json:"tcp_keepalive"`
// 内存
Maxmemory string `form:"maxmemory" json:"maxmemory"`
MaxmemoryPolicy string `form:"maxmemory_policy" json:"maxmemory_policy"`
// 持久化
Appendonly string `form:"appendonly" json:"appendonly"`
Appendfsync string `form:"appendfsync" json:"appendfsync"`
}
+297
View File
@@ -0,0 +1,297 @@
package rocketmq
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
}
func NewApp(t *gotext.Locale) *App {
return &App{t: t}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
namesrv, _ := systemctl.Status("rocketmq-namesrv")
broker, _ := systemctl.Status("rocketmq-broker")
return types.AggregateAppStatus(namesrv, broker)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
namesrvStatus, _ := systemctl.Status("rocketmq-namesrv")
brokerStatus, _ := systemctl.Status("rocketmq-broker")
namesrvStr := "stopped"
if namesrvStatus {
namesrvStr = "running"
}
brokerStr := "stopped"
if brokerStatus {
brokerStr = "running"
}
if !namesrvStatus && !brokerStatus {
service.Success(w, []types.NV{})
return
}
config, _ := io.Read(s.configPath())
data := []types.NV{
{Name: s.t.Get("NameServer Status"), Value: namesrvStr},
{Name: s.t.Get("Broker Status"), Value: brokerStr},
{Name: s.t.Get("Broker Name"), Value: s.getPropertiesValue(config, "brokerName")},
{Name: s.t.Get("Listen Port"), Value: s.getPropertiesValue(config, "listenPort")},
{Name: s.t.Get("NameServer Address"), Value: s.getPropertiesValue(config, "namesrvAddr")},
{Name: s.t.Get("Broker Role"), Value: s.getPropertiesValue(config, "brokerRole")},
{Name: s.t.Get("Flush Disk Type"), Value: s.getPropertiesValue(config, "flushDiskType")},
}
service.Success(w, data)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
conf, _ := io.Read(s.configPath())
service.Success(w, conf)
}
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(s.configPath(), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = s.restartServices(); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取 RocketMQ 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, _ := io.Read(s.configPath())
heapRaw, _ := io.Read(s.heapEnvPath())
namesrvInit, namesrvMax := s.parseHeapLine(heapRaw, "ROCKETMQ_NAMESRV_HEAP")
brokerInit, brokerMax := s.parseHeapLine(heapRaw, "ROCKETMQ_BROKER_HEAP")
tune := ConfigTune{
BrokerName: s.getPropertiesValue(config, "brokerName"),
ListenPort: s.getPropertiesValue(config, "listenPort"),
NamesrvAddr: s.getPropertiesValue(config, "namesrvAddr"),
BrokerRole: s.getPropertiesValue(config, "brokerRole"),
FlushDiskType: s.getPropertiesValue(config, "flushDiskType"),
StorePathRootDir: s.getPropertiesValue(config, "storePathRootDir"),
StorePathCommitLog: s.getPropertiesValue(config, "storePathCommitLog"),
MaxMessageSize: s.getPropertiesValue(config, "maxMessageSize"),
NamesrvHeapInitSize: namesrvInit,
NamesrvHeapMaxSize: namesrvMax,
BrokerHeapInitSize: brokerInit,
BrokerHeapMaxSize: brokerMax,
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 RocketMQ 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
config, _ := io.Read(s.configPath())
config = s.setPropertiesValue(config, "brokerName", req.BrokerName)
config = s.setPropertiesValue(config, "listenPort", req.ListenPort)
config = s.setPropertiesValue(config, "namesrvAddr", req.NamesrvAddr)
config = s.setPropertiesValue(config, "brokerRole", req.BrokerRole)
config = s.setPropertiesValue(config, "flushDiskType", req.FlushDiskType)
config = s.setPropertiesValue(config, "storePathRootDir", req.StorePathRootDir)
config = s.setPropertiesValue(config, "storePathCommitLog", req.StorePathCommitLog)
config = s.setPropertiesValue(config, "maxMessageSize", req.MaxMessageSize)
if err = io.Write(s.configPath(), config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 更新 JVM 堆内存
if req.NamesrvHeapInitSize != "" || req.NamesrvHeapMaxSize != "" || req.BrokerHeapInitSize != "" || req.BrokerHeapMaxSize != "" {
heapRaw, _ := io.Read(s.heapEnvPath())
heapRaw = s.setHeapEnv(heapRaw, *req)
if err = io.Write(s.heapEnvPath(), heapRaw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
if err = s.restartServices(); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// restartServices 重启 NameServer 和 Broker 服务
func (s *App) restartServices() error {
if err := systemctl.Restart("rocketmq-namesrv"); err != nil {
return err
}
return systemctl.Restart("rocketmq-broker")
}
// configPath 返回 broker 配置文件路径
func (s *App) configPath() string {
return fmt.Sprintf("%s/server/rocketmq/conf/broker.conf", app.Root)
}
// heapEnvPath 返回 JVM 堆内存配置文件路径
func (s *App) heapEnvPath() string {
return fmt.Sprintf("%s/server/rocketmq/conf/heap.env", app.Root)
}
// getPropertiesValue 从 properties 内容中获取指定键的值
func (s *App) getPropertiesValue(content string, key string) string {
for line := range strings.SplitSeq(content, "\n") {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
k, v, ok := strings.Cut(trimmed, "=")
if ok && strings.TrimSpace(k) == key {
return strings.TrimSpace(v)
}
}
return ""
}
// setPropertiesValue 在 properties 内容中设置指定键的值
func (s *App) setPropertiesValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
result := make([]string, 0, len(lines))
found := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
result = append(result, line)
continue
}
checkLine := trimmed
if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
k, _, ok := strings.Cut(checkLine, "=")
if ok && strings.TrimSpace(k) == key {
if found {
continue
}
found = true
if value == "" {
if !strings.HasPrefix(trimmed, "#") {
result = append(result, "#"+trimmed)
} else {
result = append(result, line)
}
continue
}
result = append(result, key+"="+value)
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, key+"="+value)
}
return strings.Join(result, "\n")
}
// parseHeapLine 从 heap.env 中提取指定环境变量的堆内存配置
func (s *App) parseHeapLine(content string, envKey string) (initSize, maxSize string) {
re := regexp.MustCompile(envKey + `=(.+)`)
m := re.FindStringSubmatch(content)
if len(m) != 2 {
return
}
opts := m[1]
if mi := regexp.MustCompile(`-Xms(\S+)`).FindStringSubmatch(opts); len(mi) == 2 {
initSize = mi[1]
}
if mx := regexp.MustCompile(`-Xmx(\S+)`).FindStringSubmatch(opts); len(mx) == 2 {
maxSize = mx[1]
}
return
}
// setHeapEnv 设置 heap.env 中的堆内存配置
func (s *App) setHeapEnv(content string, req ConfigTune) string {
// 读取已有值作为默认
oldNsInit, oldNsMax := s.parseHeapLine(content, "ROCKETMQ_NAMESRV_HEAP")
oldBrInit, oldBrMax := s.parseHeapLine(content, "ROCKETMQ_BROKER_HEAP")
nsInit := req.NamesrvHeapInitSize
if nsInit == "" {
nsInit = oldNsInit
}
if nsInit == "" {
nsInit = "512m"
}
nsMax := req.NamesrvHeapMaxSize
if nsMax == "" {
nsMax = oldNsMax
}
if nsMax == "" {
nsMax = "512m"
}
brInit := req.BrokerHeapInitSize
if brInit == "" {
brInit = oldBrInit
}
if brInit == "" {
brInit = "1g"
}
brMax := req.BrokerHeapMaxSize
if brMax == "" {
brMax = oldBrMax
}
if brMax == "" {
brMax = "1g"
}
return fmt.Sprintf("ROCKETMQ_NAMESRV_HEAP=-Xms%s -Xmx%s\nROCKETMQ_BROKER_HEAP=-Xms%s -Xmx%s\n", nsInit, nsMax, brInit, brMax)
}
+26
View File
@@ -0,0 +1,26 @@
package rocketmq
// UpdateConfig RocketMQ 配置更新
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune RocketMQ 配置调整
type ConfigTune struct {
// Broker 基础
BrokerName string `form:"broker_name" json:"broker_name"` // brokerName
ListenPort string `form:"listen_port" json:"listen_port"` // listenPort
NamesrvAddr string `form:"namesrv_addr" json:"namesrv_addr"` // namesrvAddr
BrokerRole string `form:"broker_role" json:"broker_role"` // brokerRole
FlushDiskType string `form:"flush_disk_type" json:"flush_disk_type"` // flushDiskType
// 存储
StorePathRootDir string `form:"store_path_root_dir" json:"store_path_root_dir"` // storePathRootDir
StorePathCommitLog string `form:"store_path_commit_log" json:"store_path_commit_log"` // storePathCommitLog
MaxMessageSize string `form:"max_message_size" json:"max_message_size"` // maxMessageSize
// JVM - NameServer
NamesrvHeapInitSize string `form:"namesrv_heap_init_size" json:"namesrv_heap_init_size"` // -Xms (namesrv)
NamesrvHeapMaxSize string `form:"namesrv_heap_max_size" json:"namesrv_heap_max_size"` // -Xmx (namesrv)
// JVM - Broker
BrokerHeapInitSize string `form:"broker_heap_init_size" json:"broker_heap_init_size"` // -Xms (broker)
BrokerHeapMaxSize string `form:"broker_heap_max_size" json:"broker_heap_max_size"` // -Xmx (broker)
}
+10 -4
View File
@@ -11,10 +11,11 @@ import (
"github.com/libtnb/chix"
"github.com/libtnb/utils/str"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -36,6 +37,11 @@ func (s *App) Route(r chi.Router) {
r.Post("/config", s.UpdateConfig)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("rsyncd")
return types.AggregateAppStatus(ok)
}
func (s *App) List(w http.ResponseWriter, r *http.Request) {
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
+15 -17
View File
@@ -10,11 +10,13 @@ import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/libtnb/chix"
"github.com/samber/lo"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -33,6 +35,10 @@ func (s *App) Route(r chi.Router) {
r.Delete("/mounts", s.Delete)
}
func (s *App) Status() string {
return types.AppStatusNA
}
// List 所有 S3fs 挂载
func (s *App) List(w http.ResponseWriter, r *http.Request) {
list, err := s.mounts()
@@ -81,11 +87,9 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
return
}
for _, item := range list {
if item.Path == req.Path {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path already exists"))
return
}
if lo.ContainsBy(list, func(item Mount) bool { return item.Path == req.Path }) {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path already exists"))
return
}
id := time.Now().UnixMicro()
@@ -128,14 +132,8 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
return
}
var mount Mount
for _, item := range list {
if item.ID == req.ID {
mount = item
break
}
}
if mount.ID == 0 {
mount, ok := lo.Find(list, func(item Mount) bool { return item.ID == req.ID })
if !ok {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount not found"))
return
}
@@ -178,7 +176,7 @@ func (s *App) mounts() ([]Mount, error) {
if err != nil {
return nil, err
}
for _, id := range strings.Split(ids, "\n") {
for id := range strings.SplitSeq(ids, "\n") {
if id == "" {
continue
}
+68 -78
View File
@@ -9,11 +9,12 @@ import (
"github.com/libtnb/chix"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/os"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/os"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
@@ -37,7 +38,6 @@ func NewApp(t *gotext.Locale) *App {
func (s *App) Route(r chi.Router) {
r.Get("/service", s.Service)
r.Post("/clear_log", s.ClearLog)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/processes", s.Processes)
@@ -45,7 +45,6 @@ func (s *App) Route(r chi.Router) {
r.Post("/processes/{process}/stop", s.StopProcess)
r.Post("/processes/{process}/restart", s.RestartProcess)
r.Get("/processes/{process}/log", s.ProcessLog)
r.Post("/processes/{process}/clear_log", s.ClearProcessLog)
r.Get("/processes/{process}", s.ProcessConfig)
r.Post("/processes/{process}", s.UpdateProcessConfig)
r.Delete("/processes/{process}", s.DeleteProcess)
@@ -57,14 +56,9 @@ func (s *App) Service(w http.ResponseWriter, r *http.Request) {
service.Success(w, s.name)
}
// ClearLog 清空日志
func (s *App) ClearLog(w http.ResponseWriter, r *http.Request) {
if _, err := shell.Execf(`cat /dev/null > /var/log/supervisor/supervisord.log`); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
func (s *App) Status() string {
ok, _ := systemctl.Status(s.name)
return types.AggregateAppStatus(ok)
}
// GetConfig 获取配置
@@ -114,31 +108,35 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
// Processes 进程列表
func (s *App) Processes(w http.ResponseWriter, r *http.Request) {
out, err := shell.Execf(`supervisorctl status | awk '{print $1}'`)
if err != nil {
out, err := shell.Execf(`supervisorctl status`)
if err != nil && out == "" {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
var processes []Process
for _, line := range strings.Split(out, "\n") {
if len(line) == 0 {
for line := range strings.SplitSeq(out, "\n") {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
var p Process
p.Name = line
if status, err := shell.Execf(`supervisorctl status '%s' | awk '{print $2}'`, line); err == nil {
p.Status = status
p := Process{
Name: fields[0],
Status: fields[1],
Pid: "-",
Uptime: "-",
}
// RUNNING 行格式:name RUNNING pid 1234, uptime 1:23:45 (超过 1 天为 "1 day, 1:23:45"
if p.Status == "RUNNING" {
pid, _ := shell.Execf(`supervisorctl status '%s' | awk '{print $4}'`, line)
p.Pid = strings.ReplaceAll(pid, ",", "")
uptime, _ := shell.Execf(`supervisorctl status '%s' | awk '{print $6}'`, line)
p.Uptime = uptime
} else {
p.Pid = "-"
p.Uptime = "-"
if _, rest, ok := strings.Cut(line, "pid "); ok {
if pid, _, ok := strings.Cut(rest, ","); ok {
p.Pid = pid
}
}
if _, uptime, ok := strings.Cut(line, "uptime "); ok {
p.Uptime = uptime
}
}
processes = append(processes, p)
}
@@ -159,7 +157,7 @@ func (s *App) StartProcess(w http.ResponseWriter, r *http.Request) {
return
}
if out, err := shell.Execf(`supervisorctl start %s`, req.Process); err != nil {
if out, err := shell.Execf(`supervisorctl start '%s'`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
@@ -175,7 +173,7 @@ func (s *App) StopProcess(w http.ResponseWriter, r *http.Request) {
return
}
if out, err := shell.Execf(`supervisorctl stop %s`, req.Process); err != nil {
if out, err := shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
@@ -191,7 +189,7 @@ func (s *App) RestartProcess(w http.ResponseWriter, r *http.Request) {
return
}
if out, err := shell.Execf(`supervisorctl restart %s`, req.Process); err != nil {
if out, err := shell.Execf(`supervisorctl restart '%s'`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
@@ -207,11 +205,12 @@ func (s *App) ProcessLog(w http.ResponseWriter, r *http.Request) {
return
}
name := programName(req.Process)
var logPath string
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, name)
} else {
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, name)
}
if err != nil {
@@ -222,34 +221,6 @@ func (s *App) ProcessLog(w http.ResponseWriter, r *http.Request) {
service.Success(w, logPath)
}
// ClearProcessLog 清空进程日志
func (s *App) ClearProcessLog(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ProcessName](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
var logPath string
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
} else {
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
}
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
return
}
if _, err = shell.Execf(`cat /dev/null > '%s'`, logPath); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// ProcessConfig 获取进程配置
func (s *App) ProcessConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ProcessName](r)
@@ -258,11 +229,12 @@ func (s *App) ProcessConfig(w http.ResponseWriter, r *http.Request) {
return
}
name := programName(req.Process)
var config string
if os.IsRHEL() {
config, err = io.Read(`/etc/supervisord.d/` + req.Process + `.conf`)
config, err = io.Read(`/etc/supervisord.d/` + name + `.conf`)
} else {
config, err = io.Read(`/etc/supervisor/conf.d/` + req.Process + `.conf`)
config, err = io.Read(`/etc/supervisor/conf.d/` + name + `.conf`)
}
if err != nil {
@@ -281,10 +253,11 @@ func (s *App) UpdateProcessConfig(w http.ResponseWriter, r *http.Request) {
return
}
name := programName(req.Process)
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.d/`+req.Process+`.conf`, req.Config, 0644)
err = io.Write(`/etc/supervisord.d/`+name+`.conf`, req.Config, 0644)
} else {
err = io.Write(`/etc/supervisor/conf.d/`+req.Process+`.conf`, req.Config, 0644)
err = io.Write(`/etc/supervisor/conf.d/`+name+`.conf`, req.Config, 0644)
}
if err != nil {
@@ -294,7 +267,7 @@ func (s *App) UpdateProcessConfig(w http.ResponseWriter, r *http.Request) {
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl restart '%s'`, req.Process)
_, _ = shell.Execf(`supervisorctl restart '%s:'`, name)
service.Success(w, nil)
}
@@ -307,9 +280,14 @@ func (s *App) CreateProcess(w http.ResponseWriter, r *http.Request) {
return
}
processName := `%(program_name)s`
if req.Num > 1 {
processName = `%(program_name)s_%(process_num)02d`
}
config := `[program:` + req.Name + `]
command=` + req.Command + `
process_name=%(program_name)s
process_name=` + processName + `
directory=` + req.Path + `
autostart=true
autorestart=true
@@ -320,20 +298,26 @@ stdout_logfile=/var/log/supervisor/` + req.Name + `.log
stdout_logfile_maxbytes=2MB
`
var confPath string
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.d/`+req.Name+`.conf`, config, 0644)
confPath = `/etc/supervisord.d/` + req.Name + `.conf`
} else {
err = io.Write(`/etc/supervisor/conf.d/`+req.Name+`.conf`, config, 0644)
confPath = `/etc/supervisor/conf.d/` + req.Name + `.conf`
}
if err != nil {
if io.Exists(confPath) {
service.Error(w, http.StatusConflict, s.t.Get("process %s already exists", req.Name))
return
}
if err = io.Write(confPath, config, 0644); err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl start '%s'`, req.Name)
_, _ = shell.Execf(`supervisorctl start '%s:'`, req.Name)
service.Success(w, nil)
}
@@ -346,29 +330,30 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
return
}
if out, err := shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
name := programName(req.Process)
if out, err := shell.Execf(`supervisorctl stop '%s:'`, name); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
var logPath string
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, name)
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
return
}
if err = io.Remove(`/etc/supervisord.d/` + req.Process + `.conf`); err != nil {
if err = io.Remove(`/etc/supervisord.d/` + name + `.conf`); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
} else {
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, name)
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
return
}
if err = io.Remove(`/etc/supervisor/conf.d/` + req.Process + `.conf`); err != nil {
if err = io.Remove(`/etc/supervisor/conf.d/` + name + `.conf`); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -383,3 +368,8 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
service.Success(w, nil)
}
func programName(process string) string {
name, _, _ := strings.Cut(process, ":")
return name
}
+257
View File
@@ -0,0 +1,257 @@
package valkey
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/samber/lo"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
databaseServerRepo biz.DatabaseServerRepo
}
func NewApp(t *gotext.Locale, databaseServer biz.DatabaseServerRepo) *App {
return &App{
t: t,
databaseServerRepo: databaseServer,
}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("valkey")
return types.AggregateAppStatus(ok)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("valkey")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get valkey status: %v", err))
return
}
if !status {
service.Success(w, []types.NV{})
return
}
// 检查 Valkey 密码
withPassword := ""
config, err := io.Read(fmt.Sprintf("%s/server/valkey/valkey.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
re := regexp.MustCompile(`(?m)^requirepass\s+(.+)`)
matches := re.FindStringSubmatch(config)
if len(matches) == 2 {
withPassword = " -a " + matches[1]
}
raw, err := shell.Execf("valkey-cli%s info", withPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get valkey info: %v", err))
return
}
dataRaw := lo.SliceToMap(strings.Split(raw, "\n"), func(item string) (string, string) {
parts := strings.Split(item, ":")
if len(parts) != 2 {
return "", ""
}
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
})
data := []types.NV{
{Name: s.t.Get("TCP Port"), Value: dataRaw["tcp_port"]},
{Name: s.t.Get("Uptime in Days"), Value: dataRaw["uptime_in_days"]},
{Name: s.t.Get("Connected Clients"), Value: dataRaw["connected_clients"]},
{Name: s.t.Get("Total Allocated Memory"), Value: dataRaw["used_memory_human"]},
{Name: s.t.Get("Total Memory Usage"), Value: dataRaw["used_memory_rss_human"]},
{Name: s.t.Get("Peak Memory Usage"), Value: dataRaw["used_memory_peak_human"]},
{Name: s.t.Get("Memory Fragmentation Ratio"), Value: dataRaw["mem_fragmentation_ratio"]},
{Name: s.t.Get("Total Connections Received"), Value: dataRaw["total_connections_received"]},
{Name: s.t.Get("Total Commands Processed"), Value: dataRaw["total_commands_processed"]},
{Name: s.t.Get("Commands Per Second"), Value: dataRaw["instantaneous_ops_per_sec"]},
{Name: s.t.Get("Keyspace Hits"), Value: dataRaw["keyspace_hits"]},
{Name: s.t.Get("Keyspace Misses"), Value: dataRaw["keyspace_misses"]},
{Name: s.t.Get("Latest Fork Time (ms)"), Value: dataRaw["latest_fork_usec"]},
}
service.Success(w, data)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/valkey/valkey.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, config)
}
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(fmt.Sprintf("%s/server/valkey/valkey.conf", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("valkey"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetConfigTune 获取 Valkey 配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/valkey/valkey.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
tune := ConfigTune{
Bind: s.getValkeyValue(config, "bind"),
Port: s.getValkeyValue(config, "port"),
Databases: s.getValkeyValue(config, "databases"),
Requirepass: s.getValkeyValue(config, "requirepass"),
Timeout: s.getValkeyValue(config, "timeout"),
TCPKeepalive: s.getValkeyValue(config, "tcp-keepalive"),
Maxmemory: s.getValkeyValue(config, "maxmemory"),
MaxmemoryPolicy: s.getValkeyValue(config, "maxmemory-policy"),
Appendonly: s.getValkeyValue(config, "appendonly"),
Appendfsync: s.getValkeyValue(config, "appendfsync"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新 Valkey 配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
confPath := fmt.Sprintf("%s/server/valkey/valkey.conf", app.Root)
config, err := io.Read(confPath)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
config = s.setValkeyValue(config, "bind", req.Bind)
config = s.setValkeyValue(config, "port", req.Port)
config = s.setValkeyValue(config, "databases", req.Databases)
config = s.setValkeyValue(config, "requirepass", req.Requirepass)
config = s.setValkeyValue(config, "timeout", req.Timeout)
config = s.setValkeyValue(config, "tcp-keepalive", req.TCPKeepalive)
config = s.setValkeyValue(config, "maxmemory", req.Maxmemory)
config = s.setValkeyValue(config, "maxmemory-policy", req.MaxmemoryPolicy)
config = s.setValkeyValue(config, "appendonly", req.Appendonly)
config = s.setValkeyValue(config, "appendfsync", req.Appendfsync)
if err = io.Write(confPath, config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("valkey"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
// 同步密码到数据库服务器记录
_ = s.databaseServerRepo.UpdatePassword("local_valkey", req.Requirepass)
service.Success(w, nil)
}
// getValkeyValue 从 Valkey 配置内容中获取指定键的值
func (s *App) getValkeyValue(content string, key string) string {
lines := strings.SplitSeq(content, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.Fields(trimmed)
if len(parts) >= 2 && parts[0] == key {
return strings.Join(parts[1:], " ")
}
}
return ""
}
// setValkeyValue 在 Valkey 配置内容中设置指定键的值
func (s *App) setValkeyValue(content string, key string, value string) string {
value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
lines := strings.Split(content, "\n")
result := make([]string, 0, len(lines))
found := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
result = append(result, line)
continue
}
checkLine := trimmed
if strings.HasPrefix(checkLine, "#") {
checkLine = strings.TrimSpace(checkLine[1:])
}
parts := strings.Fields(checkLine)
if len(parts) >= 1 && parts[0] == key {
if found {
continue
}
found = true
if value == "" {
if !strings.HasPrefix(trimmed, "#") {
result = append(result, "# "+trimmed)
} else {
result = append(result, line)
}
continue
}
result = append(result, key+" "+value)
} else {
result = append(result, line)
}
}
if !found && value != "" {
result = append(result, key+" "+value)
}
return strings.Join(result, "\n")
}
+22
View File
@@ -0,0 +1,22 @@
package valkey
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
// ConfigTune Valkey 配置调整
type ConfigTune struct {
// 常规设置
Bind string `form:"bind" json:"bind"`
Port string `form:"port" json:"port"`
Databases string `form:"databases" json:"databases"`
Requirepass string `form:"requirepass" json:"requirepass"`
Timeout string `form:"timeout" json:"timeout"`
TCPKeepalive string `form:"tcp_keepalive" json:"tcp_keepalive"`
// 内存
Maxmemory string `form:"maxmemory" json:"maxmemory"`
MaxmemoryPolicy string `form:"maxmemory_policy" json:"maxmemory_policy"`
// 持久化
Appendonly string `form:"appendonly" json:"appendonly"`
Appendfsync string `form:"appendfsync" json:"appendfsync"`
}
+2 -2
View File
@@ -3,8 +3,8 @@ package biz
import (
"time"
"github.com/acepanel/panel/pkg/api"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/pkg/api"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
+4 -3
View File
@@ -3,7 +3,7 @@ package biz
import (
"context"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/pkg/types"
)
type BackupType string
@@ -12,7 +12,7 @@ const (
BackupTypePath BackupType = "path"
BackupTypeWebsite BackupType = "website"
BackupTypeMySQL BackupType = "mysql"
BackupTypePostgres BackupType = "postgres"
BackupTypePostgres BackupType = "postgresql"
BackupTypeRedis BackupType = "redis"
BackupTypePanel BackupType = "panel"
)
@@ -25,7 +25,8 @@ type BackupRepo interface {
Restore(ctx context.Context, typ BackupType, backup, target string) error
ClearExpired(path, prefix string, save uint) error
ClearStorageExpired(account uint, typ BackupType, prefix string, save uint) error
CutoffLog(path, target string) error
CutoffLog(path, target string) (string, error)
CutoffUpload(account uint, typ BackupType, name string, files []string) error
GetDefaultPath(typ BackupType) string
FixPanel() error
UpdatePanel(version, url, checksum string) error
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"github.com/libtnb/utils/crypt"
"gorm.io/gorm"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type BackupStorageType string
+12 -9
View File
@@ -6,18 +6,19 @@ import (
mholtacme "github.com/mholt/acmez/v3/acme"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/acme"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/acme"
"github.com/acepanel/panel/v3/pkg/types"
)
type Cert struct {
ID uint `gorm:"primaryKey" json:"id"`
AccountID uint `gorm:"not null;default:0" json:"account_id"` // 关联的 ACME 账户 ID
WebsiteID uint `gorm:"not null;default:0" json:"website_id"` // 关联的网站 ID
DNSID uint `gorm:"not null;default:0" json:"dns_id"` // 关联的 DNS ID
Type string `gorm:"not null;default:''" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
Domains []string `gorm:"not null;default:'[]';serializer:json" json:"domains"`
AccountID uint `gorm:"not null;default:0" json:"account_id"` // 关联的 ACME 账户 ID
WebsiteID uint `gorm:"not null;default:0" json:"website_id"` // 关联的网站 ID
DNSID uint `gorm:"not null;default:0" json:"dns_id"` // 关联的 DNS ID
Type string `gorm:"not null;default:''" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
Domains []string `gorm:"not null;default:'[]';serializer:json" json:"domains"` // 域名
Alias map[string]string `gorm:"not null;default:'{}';serializer:json" json:"alias"` // DNS 验证别名
AutoRenewal bool `gorm:"not null;default:false" json:"auto_renewal"` // 自动续签
RenewalInfo mholtacme.RenewalInfo `gorm:"not null;default:'{}';serializer:json" json:"renewal_info"` // 续签信息
CertURL string `gorm:"not null;default:''" json:"cert_url"` // 证书 URL (续签时使用)
@@ -41,9 +42,11 @@ type CertRepo interface {
Update(ctx context.Context, req *request.CertUpdate) error
Delete(ctx context.Context, id uint) error
ObtainAuto(id uint) (*acme.Certificate, error)
ObtainAutoWithProgressCallback(ctx context.Context, id uint, progressCallback func(string)) (*acme.Certificate, error)
ObtainPanel(account *CertAccount, ips []string) ([]byte, []byte, error)
ObtainSelfSigned(id uint) error
Renew(id uint) (*acme.Certificate, error)
RenewWithProgressCallback(ctx context.Context, id uint, progressCallback func(string)) (*acme.Certificate, error)
RefreshRenewalInfo(id uint) (mholtacme.RenewalInfo, error)
Deploy(ID, WebsiteID uint) error
Deploy(ID, WebsiteID uint, enableHTTPS bool) error
}
+2 -2
View File
@@ -4,13 +4,13 @@ import (
"context"
"time"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/v3/internal/http/request"
)
type CertAccount struct {
ID uint `gorm:"primaryKey" json:"id"`
Email string `gorm:"not null;default:''" json:"email"`
CA string `gorm:"not null;default:'letsencrypt'" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
CA string `gorm:"not null;default:'letsencrypt'" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google)
Kid string `gorm:"not null;default:''" json:"kid"`
HmacEncoded string `gorm:"not null;default:''" json:"hmac_encoded"`
PrivateKey string `gorm:"not null;default:''" json:"private_key"`
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"context"
"time"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/acme"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/acme"
)
type CertDNS struct {
+2 -2
View File
@@ -1,8 +1,8 @@
package biz
import (
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type ContainerRepo interface {
+1 -1
View File
@@ -1,6 +1,6 @@
package biz
import "github.com/acepanel/panel/pkg/types"
import "github.com/acepanel/panel/v3/pkg/types"
type ContainerComposeRepo interface {
List() ([]types.ContainerCompose, error)
+2 -2
View File
@@ -1,8 +1,8 @@
package biz
import (
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type ContainerImageRepo interface {
+2 -2
View File
@@ -1,8 +1,8 @@
package biz
import (
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type ContainerNetworkRepo interface {
+2 -2
View File
@@ -1,8 +1,8 @@
package biz
import (
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type ContainerVolumeRepo interface {
+12 -10
View File
@@ -4,19 +4,21 @@ import (
"context"
"time"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type Cron struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;default:'';unique" json:"name"`
Status bool `gorm:"not null;default:false" json:"status"`
Type string `gorm:"not null;default:''" json:"type"`
Time string `gorm:"not null;default:''" json:"time"`
Shell string `gorm:"not null;default:''" json:"shell"`
Log string `gorm:"not null;default:''" json:"log"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;default:'';unique" json:"name"`
Status bool `gorm:"not null;default:false" json:"status"`
Type string `gorm:"not null;default:''" json:"type"`
Time string `gorm:"not null;default:''" json:"time"`
Config types.CronConfig `gorm:"serializer:json;not null;default:'{}'" json:"config"`
Shell string `gorm:"not null;default:''" json:"shell"`
Log string `gorm:"not null;default:''" json:"log"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CronRepo interface {
+9 -7
View File
@@ -3,17 +3,19 @@ package biz
import (
"context"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/v3/internal/http/request"
)
type DatabaseType string
const (
DatabaseTypeMysql DatabaseType = "mysql"
DatabaseTypePostgresql DatabaseType = "postgresql"
DatabaseTypeMongoDB DatabaseType = "mongodb"
DatabaseSQLite DatabaseType = "sqlite"
DatabaseTypeRedis DatabaseType = "redis"
DatabaseTypeMysql DatabaseType = "mysql"
DatabaseTypePostgresql DatabaseType = "postgresql"
DatabaseTypeMongoDB DatabaseType = "mongodb"
DatabaseTypeClickHouse DatabaseType = "clickhouse"
DatabaseTypeSQLite DatabaseType = "sqlite"
DatabaseTypeRedis DatabaseType = "redis"
DatabaseTypeElasticsearch DatabaseType = "elasticsearch"
)
type Database struct {
@@ -26,7 +28,7 @@ type Database struct {
}
type DatabaseRepo interface {
List(page, limit uint) ([]*Database, int64, error)
List(page, limit uint, typ string) ([]*Database, int64, error)
Create(ctx context.Context, req *request.DatabaseCreate) error
Delete(ctx context.Context, serverID uint, name string) error
Comment(req *request.DatabaseComment) error
+16
View File
@@ -0,0 +1,16 @@
package biz
import (
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/db"
)
type DatabaseElasticsearchRepo interface {
Indices(req *request.DatabaseESIndices) ([]db.ESIndex, error)
IndexCreate(req *request.DatabaseESIndexCreate) error
IndexDelete(req *request.DatabaseESIndexDelete) error
Data(req *request.DatabaseESData) ([]db.ESDocument, int64, error)
DocumentGet(req *request.DatabaseESDocumentGet) (*db.ESDocument, error)
DocumentSet(req *request.DatabaseESDocumentSet) error
DocumentDelete(req *request.DatabaseESDocumentDelete) error
}
+17
View File
@@ -0,0 +1,17 @@
package biz
import (
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/db"
)
type DatabaseRedisRepo interface {
Databases(req *request.DatabaseRedisDatabases) (int, error)
Data(req *request.DatabaseRedisData) ([]db.RedisKV, int, error)
KeyGet(req *request.DatabaseRedisKeyGet) (*db.RedisKV, error)
KeySet(req *request.DatabaseRedisKeySet) error
KeyDelete(req *request.DatabaseRedisKeyDelete) error
KeyTTL(req *request.DatabaseRedisKeyTTL) error
KeyRename(req *request.DatabaseRedisKeyRename) error
Clear(req *request.DatabaseRedisClear) error
}
+3 -3
View File
@@ -6,8 +6,8 @@ import (
"github.com/libtnb/utils/crypt"
"gorm.io/gorm"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/http/request"
)
type DatabaseServerStatus string
@@ -62,7 +62,7 @@ func (r *DatabaseServer) AfterFind(tx *gorm.DB) error {
type DatabaseServerRepo interface {
Count() (int64, error)
List(page, limit uint) ([]*DatabaseServer, int64, error)
List(page, limit uint, typ string) ([]*DatabaseServer, int64, error)
Get(id uint) (*DatabaseServer, error)
GetByName(name string) (*DatabaseServer, error)
Create(req *request.DatabaseServerCreate) error
+3 -3
View File
@@ -7,8 +7,8 @@ import (
"github.com/libtnb/utils/crypt"
"gorm.io/gorm"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/http/request"
)
type DatabaseUserStatus string
@@ -64,7 +64,7 @@ func (r *DatabaseUser) AfterFind(tx *gorm.DB) error {
type DatabaseUserRepo interface {
Count() (int64, error)
List(page, limit uint) ([]*DatabaseUser, int64, error)
List(page, limit uint, typ string) ([]*DatabaseUser, int64, error)
Get(id uint) (*DatabaseUser, error)
Create(ctx context.Context, req *request.DatabaseUserCreate) error
Update(req *request.DatabaseUserUpdate) error
+2 -2
View File
@@ -1,8 +1,8 @@
package biz
import (
"github.com/acepanel/panel/pkg/api"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/pkg/api"
"github.com/acepanel/panel/v3/pkg/types"
)
type EnvironmentRepo interface {
+4 -1
View File
@@ -47,5 +47,8 @@ type LogEntry struct {
// LogRepo 日志仓库接口
type LogRepo interface {
// List 获取日志列表
List(logType string, limit int) ([]LogEntry, error)
// date 格式为 YYYY-MM-DD,空字符串表示当天日志
List(logType string, limit int, date string) ([]LogEntry, error)
// ListDates 获取可用的日志日期列表
ListDates(logType string) ([]string, error)
}
+2 -2
View File
@@ -3,8 +3,8 @@ package biz
import (
"time"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type Monitor struct {
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"context"
"time"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/types"
)
type Project struct {
+82
View File
@@ -0,0 +1,82 @@
package biz
import "time"
// ScanEvent 扫描事件模型
type ScanEvent struct {
ID uint `gorm:"primaryKey" json:"id"`
SourceIP string `gorm:"not null;uniqueIndex:idx_scan_unique" json:"source_ip"`
Port uint `gorm:"not null;uniqueIndex:idx_scan_unique" json:"port"`
Protocol string `gorm:"not null;default:'tcp';uniqueIndex:idx_scan_unique" json:"protocol"`
Date string `gorm:"not null;uniqueIndex:idx_scan_unique;index:idx_scan_date" json:"date"` // YYYY-MM-DD
Count uint `gorm:"not null;default:1" json:"count"`
Country string `gorm:"not null;default:''" json:"country"`
Region string `gorm:"not null;default:''" json:"region"`
City string `gorm:"not null;default:''" json:"city"`
ISP string `gorm:"not null;default:''" json:"isp"`
FirstSeen time.Time `gorm:"not null" json:"first_seen"`
LastSeen time.Time `gorm:"not null" json:"last_seen"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ScanSummary 扫描汇总
type ScanSummary struct {
TotalCount uint `json:"total_count"`
UniqueIPs uint `json:"unique_ips"`
UniquePorts uint `json:"unique_ports"`
}
// ScanDayTrend 每日趋势
type ScanDayTrend struct {
Date string `json:"date"`
TotalCount uint `json:"total_count"`
UniqueIPs uint `json:"unique_ips"`
}
// ScanSourceRank 扫描源 IP 排行
type ScanSourceRank struct {
SourceIP string `json:"source_ip"`
TotalCount uint `json:"total_count"`
PortCount uint `json:"port_count"`
LastSeen string `json:"last_seen"`
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
ISP string `json:"isp"`
}
// ScanPortRank 被扫描端口排行
type ScanPortRank struct {
Port uint `json:"port"`
Protocol string `json:"protocol"`
TotalCount uint `json:"total_count"`
IPCount uint `json:"ip_count"`
}
// ScanSetting 扫描感知设置
type ScanSetting struct {
Enabled bool `json:"enabled"`
Days uint `json:"days"`
Interfaces []string `json:"interfaces"`
AutoBlock bool `json:"auto_block"`
BlockThreshold uint `json:"block_threshold"` // 扫描次数阈值
BlockWindow uint `json:"block_window"` // 检测窗口(分钟)
BlockDuration uint `json:"block_duration"` // 屏蔽时长(小时),0=永久
Whitelist []string `json:"whitelist"` // IP/CIDR 白名单
}
// ScanEventRepo 扫描事件数据访问接口
type ScanEventRepo interface {
Upsert(events []*ScanEvent) error
List(start, end, sourceIP string, port uint, location string, page, limit uint) ([]*ScanEvent, uint, error)
Summary(start, end string) (*ScanSummary, error)
Trend(start, end string) ([]*ScanDayTrend, error)
TopSourceIPs(start, end string, limit uint) ([]*ScanSourceRank, error)
TopPorts(start, end string, limit uint) ([]*ScanPortRank, error)
ClearBefore(date string) error
GetSetting() (*ScanSetting, error)
UpdateSetting(setting *ScanSetting) error
Clear() error
VacuumDB() error
}
+41 -19
View File
@@ -4,30 +4,52 @@ import (
"context"
"time"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/v3/internal/http/request"
)
type SettingKey string
const (
SettingKeyName SettingKey = "name"
SettingKeyVersion SettingKey = "version"
SettingKeyChannel SettingKey = "channel"
SettingKeyMonitor SettingKey = "monitor"
SettingKeyMonitorDays SettingKey = "monitor_days"
SettingKeyBackupPath SettingKey = "backup_path"
SettingKeyWebsitePath SettingKey = "website_path"
SettingKeyProjectPath SettingKey = "project_path"
SettingKeyWebsiteTLSVersions SettingKey = "website_tls_versions"
SettingKeyWebsiteCipherSuites SettingKey = "website_tls_cipher_suites"
SettingKeyMySQLRootPassword SettingKey = "mysql_root_password"
SettingKeyPostgresPassword SettingKey = "postgres_password"
SettingKeyOfflineMode SettingKey = "offline_mode"
SettingKeyAutoUpdate SettingKey = "auto_update"
SettingKeyWebserver SettingKey = "webserver"
SettingKeyPublicIPs SettingKey = "public_ips"
SettingHiddenMenu SettingKey = "hidden_menu"
SettingKeyCustomLogo SettingKey = "custom_logo"
SettingKeyName SettingKey = "name"
SettingKeyVersion SettingKey = "version"
SettingKeyChannel SettingKey = "channel"
SettingKeyMonitor SettingKey = "monitor"
SettingKeyMonitorDays SettingKey = "monitor_days"
SettingKeyMonitorInterval SettingKey = "monitor_interval"
SettingKeyBackupPath SettingKey = "backup_path"
SettingKeyBackupFormat SettingKey = "backup_format" // tar.xz / tar.gz / zip / 7z
SettingKeyWebsitePath SettingKey = "website_path"
SettingKeyProjectPath SettingKey = "project_path"
SettingKeyContainerSock SettingKey = "container_sock"
SettingKeyWebsiteTLSVersions SettingKey = "website_tls_versions"
SettingKeyMySQLRootPassword SettingKey = "mysql_root_password"
SettingKeyPostgresPassword SettingKey = "postgres_password"
SettingKeyMongoDBAdminPassword SettingKey = "mongodb_admin_password"
SettingKeyClickHouseDefaultPassword SettingKey = "clickhouse_default_password"
SettingKeyOfflineMode SettingKey = "offline_mode"
SettingKeyAutoUpdate SettingKey = "auto_update"
SettingKeyWebserver SettingKey = "webserver"
SettingKeyPublicIPs SettingKey = "public_ips"
SettingHiddenMenu SettingKey = "hidden_menu"
SettingKeyCustomLogo SettingKey = "custom_logo"
SettingKeyMemo SettingKey = "memo"
SettingKeyScanAware SettingKey = "scan_aware"
SettingKeyScanAwareDays SettingKey = "scan_aware_days"
SettingKeyScanAwareInterfaces SettingKey = "scan_aware_interfaces"
SettingKeyScanAwareAutoBlock SettingKey = "scan_aware_auto_block"
SettingKeyScanAwareBlockThreshold SettingKey = "scan_aware_block_threshold"
SettingKeyScanAwareBlockWindow SettingKey = "scan_aware_block_window"
SettingKeyScanAwareBlockDuration SettingKey = "scan_aware_block_duration" // 小时,0=永久
SettingKeyScanAwareWhitelist SettingKey = "scan_aware_whitelist" // JSON 数组
SettingKeyWebsiteStatDays SettingKey = "website_stat_days"
SettingKeyWebsiteStatErrBufMax SettingKey = "website_stat_err_buf_max"
SettingKeyWebsiteStatUVMaxKeys SettingKey = "website_stat_uv_max_keys"
SettingKeyWebsiteStatIPMaxKeys SettingKey = "website_stat_ip_max_keys"
SettingKeyWebsiteStatDetailMaxKeys SettingKey = "website_stat_detail_max_keys"
SettingKeyWebsiteStatBodyEnabled SettingKey = "website_stat_body_enabled"
SettingKeyIPDBType SettingKey = "ipdb_type" // "" / "custom" / "subscribe"
SettingKeyIPDBURL SettingKey = "ipdb_url" // 订阅链接
SettingKeyIPDBPath SettingKey = "ipdb_path"
)
type Setting struct {
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"github.com/libtnb/utils/crypt"
"gorm.io/gorm"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/ssh"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/http/request"
"github.com/acepanel/panel/v3/pkg/ssh"
)
type SSH struct {
+1 -1
View File
@@ -27,6 +27,6 @@ type TaskRepo interface {
Get(id uint) (*Task, error)
Delete(id uint) error
UpdateStatus(id uint, status TaskStatus) error
UpdateLog(id uint, log string) error
Push(task *Task) error
ClearZombieTasks() error
}
+2 -2
View File
@@ -1,8 +1,8 @@
package biz
import (
"github.com/acepanel/panel/pkg/api"
"github.com/acepanel/panel/pkg/types"
"github.com/acepanel/panel/v3/pkg/api"
"github.com/acepanel/panel/v3/pkg/types"
)
type TemplateRepo interface {
+36
View File
@@ -0,0 +1,36 @@
package biz
import (
"time"
"gorm.io/gorm"
)
type UserPasskey struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"index;not null" json:"user_id"`
Name string `gorm:"not null;default:''" json:"name"`
CredentialID []byte `gorm:"uniqueIndex;not null" json:"-"`
PublicKey []byte `gorm:"not null" json:"-"`
AAGUID []byte `json:"-"`
SignCount uint32 `gorm:"not null;default:0" json:"-"`
Transports string `gorm:"not null;default:''" json:"transports"` // JSON: ["internal","hybrid"]
BackupEligible bool `gorm:"not null;default:false" json:"-"`
BackupState bool `gorm:"not null;default:false" json:"-"`
LastUsedAt *time.Time `json:"last_used_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
}
type UserPasskeyRepo interface {
List(userID uint) ([]*UserPasskey, error)
Create(passkey *UserPasskey) error
UpdateSignCount(credentialID []byte, signCount uint32) error
UpdateLastUsed(credentialID []byte) error
Delete(userID, id uint) error
GetByCredentialID(credentialID []byte) (*UserPasskey, *User, error)
HasPasskey(userID uint) (bool, error)
HasAny() (bool, error)
DeleteAllByUserID(userID uint) error
}

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