mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
582cf1f533 | ||
|
|
b8a4e8e9e0 | ||
|
|
4a462aecc7 | ||
|
|
74f221b895 | ||
|
|
cbcb628bff | ||
|
|
f602645e31 | ||
|
|
ec9e609f10 | ||
|
|
1c1f3c7761 | ||
|
|
fae49284b7 | ||
|
|
072a608a63 | ||
|
|
1e6fd9c3a0 | ||
|
|
23200cbe85 | ||
|
|
08604e8c02 | ||
|
|
62a472d2d1 | ||
|
|
2c84665e8b | ||
|
|
616bd6830c | ||
|
|
e55ea96f00 | ||
|
|
aa676e139c | ||
|
|
7708fea58d | ||
|
|
34bdff01ad | ||
|
|
5a49579ddf | ||
|
|
b98a2b06c9 | ||
|
|
9073b16baa | ||
|
|
2ec4db8d7e | ||
|
|
fe9628f91c | ||
|
|
18a469e4be | ||
|
|
8c691cc074 | ||
|
|
2cd31a47f8 | ||
|
|
d97aa9c2a0 | ||
|
|
f31f0a89ff | ||
|
|
0a1621166e | ||
|
|
8623aa5318 | ||
|
|
ec50599526 | ||
|
|
458a30c520 | ||
|
|
9a660db4c3 | ||
|
|
f4fc71d32b | ||
|
|
e45a8185e3 | ||
|
|
7be9189b3c | ||
|
|
bdf13c01f6 | ||
|
|
902bc90aef | ||
|
|
27ba50d8be | ||
|
|
fb483ecb90 | ||
|
|
2de7079dd2 | ||
|
|
2e4ac604a8 | ||
|
|
46af80142e | ||
|
|
677b47dcdb | ||
|
|
56cccdf7c0 | ||
|
|
3e0d0bb809 | ||
|
|
86aa6c09c6 | ||
|
|
f67e1d897c | ||
|
|
9e518daa62 | ||
|
|
2e5455e655 | ||
|
|
868f1db4c5 | ||
|
|
efd84b1e1b | ||
|
|
5591fe50f0 | ||
|
|
5e0a598799 | ||
|
|
45d7b30abc | ||
|
|
46af88e1d0 | ||
|
|
7f331f5503 | ||
|
|
7b74e09d30 | ||
|
|
01e4455110 | ||
|
|
922a2d21c8 | ||
|
|
439c4794b7 | ||
|
|
5ac7f16b67 | ||
|
|
bfb79066b4 | ||
|
|
19602b42fb | ||
|
|
f987af264f | ||
|
|
a6c76dffc3 | ||
|
|
a2eed66bc4 | ||
|
|
a49b9b132d | ||
|
|
e8505a249a | ||
|
|
dbff9c02e9 | ||
|
|
45c5c5b40f | ||
|
|
aea71664ee | ||
|
|
fc65fb9323 | ||
|
|
f476823531 | ||
|
|
8222579d99 | ||
|
|
209c126445 | ||
|
|
c846ca3e71 | ||
|
|
e777fa143e | ||
|
|
e66000ffd2 | ||
|
|
cfdf448765 | ||
|
|
c61139c5cc | ||
|
|
571a4068c4 | ||
|
|
9ce63adb4f | ||
|
|
ef51a2f9b2 | ||
|
|
fa3e137db8 | ||
|
|
af6a708049 | ||
|
|
5d6ccf5717 | ||
|
|
59c870aca4 | ||
|
|
94cb014598 | ||
|
|
ed30567a22 | ||
|
|
791350c299 | ||
|
|
6547de1758 | ||
|
|
2151daab1f | ||
|
|
b791def0df | ||
|
|
c036d5859f | ||
|
|
6495869664 | ||
|
|
a2e6e7e879 | ||
|
|
338301907a | ||
|
|
681141f971 | ||
|
|
506ff1d46e | ||
|
|
c3b7deedb4 | ||
|
|
51252a15db | ||
|
|
375824f77a | ||
|
|
8918d10253 | ||
|
|
fe343a64ed | ||
|
|
205f12e14a | ||
|
|
aca94e470a | ||
|
|
a69f7b1144 | ||
|
|
e55abfb174 | ||
|
|
4a734b5bb6 | ||
|
|
758eb9dfda | ||
|
|
04e2770763 | ||
|
|
7391b4bcd0 | ||
|
|
d9d4d55eca | ||
|
|
9a64f8de98 | ||
|
|
9f9c9a2688 | ||
|
|
f80f4d1c19 | ||
|
|
145b39c4c0 | ||
|
|
44e264fe65 | ||
|
|
15fef4d84e | ||
|
|
b8bd1490ec | ||
|
|
ccd8923fdd | ||
|
|
e49fa620c8 | ||
|
|
654691543c | ||
|
|
929cd38184 | ||
|
|
9748a0794b | ||
|
|
a3f8c30508 | ||
|
|
602c0e8a3b | ||
|
|
7b7f840c2e | ||
|
|
ba9a8592ee | ||
|
|
389fcbf10c | ||
|
|
9cca14c382 | ||
|
|
243a2fe6f3 | ||
|
|
3d5069fd40 | ||
|
|
2b78bae451 | ||
|
|
8693cc17df | ||
|
|
9a2b7a7775 | ||
|
|
024a4a9ef3 | ||
|
|
f5b47cf74d | ||
|
|
4956b96193 | ||
|
|
a332dfd3b3 | ||
|
|
68f8d8d221 | ||
|
|
4f78060d35 | ||
|
|
adb97730c1 | ||
|
|
f1a0453615 | ||
|
|
e64cc875fe | ||
|
|
b1c028b786 | ||
|
|
d0e3914a29 | ||
|
|
fe7d1108cd | ||
|
|
462a3bbc83 | ||
|
|
b9dff99d63 | ||
|
|
612e67e4cc | ||
|
|
4f0838c98c | ||
|
|
fe70a77264 | ||
|
|
8ed33fd06a | ||
|
|
f5662769b3 | ||
|
|
61a2e20798 | ||
|
|
7d20118f38 | ||
|
|
7307b9d6fb | ||
|
|
c2971812a5 | ||
|
|
176a4bd34c | ||
|
|
4a01726058 | ||
|
|
18a372e027 | ||
|
|
483a2a867b | ||
|
|
1a0de94489 | ||
|
|
979c1588e1 | ||
|
|
e39f6b1003 | ||
|
|
4443ed1b7f | ||
|
|
81f11f13fe | ||
|
|
4fa14f055d | ||
|
|
0fe9033d23 | ||
|
|
e5ff53b1af | ||
|
|
d005e61c72 | ||
|
|
9328a79b37 | ||
|
|
56c7d5e3bd | ||
|
|
0cefe8f6ad | ||
|
|
3759fd2dad | ||
|
|
b35c771d16 | ||
|
|
3ce70cee4d | ||
|
|
8dd27a3e7b | ||
|
|
c91769e364 | ||
|
|
ea7856d591 | ||
|
|
023040a814 | ||
|
|
13fdb6caa9 | ||
|
|
81e1a41130 | ||
|
|
f3933155d6 | ||
|
|
4e692b8667 | ||
|
|
cf5f1cdb4e | ||
|
|
46986fba24 | ||
|
|
4627e7c0af | ||
|
|
38174a0b9f | ||
|
|
c0489245d9 | ||
|
|
bec23f793a | ||
|
|
e7578a9fc5 | ||
|
|
9d3313fe11 | ||
|
|
f41e5e3da5 | ||
|
|
540be68adf | ||
|
|
956bf0992a | ||
|
|
d0faee4eeb | ||
|
|
4517ae2f81 | ||
|
|
c77260193f | ||
|
|
d57c998047 | ||
|
|
3a5d5bc6d7 | ||
|
|
00afa748e0 | ||
|
|
8c9c7b9b1f | ||
|
|
1e2dc42554 | ||
|
|
cd621b9dba | ||
|
|
d10afe7a5a | ||
|
|
03fc9302d6 | ||
|
|
d522d837ff | ||
|
|
aa10760658 | ||
|
|
db6e8841ec | ||
|
|
75258bb465 | ||
|
|
7c7a59cf21 | ||
|
|
6f38c4eb71 | ||
|
|
7abedeae22 | ||
|
|
afacefaccb | ||
|
|
e5b2b5d5dc | ||
|
|
9508238805 | ||
|
|
2c21bd7286 | ||
|
|
811ddcc26c | ||
|
|
6cd0373678 | ||
|
|
11771b2b53 | ||
|
|
ff5c6e878e | ||
|
|
1896aed973 | ||
|
|
6dc5deb6d3 | ||
|
|
d7242d526c | ||
|
|
22c3fc8c40 | ||
|
|
8d44105f88 | ||
|
|
8fb8d97dcf | ||
|
|
6941014153 | ||
|
|
bfd610d255 | ||
|
|
5c61217c78 | ||
|
|
eb527857f3 | ||
|
|
d55982bde0 | ||
|
|
84a27de792 | ||
|
|
111bec547c | ||
|
|
db4f699b85 | ||
|
|
12f2d95265 | ||
|
|
a917ca00a1 | ||
|
|
69906046e8 | ||
|
|
d9cedeca09 | ||
|
|
0e28f27819 | ||
|
|
33fc7f14df |
@@ -1,69 +0,0 @@
|
||||
name: Build And Publish (OSS + R2)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.11.1'
|
||||
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache-dependency-path: |
|
||||
core/go.sum
|
||||
agent/go.sum
|
||||
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: '~> v2'
|
||||
args: release --skip=publish --clean
|
||||
|
||||
- name: Upload Assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
dist/*.tar.gz
|
||||
dist/checksums.txt
|
||||
|
||||
- name: Setup OSSUTIL
|
||||
uses: yizhoumo/setup-ossutil@v2
|
||||
with:
|
||||
endpoint: ${{ secrets.OSS_ENDPOINT }}
|
||||
access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }}
|
||||
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
|
||||
ossutil-version: '1.7.18'
|
||||
|
||||
- name: Upload Assets to OSS
|
||||
run: |
|
||||
ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload to Cloudflare R2
|
||||
run: |
|
||||
rclone copy dist/ cloudflare_r2:package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --progress
|
||||
+16
-3
@@ -40,11 +40,23 @@ core/cmd/server/web/index.html
|
||||
frontend/auto-imports.d.ts
|
||||
frontend/components.d.ts
|
||||
frontend/src/xpack
|
||||
frontend/src/xpack-ee
|
||||
frontend/src/enterprise
|
||||
agent/xpack
|
||||
agent/xpack-ee
|
||||
agent/enterprise
|
||||
agent/router/entry_xpack.go
|
||||
agent/router/entry_enterprise.go
|
||||
agent/server/init_xpack.go
|
||||
agent/server/init_enterprise.go
|
||||
agent/utils/xpack/xpack.go
|
||||
agent/utils/xpack/enterprise.go
|
||||
core/xpack
|
||||
core/xpack-ee
|
||||
core/enterprise
|
||||
core/router/entry_xpack.go
|
||||
core/router/entry_enterprise.go
|
||||
core/server/init_xpack.go
|
||||
core/server/init_enterprise.go
|
||||
core/utils/xpack/xpack.go
|
||||
core/utils/xpack/enterprise.go
|
||||
|
||||
.history/
|
||||
dist/
|
||||
@@ -66,3 +78,4 @@ AGENTS.md
|
||||
opencode.json
|
||||
superpowers
|
||||
.worktrees/
|
||||
.codex/
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=jcroql
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# - export NODE_OPTIONS="--max-old-space-size=8192"
|
||||
# - make build_web
|
||||
- chmod +x ./ci/script.sh
|
||||
- ./ci/script.sh
|
||||
- sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl
|
||||
|
||||
builds:
|
||||
- id: agent
|
||||
dir: agent
|
||||
main: cmd/server/main.go
|
||||
binary: 1panel-agent
|
||||
flags:
|
||||
- -tags=xpack
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarm:
|
||||
- 7
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- arm
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
|
||||
- id: core
|
||||
dir: core
|
||||
main: cmd/server/main.go
|
||||
binary: 1panel-core
|
||||
flags:
|
||||
- -tags=xpack
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarm:
|
||||
- 7
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- arm
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
|
||||
archives:
|
||||
- formats: [ 'tar.gz' ]
|
||||
ids: [core, agent]
|
||||
name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- 1pctl
|
||||
- install.sh
|
||||
- 1panel-core.service
|
||||
- 1panel-agent.service
|
||||
- initscript/*
|
||||
- lang/*
|
||||
- GeoIP.mmdb
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
@@ -2,8 +2,10 @@ reviewers:
|
||||
- wanghe-fit2cloud
|
||||
- zhengkunwang223
|
||||
- ssongliu
|
||||
- lan-yonghui
|
||||
|
||||
approvers:
|
||||
- wanghe-fit2cloud
|
||||
- zhengkunwang223
|
||||
- ssongliu
|
||||
- lan-yonghui
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<a href="/docs/README.pt-br.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-d9d9d9"></a>
|
||||
<a href="/docs/README.ar.md"><img alt="العربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
|
||||
<a href="/docs/README.de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-d9d9d9"></a>
|
||||
<a href="/docs/README.es.md"><img alt="Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
|
||||
<a href="/docs/README.es-es.md"><img alt="Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
|
||||
<a href="/docs/README.fr.md"><img alt="français" src="https://img.shields.io/badge/français-d9d9d9"></a>
|
||||
<a href="/docs/README.ko.md"><img alt="한국어" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="/docs/README.id.md"><img alt="Bahasa Indonesia" src="https://img.shields.io/badge/Bahasa Indonesia-d9d9d9"></a>
|
||||
@@ -33,6 +33,7 @@
|
||||
<a href="/docs/README.tr.md"><img alt="Türkçe" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
|
||||
<a href="/docs/README.ru.md"><img alt="Русский" src="https://img.shields.io/badge/Русский-d9d9d9"></a>
|
||||
<a href="/docs/README.ms.md"><img alt="Bahasa Melayu" src="https://img.shields.io/badge/Bahasa Melayu-d9d9d9"></a>
|
||||
<a href="/docs/README.fa.md"><img alt="Persian" src="https://img.shields.io/badge/%D9%81%D8%A7%D8%B1%D8%B3%DB%8C-d9d9d9"></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
+125
-20
@@ -27,6 +27,90 @@ func (b *BaseApi) CreateAgent(c *gin.Context) {
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Batch install Agent
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentBatchInstallReq true "request"
|
||||
// @Success 200 {object} dto.AgentItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/batch/install [post]
|
||||
func (b *BaseApi) BatchInstallAgent(c *gin.Context) {
|
||||
var req dto.AgentBatchInstallReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := agentService.BatchInstall(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Batch upgrade Agent
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentBatchUpgradeReq true "request"
|
||||
// @Success 200 {array} dto.AgentBatchUpgradeResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/batch/upgrade [post]
|
||||
func (b *BaseApi) BatchUpgradeAgent(c *gin.Context) {
|
||||
var req dto.AgentBatchUpgradeReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := agentService.BatchUpgrade(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Batch install Agent Skill
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentBatchSkillInstallReq true "request"
|
||||
// @Success 200 {array} dto.AgentBatchSkillInstallResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/batch/skill/install [post]
|
||||
func (b *BaseApi) BatchInstallAgentSkill(c *gin.Context) {
|
||||
var req dto.AgentBatchSkillInstallReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := agentService.BatchInstallSkill(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Batch operate Agent
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentBatchOperateReq true "request"
|
||||
// @Success 200 {array} dto.AgentBatchOperateResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/batch/operate [post]
|
||||
func (b *BaseApi) BatchOperateAgent(c *gin.Context) {
|
||||
var req dto.AgentBatchOperateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := agentService.BatchOperate(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Agents
|
||||
// @Accept json
|
||||
@@ -296,11 +380,11 @@ func (b *BaseApi) DeleteHermesChatSession(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Providers
|
||||
// @Summary Get model account providers
|
||||
// @Success 200 {array} dto.ProviderInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/providers [get]
|
||||
// @Router /ai/accounts/providers [get]
|
||||
func (b *BaseApi) GetAgentProviders(c *gin.Context) {
|
||||
list, err := agentService.GetProviders()
|
||||
if err != nil {
|
||||
@@ -311,13 +395,13 @@ func (b *BaseApi) GetAgentProviders(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Create Agent account
|
||||
// @Summary Create model account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountCreateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts [post]
|
||||
// @Router /ai/accounts [post]
|
||||
func (b *BaseApi) CreateAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountCreateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -331,13 +415,13 @@ func (b *BaseApi) CreateAgentAccount(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent account
|
||||
// @Summary Update model account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/update [post]
|
||||
// @Router /ai/accounts/update [post]
|
||||
func (b *BaseApi) UpdateAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -351,13 +435,13 @@ func (b *BaseApi) UpdateAgentAccount(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Agent accounts
|
||||
// @Summary Page model accounts
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/search [post]
|
||||
// @Router /ai/accounts/search [post]
|
||||
func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
||||
var req dto.AgentAccountSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -375,13 +459,34 @@ func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary List Agent account models
|
||||
// @Summary Count model accounts by provider
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountProviderCountReq true "request"
|
||||
// @Success 200 {object} map[string]int64
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/accounts/counts [post]
|
||||
func (b *BaseApi) CountAgentAccountsByProviders(c *gin.Context) {
|
||||
var req dto.AgentAccountProviderCountReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
counts, err := agentService.CountAccountsByProviders(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, counts)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary List model account models
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelReq true "request"
|
||||
// @Success 200 {array} dto.AgentAccountModel
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models [post]
|
||||
// @Router /ai/accounts/models [post]
|
||||
func (b *BaseApi) GetAgentAccountModels(c *gin.Context) {
|
||||
var req dto.AgentAccountModelReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -396,13 +501,13 @@ func (b *BaseApi) GetAgentAccountModels(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Create Agent account model
|
||||
// @Summary Create model account model
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelCreateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models/create [post]
|
||||
// @Router /ai/accounts/models/create [post]
|
||||
func (b *BaseApi) CreateAgentAccountModel(c *gin.Context) {
|
||||
var req dto.AgentAccountModelCreateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -416,13 +521,13 @@ func (b *BaseApi) CreateAgentAccountModel(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent account model
|
||||
// @Summary Update model account model
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models/update [post]
|
||||
// @Router /ai/accounts/models/update [post]
|
||||
func (b *BaseApi) UpdateAgentAccountModel(c *gin.Context) {
|
||||
var req dto.AgentAccountModelUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -436,13 +541,13 @@ func (b *BaseApi) UpdateAgentAccountModel(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Delete Agent account model
|
||||
// @Summary Delete model account model
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelDeleteReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models/delete [post]
|
||||
// @Router /ai/accounts/models/delete [post]
|
||||
func (b *BaseApi) DeleteAgentAccountModel(c *gin.Context) {
|
||||
var req dto.AgentAccountModelDeleteReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -456,13 +561,13 @@ func (b *BaseApi) DeleteAgentAccountModel(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Verify Agent account
|
||||
// @Summary Verify model account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountVerifyReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/verify [post]
|
||||
// @Router /ai/accounts/verify [post]
|
||||
func (b *BaseApi) VerifyAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountVerifyReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -476,13 +581,13 @@ func (b *BaseApi) VerifyAgentAccount(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Delete Agent account
|
||||
// @Summary Delete model account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountDeleteReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/delete [post]
|
||||
// @Router /ai/accounts/delete [post]
|
||||
func (b *BaseApi) DeleteAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountDeleteReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
+9
-42
@@ -3,9 +3,6 @@ package v2
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -163,37 +160,6 @@ func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Load gpu / xpu info
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/gpu/load [get]
|
||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||
ok, client := gpu.New()
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
xpuOK, xpuClient := xpu.New()
|
||||
if xpuOK {
|
||||
info, err := xpuClient.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, &common.GpuInfo{})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Bind domain
|
||||
// @Accept json
|
||||
@@ -235,14 +201,15 @@ func (b *BaseApi) GetBindDomain(c *gin.Context) {
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// Tags AI
|
||||
// Summary Update bind domain
|
||||
// Accept json
|
||||
// Param request body dto.OllamaBindDomain true "request"
|
||||
// Success 200
|
||||
// Security ApiKeyAuth
|
||||
// Security Timestamp
|
||||
// Router /ai/domain/update [post]
|
||||
// @Tags AI
|
||||
// @Summary Update bind domain
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaBindDomain true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/domain/update [post]
|
||||
// @x-panel-log {"bodyKeys":["domain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 Ollama 绑定域名 [domain]","formatEN":"update Ollama bind domain [domain]"}
|
||||
func (b *BaseApi) UpdateBindDomain(c *gin.Context) {
|
||||
var req dto.OllamaBindDomain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
+159
-4
@@ -2,11 +2,24 @@ package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const defaultAuditUser = "system"
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Page alert
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertSearch true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/search [post]
|
||||
func (b *BaseApi) PageAlert(c *gin.Context) {
|
||||
var req dto.AlertSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -32,12 +45,21 @@ func (b *BaseApi) GetAlerts(c *gin.Context) {
|
||||
helper.SuccessWithData(c, alerts)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Create alert
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert [post]
|
||||
// @x-panel-log {"bodyKeys":["title"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建告警任务 [title]","formatEN":"create alert [title]"}
|
||||
func (b *BaseApi) CreateAlert(c *gin.Context) {
|
||||
var req dto.AlertCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := alertService.CreateAlert(req)
|
||||
err := alertService.CreateAlert(req, loadAuditUser(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -45,6 +67,15 @@ func (b *BaseApi) CreateAlert(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Delete alert
|
||||
// @Accept json
|
||||
// @Param request body dto.DeleteRequest true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除告警任务 [id]","formatEN":"delete alert [id]"}
|
||||
func (b *BaseApi) DeleteAlert(c *gin.Context) {
|
||||
var req dto.DeleteRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -58,12 +89,21 @@ func (b *BaseApi) DeleteAlert(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Update alert
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/update [post]
|
||||
// @x-panel-log {"bodyKeys":["title"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警任务 [title]","formatEN":"update alert [title]"}
|
||||
func (b *BaseApi) UpdateAlert(c *gin.Context) {
|
||||
var req dto.AlertUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlert(req); err != nil {
|
||||
if err := alertService.UpdateAlert(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -84,6 +124,15 @@ func (b *BaseApi) GetAlert(c *gin.Context) {
|
||||
helper.SuccessWithData(c, alert)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Update alert status
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertUpdateStatus true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/status [post]
|
||||
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警任务 [id] 状态 [status]","formatEN":"update alert [id] status [status]"}
|
||||
func (b *BaseApi) UpdateAlertStatus(c *gin.Context) {
|
||||
var req dto.AlertUpdateStatus
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -97,6 +146,12 @@ func (b *BaseApi) UpdateAlertStatus(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Get disks
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/disks/list [get]
|
||||
func (b *BaseApi) GetDisks(c *gin.Context) {
|
||||
alerts, err := alertService.GetDisks()
|
||||
if err != nil {
|
||||
@@ -106,6 +161,14 @@ func (b *BaseApi) GetDisks(c *gin.Context) {
|
||||
helper.SuccessWithData(c, alerts)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Page alert logs
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertLogSearch true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/logs/search [post]
|
||||
func (b *BaseApi) PageAlertLogs(c *gin.Context) {
|
||||
var req dto.AlertLogSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -122,6 +185,13 @@ func (b *BaseApi) PageAlertLogs(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Clean alert logs
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/logs/clean [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空告警日志","formatEN":"clean alert logs"}
|
||||
func (b *BaseApi) CleanAlertLogs(c *gin.Context) {
|
||||
if err := alertService.CleanAlertLogs(); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -130,6 +200,12 @@ func (b *BaseApi) CleanAlertLogs(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Get clams
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/clams/list [get]
|
||||
func (b *BaseApi) GetClams(c *gin.Context) {
|
||||
clams, err := alertService.GetClams()
|
||||
if err != nil {
|
||||
@@ -139,6 +215,14 @@ func (b *BaseApi) GetClams(c *gin.Context) {
|
||||
helper.SuccessWithData(c, clams)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Get cron jobs
|
||||
// @Accept json
|
||||
// @Param request body dto.CronJobReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/cronjob/list [post]
|
||||
func (b *BaseApi) GetCronJobs(c *gin.Context) {
|
||||
var req dto.CronJobReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -152,8 +236,18 @@ func (b *BaseApi) GetCronJobs(c *gin.Context) {
|
||||
helper.SuccessWithData(c, cronJobs)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Get alert config
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/info [post]
|
||||
func (b *BaseApi) GetAlertConfig(c *gin.Context) {
|
||||
config, err := alertService.GetAlertConfig()
|
||||
var req dto.AlertConfigQuery
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
config, err := alertService.GetAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -161,18 +255,71 @@ func (b *BaseApi) GetAlertConfig(c *gin.Context) {
|
||||
helper.SuccessWithData(c, config)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Page alert config
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertConfigPageReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/search [post]
|
||||
func (b *BaseApi) PageAlertConfig(c *gin.Context) {
|
||||
var req dto.AlertConfigPageReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, configs, err := alertService.PageAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Total: total,
|
||||
Items: configs,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Update alert config
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertConfigUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/update [post]
|
||||
// @x-panel-log {"bodyKeys":["id","displayName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置 [id][displayName]","formatEN":"update alert config [id][displayName]"}
|
||||
func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
|
||||
var req dto.AlertConfigUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlertConfig(req); err != nil {
|
||||
if err := alertService.UpdateAlertConfig(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func loadAuditUser(c *gin.Context) string {
|
||||
userName := strings.TrimSpace(c.GetHeader("X-Panel-User"))
|
||||
if userName == "" {
|
||||
return defaultAuditUser
|
||||
}
|
||||
if decoded, err := url.QueryUnescape(userName); err == nil {
|
||||
return decoded
|
||||
}
|
||||
return userName
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Delete alert config
|
||||
// @Accept json
|
||||
// @Param request body dto.DeleteRequest true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除告警配置 [id]","formatEN":"delete alert config [id]"}
|
||||
func (b *BaseApi) DeleteAlertConfig(c *gin.Context) {
|
||||
var req dto.DeleteRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -186,6 +333,14 @@ func (b *BaseApi) DeleteAlertConfig(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Test alert config
|
||||
// @Accept json
|
||||
// @Param request body dto.AlertConfigTest true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/test [post]
|
||||
func (b *BaseApi) TestAlertConfig(c *gin.Context) {
|
||||
var req dto.AlertConfigTest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
+10
-4
@@ -107,7 +107,7 @@ func (b *BaseApi) GetApp(c *gin.Context) {
|
||||
// @Accept json
|
||||
// @Param appId path integer true "app id"
|
||||
// @Param version path string true "app 版本"
|
||||
// @Param version path string true "app 类型"
|
||||
// @Param type path string true "app 类型"
|
||||
// @Success 200 {object} response.AppDetailDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -131,7 +131,7 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
||||
// @Tags App
|
||||
// @Summary Get app detail by id
|
||||
// @Accept json
|
||||
// @Param appId path integer true "id"
|
||||
// @Param id path integer true "id"
|
||||
// @Success 200 {object} response.AppDetailDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -172,6 +172,12 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
|
||||
helper.SuccessWithData(c, install)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary Get app tags
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/tags [get]
|
||||
func (b *BaseApi) GetAppTags(c *gin.Context) {
|
||||
tags, err := appService.GetAppTags(c)
|
||||
if err != nil {
|
||||
@@ -199,7 +205,7 @@ func (b *BaseApi) GetAppListUpdate(c *gin.Context) {
|
||||
// @Tags App
|
||||
// @Summary Get app icon by app_id
|
||||
// @Accept json
|
||||
// @Param appId path integer true "app id"
|
||||
// @Param key path string true "app key"
|
||||
// @Success 200 {file} file "app icon"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -237,7 +243,7 @@ func (b *BaseApi) GetAppIcon(c *gin.Context) {
|
||||
// @Tags App
|
||||
// @Summary Search app detail by appkey and version
|
||||
// @Accept json
|
||||
// @Param appId path integer true "app key"
|
||||
// @Param appKey path string true "app key"
|
||||
// @Param version path string true "app version"
|
||||
// @Success 200 {object} response.AppDetailSimpleDTO
|
||||
// @Security ApiKeyAuth
|
||||
|
||||
@@ -203,7 +203,6 @@ func (b *BaseApi) GetServices(c *gin.Context) {
|
||||
// @Tags App
|
||||
// @Summary Search app update version by install id
|
||||
// @Accept json
|
||||
// @Param appInstallId path integer true "request"
|
||||
// @Success 200 {array} dto.AppVersion
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -267,7 +266,7 @@ func (b *BaseApi) GetDefaultConfig(c *gin.Context) {
|
||||
// @Tags App
|
||||
// @Summary Search params by appInstallId
|
||||
// @Accept json
|
||||
// @Param appInstallId path string true "request"
|
||||
// @Param appInstallId path integer true "request"
|
||||
// @Success 200 {object} response.AppConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -350,6 +349,15 @@ func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary Update app install sort
|
||||
// @Accept json
|
||||
// @Param request body request.AppInstallSort true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/installed/sort/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新已安装应用排序","formatEN":"update installed app sort"}
|
||||
func (b *BaseApi) UpdateAppInstallSort(c *gin.Context) {
|
||||
var req request.AppInstallSort
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -10,6 +10,13 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Check backup used
|
||||
// @Param name path string true "name"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /backups/check/{name} [get]
|
||||
func (b *BaseApi) CheckBackupUsed(c *gin.Context) {
|
||||
name, err := helper.GetStrParamByKey(c, "name")
|
||||
if err != nil {
|
||||
@@ -32,7 +39,7 @@ func (b *BaseApi) CheckBackupUsed(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /backups/check [post]
|
||||
// @Router /backups/conn/check [post]
|
||||
func (b *BaseApi) CheckBackup(c *gin.Context) {
|
||||
var req dto.BackupOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -136,7 +143,7 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /backups/update [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [types]","formatEN":"update backup account [types]"}
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [type]","formatEN":"update backup account [type]"}
|
||||
func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
||||
var req dto.BackupOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -406,10 +413,15 @@ func (b *BaseApi) Backup(c *gin.Context) {
|
||||
|
||||
switch req.Type {
|
||||
case "app":
|
||||
if _, err := backupService.AppBackup(req); err != nil {
|
||||
record, err := backupService.AppBackup(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
if req.IsImmediate {
|
||||
helper.SuccessWithData(c, record)
|
||||
return
|
||||
}
|
||||
case "mysql", "mariadb", constant.AppMysqlCluster:
|
||||
if err := backupService.MysqlBackup(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
@@ -21,7 +21,7 @@ func (b *BaseApi) CreateClam(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := clamService.Create(req); err != nil {
|
||||
if err := clamService.Create(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func (b *BaseApi) UpdateClam(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := clamService.Update(req); err != nil {
|
||||
if err := clamService.Update(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/clam/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Clam","formatEN":"[operation] FTP"}
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Clam","formatEN":"[operation] Clam"}
|
||||
func (b *BaseApi) OperateClam(c *gin.Context) {
|
||||
var req dto.Operate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
@@ -394,6 +395,7 @@ func (b *BaseApi) ContainerInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Load container limits
|
||||
// @Success 200 {object} dto.ResourceLimit
|
||||
// @Security ApiKeyAuth
|
||||
@@ -408,6 +410,7 @@ func (b *BaseApi) LoadResourceLimit(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Load container stats
|
||||
// @Success 200 {array} dto.ContainerListStats
|
||||
// @Security ApiKeyAuth
|
||||
@@ -422,6 +425,7 @@ func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Load container stats size
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
@@ -662,6 +666,14 @@ func (b *BaseApi) Inspect(c *gin.Context) {
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Download container logs
|
||||
// @Accept json
|
||||
// @Param request body dto.ContainerLog true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/download/log [post]
|
||||
func (b *BaseApi) DownloadContainerLogs(c *gin.Context) {
|
||||
var req dto.ContainerLog
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -901,6 +913,10 @@ func (b *BaseApi) LoadComposeEnv(c *gin.Context) {
|
||||
// @Security Timestamp
|
||||
// @Router /containers/search/log [get]
|
||||
func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
||||
if !strings.Contains(strings.ToLower(c.GetHeader("Accept")), "text/event-stream") {
|
||||
helper.Success(c)
|
||||
return
|
||||
}
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
|
||||
@@ -26,7 +26,7 @@ func (b *BaseApi) CreateCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cronjobService.Create(req); err != nil {
|
||||
if err := cronjobService.Create(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (b *BaseApi) ImportCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cronjobService.Import(req.Cronjobs); err != nil {
|
||||
if err := cronjobService.Import(req.Cronjobs, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -285,7 +285,7 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cronjobService.Update(req.ID, req); err != nil {
|
||||
if err := cronjobService.Update(req.ID, req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ func (b *BaseApi) SearchDatabase(c *gin.Context) {
|
||||
// @Success 200 {array} dto.DatabaseOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param type path string true "type"
|
||||
// @Router /databases/db/list/:type [get]
|
||||
func (b *BaseApi) ListDatabase(c *gin.Context) {
|
||||
dbType, err := helper.GetStrParamByKey(c, "type")
|
||||
@@ -116,6 +117,7 @@ func (b *BaseApi) ListDatabase(c *gin.Context) {
|
||||
// @Success 200 {array} dto.DatabaseItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param type path string true "type"
|
||||
// @Router /databases/db/item/:type [get]
|
||||
func (b *BaseApi) LoadDatabaseItems(c *gin.Context) {
|
||||
dbType, err := helper.GetStrParamByKey(c, "type")
|
||||
@@ -137,6 +139,7 @@ func (b *BaseApi) LoadDatabaseItems(c *gin.Context) {
|
||||
// @Success 200 {object} dto.DatabaseInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param name path string true "name"
|
||||
// @Router /databases/db/:name [get]
|
||||
func (b *BaseApi) GetDatabase(c *gin.Context) {
|
||||
name, err := helper.GetStrParamByKey(c, "name")
|
||||
|
||||
@@ -92,7 +92,7 @@ func (b *BaseApi) UpdatePostgresqlDescription(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/pg/privileges [post]
|
||||
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] 用户 [username] 权限","formatEN":"Update [user] privileges of database [database]"}
|
||||
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] 用户 [username] 权限","formatEN":"Update [username] privileges of database [database]"}
|
||||
func (b *BaseApi) ChangePostgresqlPrivileges(c *gin.Context) {
|
||||
var req dto.PostgresqlPrivileges
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -170,6 +170,7 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param database path string true "database"
|
||||
// @Router /databases/pg/:database/load [post]
|
||||
func (b *BaseApi) LoadPostgresqlDBFromRemote(c *gin.Context) {
|
||||
database, err := helper.GetStrParamByKey(c, "database")
|
||||
|
||||
@@ -74,6 +74,12 @@ func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database Redis
|
||||
// @Summary Check has cli
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/redis/check [get]
|
||||
func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
||||
helper.SuccessWithData(c, redisService.CheckHasCli())
|
||||
}
|
||||
|
||||
+35
-13
@@ -193,7 +193,7 @@ func (b *BaseApi) BatchDeleteFile(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/mode [post]
|
||||
// @x-panel-log {"bodyKeys":["path","mode"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改权限 [paths] => [mode]","formatEN":"Change mode [paths] => [mode]"}
|
||||
// @x-panel-log {"bodyKeys":["path","mode"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改权限 [path] => [mode]","formatEN":"Change mode [path] => [mode]"}
|
||||
func (b *BaseApi) ChangeFileMode(c *gin.Context) {
|
||||
var req request.FileCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -215,7 +215,7 @@ func (b *BaseApi) ChangeFileMode(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/owner [post]
|
||||
// @x-panel-log {"bodyKeys":["path","user","group"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改用户/组 [paths] => [user]/[group]","formatEN":"Change owner [paths] => [user]/[group]"}
|
||||
// @x-panel-log {"bodyKeys":["path","user","group"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改用户/组 [path] => [user]/[group]","formatEN":"Change owner [path] => [user]/[group]"}
|
||||
func (b *BaseApi) ChangeFileOwner(c *gin.Context) {
|
||||
var req request.FileRoleUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -640,6 +640,10 @@ func (b *BaseApi) MoveFile(c *gin.Context) {
|
||||
// @Router /files/download [get]
|
||||
func (b *BaseApi) Download(c *gin.Context) {
|
||||
filePath := c.Query("path")
|
||||
if files.ShouldDenySensitiveFileRead(filePath) {
|
||||
helper.InternalServer(c, buserr.New("ErrSensitiveFileRead"))
|
||||
return
|
||||
}
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -675,6 +679,10 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrPathNotFound", nil)
|
||||
return
|
||||
}
|
||||
if files.ShouldDenySensitiveFileRead(req.Path) {
|
||||
helper.InternalServer(c, buserr.New("ErrSensitiveFileRead"))
|
||||
return
|
||||
}
|
||||
filePath := req.Path
|
||||
fstFile, err := fileOp.OpenFile(filePath)
|
||||
if err != nil {
|
||||
@@ -926,7 +934,17 @@ var wsUpgrade = websocket.Upgrader{
|
||||
},
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Wget process
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/wget/process [get]
|
||||
func (b *BaseApi) WgetProcess(c *gin.Context) {
|
||||
if !websocket.IsWebSocketUpgrade(c.Request) {
|
||||
helper.Success(c)
|
||||
return
|
||||
}
|
||||
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -936,6 +954,12 @@ func (b *BaseApi) WgetProcess(c *gin.Context) {
|
||||
go wsClient.Write()
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Process keys
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/wget/process/keys [get]
|
||||
func (b *BaseApi) ProcessKeys(c *gin.Context) {
|
||||
res := &response.FileProcessKeys{}
|
||||
keys := global.CACHE.PrefixScanKey("file-wget-")
|
||||
@@ -955,16 +979,20 @@ func (b *BaseApi) ProcessKeys(c *gin.Context) {
|
||||
|
||||
// @Tags File
|
||||
// @Summary Read file by Line
|
||||
// @Param type path string true "type"
|
||||
// @Param request body request.FileReadByLineReq true "request"
|
||||
// @Success 200 {object} response.FileLineContent
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/read [post]
|
||||
// @Router /files/read/{type} [post]
|
||||
func (b *BaseApi) ReadFileByLine(c *gin.Context) {
|
||||
var req request.FileReadByLineReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if readType := strings.TrimSpace(c.Param("type")); readType != "" {
|
||||
req.Type = readType
|
||||
}
|
||||
res, err := fileService.ReadLogByLine(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
@@ -997,16 +1025,6 @@ func (b *BaseApi) BatchChangeModeAndOwner(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetPathByType(c *gin.Context) {
|
||||
pathType, ok := c.Params.Get("type")
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("error pathType id in path"))
|
||||
return
|
||||
}
|
||||
resPath := fileService.GetPathByType(pathType)
|
||||
helper.SuccessWithData(c, resPath)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary system mount
|
||||
// @Accept json
|
||||
@@ -1338,6 +1356,10 @@ func (b *BaseApi) DownloadFileShare(c *gin.Context) {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
if files.ShouldDenySensitiveFileRead(filePath) {
|
||||
helper.InternalServer(c, buserr.New("ErrSensitiveFileRead"))
|
||||
return
|
||||
}
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// @Summary Load file history list
|
||||
// @Accept json
|
||||
// @Param request body request.FileHistorySearchReq true "request"
|
||||
// @Success 200 {object} response.PageResult
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/history/search [post]
|
||||
|
||||
@@ -236,7 +236,7 @@ func (b *BaseApi) UpdateAddrRule(c *gin.Context) {
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/search [post]
|
||||
// @Router /hosts/firewall/filter/rule/search [post]
|
||||
func (b *BaseApi) SearchFilterRules(c *gin.Context) {
|
||||
var req dto.SearchPageWithType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Load gpu / xpu info
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/gpu/load [get]
|
||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||
ok, client := gpu.New()
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
xpuOK, xpuClient := xpu.New()
|
||||
if xpuOK {
|
||||
info, err := xpuClient.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, &common.GpuInfo{})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get CPU options
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/gpu/options [get]
|
||||
func (b *BaseApi) GetCPUOptions(c *gin.Context) {
|
||||
helper.SuccessWithData(c, monitorService.LoadGPUOptions())
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Load monitor data
|
||||
// @Param request body dto.MonitorGPUSearch true "request"
|
||||
// @Success 200 {object} dto.MonitorGPUData
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/gpu/search [post]
|
||||
func (b *BaseApi) LoadGPUMonitor(c *gin.Context) {
|
||||
var req dto.MonitorGPUSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := monitorService.LoadGPUMonitorData(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
@@ -5,6 +5,12 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Health
|
||||
// @Summary Check health
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /health/check [get]
|
||||
func (b *BaseApi) CheckHealth(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,15 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Create host
|
||||
// @Accept json
|
||||
// @Param request body dto.HostOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts [post]
|
||||
// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建主机 [name][addr]","formatEN":"create host [name][addr]"}
|
||||
func (b *BaseApi) CreateHost(c *gin.Context) {
|
||||
var req dto.HostOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -21,6 +30,14 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
|
||||
helper.SuccessWithData(c, host)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Test by info
|
||||
// @Accept json
|
||||
// @Param request body dto.HostConnTest true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/test/byinfo [post]
|
||||
func (b *BaseApi) TestByInfo(c *gin.Context) {
|
||||
var req dto.HostConnTest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -30,6 +47,14 @@ func (b *BaseApi) TestByInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, hostService.TestByInfo(req))
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Test by ID
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/test/byid [post]
|
||||
func (b *BaseApi) TestByID(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -39,6 +64,14 @@ func (b *BaseApi) TestByID(c *gin.Context) {
|
||||
helper.SuccessWithData(c, hostService.TestLocalConn(req.ID))
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Host tree
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchForTree true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tree [post]
|
||||
func (b *BaseApi) HostTree(c *gin.Context) {
|
||||
var req dto.SearchForTree
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -53,6 +86,14 @@ func (b *BaseApi) HostTree(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Search host
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchPageWithGroup true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/search [post]
|
||||
func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
var req dto.SearchPageWithGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -68,6 +109,15 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
helper.SuccessWithData(c, dto.PageResult{Items: list, Total: total})
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Delete host
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByIDs true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"hosts","output_column":"name","output_value":"names"}],"formatZH":"删除主机 [names]","formatEN":"delete host [names]"}
|
||||
func (b *BaseApi) DeleteHost(c *gin.Context) {
|
||||
var req dto.OperateByIDs
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -81,6 +131,15 @@ func (b *BaseApi) DeleteHost(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Update host
|
||||
// @Accept json
|
||||
// @Param request body dto.HostOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/update [post]
|
||||
// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新主机 [name][addr]","formatEN":"update host [name][addr]"}
|
||||
func (b *BaseApi) UpdateHost(c *gin.Context) {
|
||||
var req dto.HostOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -140,6 +199,15 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
|
||||
helper.SuccessWithData(c, hostItem)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Update host group
|
||||
// @Accept json
|
||||
// @Param request body dto.ChangeGroup true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/update/group [post]
|
||||
// @x-panel-log {"bodyKeys":["id","groupID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"hosts","output_column":"name","output_value":"name"}],"formatZH":"更新主机 [name] 分组","formatEN":"update host [name] group"}
|
||||
func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
|
||||
var req dto.ChangeGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -153,6 +221,14 @@ func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Get host by ID
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/info [post]
|
||||
func (b *BaseApi) GetHostByID(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool status
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolReq true "request"
|
||||
// @Param request body request.HostToolTypeReq true "request"
|
||||
// @Success 200 {object} response.HostToolRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tool [post]
|
||||
// @Router /hosts/tool/status [post]
|
||||
func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
var req request.HostToolTypeReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -53,14 +53,14 @@ func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
// @Tags Host tool
|
||||
// @Summary Operate tool
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolReq true "request"
|
||||
// @Param request body request.HostToolOperateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tool/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
|
||||
func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
var req request.HostToolOperateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -75,19 +75,18 @@ func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool config
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolConfig true "request"
|
||||
// @Param request body request.HostToolTypeReq true "request"
|
||||
// @Success 200 {object} response.HostToolConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tool/config [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
|
||||
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
var req request.HostToolConfig
|
||||
// @Router /hosts/tool/config/get [post]
|
||||
func (b *BaseApi) GetToolConfig(c *gin.Context) {
|
||||
var req request.HostToolTypeReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config, err := hostToolService.OperateToolConfig(req)
|
||||
config, err := hostToolService.GetToolConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -95,6 +94,28 @@ func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
helper.SuccessWithData(c, config)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Update tool config
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolConfigUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tool/config/set [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 [type] 主机工具配置文件 ","formatEN":"update [type] tool config"}
|
||||
func (b *BaseApi) UpdateToolConfig(c *gin.Context) {
|
||||
var req request.HostToolConfigUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := hostToolService.UpdateToolConfig(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Create Supervisor process
|
||||
// @Accept json
|
||||
@@ -137,18 +158,44 @@ func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
// @Tags Host tool
|
||||
// @Summary Get Supervisor process config file
|
||||
// @Accept json
|
||||
// @Param request body request.SupervisorProcessFileReq true "request"
|
||||
// @Param request body request.HostSupervisorProcessFileGetReq true "request"
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tool/supervisor/process/file [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
|
||||
// @Router /hosts/tool/supervisor/process/file/get [post]
|
||||
func (b *BaseApi) GetProcessFile(c *gin.Context) {
|
||||
var req request.SupervisorProcessFileReq
|
||||
var req request.HostSupervisorProcessFileGetReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
content, err := hostToolService.OperateSupervisorProcessFile(req)
|
||||
content, err := hostToolService.GetSupervisorProcessFile(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Operate Supervisor process config file
|
||||
// @Accept json
|
||||
// @Param request body request.HostSupervisorProcessFileOperateReq true "request"
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tool/supervisor/process/file [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
|
||||
func (b *BaseApi) OperateProcessFile(c *gin.Context) {
|
||||
var req request.HostSupervisorProcessFileOperateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
content, err := hostToolService.OperateSupervisorProcessFile(request.SupervisorProcessFileReq{
|
||||
Name: req.Name,
|
||||
Operate: req.Operate,
|
||||
Content: req.Content,
|
||||
File: req.File,
|
||||
})
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
|
||||
@@ -23,6 +23,27 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Load mcp server detail
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerDetail true "request"
|
||||
// @Success 200 {object} response.McpServerDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/mcp/server/detail [post]
|
||||
func (b *BaseApi) LoadMcpServerDetail(c *gin.Context) {
|
||||
var req request.McpServerDetail
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := mcpServerService.Detail(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Create mcp server
|
||||
// @Accept json
|
||||
@@ -107,6 +128,48 @@ func (b *BaseApi) OperateMcpServer(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Sync mcp server status
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerStatusSync true "request"
|
||||
// @Success 200 {array} response.McpServerStatusDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/mcp/server/status/sync [post]
|
||||
func (b *BaseApi) SyncMcpServerStatus(c *gin.Context) {
|
||||
var req request.McpServerStatusSync
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := mcpServerService.SyncStatus(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Test mcp server connection
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerConnectionTest true "request"
|
||||
// @Success 200 {object} response.McpServerConnectionTestRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/mcp/server/connection/test [post]
|
||||
func (b *BaseApi) TestMcpServerConnection(c *gin.Context) {
|
||||
var req request.McpServerConnectionTest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := mcpServerService.TestConnection(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Bind Domain for mcp server
|
||||
// @Accept json
|
||||
|
||||
+13
-26
@@ -31,27 +31,6 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
helper.SuccessWithDataGzipped(c, data)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Load monitor data
|
||||
// @Param request body dto.MonitorGPUSearch true "request"
|
||||
// @Success 200 {object} dto.MonitorGPUData
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/gpu/search [post]
|
||||
func (b *BaseApi) LoadGPUMonitor(c *gin.Context) {
|
||||
var req dto.MonitorGPUSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := monitorService.LoadGPUMonitorData(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Clean monitor data
|
||||
// @Success 200
|
||||
@@ -91,7 +70,7 @@ func (b *BaseApi) LoadMonitorSetting(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/setting/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改默认监控网卡 [name]-[value]","formatEN":"update default monitor [name]-[value]"}
|
||||
// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改默认监控网卡 [key]-[value]","formatEN":"update default monitor [key]-[value]"}
|
||||
func (b *BaseApi) UpdateMonitorSetting(c *gin.Context) {
|
||||
var req dto.MonitorSettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -105,6 +84,12 @@ func (b *BaseApi) UpdateMonitorSetting(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Get network options
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/netoptions [get]
|
||||
func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
netStat, _ := net.IOCounters(true)
|
||||
var options []string
|
||||
@@ -116,6 +101,12 @@ func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
helper.SuccessWithData(c, options)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Get IO options
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/iooptions [get]
|
||||
func (b *BaseApi) GetIOOptions(c *gin.Context) {
|
||||
diskStat, _ := disk.IOCounters()
|
||||
var options []string
|
||||
@@ -126,7 +117,3 @@ func (b *BaseApi) GetIOOptions(c *gin.Context) {
|
||||
sort.Strings(options)
|
||||
helper.SuccessWithData(c, options)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetCPUOptions(c *gin.Context) {
|
||||
helper.SuccessWithData(c, monitorService.LoadGPUOptions())
|
||||
}
|
||||
|
||||
@@ -5,9 +5,20 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
websocket2 "github.com/1Panel-dev/1Panel/agent/utils/websocket"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// @Tags Process
|
||||
// @Summary Process ws
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /process/ws [get]
|
||||
func (b *BaseApi) ProcessWs(c *gin.Context) {
|
||||
if !websocket.IsWebSocketUpgrade(c.Request) {
|
||||
helper.Success(c)
|
||||
return
|
||||
}
|
||||
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -61,7 +61,7 @@ func (b *BaseApi) CreateRuntime(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除运行环境 [name]","formatEN":"Delete runtime [name]"}
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除运行环境 [id]","formatEN":"Delete runtime [id]"}
|
||||
func (b *BaseApi) DeleteRuntime(c *gin.Context) {
|
||||
var req request.RuntimeDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -81,6 +81,7 @@ func (b *BaseApi) DeleteRuntime(c *gin.Context) {
|
||||
// @Success 200 {array} dto.AppResource
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param id path integer true "id"
|
||||
// @Router /runtimes/installed/delete/check/:id [get]
|
||||
func (b *BaseApi) DeleteRuntimeCheck(c *gin.Context) {
|
||||
runTimeId, err := helper.GetIntParamByKey(c, "id")
|
||||
@@ -120,7 +121,7 @@ func (b *BaseApi) UpdateRuntime(c *gin.Context) {
|
||||
// @Tags Runtime
|
||||
// @Summary Get runtime
|
||||
// @Accept json
|
||||
// @Param id path string true "request"
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} response.RuntimeDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -168,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [id]","formatEN":"Operate runtime [id]"}
|
||||
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -242,7 +243,7 @@ func (b *BaseApi) SyncStatus(c *gin.Context) {
|
||||
// @Tags Runtime
|
||||
// @Summary Get php runtime extension
|
||||
// @Accept json
|
||||
// @Param id path string true "request"
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} response.PHPExtensionRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
|
||||
+51
-30
@@ -1,16 +1,12 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// @Tags System Setting
|
||||
@@ -28,6 +24,12 @@ func (b *BaseApi) GetSettingInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, setting)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Get terminal AI setting info
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/terminal/ai/search [post]
|
||||
func (b *BaseApi) GetTerminalAISettingInfo(c *gin.Context) {
|
||||
setting, err := settingService.GetTerminalAIInfo()
|
||||
if err != nil {
|
||||
@@ -50,14 +52,14 @@ func (b *BaseApi) GetSystemAvailable(c *gin.Context) {
|
||||
// @Tags System Setting
|
||||
// @Summary Update system setting
|
||||
// @Accept json
|
||||
// @Param request body dto.SettingUpdate true "request"
|
||||
// @Param request body dto.AgentSettingUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统配置 [key] => [value]","formatEN":"update system setting [key] => [value]"}
|
||||
func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
var req dto.SettingUpdate
|
||||
var req dto.AgentSettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -69,6 +71,15 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update terminal AI setting
|
||||
// @Accept json
|
||||
// @Param request body dto.TerminalAIInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/terminal/ai/update [post]
|
||||
// @x-panel-log {"bodyKeys":["aiStatus","aiAccountId"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新终端 AI 设置 [aiStatus][aiAccountId]","formatEN":"update terminal AI setting [aiStatus][aiAccountId]"}
|
||||
func (b *BaseApi) UpdateTerminalAISetting(c *gin.Context) {
|
||||
var req dto.TerminalAIInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -82,6 +93,12 @@ func (b *BaseApi) UpdateTerminalAISetting(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Get file manage AI setting info
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/files/ai/search [post]
|
||||
func (b *BaseApi) GetFileManageAISettingInfo(c *gin.Context) {
|
||||
setting, err := settingService.GetFileManageAIInfo()
|
||||
if err != nil {
|
||||
@@ -91,6 +108,15 @@ func (b *BaseApi) GetFileManageAISettingInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, setting)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update file manage AI setting
|
||||
// @Accept json
|
||||
// @Param request body dto.FileManageAIInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/files/ai/update [post]
|
||||
// @x-panel-log {"bodyKeys":["aiStatus","aiAccountId"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新文件管理 AI 设置 [aiStatus][aiAccountId]","formatEN":"update file manage AI setting [aiStatus][aiAccountId]"}
|
||||
func (b *BaseApi) UpdateFileManageAISetting(c *gin.Context) {
|
||||
var req dto.FileManageAIInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -138,6 +164,16 @@ func (b *BaseApi) UpdateFileHistorySetting(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load website dir
|
||||
// @Success 200 {string} path
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/website/dir [get]
|
||||
func (b *BaseApi) LoadWebsiteDir(c *gin.Context) {
|
||||
helper.SuccessWithData(c, settingService.GetWebsiteDir())
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load local backup dir
|
||||
// @Success 200 {string} path
|
||||
@@ -158,6 +194,12 @@ func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
||||
helper.SuccessWithData(c, settingService.GetLocalConn())
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Check local conn
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/check [post]
|
||||
func (b *BaseApi) CheckLocalConn(c *gin.Context) {
|
||||
client, err := loadLocalConn()
|
||||
if err == nil && client != nil {
|
||||
@@ -173,7 +215,7 @@ func (b *BaseApi) CheckLocalConn(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/conn/default [post]
|
||||
// @Router /settings/ssh/default [post]
|
||||
// @x-panel-log {"bodyKeys":["defaultConn"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"本地终端默认连接 [defaultConn]","formatEN":"update system default conn [defaultConn]"}
|
||||
func (b *BaseApi) SetDefaultIsConn(c *gin.Context) {
|
||||
var req dto.SSHDefaultConn
|
||||
@@ -223,12 +265,8 @@ func (b *BaseApi) SaveLocalConn(c *gin.Context) {
|
||||
}
|
||||
|
||||
func loadLocalConn() (*ssh.SSHClient, error) {
|
||||
connInfoInDB := settingService.GetSettingByKey("LocalSSHConn")
|
||||
if len(connInfoInDB) == 0 {
|
||||
return nil, errors.New("no such ssh conn info in db!")
|
||||
}
|
||||
var connInDB model.LocalConnInfo
|
||||
if err := json.Unmarshal([]byte(connInfoInDB), &connInDB); err != nil {
|
||||
connInDB, err := settingService.GetLocalConnForSSH()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshInfo := ssh.ConnInfo{
|
||||
@@ -243,23 +281,6 @@ func loadLocalConn() (*ssh.SSHClient, error) {
|
||||
return ssh.NewClient(sshInfo)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system setting by key
|
||||
// @Param key path string true "key"
|
||||
// @Success 200 {object} dto.SettingInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/get/{key} [get]
|
||||
func (b *BaseApi) GetSettingByKey(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
if len(key) == 0 {
|
||||
helper.BadRequest(c, errors.New("key is empty"))
|
||||
return
|
||||
}
|
||||
value := settingService.GetSettingByKey(key)
|
||||
helper.SuccessWithData(c, value)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Save common description
|
||||
// @Accept json
|
||||
|
||||
@@ -3,6 +3,8 @@ package v2
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -30,6 +32,31 @@ func (b *BaseApi) PageTasks(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags TaskLog
|
||||
// @Summary Read task log by Line
|
||||
// @Param request body request.TaskLogReadReq true "request"
|
||||
// @Success 200 {object} response.FileLineContent
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/tasks/read [post]
|
||||
func (b *BaseApi) ReadTaskLogByLine(c *gin.Context) {
|
||||
var req request.TaskLogReadReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
var res *response.FileLineContent
|
||||
res, err := taskService.ReadByLine(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
if res.TotalLines > 100 {
|
||||
helper.SuccessWithDataGzipped(c, res)
|
||||
} else {
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
}
|
||||
|
||||
// @Tags TaskLog
|
||||
// @Summary Get the number of executing tasks
|
||||
// @Success 200 {integer} int64
|
||||
|
||||
@@ -6,6 +6,14 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags TensorRT LLM
|
||||
// @Summary Page TensorRT LLMs
|
||||
// @Accept json
|
||||
// @Param request body request.TensorRTLLMSearch true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/tensorrt/search [post]
|
||||
func (b *BaseApi) PageTensorRTLLMs(c *gin.Context) {
|
||||
var req request.TensorRTLLMSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -15,6 +23,15 @@ func (b *BaseApi) PageTensorRTLLMs(c *gin.Context) {
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags TensorRT LLM
|
||||
// @Summary Create TensorRT LLM
|
||||
// @Accept json
|
||||
// @Param request body request.TensorRTLLMCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/tensorrt/create [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 TensorRT LLM [name]","formatEN":"create TensorRT LLM [name]"}
|
||||
func (b *BaseApi) CreateTensorRTLLM(c *gin.Context) {
|
||||
var req request.TensorRTLLMCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -28,6 +45,15 @@ func (b *BaseApi) CreateTensorRTLLM(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags TensorRT LLM
|
||||
// @Summary Update TensorRT LLM
|
||||
// @Accept json
|
||||
// @Param request body request.TensorRTLLMUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/tensorrt/update [post]
|
||||
// @x-panel-log {"bodyKeys":["id","name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 TensorRT LLM [id][name]","formatEN":"update TensorRT LLM [id][name]"}
|
||||
func (b *BaseApi) UpdateTensorRTLLM(c *gin.Context) {
|
||||
var req request.TensorRTLLMUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -41,6 +67,15 @@ func (b *BaseApi) UpdateTensorRTLLM(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags TensorRT LLM
|
||||
// @Summary Delete TensorRT LLM
|
||||
// @Accept json
|
||||
// @Param request body request.TensorRTLLMDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/tensorrt/delete [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 TensorRT LLM [id]","formatEN":"delete TensorRT LLM [id]"}
|
||||
func (b *BaseApi) DeleteTensorRTLLM(c *gin.Context) {
|
||||
var req request.TensorRTLLMDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -54,6 +89,15 @@ func (b *BaseApi) DeleteTensorRTLLM(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags TensorRT LLM
|
||||
// @Summary Operate TensorRT LLM
|
||||
// @Accept json
|
||||
// @Param request body request.TensorRTLLMOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/tensorrt/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["id","operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作 TensorRT LLM [id][operate]","formatEN":"operate TensorRT LLM [id][operate]"}
|
||||
func (b *BaseApi) OperateTensorRTLLM(c *gin.Context) {
|
||||
var req request.TensorRTLLMOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
+144
-105
@@ -8,7 +8,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
@@ -19,117 +21,52 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (b *BaseApi) WsSSH(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.Base.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
|
||||
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
var client *ssh.SSHClient
|
||||
if hostID > 0 {
|
||||
host, err := service.GetHostInfo(uint(hostID))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
|
||||
return
|
||||
}
|
||||
connInfo := ssh.ConnInfo{
|
||||
Addr: host.Addr,
|
||||
Port: int(host.Port),
|
||||
User: host.User,
|
||||
AuthMode: host.AuthMode,
|
||||
Password: host.Password,
|
||||
PrivateKey: []byte(host.PrivateKey),
|
||||
}
|
||||
if len(host.PassPhrase) != 0 {
|
||||
connInfo.PassPhrase = []byte(host.PassPhrase)
|
||||
}
|
||||
client, err = ssh.NewClient(connInfo)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
client, err = loadLocalConn()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
defer client.Close()
|
||||
command := c.DefaultQuery("command", "")
|
||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer sws.Close()
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
sws.Start(quitChan)
|
||||
go sws.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
// @Tags Terminal
|
||||
// @Summary Ws local terminal
|
||||
// @Param command query string false "command"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/local [get]
|
||||
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
||||
b.runSSHSession(c, loadLocalConn, c.DefaultQuery("command", ""))
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
// @Tags Terminal
|
||||
// @Summary Ws host SSH
|
||||
// @Param id query integer false "id"
|
||||
// @Param command query string false "command"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/ssh [get]
|
||||
func (b *BaseApi) WsHostSSH(c *gin.Context) {
|
||||
b.runSSHSession(c, func() (*ssh.SSHClient, error) {
|
||||
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
if hostID <= 0 {
|
||||
return nil, errors.New("missing host id")
|
||||
}
|
||||
host, err := service.GetHostInfo(uint(hostID))
|
||||
return newHostSSHClient(host, err)
|
||||
}, c.DefaultQuery("command", ""))
|
||||
}
|
||||
|
||||
// @Tags Terminal
|
||||
// @Summary Ws container terminal
|
||||
// @Param cols query integer false "cols"
|
||||
// @Param rows query integer false "rows"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/terminal/container [get]
|
||||
func (b *BaseApi) WsContainerTerminal(c *gin.Context) {
|
||||
wsConn, cols, rows, ok := prepareTerminalSession(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.Base.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
source := c.Query("source")
|
||||
var initCmd []string
|
||||
switch source {
|
||||
case "redis", "redis-cluster":
|
||||
initCmd, err = loadRedisInitCmd(c, source)
|
||||
case "ollama":
|
||||
initCmd, err = loadOllamaInitCmd(c)
|
||||
case "container":
|
||||
initCmd, err = loadContainerInitCmd(c)
|
||||
case "database":
|
||||
initCmd, err = loadDatabaseInitCmd(c)
|
||||
default:
|
||||
if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
slave, err := terminal.NewCommand("docker", initCmd...)
|
||||
slave, err := loadContainerTerminalCommand(c)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
@@ -147,10 +84,112 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
closeTerminalConn(wsConn)
|
||||
}
|
||||
|
||||
func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||
if !websocket.IsWebSocketUpgrade(c.Request) {
|
||||
helper.Success(c)
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
|
||||
if global.CONF.Base.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
}
|
||||
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return nil, 0, 0, false
|
||||
}
|
||||
return wsConn, cols, rows, true
|
||||
}
|
||||
|
||||
func (b *BaseApi) runSSHSession(c *gin.Context, connect func() (*ssh.SSHClient, error), command string) {
|
||||
wsConn, cols, rows, ok := prepareTerminalSession(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
client, clientErr := connect()
|
||||
if wshandleError(wsConn, errors.WithMessage(clientErr, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer sws.Close()
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
sws.Start(quitChan)
|
||||
go sws.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
closeTerminalConn(wsConn)
|
||||
}
|
||||
|
||||
func closeTerminalConn(wsConn *websocket.Conn) {
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
}
|
||||
|
||||
func newHostSSHClient(host *model.Host, err error) (*ssh.SSHClient, error) {
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "load host info by id failed")
|
||||
}
|
||||
connInfo := ssh.ConnInfo{
|
||||
Addr: host.Addr,
|
||||
Port: int(host.Port),
|
||||
User: host.User,
|
||||
AuthMode: host.AuthMode,
|
||||
Password: host.Password,
|
||||
PrivateKey: []byte(host.PrivateKey),
|
||||
}
|
||||
if len(host.PassPhrase) != 0 {
|
||||
connInfo.PassPhrase = []byte(host.PassPhrase)
|
||||
}
|
||||
return ssh.NewClient(connInfo)
|
||||
}
|
||||
|
||||
func loadContainerTerminalCommand(c *gin.Context) (*terminal.LocalCommand, error) {
|
||||
source := c.Query("source")
|
||||
var (
|
||||
initCmd []string
|
||||
err error
|
||||
)
|
||||
switch source {
|
||||
case "redis", "redis-cluster":
|
||||
initCmd, err = loadRedisInitCmd(c, source)
|
||||
case "ollama":
|
||||
initCmd, err = loadOllamaInitCmd(c)
|
||||
case "container":
|
||||
initCmd, err = loadContainerInitCmd(c)
|
||||
case "database":
|
||||
initCmd, err = loadDatabaseInitCmd(c)
|
||||
default:
|
||||
return nil, fmt.Errorf("not support such source %s", source)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return terminal.NewCommand("docker", initCmd...)
|
||||
}
|
||||
|
||||
func loadRedisInitCmd(c *gin.Context, redisType string) ([]string, error) {
|
||||
name := c.Query("name")
|
||||
from := c.Query("from")
|
||||
|
||||
@@ -181,6 +181,7 @@ func (b *BaseApi) GetWebsite(c *gin.Context) {
|
||||
// @Success 200 {object} response.FileInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param type path string true "type"
|
||||
// @Router /websites/:id/config/:type [get]
|
||||
func (b *BaseApi) GetWebsiteNginx(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
@@ -269,6 +270,7 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) {
|
||||
// @Success 200 {object} response.WebsiteHTTPS
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param id path string true "id"
|
||||
// @Router /websites/:id/https [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] https 配置","formatEN":"Update website https [domain] conf"}
|
||||
func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) {
|
||||
@@ -330,25 +332,45 @@ func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Operate website log
|
||||
// @Summary Get website log
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteLogReq true "request"
|
||||
// @Param request body request.WebsiteLogSearchReq true "request"
|
||||
// @Success 200 {object} response.WebsiteLog
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/log [post]
|
||||
// @Router /websites/log/search [post]
|
||||
func (b *BaseApi) GetWebsiteLog(c *gin.Context) {
|
||||
var req request.WebsiteLogSearchReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := websiteService.GetWebsiteLog(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Operate website log
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteLogReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/log/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain][operate] 日志","formatEN":"[domain][operate] logs"}
|
||||
func (b *BaseApi) OpWebsiteLog(c *gin.Context) {
|
||||
var req request.WebsiteLogReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := websiteService.OpWebsiteLog(req)
|
||||
if err != nil {
|
||||
if err := websiteService.OpWebsiteLog(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
@@ -799,6 +821,7 @@ func (b *BaseApi) GetDirConfig(c *gin.Context) {
|
||||
// @Success 200 {object} response.WebsiteHtmlRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param type path string true "type"
|
||||
// @Router /websites/default/html/:type [get]
|
||||
func (b *BaseApi) GetDefaultHtml(c *gin.Context) {
|
||||
resourceType, err := helper.GetStrParamByKey(c, "type")
|
||||
@@ -838,11 +861,11 @@ func (b *BaseApi) UpdateDefaultHtml(c *gin.Context) {
|
||||
// @Tags Website
|
||||
// @Summary Get website upstreams
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteCommonReq true "request"
|
||||
// @Param id path integer true "id"
|
||||
// @Success 200 {array} dto.NginxUpstream
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/lbs [get]
|
||||
// @Router /websites/{id}/lbs [get]
|
||||
func (b *BaseApi) GetLoadBalances(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
@@ -937,6 +960,15 @@ func (b *BaseApi) UpdateLoadBalanceFile(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Change website group
|
||||
// @Accept json
|
||||
// @Param request body dto.UpdateGroup true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/group/change [post]
|
||||
// @x-panel-log {"bodyKeys":["group","newGroup"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"网站分组 [group] 迁移到 [newGroup]","formatEN":"change website group [group] to [newGroup]"}
|
||||
func (b *BaseApi) ChangeWebsiteGroup(c *gin.Context) {
|
||||
var req dto.UpdateGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -969,6 +1001,7 @@ func (b *BaseApi) UpdateProxyCache(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Get website proxy cache config
|
||||
// @Accept json
|
||||
// @Param id path int true "id"
|
||||
@@ -1295,7 +1328,7 @@ func (b *BaseApi) UpdateStreamConfig(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/batch/https [post]
|
||||
// @Router /websites/batch/ssl [post]
|
||||
func (b *BaseApi) BatchSetHttps(c *gin.Context) {
|
||||
var req request.BatchWebsiteHttps
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -210,6 +210,27 @@ func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
// @Summary Push ssl to nodes
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteSSLPush true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/ssl/push [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"推送证书到节点 [domain]","formatEN":"Push ssl to nodes [domain]"}
|
||||
func (b *BaseApi) PushWebsiteSSLToNode(c *gin.Context) {
|
||||
var req request.WebsiteSSLPush
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteSSLService.PushToNode(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
// @Summary Upload ssl
|
||||
// @Accept json
|
||||
@@ -248,6 +269,8 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
|
||||
var req request.WebsiteSSLFileUpload
|
||||
|
||||
req.Description = c.PostForm("description")
|
||||
req.Nodes = c.PostForm("nodes")
|
||||
req.PushNode, _ = strconv.ParseBool(c.PostForm("pushNode"))
|
||||
sslID := c.PostForm("sslID")
|
||||
if sslID != "" {
|
||||
req.SSLID, _ = strconv.ParseUint(sslID, 10, 64)
|
||||
@@ -283,6 +306,8 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
|
||||
Certificate: string(certificateContent),
|
||||
Description: req.Description,
|
||||
SSLID: uint(req.SSLID),
|
||||
PushNode: req.PushNode,
|
||||
Nodes: req.Nodes,
|
||||
}
|
||||
|
||||
if err := websiteSSLService.Upload(uploadReq); err != nil {
|
||||
@@ -333,6 +358,15 @@ func (b *BaseApi) DownloadWebsiteSSL(c *gin.Context) {
|
||||
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
// @Summary Import master SSL
|
||||
// @Accept json
|
||||
// @Param request body model.WebsiteSSL true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/ssl/import [post]
|
||||
// @x-panel-log {"bodyKeys":["primaryDomain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"导入主节点证书 [primaryDomain]","formatEN":"import master SSL [primaryDomain]"}
|
||||
func (b *BaseApi) ImportMasterSSL(c *gin.Context) {
|
||||
var req model.WebsiteSSL
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
+136
-48
@@ -3,28 +3,110 @@ package dto
|
||||
import "time"
|
||||
|
||||
type AgentCreateReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
AppVersion string `json:"appVersion" validate:"required"`
|
||||
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
AllowedOrigins []string `json:"allowedOrigins"`
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
||||
Model string `json:"model"`
|
||||
AccountID uint `json:"accountId"`
|
||||
Token string `json:"token"`
|
||||
TaskID string `json:"taskID"`
|
||||
Advanced bool `json:"advanced"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AllowPort bool `json:"allowPort"`
|
||||
SpecifyIP string `json:"specifyIP"`
|
||||
RestartPolicy string `json:"restartPolicy"`
|
||||
CpuQuota float64 `json:"cpuQuota"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
MemoryUnit string `json:"memoryUnit"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
EditCompose bool `json:"editCompose"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
AppVersion string `json:"appVersion" validate:"required"`
|
||||
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
AllowedOrigins []string `json:"allowedOrigins"`
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
||||
Model string `json:"model"`
|
||||
AccountID uint `json:"accountId"`
|
||||
Token string `json:"token"`
|
||||
DashboardUsername string `json:"dashboardUsername"`
|
||||
DashboardPassword string `json:"dashboardPassword"`
|
||||
TaskID string `json:"taskID"`
|
||||
Advanced bool `json:"advanced"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AllowPort bool `json:"allowPort"`
|
||||
SpecifyIP string `json:"specifyIP"`
|
||||
RestartPolicy string `json:"restartPolicy"`
|
||||
CpuQuota float64 `json:"cpuQuota"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
MemoryUnit string `json:"memoryUnit"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
EditCompose bool `json:"editCompose"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
}
|
||||
|
||||
type AgentBatchInstallReq struct {
|
||||
Name string `json:"name"`
|
||||
Remark string `json:"remark"`
|
||||
AppVersion string `json:"appVersion" validate:"required"`
|
||||
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
AllowedOrigins []string `json:"allowedOrigins"`
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
||||
Model string `json:"model"`
|
||||
AccountID uint `json:"accountId"`
|
||||
Token string `json:"token"`
|
||||
DashboardUsername string `json:"dashboardUsername"`
|
||||
DashboardPassword string `json:"dashboardPassword"`
|
||||
TaskID string `json:"taskID"`
|
||||
Advanced bool `json:"advanced"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AllowPort bool `json:"allowPort"`
|
||||
SpecifyIP string `json:"specifyIP"`
|
||||
RestartPolicy string `json:"restartPolicy"`
|
||||
CpuQuota float64 `json:"cpuQuota"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
MemoryUnit string `json:"memoryUnit"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
EditCompose bool `json:"editCompose"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
MasterAccountID uint `json:"masterAccountId"`
|
||||
FallbackAccessHost string `json:"fallbackAccessHost"`
|
||||
AccountSnapshot AgentAccountInfo `json:"accountSnapshot"`
|
||||
AccountModels []AgentAccountModel `json:"accountModels"`
|
||||
}
|
||||
|
||||
type AgentBatchUpgradeReq struct {
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
||||
TargetVersion string `json:"targetVersion" validate:"required"`
|
||||
Backup bool `json:"backup"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type AgentBatchUpgradeResult struct {
|
||||
AgentID uint `json:"agentID"`
|
||||
AgentName string `json:"agentName"`
|
||||
AppInstallID uint `json:"appInstallID"`
|
||||
Success bool `json:"success"`
|
||||
Skipped bool `json:"skipped"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AgentBatchSkillInstallReq struct {
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw hermes-agent"`
|
||||
SkillName string `json:"skillName" validate:"required"`
|
||||
PackagePath string `json:"packagePath" validate:"required"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type AgentBatchSkillInstallResult struct {
|
||||
AgentID uint `json:"agentID"`
|
||||
AgentName string `json:"agentName"`
|
||||
AppInstallID uint `json:"appInstallID"`
|
||||
Success bool `json:"success"`
|
||||
Skipped bool `json:"skipped"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AgentBatchOperateReq struct {
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
||||
Operate string `json:"operate" validate:"required,oneof=start stop restart delete"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type AgentBatchOperateResult struct {
|
||||
AgentID uint `json:"agentID"`
|
||||
AgentName string `json:"agentName"`
|
||||
AppInstallID uint `json:"appInstallID"`
|
||||
Success bool `json:"success"`
|
||||
Skipped bool `json:"skipped"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AgentItem struct {
|
||||
@@ -41,6 +123,8 @@ type AgentItem struct {
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Token string `json:"token"`
|
||||
DashboardUsername string `json:"dashboardUsername"`
|
||||
DashboardPassword string `json:"dashboardPassword"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
@@ -274,19 +358,24 @@ type AgentAccountSearch struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AgentAccountProviderCountReq struct {
|
||||
Providers []string `json:"providers"`
|
||||
}
|
||||
|
||||
type AgentAccountInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Models []AgentAccountModel `json:"models"`
|
||||
APIType string `json:"apiType"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ID uint `json:"id"`
|
||||
MasterAccountID uint `json:"masterAccountId"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Models []AgentAccountModel `json:"models"`
|
||||
APIType string `json:"apiType"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ProviderModelInfo struct {
|
||||
@@ -324,11 +413,6 @@ type AgentFeishuConfigUpdateReq struct {
|
||||
Bots []AgentFeishuBot `json:"bots" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type AgentFeishuPairingApproveReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
PairingCode string `json:"pairingCode" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentFeishuConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ThreadSession bool `json:"threadSession"`
|
||||
@@ -577,16 +661,20 @@ type AgentSecurityConfig struct {
|
||||
}
|
||||
|
||||
type AgentOtherConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
UserTimezone string `json:"userTimezone" validate:"required"`
|
||||
BrowserEnabled bool `json:"browserEnabled"`
|
||||
NPMRegistry string `json:"npmRegistry" validate:"required"`
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
UserTimezone string `json:"userTimezone" validate:"required"`
|
||||
BrowserEnabled bool `json:"browserEnabled"`
|
||||
NPMRegistry string `json:"npmRegistry" validate:"required"`
|
||||
DashboardUsername string `json:"dashboardUsername"`
|
||||
DashboardPassword string `json:"dashboardPassword"`
|
||||
}
|
||||
|
||||
type AgentOtherConfig struct {
|
||||
UserTimezone string `json:"userTimezone"`
|
||||
BrowserEnabled bool `json:"browserEnabled"`
|
||||
NPMRegistry string `json:"npmRegistry"`
|
||||
UserTimezone string `json:"userTimezone"`
|
||||
BrowserEnabled bool `json:"browserEnabled"`
|
||||
NPMRegistry string `json:"npmRegistry"`
|
||||
DashboardUsername string `json:"dashboardUsername"`
|
||||
DashboardPassword string `json:"dashboardPassword"`
|
||||
}
|
||||
|
||||
type AgentConfigFileReq struct {
|
||||
@@ -604,7 +692,7 @@ type AgentConfigFile struct {
|
||||
|
||||
type AgentSkillSearchReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh local-hub"`
|
||||
Keyword string `json:"keyword" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -641,7 +729,7 @@ type AgentSkillUpdateReq struct {
|
||||
|
||||
type AgentSkillInstallReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh local-hub"`
|
||||
Slug string `json:"slug" validate:"required"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
+19
-6
@@ -2,8 +2,9 @@ package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
type CreateOrUpdateAlert struct {
|
||||
@@ -35,6 +36,15 @@ type AlertSearch struct {
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
type AlertConfigQuery struct {
|
||||
ExcludeTypes []string `json:"excludeTypes"`
|
||||
}
|
||||
|
||||
type AlertConfigPageReq struct {
|
||||
PageInfo
|
||||
ExcludeTypes []string `json:"excludeTypes"`
|
||||
}
|
||||
|
||||
type AlertDTO struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
@@ -46,6 +56,8 @@ type AlertDTO struct {
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
CreateUser string `json:"createUser"`
|
||||
UpdateUser string `json:"updateUser"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -281,11 +293,12 @@ type OfflineQueryRequest struct {
|
||||
}
|
||||
|
||||
type AlertConfigUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Config string `json:"config"`
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Config string `json:"config"`
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
|
||||
type AlertConfigTest struct {
|
||||
|
||||
@@ -132,6 +132,7 @@ type Locale struct {
|
||||
Ko string `json:"ko"`
|
||||
Tr string `json:"tr"`
|
||||
Es string `json:"es-es" yaml:"es-es"`
|
||||
Fa string `json:"fa"`
|
||||
}
|
||||
|
||||
type AppForm struct {
|
||||
|
||||
@@ -65,14 +65,15 @@ type UploadForRecover struct {
|
||||
}
|
||||
|
||||
type CommonBackup struct {
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mongodb mysql-cluster postgresql-cluster redis-cluster container compose"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
Secret string `json:"secret"`
|
||||
StopBefore bool `json:"stopBefore"`
|
||||
TaskID string `json:"taskID"`
|
||||
FileName string `json:"fileName"`
|
||||
Args []string `json:"args"`
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mongodb mysql-cluster postgresql-cluster redis-cluster container compose"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
Secret string `json:"secret"`
|
||||
IsImmediate bool `json:"isImmediate"`
|
||||
StopBefore bool `json:"stopBefore"`
|
||||
TaskID string `json:"taskID"`
|
||||
FileName string `json:"fileName"`
|
||||
Args []string `json:"args"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ type ComposeCreate struct {
|
||||
type ComposeOperation struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete rebuild"`
|
||||
WithFile bool `json:"withFile"`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
|
||||
@@ -79,8 +79,9 @@ type NodeCurrent struct {
|
||||
}
|
||||
|
||||
type DashboardCurrent struct {
|
||||
Uptime uint64 `json:"uptime"`
|
||||
TimeSinceUptime string `json:"timeSinceUptime"`
|
||||
Uptime uint64 `json:"uptime"`
|
||||
TimeSinceUptime string `json:"timeSinceUptime"`
|
||||
RunningTime RunningTime `json:"runningTime"`
|
||||
|
||||
Procs uint64 `json:"procs"`
|
||||
|
||||
@@ -128,6 +129,13 @@ type DashboardCurrent struct {
|
||||
ShotTime time.Time `json:"shotTime"`
|
||||
}
|
||||
|
||||
type RunningTime struct {
|
||||
Days uint64 `json:"days"`
|
||||
Hours uint64 `json:"hours"`
|
||||
Minutes uint64 `json:"minutes"`
|
||||
Seconds uint64 `json:"seconds"`
|
||||
}
|
||||
|
||||
type AppLauncherSync struct {
|
||||
Keys []string `json:"keys"`
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ type AppInstallUpgrade struct {
|
||||
DetailID uint `json:"detailId"`
|
||||
Backup bool `json:"backup"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
DeleteImage bool `json:"deleteImage"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ type FileWget struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
IgnoreCertificate bool `json:"ignoreCertificate"`
|
||||
UseProxy bool `json:"useProxy"`
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
@@ -162,13 +163,20 @@ type FileRoleUpdate struct {
|
||||
type FileReadByLineReq struct {
|
||||
Page int `json:"page" validate:"required"`
|
||||
PageSize int `json:"pageSize" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Type string `json:"type"`
|
||||
ID uint `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
Latest bool `json:"latest"`
|
||||
TaskReq
|
||||
}
|
||||
|
||||
type TaskLogReadReq struct {
|
||||
Page int `json:"page" validate:"required,min=1"`
|
||||
PageSize int `json:"pageSize" validate:"required,min=1"`
|
||||
Latest bool `json:"latest"`
|
||||
TaskReq
|
||||
}
|
||||
|
||||
type TaskReq struct {
|
||||
TaskID string `json:"taskID"`
|
||||
TaskType string `json:"taskType"`
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package request
|
||||
|
||||
type HostToolReq struct {
|
||||
type HostToolTypeReq struct {
|
||||
Type string `json:"type" validate:"required,oneof=supervisord"`
|
||||
}
|
||||
|
||||
type HostToolOperateReq struct {
|
||||
Type string `json:"type" validate:"required,oneof=supervisord"`
|
||||
Operate string `json:"operate" validate:"oneof=status restart start stop"`
|
||||
Operate string `json:"operate" validate:"required,oneof=restart start stop"`
|
||||
}
|
||||
|
||||
type HostToolCreate struct {
|
||||
@@ -15,13 +19,8 @@ type SupervisorConfig struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
}
|
||||
|
||||
type HostToolLogReq struct {
|
||||
Type string `json:"type" validate:"required,oneof=supervisord"`
|
||||
}
|
||||
|
||||
type HostToolConfig struct {
|
||||
type HostToolConfigUpdate struct {
|
||||
Type string `json:"type" validate:"required,oneof=supervisord"`
|
||||
Operate string `json:"operate" validate:"oneof=get set"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
@@ -43,3 +42,15 @@ type SupervisorProcessFileReq struct {
|
||||
Content string `json:"content"`
|
||||
File string `json:"file" validate:"required,oneof=out.log err.log config"`
|
||||
}
|
||||
|
||||
type HostSupervisorProcessFileGetReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
File string `json:"file" validate:"required,oneof=out.log err.log config"`
|
||||
}
|
||||
|
||||
type HostSupervisorProcessFileOperateReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required,oneof=clear update"`
|
||||
Content string `json:"content"`
|
||||
File string `json:"file" validate:"required,oneof=out.log err.log config"`
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ type McpServerCreate struct {
|
||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||
OutputTransport string `json:"outputTransport" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
GatewayImage string `json:"gatewayImage"`
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type McpServerUpdate struct {
|
||||
@@ -32,11 +35,23 @@ type McpServerDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type McpServerDetail struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type McpServerStatusSync struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
|
||||
type McpServerOperate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
}
|
||||
|
||||
type McpServerConnectionTest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type McpBindDomain struct {
|
||||
Domain string `json:"domain" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
|
||||
@@ -21,13 +21,14 @@ type RuntimeCreate struct {
|
||||
Source string `json:"source"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
Remark string `json:"remark"`
|
||||
TaskID string `json:"taskID"`
|
||||
|
||||
Params map[string]interface{} `json:"params"`
|
||||
NodeConfig
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
Install bool `json:"install"`
|
||||
Install *bool `json:"install"`
|
||||
Clean bool `json:"clean"`
|
||||
ExposedPorts []ExposedPort `json:"exposedPorts"`
|
||||
Environments []Environment `json:"environments"`
|
||||
@@ -42,12 +43,14 @@ type Environment struct {
|
||||
type Volume struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
type ExposedPort struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
HostIP string `json:"hostIP"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
type ExtraHost struct {
|
||||
@@ -56,8 +59,10 @@ type ExtraHost struct {
|
||||
}
|
||||
|
||||
type RuntimeDelete struct {
|
||||
ID uint `json:"id"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
ID uint `json:"id"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
DeleteImage bool `json:"deleteImage"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type RuntimeUpdate struct {
|
||||
|
||||
@@ -223,9 +223,14 @@ type WebsiteNginxUpdate struct {
|
||||
}
|
||||
|
||||
type WebsiteLogReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required,oneof=enable disable delete"`
|
||||
LogType string `json:"logType" validate:"required,oneof=access.log error.log"`
|
||||
}
|
||||
|
||||
type WebsiteLogSearchReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
LogType string `json:"logType" validate:"required"`
|
||||
LogType string `json:"logType" validate:"required,oneof=access.log error.log"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ type WebsiteSSLSearch struct {
|
||||
dto.PageInfo
|
||||
AcmeAccountID string `json:"acmeAccountID"`
|
||||
Domain string `json:"domain"`
|
||||
OrderBy string `json:"orderBy" validate:"omitempty,oneof=created_at expire_date"`
|
||||
OrderBy string `json:"orderBy" validate:"omitempty,oneof=created_at updated_at expire_date"`
|
||||
Order string `json:"order" validate:"omitempty,oneof=null ascending descending"`
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ type WebsiteSSLObtain struct {
|
||||
type WebsiteAcmeAccountCreate struct {
|
||||
Email string `json:"email" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=letsencrypt zerossl buypass google custom"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=EC256 EC384 RSA2048 RSA3072 RSA4096 RSA8192"`
|
||||
EabKid string `json:"eabKid"`
|
||||
EabHmacKey string `json:"eabHmacKey"`
|
||||
UseProxy bool `json:"useProxy"`
|
||||
@@ -126,6 +126,16 @@ type WebsiteSSLUpload struct {
|
||||
Type string `json:"type" validate:"required,oneof=paste local"`
|
||||
SSLID uint `json:"sslID"`
|
||||
Description string `json:"description"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
}
|
||||
|
||||
type WebsiteSSLPush struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
Sync bool `json:"sync"`
|
||||
}
|
||||
|
||||
type WebsiteCASearch struct {
|
||||
@@ -138,7 +148,7 @@ type WebsiteCACreate struct {
|
||||
Organization string `json:"organization" validate:"required"`
|
||||
OrganizationUint string `json:"organizationUint"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=EC256 EC384 RSA2048 RSA3072 RSA4096 RSA8192"`
|
||||
Province string `json:"province" `
|
||||
City string `json:"city"`
|
||||
}
|
||||
@@ -146,7 +156,7 @@ type WebsiteCACreate struct {
|
||||
type WebsiteCAObtain struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Domains string `json:"domains" validate:"required"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=EC256 EC384 RSA2048 RSA3072 RSA4096 RSA8192"`
|
||||
Time int `json:"time" validate:"required"`
|
||||
Unit string `json:"unit" validate:"required"`
|
||||
PushDir bool `json:"pushDir"`
|
||||
@@ -157,6 +167,8 @@ type WebsiteCAObtain struct {
|
||||
Description string `json:"description"`
|
||||
ExecShell bool `json:"execShell"`
|
||||
Shell string `json:"shell"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
}
|
||||
|
||||
type WebsiteCARenew struct {
|
||||
@@ -167,4 +179,6 @@ type WebsiteSSLFileUpload struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
SSLID uint64 `json:"sslID"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
}
|
||||
|
||||
@@ -104,34 +104,36 @@ type AppDetail struct {
|
||||
}
|
||||
|
||||
type AppInstallDTO struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AppID uint `json:"appID"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
Path string `json:"path"`
|
||||
CanUpdate bool `json:"canUpdate"`
|
||||
Icon string `json:"icon"`
|
||||
AppName string `json:"appName"`
|
||||
Ready int `json:"ready"`
|
||||
Total int `json:"total"`
|
||||
AppKey string `json:"appKey"`
|
||||
AppType string `json:"appType"`
|
||||
AppStatus string `json:"appStatus"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
WebUI string `json:"webUI"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Favorite bool `json:"favorite"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
App AppDetail `json:"app"`
|
||||
Container string `json:"container"`
|
||||
IsEdit bool `json:"isEdit"`
|
||||
LinkDB bool `json:"linkDB"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AppID uint `json:"appID"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
Path string `json:"path"`
|
||||
CanUpdate bool `json:"canUpdate"`
|
||||
Icon string `json:"icon"`
|
||||
AppName string `json:"appName"`
|
||||
Ready int `json:"ready"`
|
||||
Total int `json:"total"`
|
||||
AppKey string `json:"appKey"`
|
||||
AppType string `json:"appType"`
|
||||
AppStatus string `json:"appStatus"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
WebUI string `json:"webUI"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Favorite bool `json:"favorite"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
App AppDetail `json:"app"`
|
||||
Container string `json:"container"`
|
||||
IsEdit bool `json:"isEdit"`
|
||||
LinkDB bool `json:"linkDB"`
|
||||
ResourceKeys []string `json:"resourceKeys"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
Env map[string]interface{} `json:"env"`
|
||||
}
|
||||
|
||||
type AppInstallInfo struct {
|
||||
|
||||
@@ -16,6 +16,12 @@ type McpServerDTO struct {
|
||||
Volumes []request.Volume `json:"volumes"`
|
||||
}
|
||||
|
||||
type McpServerStatusDTO struct {
|
||||
ID uint `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type McpBindDomainRes struct {
|
||||
Domain string `json:"domain"`
|
||||
SSLID uint `json:"sslID"`
|
||||
@@ -24,3 +30,11 @@ type McpBindDomainRes struct {
|
||||
WebsiteID uint `json:"websiteID"`
|
||||
ConnUrl string `json:"connUrl"`
|
||||
}
|
||||
|
||||
type McpServerConnectionTestRes struct {
|
||||
Success bool `json:"success"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
OutputTransport string `json:"outputTransport"`
|
||||
ProtocolVersion string `json:"protocolVersion,omitempty"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ type SettingInfo struct {
|
||||
AppStoreLastModified string `json:"appStoreLastModified"`
|
||||
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
|
||||
|
||||
FileRecycleBin string `json:"fileRecycleBin"`
|
||||
FileRecycleBin string `json:"fileRecycleBin"`
|
||||
LocalSSHConnShow string `json:"localSSHConnShow"`
|
||||
|
||||
FirewallPortWhiteList string `json:"firewallPortWhiteList"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
@@ -31,6 +34,11 @@ type SettingUpdate struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type AgentSettingUpdate struct {
|
||||
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin FirewallPortWhiteList"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type SyncTime struct {
|
||||
NtpSite string `json:"ntpSite" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -57,8 +57,10 @@ type SSHConfUpdate struct {
|
||||
}
|
||||
type SearchSSHLog struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
Status string `json:"Status" validate:"required,oneof=Success Failed All"`
|
||||
Info string `json:"info"`
|
||||
Status string `json:"Status" validate:"required,oneof=Success Failed All"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
}
|
||||
|
||||
type SSHHistory struct {
|
||||
|
||||
@@ -2,14 +2,15 @@ package model
|
||||
|
||||
type AgentAccount struct {
|
||||
BaseModel
|
||||
Provider string `json:"provider"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIType string `json:"apiType"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
Provider string `json:"provider"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIType string `json:"apiType"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
MasterAccountID uint `json:"masterAccountId" gorm:"index"`
|
||||
}
|
||||
|
||||
func (AgentAccount) TableName() string {
|
||||
|
||||
@@ -9,9 +9,11 @@ type Alert struct {
|
||||
Count uint `gorm:"type:integer;not null" json:"count"`
|
||||
Project string `gorm:"type:varchar(64)" json:"project"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Method string `gorm:"type:varchar(64);not null" json:"method"`
|
||||
Method string `gorm:"type:text;not null" json:"method"`
|
||||
SendCount uint `gorm:"type:integer" json:"sendCount"`
|
||||
AdvancedParams string `gorm:"type:longText" json:"advancedParams"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
}
|
||||
|
||||
type AlertTask struct {
|
||||
@@ -19,7 +21,7 @@ type AlertTask struct {
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||
Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
}
|
||||
|
||||
type AlertLog struct {
|
||||
@@ -34,15 +36,17 @@ type AlertLog struct {
|
||||
Message string `gorm:"type:varchar(256);" json:"message"`
|
||||
RecordId uint `gorm:"type:integer;" json:"recordId"`
|
||||
LicenseId string `gorm:"type:varchar(256);not null;" json:"licenseId" `
|
||||
Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
}
|
||||
|
||||
type AlertConfig struct {
|
||||
BaseModel
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
|
||||
@@ -18,4 +18,6 @@ type McpServer struct {
|
||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||
OutputTransport string `json:"outputTransport"`
|
||||
Type string `json:"type"`
|
||||
GatewayImage string `json:"gatewayImage"`
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ type WebsiteAcmeAccount struct {
|
||||
Type string `gorm:"not null;default:letsencrypt" json:"type"`
|
||||
EabKid string `json:"eabKid"`
|
||||
EabHmacKey string `json:"eabHmacKey"`
|
||||
KeyType string `gorm:"not null;default:2048" json:"keyType"`
|
||||
KeyType string `gorm:"not null;default:RSA2048" json:"keyType"`
|
||||
UseProxy bool `gorm:"default:false" json:"useProxy"`
|
||||
CaDirURL string `json:"caDirURL"`
|
||||
UseEAB bool `json:"useEAB"`
|
||||
|
||||
@@ -5,5 +5,5 @@ type WebsiteCA struct {
|
||||
CSR string `gorm:"not null;" json:"csr"`
|
||||
Name string `gorm:"not null;" json:"name"`
|
||||
PrivateKey string `gorm:"not null" json:"privateKey"`
|
||||
KeyType string `gorm:"not null;default:2048" json:"keyType"`
|
||||
KeyType string `gorm:"not null;default:RSA2048" json:"keyType"`
|
||||
}
|
||||
|
||||
@@ -163,10 +163,9 @@ var catalog = map[string]Meta{
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "minimax/MiniMax-M3", Name: "MiniMax M3", ContextWindow: 1000000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
||||
{ID: "minimax/MiniMax-M2.7", Name: "MiniMax M2.7"},
|
||||
{ID: "minimax/MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"},
|
||||
{ID: "minimax/MiniMax-M2.5", Name: "MiniMax M2.5", Reasoning: true},
|
||||
{ID: "minimax/MiniMax-M2.5-highspeed", Name: "MiniMax M2.5 highspeed"},
|
||||
},
|
||||
},
|
||||
"xiaomi": {
|
||||
|
||||
@@ -131,7 +131,7 @@ func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
|
||||
request.URL = base + "/v1/messages"
|
||||
}
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": "MiniMax-M2.5",
|
||||
"model": "MiniMax-M3",
|
||||
"max_tokens": 1,
|
||||
"messages": []map[string]interface{}{{
|
||||
"role": "user",
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package repo
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AgentAccountRepo struct{}
|
||||
|
||||
type IAgentAccountRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.AgentAccount, error)
|
||||
GetFirst(opts ...DBOption) (*model.AgentAccount, error)
|
||||
WithByMasterAccountID(masterID uint) DBOption
|
||||
Create(account *model.AgentAccount) error
|
||||
Save(account *model.AgentAccount) error
|
||||
DeleteByID(id uint) error
|
||||
List(opts ...DBOption) ([]model.AgentAccount, error)
|
||||
CountByProviders(providers []string) (map[string]int64, error)
|
||||
}
|
||||
|
||||
func NewIAgentAccountRepo() IAgentAccountRepo {
|
||||
@@ -34,6 +41,12 @@ func (a AgentAccountRepo) GetFirst(opts ...DBOption) (*model.AgentAccount, error
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) WithByMasterAccountID(masterID uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("master_account_id = ?", masterID)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) Create(account *model.AgentAccount) error {
|
||||
return getDb().Create(account).Error
|
||||
}
|
||||
@@ -53,3 +66,49 @@ func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) {
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) CountByProviders(providers []string) (map[string]int64, error) {
|
||||
normalizedProviders := normalizeProviders(providers)
|
||||
counts := make(map[string]int64, len(normalizedProviders))
|
||||
for _, provider := range normalizedProviders {
|
||||
counts[provider] = 0
|
||||
}
|
||||
if len(normalizedProviders) == 0 {
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
type providerCount struct {
|
||||
Provider string
|
||||
Count int64
|
||||
}
|
||||
var rows []providerCount
|
||||
if err := getDb().
|
||||
Model(&model.AgentAccount{}).
|
||||
Select("provider, COUNT(*) as count").
|
||||
Where("provider IN ?", normalizedProviders).
|
||||
Group("provider").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, row := range rows {
|
||||
counts[row.Provider] = row.Count
|
||||
}
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
func normalizeProviders(providers []string) []string {
|
||||
seen := make(map[string]struct{}, len(providers))
|
||||
result := make([]string, 0, len(providers))
|
||||
for _, provider := range providers {
|
||||
provider = strings.TrimSpace(provider)
|
||||
if provider == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[provider]; ok {
|
||||
continue
|
||||
}
|
||||
seen[provider] = struct{}{}
|
||||
result = append(result, provider)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+235
-14
@@ -1,11 +1,15 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"google.golang.org/genproto/googleapis/type/date"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -20,7 +24,8 @@ type IAlertRepo interface {
|
||||
WithByCreateAt(date *date.Date) DBOption
|
||||
WithByLicenseId(licenseId string) DBOption
|
||||
WithByRecordId(recordId uint) DBOption
|
||||
WithByMethod(method string) DBOption
|
||||
WithByAlertMethodContainsConfigID(id uint) DBOption
|
||||
WithByMethodConfigIDs(ids []uint) DBOption
|
||||
|
||||
Create(alert *model.Alert) error
|
||||
Get(opts ...DBOption) (model.Alert, error)
|
||||
@@ -47,11 +52,15 @@ type IAlertRepo interface {
|
||||
GetLicensePushCount(method string) (uint, error)
|
||||
|
||||
GetConfig(opts ...DBOption) (model.AlertConfig, error)
|
||||
GetConfigById(id uint) (model.AlertConfig, error)
|
||||
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
||||
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
|
||||
CreateAlertConfig(config *model.AlertConfig) error
|
||||
DeleteAlertConfig(opts ...DBOption) error
|
||||
|
||||
WithByTypeNotIn(types []string) DBOption
|
||||
PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error)
|
||||
|
||||
SyncAll(data []model.AlertConfig) error
|
||||
}
|
||||
|
||||
@@ -101,9 +110,20 @@ func (a *AlertRepo) WithByRecordId(recordId uint) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) WithByMethod(method string) DBOption {
|
||||
func (a *AlertRepo) WithByAlertMethodContainsConfigID(id uint) DBOption {
|
||||
method := strconv.Itoa(int(id))
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("method = ?", method)
|
||||
return g.Where("(method = ? OR method LIKE ? OR method LIKE ? OR method LIKE ?)", method, method+",%", "%,"+method, "%,"+method+",%")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) WithByMethodConfigIDs(ids []uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
methods := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
methods = append(methods, strconv.Itoa(int(id)))
|
||||
}
|
||||
return g.Where("method IN ?", methods)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,32 +326,233 @@ func (a *AlertRepo) GetConfig(opts ...DBOption) (model.AlertConfig, error) {
|
||||
return alertConfig, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) GetConfigById(id uint) (model.AlertConfig, error) {
|
||||
var config model.AlertConfig
|
||||
err := global.AlertDB.First(&config, id).Error
|
||||
return config, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) WithByTypeNotIn(types []string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("`type` NOT IN (?)", types)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
|
||||
var configs []model.AlertConfig
|
||||
db := global.AlertDB.Model(&model.AlertConfig{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&configs).Error
|
||||
return count, configs, err
|
||||
}
|
||||
|
||||
var singletonTypes = map[string]bool{
|
||||
constant.CommonConfig: true,
|
||||
}
|
||||
|
||||
func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
tx := global.AlertDB.Begin()
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
var oldConfigs []model.AlertConfig
|
||||
_ = tx.Find(&oldConfigs).Error
|
||||
if err := tx.Find(&oldConfigs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
usedConfigIDs, err := loadUsedAlertConfigIDs(tx)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
oldConfigMap := make(map[string]uint)
|
||||
oldConfigByType := make(map[string][]model.AlertConfig)
|
||||
oldConfigByKey := make(map[string][]model.AlertConfig)
|
||||
consumedConfigIDs := make(map[uint]struct{})
|
||||
for _, item := range oldConfigs {
|
||||
oldConfigMap[item.Type] = item.ID
|
||||
if singletonTypes[item.Type] {
|
||||
oldConfigMap[item.Type] = item.ID
|
||||
continue
|
||||
}
|
||||
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
|
||||
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
|
||||
}
|
||||
for _, item := range data {
|
||||
if val, ok := oldConfigMap[item.Type]; ok {
|
||||
item.ID = val
|
||||
delete(oldConfigMap, item.Type)
|
||||
} else {
|
||||
item.ID = 0
|
||||
if singletonTypes[item.Type] {
|
||||
if val, ok := oldConfigMap[item.Type]; ok {
|
||||
item.ID = val
|
||||
delete(oldConfigMap, item.Type)
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
} else {
|
||||
item.ID = 0
|
||||
}
|
||||
if item.ID == 0 {
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
} else if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := tx.Model(model.AlertConfig{}).Where("id = ?", item.ID).Save(&item).Error; err != nil {
|
||||
|
||||
key := alertConfigSyncKey(item)
|
||||
if matched, ok := popAlertConfigByKey(oldConfigByKey, key); ok {
|
||||
item.ID = matched.ID
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
deleteAlertConfigByID(oldConfigByType, matched.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
if matched, ok := popUnusedAlertConfigByType(oldConfigByType, usedConfigIDs, item.Type); ok {
|
||||
item.ID = matched.ID
|
||||
consumedConfigIDs[item.ID] = struct{}{}
|
||||
if err := tx.Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
item.ID = 0
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, val := range oldConfigMap {
|
||||
if err := tx.Where("id = ?", val).Delete(&model.AlertConfig{}).Error; err != nil {
|
||||
for _, item := range oldConfigs {
|
||||
if _, used := usedConfigIDs[item.ID]; used {
|
||||
continue
|
||||
}
|
||||
if _, kept := consumedConfigIDs[item.ID]; kept {
|
||||
continue
|
||||
}
|
||||
if err := tx.Where("id = ?", item.ID).Delete(&model.AlertConfig{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUsedAlertConfigIDs(tx *gorm.DB) (map[uint]struct{}, error) {
|
||||
var alerts []model.Alert
|
||||
if err := tx.Select("method").Find(&alerts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usedIDs := make(map[uint]struct{})
|
||||
for _, alert := range alerts {
|
||||
for _, item := range strings.Split(alert.Method, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
id, err := strconv.ParseUint(item, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
usedIDs[uint(id)] = struct{}{}
|
||||
}
|
||||
}
|
||||
return usedIDs, nil
|
||||
}
|
||||
|
||||
func alertConfigSyncKey(item model.AlertConfig) string {
|
||||
return item.Type + "::" + normalizeAlertConfigJSON(item.Config)
|
||||
}
|
||||
|
||||
func normalizeAlertConfigJSON(config string) string {
|
||||
trimmed := strings.TrimSpace(config)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var data any
|
||||
if err := json.Unmarshal([]byte(trimmed), &data); err != nil {
|
||||
return trimmed
|
||||
}
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return trimmed
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func popAlertConfigByKey(configMap map[string][]model.AlertConfig, key string) (model.AlertConfig, bool) {
|
||||
items := configMap[key]
|
||||
if len(items) == 0 {
|
||||
return model.AlertConfig{}, false
|
||||
}
|
||||
|
||||
item := items[0]
|
||||
if len(items) == 1 {
|
||||
delete(configMap, key)
|
||||
} else {
|
||||
configMap[key] = items[1:]
|
||||
}
|
||||
return item, true
|
||||
}
|
||||
|
||||
func popUnusedAlertConfigByType(configMap map[string][]model.AlertConfig, usedConfigIDs map[uint]struct{}, configType string) (model.AlertConfig, bool) {
|
||||
items := configMap[configType]
|
||||
if len(items) == 0 {
|
||||
return model.AlertConfig{}, false
|
||||
}
|
||||
|
||||
for idx, item := range items {
|
||||
if _, used := usedConfigIDs[item.ID]; used {
|
||||
continue
|
||||
}
|
||||
if idx == 0 {
|
||||
if len(items) == 1 {
|
||||
delete(configMap, configType)
|
||||
} else {
|
||||
configMap[configType] = items[1:]
|
||||
}
|
||||
} else {
|
||||
configMap[configType] = append(items[:idx], items[idx+1:]...)
|
||||
}
|
||||
return item, true
|
||||
}
|
||||
return model.AlertConfig{}, false
|
||||
}
|
||||
|
||||
func deleteAlertConfigByID(configMap map[string][]model.AlertConfig, id uint) {
|
||||
for key, items := range configMap {
|
||||
for idx, item := range items {
|
||||
if item.ID != id {
|
||||
continue
|
||||
}
|
||||
if len(items) == 1 {
|
||||
delete(configMap, key)
|
||||
} else {
|
||||
configMap[key] = append(items[:idx], items[idx+1:]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type IWebsiteRepo interface {
|
||||
WithGroupID(groupId uint) DBOption
|
||||
WithDefaultServer() DBOption
|
||||
WithDomainLike(domain string) DBOption
|
||||
WithSearchKeyword(keyword string, ids []uint) DBOption
|
||||
WithRuntimeID(runtimeID uint) DBOption
|
||||
WithParentID(websiteID uint) DBOption
|
||||
WithType(websiteType string) DBOption
|
||||
@@ -76,6 +77,19 @@ func (w *WebsiteRepo) WithDomainLike(domain string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithSearchKeyword(keyword string, ids []uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
if keyword == "" {
|
||||
return db
|
||||
}
|
||||
keyword = "%" + keyword + "%"
|
||||
if len(ids) == 0 {
|
||||
return db.Where("(primary_domain like ? OR alias like ?)", keyword, keyword)
|
||||
}
|
||||
return db.Where("(primary_domain like ? OR alias like ? OR id in (?))", keyword, keyword, ids)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithAlias(alias string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("alias = ?", alias)
|
||||
|
||||
@@ -2,8 +2,10 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -21,6 +23,7 @@ type ISSLRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error)
|
||||
GetFirst(opts ...DBOption) (*model.WebsiteSSL, error)
|
||||
List(opts ...DBOption) ([]model.WebsiteSSL, error)
|
||||
TryMarkApplying(id uint) (bool, error)
|
||||
Create(ctx context.Context, ssl *model.WebsiteSSL) error
|
||||
Save(ssl *model.WebsiteSSL) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
@@ -76,12 +79,12 @@ func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.W
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) {
|
||||
var website *model.WebsiteSSL
|
||||
var website model.WebsiteSSL
|
||||
db := getDb(opts...).Model(&model.WebsiteSSL{})
|
||||
if err := db.Preload("AcmeAccount").Preload("DnsAccount").First(&website).Error; err != nil {
|
||||
return website, err
|
||||
return nil, err
|
||||
}
|
||||
return website, nil
|
||||
return &website, nil
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) {
|
||||
@@ -93,14 +96,25 @@ func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) {
|
||||
return websites, nil
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) TryMarkApplying(id uint) (bool, error) {
|
||||
db := getDb().Model(&model.WebsiteSSL{}).
|
||||
Where("id = ? AND status <> ?", id, constant.SSLApply).
|
||||
Updates(map[string]interface{}{
|
||||
"status": constant.SSLApply,
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
if db.Error != nil {
|
||||
return false, db.Error
|
||||
}
|
||||
return db.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) Create(ctx context.Context, ssl *model.WebsiteSSL) error {
|
||||
return getTx(ctx).Create(ssl).Error
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) Save(ssl *model.WebsiteSSL) error {
|
||||
return getDb().Model(&model.WebsiteSSL{BaseModel: model.BaseModel{
|
||||
ID: ssl.ID,
|
||||
}}).Save(&ssl).Error
|
||||
return getDb().Save(ssl).Error
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) SaveByMap(ssl *model.WebsiteSSL, params map[string]interface{}) error {
|
||||
|
||||
+490
-20
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -30,6 +32,10 @@ import (
|
||||
|
||||
type IAgentService interface {
|
||||
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
|
||||
BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error)
|
||||
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
||||
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
||||
BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error)
|
||||
Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error)
|
||||
DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error)
|
||||
Delete(req dto.AgentDeleteReq) error
|
||||
@@ -69,6 +75,7 @@ type IAgentService interface {
|
||||
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
||||
SyncAgentsByAccount(account *model.AgentAccount) error
|
||||
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
||||
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
||||
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
||||
CreateAccountModel(req dto.AgentAccountModelCreateReq) error
|
||||
UpdateAccountModel(req dto.AgentAccountModelUpdateReq) error
|
||||
@@ -98,6 +105,16 @@ type IAgentService interface {
|
||||
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
|
||||
}
|
||||
|
||||
type batchUpgradePlan struct {
|
||||
ctx batchAgentInstallContext
|
||||
req request.AppInstallUpgrade
|
||||
}
|
||||
|
||||
type batchAgentInstallContext struct {
|
||||
agent model.Agent
|
||||
install model.AppInstall
|
||||
}
|
||||
|
||||
const (
|
||||
defaultBrowserExecutablePath = "/home/node/.cache/ms-playwright/openclaw-browser"
|
||||
defaultBrowserProfile = "openclaw"
|
||||
@@ -127,7 +144,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
|
||||
return nil, buserr.New("ErrNameIsExist")
|
||||
}
|
||||
if !xpack.IsXpack() {
|
||||
if !xpack.MultiNodeProvider.IsXpack() {
|
||||
count, _, err := agentRepo.Page(1, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -159,6 +176,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
var allowedOrigins []string
|
||||
var account *model.AgentAccount
|
||||
var installHooks *appInstallHooks
|
||||
var hermesAuth hermesDashboardAuth
|
||||
|
||||
if agentType == constant.AppOpenclaw || agentType == constant.AppHermesAgent {
|
||||
if req.AccountID == 0 {
|
||||
@@ -205,9 +223,13 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
},
|
||||
}
|
||||
} else if agentType == constant.AppHermesAgent {
|
||||
hermesAuth = normalizeHermesDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
||||
installHooks = &appInstallHooks{
|
||||
AfterCopyData: func(appInstall *model.AppInstall) error {
|
||||
return prepareHermesInstallFiles(appInstall, account, storedModel)
|
||||
if err := prepareHermesInstallFiles(appInstall, account, storedModel); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeHermesDashboardAuthEnv(path.Join(appInstall.GetPath(), ".env"), hermesAuth, false)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -231,6 +253,10 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
params["API_KEY"] = apiKey
|
||||
params["OPENCLAW_GATEWAY_TOKEN"] = token
|
||||
}
|
||||
if agentType == constant.AppHermesAgent {
|
||||
params[hermesDashboardUsernameEnvKey] = hermesAuth.Username
|
||||
params[hermesDashboardPasswordEnvKey] = hermesAuth.Password
|
||||
}
|
||||
|
||||
if req.EditCompose && strings.TrimSpace(req.DockerCompose) == "" {
|
||||
return nil, buserr.New("ErrAgentComposeRequired")
|
||||
@@ -290,6 +316,433 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (a AgentService) BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error) {
|
||||
accountID, err := a.ensureBatchInstallAccount(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createReq := buildCreateReqFromBatchInstallReq(req)
|
||||
createReq.AccountID = accountID
|
||||
if strings.TrimSpace(createReq.Name) == "" {
|
||||
name, err := buildBatchInstallAgentName(createReq.AgentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createReq.Name = name
|
||||
}
|
||||
if createReq.AgentType == constant.AppOpenclaw && len(createReq.AllowedOrigins) == 0 {
|
||||
allowedOrigin, err := buildBatchInstallOpenclawAllowedOrigin(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createReq.AllowedOrigins = []string{allowedOrigin}
|
||||
}
|
||||
return a.Create(createReq)
|
||||
}
|
||||
|
||||
func (a AgentService) BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error) {
|
||||
plans, results, err := buildBatchUpgradePlans(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, plan := range plans {
|
||||
result := dto.AgentBatchUpgradeResult{
|
||||
AgentID: plan.ctx.agent.ID,
|
||||
AgentName: plan.ctx.agent.Name,
|
||||
AppInstallID: plan.ctx.agent.AppInstallID,
|
||||
}
|
||||
if err := upgradeInstall(plan.req); err != nil {
|
||||
result.Message = err.Error()
|
||||
} else {
|
||||
result.Success = true
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (a AgentService) BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error) {
|
||||
if req.AgentType != constant.AppOpenclaw && req.AgentType != constant.AppHermesAgent {
|
||||
return nil, fmt.Errorf("%s does not support skill batch install", req.AgentType)
|
||||
}
|
||||
packagePath, err := validateLocalSkillPackagePath(req.PackagePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if format, err := localSkillPackageFormat(packagePath); err != nil {
|
||||
return nil, err
|
||||
} else if format != "zip" {
|
||||
return nil, fmt.Errorf("only .zip skill packages can be installed")
|
||||
}
|
||||
skillName := sanitizeLocalSkillDirName(req.SkillName, packagePath, req.SkillName)
|
||||
agents, err := listBatchAgents(req.AgentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]dto.AgentBatchSkillInstallResult, 0, len(agents))
|
||||
for _, agent := range agents {
|
||||
result := dto.AgentBatchSkillInstallResult{
|
||||
AgentID: agent.ID,
|
||||
AgentName: agent.Name,
|
||||
AppInstallID: agent.AppInstallID,
|
||||
}
|
||||
ctx, message := loadBatchAgentInstall(agent, req.AgentType)
|
||||
if message != "" {
|
||||
result.Message = message
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
install := ctx.install
|
||||
result.AppInstallID = install.ID
|
||||
if install.Status == constant.StatusInstalling || install.Status == constant.StatusUpgrading {
|
||||
result.Message = fmt.Sprintf("agent status is %s", install.Status)
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
result.Message = err.Error()
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, buildBatchSkillInstallTaskID(req.TaskID, agent.ID), agent.ID)
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
currentAgent := ctx.agent
|
||||
currentInstall := ctx.install
|
||||
installTask.AddSubTask("Install local skill", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute))
|
||||
return installLocalSkillPackage(mgr, currentInstall.ContainerName, currentAgent.AgentType, currentAgent.ConfigPath, packagePath, skillName)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := installTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("batch install local skill failed: %v", err)
|
||||
}
|
||||
}()
|
||||
result.Success = true
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (a AgentService) BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error) {
|
||||
operate := constant.AppOperate(strings.TrimSpace(req.Operate))
|
||||
if operate != constant.Start && operate != constant.Stop && operate != constant.Restart && operate != constant.Delete {
|
||||
return nil, fmt.Errorf("operate %s is not supported", req.Operate)
|
||||
}
|
||||
agents, err := listBatchAgents(req.AgentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]dto.AgentBatchOperateResult, 0, len(agents))
|
||||
for _, agent := range agents {
|
||||
result := dto.AgentBatchOperateResult{
|
||||
AgentID: agent.ID,
|
||||
AgentName: agent.Name,
|
||||
AppInstallID: agent.AppInstallID,
|
||||
}
|
||||
if operate == constant.Delete {
|
||||
if err := a.Delete(dto.AgentDeleteReq{
|
||||
ID: agent.ID,
|
||||
TaskID: buildBatchOperateTaskID(req.TaskID, agent.ID),
|
||||
ForceDelete: req.ForceDelete,
|
||||
}); err != nil {
|
||||
result.Message = err.Error()
|
||||
} else {
|
||||
result.Success = true
|
||||
}
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
ctx, message := loadBatchAgentInstall(agent, req.AgentType)
|
||||
if message != "" {
|
||||
result.Message = message
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
install := ctx.install
|
||||
result.AppInstallID = install.ID
|
||||
if message := batchOperateSkipMessage(operate, install.Status); message != "" {
|
||||
result.Success = true
|
||||
result.Skipped = true
|
||||
result.Message = message
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
if err := NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
||||
InstallId: install.ID,
|
||||
Operate: operate,
|
||||
TaskID: buildBatchOperateTaskID(req.TaskID, agent.ID),
|
||||
}); err != nil {
|
||||
result.Message = err.Error()
|
||||
} else {
|
||||
result.Success = true
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func listBatchAgents(agentType string) ([]model.Agent, error) {
|
||||
return agentRepo.List(func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("agent_type = ?", agentType).Order("id ASC")
|
||||
})
|
||||
}
|
||||
|
||||
func loadBatchAgentInstall(agent model.Agent, agentType string) (batchAgentInstallContext, string) {
|
||||
ctx := batchAgentInstallContext{agent: agent}
|
||||
if agent.AppInstallID == 0 {
|
||||
return ctx, "agent app install id is empty"
|
||||
}
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
|
||||
if err != nil {
|
||||
return ctx, err.Error()
|
||||
}
|
||||
ctx.install = install
|
||||
if install.App.Key != agentType {
|
||||
return ctx, fmt.Sprintf("app key %s does not match agent type %s", install.App.Key, agentType)
|
||||
}
|
||||
return ctx, ""
|
||||
}
|
||||
|
||||
func buildBatchUpgradePlans(req dto.AgentBatchUpgradeReq) ([]batchUpgradePlan, []dto.AgentBatchUpgradeResult, error) {
|
||||
agents, err := listBatchAgents(req.AgentType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
plans := make([]batchUpgradePlan, 0, len(agents))
|
||||
results := make([]dto.AgentBatchUpgradeResult, 0)
|
||||
for _, agent := range agents {
|
||||
result := dto.AgentBatchUpgradeResult{
|
||||
AgentID: agent.ID,
|
||||
AgentName: agent.Name,
|
||||
AppInstallID: agent.AppInstallID,
|
||||
}
|
||||
ctx, message := loadBatchAgentInstall(agent, req.AgentType)
|
||||
if message != "" {
|
||||
result.Message = message
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
install := ctx.install
|
||||
result.AppInstallID = install.ID
|
||||
if install.Status == constant.StatusInstalling || install.Status == constant.StatusUpgrading {
|
||||
result.Message = fmt.Sprintf("agent status is %s", install.Status)
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
if install.Version == req.TargetVersion {
|
||||
result.Success = true
|
||||
result.Skipped = true
|
||||
result.Message = "already target version"
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
detail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(install.AppId), appDetailRepo.WithVersion(req.TargetVersion))
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
if detail.ID == 0 {
|
||||
result.Message = fmt.Sprintf("target version %s not found", req.TargetVersion)
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
plans = append(plans, batchUpgradePlan{
|
||||
ctx: ctx,
|
||||
req: request.AppInstallUpgrade{
|
||||
InstallID: install.ID,
|
||||
DetailID: detail.ID,
|
||||
Backup: req.Backup,
|
||||
PullImage: req.PullImage,
|
||||
TaskID: buildBatchUpgradeTaskID(req.TaskID, install.ID),
|
||||
},
|
||||
})
|
||||
}
|
||||
return plans, results, nil
|
||||
}
|
||||
|
||||
func buildBatchUpgradeTaskID(taskID string, appInstallID uint) string {
|
||||
taskID = strings.TrimSpace(taskID)
|
||||
if taskID == "" {
|
||||
taskID = fmt.Sprintf("batch-upgrade-%d-%d", appInstallID, time.Now().UnixNano())
|
||||
}
|
||||
return fmt.Sprintf("%s-%d", taskID, appInstallID)
|
||||
}
|
||||
|
||||
func buildBatchSkillInstallTaskID(taskID string, agentID uint) string {
|
||||
taskID = strings.TrimSpace(taskID)
|
||||
if taskID == "" {
|
||||
taskID = fmt.Sprintf("batch-skill-install-%d-%d", agentID, time.Now().UnixNano())
|
||||
}
|
||||
return fmt.Sprintf("%s-%d", taskID, agentID)
|
||||
}
|
||||
|
||||
func buildBatchOperateTaskID(taskID string, agentID uint) string {
|
||||
taskID = strings.TrimSpace(taskID)
|
||||
if taskID == "" {
|
||||
taskID = fmt.Sprintf("batch-operate-%d-%d", agentID, time.Now().UnixNano())
|
||||
}
|
||||
return fmt.Sprintf("%s-%d", taskID, agentID)
|
||||
}
|
||||
|
||||
func batchOperateSkipMessage(operate constant.AppOperate, status string) string {
|
||||
switch status {
|
||||
case constant.StatusInstalling, constant.StatusUpgrading, constant.StatusUninstalling, constant.StatusRebuilding:
|
||||
return fmt.Sprintf("agent status is %s", status)
|
||||
}
|
||||
switch operate {
|
||||
case constant.Start:
|
||||
if status == constant.StatusRunning || status == constant.StatusStarting || status == constant.StatusRestarting {
|
||||
return fmt.Sprintf("agent status is %s", status)
|
||||
}
|
||||
case constant.Stop:
|
||||
if status != constant.StatusRunning {
|
||||
return fmt.Sprintf("agent status is %s", status)
|
||||
}
|
||||
case constant.Restart:
|
||||
if status == constant.StatusStarting {
|
||||
return fmt.Sprintf("agent status is %s", status)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func buildCreateReqFromBatchInstallReq(req dto.AgentBatchInstallReq) dto.AgentCreateReq {
|
||||
return dto.AgentCreateReq{
|
||||
Name: req.Name,
|
||||
Remark: req.Remark,
|
||||
AppVersion: req.AppVersion,
|
||||
WebUIPort: req.WebUIPort,
|
||||
BridgePort: req.BridgePort,
|
||||
AllowedOrigins: req.AllowedOrigins,
|
||||
AgentType: req.AgentType,
|
||||
Model: req.Model,
|
||||
AccountID: req.AccountID,
|
||||
Token: req.Token,
|
||||
DashboardUsername: req.DashboardUsername,
|
||||
DashboardPassword: req.DashboardPassword,
|
||||
TaskID: req.TaskID,
|
||||
Advanced: req.Advanced,
|
||||
ContainerName: req.ContainerName,
|
||||
AllowPort: req.AllowPort,
|
||||
SpecifyIP: req.SpecifyIP,
|
||||
RestartPolicy: req.RestartPolicy,
|
||||
CpuQuota: req.CpuQuota,
|
||||
MemoryLimit: req.MemoryLimit,
|
||||
MemoryUnit: req.MemoryUnit,
|
||||
PullImage: req.PullImage,
|
||||
EditCompose: req.EditCompose,
|
||||
DockerCompose: req.DockerCompose,
|
||||
}
|
||||
}
|
||||
|
||||
func buildBatchInstallAgentName(agentType string) (string, error) {
|
||||
prefix := strings.ToLower(strings.TrimSpace(agentType))
|
||||
if prefix == "" {
|
||||
prefix = "agent"
|
||||
}
|
||||
for i := 1; i <= 10000; i++ {
|
||||
name := fmt.Sprintf("%s-%d", prefix, i)
|
||||
exists, err := agentNameExists(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !exists {
|
||||
return name, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no available agent name")
|
||||
}
|
||||
|
||||
func agentNameExists(name string) (bool, error) {
|
||||
if exist, err := agentRepo.GetFirst(repo.WithByLowerName(name)); err == nil && exist != nil && exist.ID > 0 {
|
||||
return true, nil
|
||||
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, err
|
||||
}
|
||||
installs, err := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(name))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(installs) > 0, nil
|
||||
}
|
||||
|
||||
func buildBatchInstallOpenclawAllowedOrigin(req dto.AgentBatchInstallReq) (string, error) {
|
||||
host := ""
|
||||
if value, err := settingRepo.GetValueByKey("SystemIP"); err == nil {
|
||||
host = strings.TrimSpace(value)
|
||||
}
|
||||
if host == "" {
|
||||
host = strings.TrimSpace(req.FallbackAccessHost)
|
||||
}
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
return buildOpenclawAllowedOrigin(openclawAllowedOriginScheme(req.AppVersion), host, req.WebUIPort)
|
||||
}
|
||||
|
||||
func (a AgentService) ensureBatchInstallAccount(req dto.AgentBatchInstallReq) (uint, error) {
|
||||
if !requiresAgentAccount(req.AgentType) {
|
||||
return 0, nil
|
||||
}
|
||||
if req.MasterAccountID == 0 {
|
||||
return 0, buserr.New("ErrInvalidParams")
|
||||
}
|
||||
snapshot := req.AccountSnapshot
|
||||
account, err := agentAccountRepo.GetFirst(agentAccountRepo.WithByMasterAccountID(req.MasterAccountID))
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, err
|
||||
}
|
||||
if account == nil && global.IsMaster {
|
||||
account, err = agentAccountRepo.GetFirst(repo.WithByID(req.MasterAccountID))
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if account == nil {
|
||||
account = &model.AgentAccount{
|
||||
MasterAccountID: req.MasterAccountID,
|
||||
}
|
||||
}
|
||||
account.MasterAccountID = req.MasterAccountID
|
||||
account.Provider = snapshot.Provider
|
||||
account.Name = snapshot.Name
|
||||
account.APIKey = snapshot.APIKey
|
||||
account.RememberAPIKey = snapshot.RememberAPIKey
|
||||
account.BaseURL = snapshot.BaseURL
|
||||
account.APIType = snapshot.APIType
|
||||
account.Remark = snapshot.Remark
|
||||
account.Verified = true
|
||||
|
||||
initialModels, err := buildInitialAgentAccountModels(account, req.AccountModels)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if account.ID == 0 {
|
||||
if err := tx.Create(account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := tx.Save(account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return replacePersistedAgentAccountModelsWithTx(tx, account.ID, initialModels)
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return account.ID, nil
|
||||
}
|
||||
|
||||
func requiresAgentAccount(agentType string) bool {
|
||||
return agentType != constant.AppCopaw
|
||||
}
|
||||
|
||||
func setAgentWebUIParams(params map[string]interface{}, agentType, appVersion string, webUIPort int) {
|
||||
if agentType == constant.AppOpenclaw && isOpenclawHTTPSWindowVersion(appVersion) {
|
||||
params["PANEL_APP_PORT_HTTPS"] = webUIPort
|
||||
@@ -652,18 +1105,19 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
||||
apiKey = item.APIKey
|
||||
}
|
||||
items = append(items, dto.AgentAccountInfo{
|
||||
ID: item.ID,
|
||||
Provider: item.Provider,
|
||||
ProviderName: providercatalog.DisplayName(item.Provider),
|
||||
Name: item.Name,
|
||||
APIKey: apiKey,
|
||||
RememberAPIKey: item.RememberAPIKey,
|
||||
BaseURL: item.BaseURL,
|
||||
Models: nil,
|
||||
APIType: item.APIType,
|
||||
Verified: item.Verified,
|
||||
Remark: item.Remark,
|
||||
CreatedAt: item.CreatedAt,
|
||||
ID: item.ID,
|
||||
MasterAccountID: item.MasterAccountID,
|
||||
Provider: item.Provider,
|
||||
ProviderName: providercatalog.DisplayName(item.Provider),
|
||||
Name: item.Name,
|
||||
APIKey: apiKey,
|
||||
RememberAPIKey: item.RememberAPIKey,
|
||||
BaseURL: item.BaseURL,
|
||||
Models: nil,
|
||||
APIType: item.APIType,
|
||||
Verified: item.Verified,
|
||||
Remark: item.Remark,
|
||||
CreatedAt: item.CreatedAt,
|
||||
})
|
||||
}
|
||||
for i := range items {
|
||||
@@ -676,6 +1130,10 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
||||
return count, items, nil
|
||||
}
|
||||
|
||||
func (a AgentService) CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error) {
|
||||
return agentAccountRepo.CountByProviders(req.Providers)
|
||||
}
|
||||
|
||||
func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error) {
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
||||
if err != nil {
|
||||
@@ -880,10 +1338,13 @@ func (a AgentService) GetOtherConfig(req dto.AgentIDReq) (*dto.AgentOtherConfig,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth := readHermesDashboardAuthFromInstall(install)
|
||||
return &dto.AgentOtherConfig{
|
||||
UserTimezone: cfg.Timezone,
|
||||
BrowserEnabled: true,
|
||||
NPMRegistry: "https://registry.npmjs.org/",
|
||||
UserTimezone: cfg.Timezone,
|
||||
BrowserEnabled: true,
|
||||
NPMRegistry: "https://registry.npmjs.org/",
|
||||
DashboardUsername: auth.Username,
|
||||
DashboardPassword: auth.Password,
|
||||
}, nil
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
@@ -908,12 +1369,21 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
previousAuth := readHermesDashboardAuthFromInstall(install)
|
||||
nextAuth := normalizeHermesDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
||||
if err := writeHermesConfig(path.Dir(agent.ConfigPath), account, agent.Model, strings.TrimSpace(req.UserTimezone)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeHermesDashboardAuthEnv(path.Join(install.GetPath(), ".env"), nextAuth, true); err != nil {
|
||||
return err
|
||||
}
|
||||
operate := constant.Restart
|
||||
if previousAuth.Username != nextAuth.Username || previousAuth.Password != nextAuth.Password {
|
||||
operate = constant.Rebuild
|
||||
}
|
||||
return NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
||||
InstallId: install.ID,
|
||||
Operate: constant.Restart,
|
||||
Operate: operate,
|
||||
})
|
||||
}
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
@@ -990,7 +1460,7 @@ func validateAgentConfigFileContent(agentType, content string) error {
|
||||
}
|
||||
|
||||
func getOpenclawNPMRegistry(containerName string) (string, error) {
|
||||
registry, err := runDockerExecWithStdout(20*time.Second, containerName, "npm", "get", "registry")
|
||||
registry, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "npm", "get", "registry")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1002,7 +1472,7 @@ func getOpenclawNPMRegistry(containerName string) (string, error) {
|
||||
}
|
||||
|
||||
func setOpenclawNPMRegistry(containerName, registry string) error {
|
||||
return cmd.RunDefaultBashCf("docker exec %s npm set registry %q", containerName, registry)
|
||||
return cmd.NewCommandMgr().Run("docker", "exec", containerName, "npm", "set", "registry", registry)
|
||||
}
|
||||
|
||||
func (a AgentService) loadAgentAndInstall(agentID uint) (*model.Agent, *model.AppInstall, error) {
|
||||
|
||||
@@ -23,7 +23,7 @@ func (a AgentService) CreateRole(req dto.AgentRoleCreateReq) (*dto.AgentRoleCrea
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := []string{"exec", install.ContainerName, "openclaw", "agents", "add", req.Name}
|
||||
args := []string{"openclaw", "agents", "add", req.Name}
|
||||
workspace := "/home/node/.openclaw/workspace-agent_" + req.Name
|
||||
agentDir := "/home/node/.openclaw/agents/" + req.Name
|
||||
args = append(args, "--workspace", workspace)
|
||||
@@ -43,8 +43,7 @@ func (a AgentService) CreateRole(req dto.AgentRoleCreateReq) (*dto.AgentRoleCrea
|
||||
args = append(args, "--agent-dir", agentDir)
|
||||
args = append(args, "--non-interactive", "--json")
|
||||
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Minute))
|
||||
output, err := mgr.RunWithStdout("docker", args...)
|
||||
output, err := cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,10 +124,8 @@ func (a AgentService) DeleteRole(req dto.AgentRoleDeleteReq) error {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
|
||||
args := []string{"exec", install.ContainerName, "openclaw", "agents", "delete", req.ID, "--force"}
|
||||
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Minute))
|
||||
if _, err = mgr.RunWithStdout("docker", args...); err != nil {
|
||||
args := []string{"openclaw", "agents", "delete", req.ID, "--force"}
|
||||
if _, err = cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, args...); err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Workspace != "" {
|
||||
@@ -171,8 +168,6 @@ func (a AgentService) operateRoleBinding(req dto.AgentRoleBindReq, action string
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
args := []string{
|
||||
"exec",
|
||||
install.ContainerName,
|
||||
"openclaw",
|
||||
"agents",
|
||||
action,
|
||||
@@ -182,8 +177,7 @@ func (a AgentService) operateRoleBinding(req dto.AgentRoleBindReq, action string
|
||||
binding,
|
||||
}
|
||||
args = append(args, "--json")
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Minute))
|
||||
_, err = mgr.RunWithStdout("docker", args...)
|
||||
_, err = cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -372,14 +372,14 @@ func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
if req.Type == "qqbot" {
|
||||
legacyPluginPath := path.Join(openclawPluginBaseDir, "qqbot")
|
||||
if err := mgr.RunBashCf("docker exec %s test -d %s", install.ContainerName, legacyPluginPath); err == nil {
|
||||
if _, err := mgr.RunWithStdout("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript("qqbot")); err != nil {
|
||||
if err := mgr.Run("docker", "exec", install.ContainerName, "test", "-d", legacyPluginPath); err == nil {
|
||||
if err := uninstallOpenclawPlugin(mgr, install.ContainerName, "qqbot"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
if _, err := mgr.RunWithStdout("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawPluginInstallScript(spec, pluginID)); err != nil {
|
||||
if err := installOpenclawPlugin(mgr, install.ContainerName, spec, pluginID); err != nil {
|
||||
return err
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
@@ -415,11 +415,11 @@ func (a AgentService) UpgradePlugin(req dto.AgentPluginUpgradeReq) error {
|
||||
}
|
||||
upgradeTask.AddSubTask("Upgrade OpenClaw plugin", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
if _, err := mgr.RunWithStdout("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript(pluginID)); err != nil {
|
||||
if err := uninstallOpenclawPlugin(mgr, install.ContainerName, pluginID); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
if _, err := mgr.RunWithStdout("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawPluginInstallScript(spec, pluginID)); err != nil {
|
||||
if err := installOpenclawPlugin(mgr, install.ContainerName, spec, pluginID); err != nil {
|
||||
return err
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
@@ -455,7 +455,7 @@ func (a AgentService) UninstallPlugin(req dto.AgentPluginUninstallReq) error {
|
||||
}
|
||||
uninstallTask.AddSubTask("Uninstall OpenClaw plugin", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
if _, err := mgr.RunWithStdout("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript(pluginID)); err != nil {
|
||||
if err := uninstallOpenclawPlugin(mgr, install.ContainerName, pluginID); err != nil {
|
||||
return err
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
@@ -487,7 +487,7 @@ func (a AgentService) LoginWeixinChannel(req dto.AgentWeixinLoginReq) error {
|
||||
if agent.AgentType == constant.AppHermesAgent {
|
||||
return mgr.Run("docker", buildHermesWeixinLoginArgs(install.ContainerName)...)
|
||||
}
|
||||
return mgr.RunBashCf("docker exec %s openclaw channels login --channel openclaw-weixin", install.ContainerName)
|
||||
return mgr.Run("docker", "exec", install.ContainerName, "openclaw", "channels", "login", "--channel", "openclaw-weixin")
|
||||
}, nil)
|
||||
if agent.AgentType == constant.AppHermesAgent {
|
||||
loginTask.AddSubTask("Restart Hermes-Agent container", func(t *task.Task) error {
|
||||
@@ -584,17 +584,26 @@ func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveRe
|
||||
return validateHermesPairingApproveResult(output, err)
|
||||
}
|
||||
if req.AccountID != "" {
|
||||
return cmd.RunDefaultBashCf(
|
||||
"docker exec %s openclaw pairing approve %s %q --account %q",
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run(
|
||||
"docker",
|
||||
"exec",
|
||||
install.ContainerName,
|
||||
"openclaw",
|
||||
"pairing",
|
||||
"approve",
|
||||
req.Type,
|
||||
req.PairingCode,
|
||||
"--account",
|
||||
req.AccountID,
|
||||
)
|
||||
}
|
||||
return cmd.RunDefaultBashCf(
|
||||
"docker exec %s openclaw pairing approve %s %q",
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run(
|
||||
"docker",
|
||||
"exec",
|
||||
install.ContainerName,
|
||||
"openclaw",
|
||||
"pairing",
|
||||
"approve",
|
||||
req.Type,
|
||||
req.PairingCode,
|
||||
)
|
||||
@@ -802,9 +811,7 @@ func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig
|
||||
result.RequireMention = result.GroupPolicy == "allowlist"
|
||||
result.GroupAllowFrom = extractStringList(telegram["groupAllowFrom"])
|
||||
result.Proxy = extractStringValue(telegram["proxy"])
|
||||
if streaming := extractStringValue(telegram["streaming"]); streaming != "" {
|
||||
result.Streaming = streaming
|
||||
}
|
||||
result.Streaming = normalizeTelegramStreamingMode(telegram["streaming"], result.Streaming)
|
||||
accounts := childMap(telegram, "accounts")
|
||||
if len(accounts) == 0 {
|
||||
botToken := extractStringValue(telegram["botToken"])
|
||||
@@ -830,7 +837,7 @@ func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig
|
||||
BotToken: extractStringValue(account["botToken"]),
|
||||
DmPolicy: extractStringValue(account["dmPolicy"]),
|
||||
GroupPolicy: extractStringValue(account["groupPolicy"]),
|
||||
Streaming: extractStringValue(account["streaming"]),
|
||||
Streaming: normalizeTelegramStreamingMode(account["streaming"], result.Streaming),
|
||||
})
|
||||
}
|
||||
result.DefaultAccount = normalizeDefaultAccount(extractStringValue(telegram["defaultAccount"]), getTelegramBotAccountIDs(bots))
|
||||
@@ -870,7 +877,7 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
|
||||
} else {
|
||||
delete(telegram, "proxy")
|
||||
}
|
||||
telegram["streaming"] = config.Streaming
|
||||
telegram["streaming"] = buildTelegramStreamingConfig(config.Streaming)
|
||||
accounts := make(map[string]interface{}, len(config.Bots))
|
||||
for _, bot := range config.Bots {
|
||||
account := map[string]interface{}{
|
||||
@@ -879,7 +886,7 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
|
||||
"botToken": bot.BotToken,
|
||||
"dmPolicy": bot.DmPolicy,
|
||||
"groupPolicy": bot.GroupPolicy,
|
||||
"streaming": bot.Streaming,
|
||||
"streaming": buildTelegramStreamingConfig(bot.Streaming),
|
||||
}
|
||||
if bot.DmPolicy == "open" {
|
||||
account["allowFrom"] = []string{"*"}
|
||||
@@ -890,6 +897,29 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
|
||||
delete(telegram, "botToken")
|
||||
}
|
||||
|
||||
func normalizeTelegramStreamingMode(value interface{}, defaultMode string) string {
|
||||
mode := defaultMode
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
mode = typed
|
||||
case map[string]interface{}:
|
||||
mode = extractStringValue(typed["mode"])
|
||||
}
|
||||
mode = strings.ToLower(strings.TrimSpace(mode))
|
||||
switch mode {
|
||||
case "off", "partial", "block", "progress":
|
||||
return mode
|
||||
default:
|
||||
return "partial"
|
||||
}
|
||||
}
|
||||
|
||||
func buildTelegramStreamingConfig(mode string) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"mode": normalizeTelegramStreamingMode(mode, "partial"),
|
||||
}
|
||||
}
|
||||
|
||||
func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig {
|
||||
result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", AllowFrom: []string{}, RequireMention: false, GroupPolicy: "open"}
|
||||
discord := getChannelConfig(conf, "discord")
|
||||
@@ -1289,20 +1319,40 @@ func appendPluginAllow(conf map[string]interface{}, pluginID string) {
|
||||
plugins["allow"] = append(allow, pluginID)
|
||||
}
|
||||
|
||||
func buildOpenclawPluginInstallScript(spec, pluginID string) string {
|
||||
return fmt.Sprintf(
|
||||
"set -e; workdir=%s/%s; rm -rf \"$workdir\"; mkdir -p \"$workdir\"; cd \"$workdir\"; npm pack --silent %q >/dev/null 2>&1; pkg=$(find \"$workdir\" -maxdepth 1 -type f -name '*.tgz' | head -n 1); printf '%%s\\n' \"$pkg\"; openclaw plugins install \"$pkg\" --dangerously-force-unsafe-install; rm -rf \"$workdir\"",
|
||||
openclawPluginPackageTmpDir,
|
||||
pluginID,
|
||||
spec,
|
||||
)
|
||||
func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error {
|
||||
workdir := path.Join(openclawPluginPackageTmpDir, pluginID)
|
||||
defer func() {
|
||||
_ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir)
|
||||
}()
|
||||
if err := mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mgr.Run("docker", "exec", containerName, "mkdir", "-p", workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mgr.Run("docker", "exec", "-w", workdir, containerName, "npm", "pack", "--silent", spec); err != nil {
|
||||
return err
|
||||
}
|
||||
pkgPath, err := cmd.RunDockerExecWithStdout(10*time.Minute, containerName, "find", workdir, "-maxdepth", "1", "-type", "f", "-name", "*.tgz", "-print", "-quit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkgPath = strings.TrimSpace(pkgPath)
|
||||
if pkgPath == "" {
|
||||
return fmt.Errorf("openclaw plugin package not found")
|
||||
}
|
||||
return mgr.Run("docker", "exec", containerName, "openclaw", "plugins", "install", pkgPath, "--dangerously-force-unsafe-install")
|
||||
}
|
||||
|
||||
func buildOpenclawPluginUninstallScript(pluginID string) string {
|
||||
return fmt.Sprintf(
|
||||
"set +e; printf 'yes\\n' | openclaw plugins uninstall %s; code=$?; if [ \"$code\" -eq 137 ]; then exit 0; fi; exit \"$code\"",
|
||||
pluginID,
|
||||
func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error {
|
||||
_, err := mgr.RunPipe(
|
||||
cmd.PipeCommand{Name: "printf", Args: []string{"yes\n"}},
|
||||
cmd.PipeCommand{Name: "docker", Args: []string{"exec", "-i", containerName, "openclaw", "plugins", "uninstall", pluginID}},
|
||||
)
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 137") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func resolvePluginMeta(pluginType string) (string, string, error) {
|
||||
@@ -1357,7 +1407,7 @@ func loadOpenclawPluginLatestVersion(containerName, pluginType string) (string,
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output, err := runDockerExecWithStdout(20*time.Second, containerName, "npm", "view", spec, "version", "--json")
|
||||
output, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "npm", "view", spec, "version", "--json")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -13,14 +13,19 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
agentenv "github.com/1Panel-dev/1Panel/agent/utils/env"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/joho/godotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const hermesWorkspaceDir = "/opt/data/workspace"
|
||||
const hermesExecutablePath = "/opt/hermes/.venv/bin/hermes"
|
||||
const hermesDashboardUsernameEnvKey = "HERMES_DASHBOARD_USERNAME"
|
||||
const hermesDashboardPasswordEnvKey = "HERMES_DASHBOARD_PASSWORD"
|
||||
|
||||
type hermesDashboardAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type hermesConfig struct {
|
||||
Model hermesModelConfig `yaml:"model"`
|
||||
@@ -112,6 +117,52 @@ func prepareHermesInstallFiles(appInstall *model.AppInstall, account *model.Agen
|
||||
return files.NewFileOp().ChownR(dataDir, "1000", "1000", true)
|
||||
}
|
||||
|
||||
func normalizeHermesDashboardAuth(username, password string) hermesDashboardAuth {
|
||||
auth := hermesDashboardAuth{
|
||||
Username: strings.TrimSpace(username),
|
||||
Password: strings.TrimSpace(password),
|
||||
}
|
||||
if auth.Username == "" {
|
||||
auth.Username = "admin"
|
||||
}
|
||||
if auth.Password == "" {
|
||||
auth.Password = common.RandStr(8)
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
func writeHermesDashboardAuthEnv(envPath string, auth hermesDashboardAuth, overwrite bool) error {
|
||||
return upsertAgentEnv(envPath, map[string]string{
|
||||
hermesDashboardUsernameEnvKey: auth.Username,
|
||||
hermesDashboardPasswordEnvKey: auth.Password,
|
||||
}, []string{
|
||||
hermesDashboardUsernameEnvKey,
|
||||
hermesDashboardPasswordEnvKey,
|
||||
}, overwrite)
|
||||
}
|
||||
|
||||
func readHermesDashboardAuthEnv(envPath string) (hermesDashboardAuth, error) {
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return hermesDashboardAuth{}, err
|
||||
}
|
||||
return hermesDashboardAuth{
|
||||
Username: strings.TrimSpace(envMap[hermesDashboardUsernameEnvKey]),
|
||||
Password: strings.TrimSpace(envMap[hermesDashboardPasswordEnvKey]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readHermesDashboardAuthFromInstall(appInstall *model.AppInstall) hermesDashboardAuth {
|
||||
if appInstall == nil || appInstall.ID == 0 {
|
||||
return hermesDashboardAuth{}
|
||||
}
|
||||
auth, err := readHermesDashboardAuthEnv(path.Join(appInstall.GetPath(), ".env"))
|
||||
if err != nil {
|
||||
return hermesDashboardAuth{}
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
func readHermesConfig(configPath string) (*hermesConfig, error) {
|
||||
content, err := files.NewFileOp().GetContent(configPath)
|
||||
if err != nil {
|
||||
@@ -152,7 +203,7 @@ func writeHermesConfigMap(configPath string, cfg map[string]interface{}) error {
|
||||
}
|
||||
|
||||
func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig, error) {
|
||||
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,7 +260,7 @@ func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig,
|
||||
|
||||
func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramConfig) error {
|
||||
envPath := path.Join(confDir, ".env")
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -229,7 +280,7 @@ func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramCo
|
||||
envMap["TELEGRAM_ALLOWED_USERS"] = allow
|
||||
}
|
||||
}
|
||||
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
||||
"TELEGRAM_BOT_TOKEN",
|
||||
"TELEGRAM_ALLOWED_USERS",
|
||||
"TELEGRAM_ALLOW_ALL_USERS",
|
||||
@@ -248,7 +299,7 @@ func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramCo
|
||||
}
|
||||
|
||||
func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, error) {
|
||||
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -300,7 +351,7 @@ func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, er
|
||||
|
||||
func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConfig) error {
|
||||
envPath := path.Join(confDir, ".env")
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -320,7 +371,7 @@ func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConf
|
||||
envMap["DISCORD_ALLOWED_USERS"] = allow
|
||||
}
|
||||
}
|
||||
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
||||
"DISCORD_BOT_TOKEN",
|
||||
"DISCORD_ALLOWED_USERS",
|
||||
"DISCORD_ALLOW_ALL_USERS",
|
||||
@@ -340,14 +391,14 @@ func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConf
|
||||
|
||||
func deleteHermesEnvKeys(confDir string, keys ...string) error {
|
||||
envPath := path.Join(confDir, ".env")
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, key := range keys {
|
||||
delete(envMap, key)
|
||||
}
|
||||
return writeHermesEnvMap(envPath, envMap, keys)
|
||||
return writeAgentEnvMap(envPath, envMap, keys)
|
||||
}
|
||||
|
||||
func deleteHermesConfigSections(confDir string, topLevelKeys []string, platformKeys []string) error {
|
||||
@@ -532,7 +583,7 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
||||
}
|
||||
|
||||
func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -548,7 +599,7 @@ func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
|
||||
envMap[entry.Key] = entry.Value
|
||||
order = append(order, entry.Key)
|
||||
}
|
||||
return writeHermesEnvMap(envPath, envMap, order)
|
||||
return writeAgentEnvMap(envPath, envMap, order)
|
||||
}
|
||||
|
||||
func hermesManagedModelEnvKeys() []string {
|
||||
@@ -598,46 +649,6 @@ func hermesManagedModelEnvKeys() []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func writeHermesEnv(envPath string, entries []hermesEnvEntry) error {
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.Key == "" || entry.Value == "" {
|
||||
continue
|
||||
}
|
||||
envMap[entry.Key] = entry.Value
|
||||
}
|
||||
order := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.Key == "" {
|
||||
continue
|
||||
}
|
||||
order = append(order, entry.Key)
|
||||
}
|
||||
return writeHermesEnvMap(envPath, envMap, order)
|
||||
}
|
||||
|
||||
func readHermesEnvMap(envPath string) (map[string]string, error) {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(envPath) {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
envMap, err := godotenv.Read(envPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return envMap, nil
|
||||
}
|
||||
|
||||
func writeHermesEnvMap(envPath string, envMap map[string]string, order []string) error {
|
||||
if len(envMap) == 0 {
|
||||
return files.NewFileOp().SaveFile(envPath, "", 0600)
|
||||
}
|
||||
return agentenv.WriteWithOrder(envMap, envPath, order)
|
||||
}
|
||||
|
||||
func splitHermesEnvList(value string) []string {
|
||||
if value == "" {
|
||||
return []string{}
|
||||
|
||||
@@ -34,7 +34,7 @@ print('Restart the Hermes-Agent container to apply the new Weixin settings.')
|
||||
`
|
||||
|
||||
func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error) {
|
||||
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error)
|
||||
|
||||
func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig) error {
|
||||
envPath := path.Join(confDir, ".env")
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig)
|
||||
envMap["QQ_GROUP_ALLOWED_USERS"] = allow
|
||||
}
|
||||
}
|
||||
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
||||
"QQ_APP_ID",
|
||||
"QQ_CLIENT_SECRET",
|
||||
"QQ_ALLOW_ALL_USERS",
|
||||
@@ -163,7 +163,7 @@ func deleteHermesQQBotChannelConfig(confDir string) error {
|
||||
}
|
||||
|
||||
func readHermesWecomChannelConfig(confDir string) (*dto.AgentWecomConfig, error) {
|
||||
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -194,7 +194,7 @@ func readHermesWecomChannelConfig(confDir string) (*dto.AgentWecomConfig, error)
|
||||
|
||||
func writeHermesWecomChannelConfig(confDir string, config dto.AgentWecomConfig) error {
|
||||
envPath := path.Join(confDir, ".env")
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -229,7 +229,7 @@ func writeHermesWecomChannelConfig(confDir string, config dto.AgentWecomConfig)
|
||||
envMap["WECOM_GROUP_ALLOWED_USERS"] = allow
|
||||
}
|
||||
}
|
||||
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
||||
"WECOM_BOT_ID",
|
||||
"WECOM_SECRET",
|
||||
"WECOM_DM_POLICY",
|
||||
@@ -282,7 +282,7 @@ func deleteHermesWecomChannelConfig(confDir string) error {
|
||||
}
|
||||
|
||||
func readHermesDingTalkChannelConfig(confDir string) (*dto.AgentDingTalkConfig, error) {
|
||||
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -332,7 +332,7 @@ func readHermesDingTalkChannelConfig(confDir string) (*dto.AgentDingTalkConfig,
|
||||
|
||||
func writeHermesDingTalkChannelConfig(confDir string, config dto.AgentDingTalkConfig) error {
|
||||
envPath := path.Join(confDir, ".env")
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -356,7 +356,7 @@ func writeHermesDingTalkChannelConfig(confDir string, config dto.AgentDingTalkCo
|
||||
} else if config.DmPolicy == "open" {
|
||||
envMap["DINGTALK_ALLOW_ALL_USERS"] = "true"
|
||||
}
|
||||
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
||||
"DINGTALK_CLIENT_ID",
|
||||
"DINGTALK_CLIENT_SECRET",
|
||||
"DINGTALK_ALLOW_ALL_USERS",
|
||||
@@ -398,7 +398,7 @@ func deleteHermesDingTalkChannelConfig(confDir string) error {
|
||||
}
|
||||
|
||||
func readHermesFeishuChannelConfig(confDir string) (*dto.AgentFeishuConfig, error) {
|
||||
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -452,7 +452,7 @@ func readHermesFeishuChannelConfig(confDir string) (*dto.AgentFeishuConfig, erro
|
||||
|
||||
func writeHermesFeishuChannelConfig(confDir string, config dto.AgentFeishuConfig) error {
|
||||
envPath := path.Join(confDir, ".env")
|
||||
envMap, err := readHermesEnvMap(envPath)
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -488,7 +488,7 @@ func writeHermesFeishuChannelConfig(confDir string, config dto.AgentFeishuConfig
|
||||
envMap["FEISHU_ALLOWED_USERS"] = allow
|
||||
}
|
||||
}
|
||||
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
||||
"FEISHU_APP_ID",
|
||||
"FEISHU_APP_SECRET",
|
||||
"FEISHU_DOMAIN",
|
||||
@@ -529,7 +529,7 @@ func deleteHermesFeishuChannelConfig(confDir string) error {
|
||||
}
|
||||
|
||||
func readHermesWeixinChannelConfig(confDir string) (*dto.AgentWeixinConfig, error) {
|
||||
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -101,7 +102,7 @@ func countOpenclawConfiguredChannels(conf map[string]interface{}) int {
|
||||
}
|
||||
|
||||
func loadOpenclawOverviewSkillStats(containerName string) (int, error) {
|
||||
output, err := runDockerExecWithStdout(5*time.Minute, containerName, "sh", "-c", "openclaw skills list --json 2>&1")
|
||||
output, err := cmd.RunDockerExecWithStdout(5*time.Minute, containerName, "openclaw", "skills", "list", "--json")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -12,10 +21,15 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
)
|
||||
|
||||
const clawhubGlobalRegistry = "https://clawhub.com"
|
||||
const clawhubChinaRegistry = "https://mirror-cn.clawhub.com"
|
||||
const localSkillHubSource = "local-hub"
|
||||
const localSkillHubPublishedStatus = "published"
|
||||
const clawHubSkillTmpSubDir = "1panel/tmp/clawhub-skills"
|
||||
const hermesManagedSkillsDir = "/opt/data/skills"
|
||||
|
||||
type openclawSkillsList struct {
|
||||
Skills []openclawSkillListItem `json:"skills"`
|
||||
@@ -38,8 +52,17 @@ type skillhubSearchPayload struct {
|
||||
Results []dto.AgentSkillSearchItem `json:"results"`
|
||||
}
|
||||
|
||||
type localSkillHubItem struct {
|
||||
ID uint
|
||||
Name string
|
||||
Status string
|
||||
ApplicableAgent string
|
||||
PackagePath string
|
||||
}
|
||||
|
||||
var clawhubSearchLinePattern = regexp.MustCompile(`^(\S+)\s+(.+?)\s+\(([\d.]+)\)$`)
|
||||
var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`)
|
||||
var safeLocalSkillDirNamePattern = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
||||
|
||||
func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, error) {
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
@@ -55,7 +78,7 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro
|
||||
if agent.AgentType != constant.AppOpenclaw {
|
||||
return nil, fmt.Errorf("%s does not support", agent.AgentType)
|
||||
}
|
||||
output, err := runDockerExecWithStdout(5*time.Minute, install.ContainerName, "sh", "-c", "openclaw skills list --json 2>&1")
|
||||
output, err := cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, "openclaw", "skills", "list", "--json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,6 +89,12 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro
|
||||
}
|
||||
|
||||
func (a AgentService) SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkillSearchItem, error) {
|
||||
if req.Source == localSkillHubSource {
|
||||
return nil, fmt.Errorf("local skills hub is provided by enterprise edition")
|
||||
}
|
||||
if global.CONF.Base.IsOffline {
|
||||
return nil, fmt.Errorf("offline environment cannot access remote Skills Hub")
|
||||
}
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -115,6 +144,9 @@ func (a AgentService) UpdateSkill(req dto.AgentSkillUpdateReq) error {
|
||||
}
|
||||
|
||||
func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error {
|
||||
if global.CONF.Base.IsOffline && req.Source != localSkillHubSource {
|
||||
return fmt.Errorf("offline environment cannot access remote Skills Hub")
|
||||
}
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -122,6 +154,29 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error {
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Source == localSkillHubSource {
|
||||
if !global.IsMaster {
|
||||
return fmt.Errorf("local skills hub is only available on the master node")
|
||||
}
|
||||
hostPath, skillName, err := resolveLocalSkillPackage(agent.AgentType, req.Slug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask.AddSubTask("Install local skill", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute))
|
||||
return installLocalSkillPackage(mgr, install.ContainerName, agent.AgentType, agent.ConfigPath, hostPath, skillName)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := installTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("install local skill failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
installTask, err := task.NewTaskWithOps(req.Slug, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -143,7 +198,7 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error {
|
||||
}
|
||||
installTask.AddSubTask("Install OpenClaw skill", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute))
|
||||
return mgr.Run("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawSkillInstallCommand(req.Source, req.Slug))
|
||||
return installOpenclawSkill(mgr, install.ContainerName, req.Source, req.Slug)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := installTask.Execute(); err != nil {
|
||||
@@ -153,6 +208,513 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func installLocalSkillPackage(mgr *cmd.CommandHelper, containerName, agentType, configPath, hostPath, skillName string) error {
|
||||
targetDir, err := resolveAgentLocalSkillDir(agentType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempDir, err := os.MkdirTemp("", "1panel-agent-skill-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
extractRoot := filepath.Join(tempDir, "extract")
|
||||
if err := os.MkdirAll(extractRoot, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := extractLocalSkillPackage(hostPath, extractRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
copyRoot, installedSkillName, err := normalizeLocalSkillInstallRoot(extractRoot, filepath.Join(tempDir, "install"), skillName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mgr.Run("docker", "exec", containerName, "mkdir", "-p", targetDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mgr.Run("docker", "cp", copyRoot+"/.", containerName+":"+targetDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if agentType == constant.AppOpenclaw {
|
||||
return registerOpenclawLocalSkill(containerName, configPath, installedSkillName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveAgentLocalSkillDir(agentType string) (string, error) {
|
||||
switch agentType {
|
||||
case constant.AppOpenclaw:
|
||||
return openclawManagedSkillsDir, nil
|
||||
case constant.AppHermesAgent:
|
||||
return hermesManagedSkillsDir, nil
|
||||
default:
|
||||
return "", fmt.Errorf("%s does not support", agentType)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveLocalSkillPackage(agentType, skillID string) (string, string, error) {
|
||||
skillID = strings.TrimSpace(skillID)
|
||||
id, err := strconv.ParseUint(skillID, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
return "", "", fmt.Errorf("invalid local skill id")
|
||||
}
|
||||
dbPath := filepath.Join(global.CONF.Base.InstallDir, "1panel", "db", "enterprise.db")
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
return "", "", fmt.Errorf("local skills hub is unavailable")
|
||||
}
|
||||
db, err := common.GetDBWithPath(dbPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer common.CloseDB(db)
|
||||
var item localSkillHubItem
|
||||
if err := db.Table("skill_hub_items").
|
||||
Select("id,name,status,applicable_agent,package_path").
|
||||
Where("id = ? AND status = ?", uint(id), localSkillHubPublishedStatus).
|
||||
First(&item).Error; err != nil {
|
||||
return "", "", fmt.Errorf("local skill is not published or does not exist")
|
||||
}
|
||||
if !matchLocalSkillAgent(item.ApplicableAgent, agentType) {
|
||||
return "", "", fmt.Errorf("local skill does not support %s", agentType)
|
||||
}
|
||||
packagePath, err := validateLocalSkillPackagePath(item.PackagePath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return packagePath, sanitizeLocalSkillDirName(item.Name, packagePath, skillID), nil
|
||||
}
|
||||
|
||||
func matchLocalSkillAgent(applicableAgent, agentType string) bool {
|
||||
applicableAgent = strings.TrimSpace(applicableAgent)
|
||||
if applicableAgent == "" {
|
||||
return true
|
||||
}
|
||||
for _, item := range strings.Split(applicableAgent, ",") {
|
||||
normalized := normalizeLocalSkillAgent(strings.TrimSpace(item))
|
||||
if normalized == "common" || normalized == normalizeLocalSkillAgent(agentType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeLocalSkillAgent(agentType string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(agentType)) {
|
||||
case constant.AppHermesAgent:
|
||||
return "hermes"
|
||||
default:
|
||||
return strings.ToLower(strings.TrimSpace(agentType))
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeLocalSkillDirName(name, packagePath, fallback string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
name = strings.TrimSuffix(filepath.Base(packagePath), localSkillPackageExtension(packagePath))
|
||||
}
|
||||
name = strings.Trim(safeLocalSkillDirNamePattern.ReplaceAllString(name, "-"), "-")
|
||||
if name == "" || name == "." || name == ".." {
|
||||
name = "skill-" + strings.TrimSpace(fallback)
|
||||
}
|
||||
if name == "skill-" {
|
||||
return "skill"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func validateLocalSkillPackagePath(packagePath string) (string, error) {
|
||||
packagePath = strings.TrimSpace(packagePath)
|
||||
if packagePath == "" {
|
||||
return "", fmt.Errorf("skill package path is required")
|
||||
}
|
||||
if _, err := localSkillPackageFormat(packagePath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
absPath, err := filepath.Abs(packagePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolvedPath, err := filepath.EvalSymlinks(absPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !isLocalSkillPackageAllowedPath(resolvedPath) {
|
||||
return "", fmt.Errorf("invalid local skill package path")
|
||||
}
|
||||
info, err := os.Stat(resolvedPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return "", fmt.Errorf("skill package must be a file")
|
||||
}
|
||||
return resolvedPath, nil
|
||||
}
|
||||
|
||||
func isLocalSkillPackageAllowedPath(resolvedPath string) bool {
|
||||
roots := []string{
|
||||
filepath.Join(global.CONF.Base.InstallDir, "1panel", "uploads", "skills-hub"),
|
||||
filepath.Join(global.CONF.Base.InstallDir, filepath.FromSlash(clawHubSkillTmpSubDir)),
|
||||
}
|
||||
for _, root := range roots {
|
||||
if isPathInsideResolvedRoot(resolvedPath, root) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isPathInsideResolvedRoot(resolvedPath, root string) bool {
|
||||
absRoot, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
resolvedRoot, err := filepath.EvalSymlinks(absRoot)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rel, err := filepath.Rel(resolvedRoot, resolvedPath)
|
||||
return err == nil && rel != "." && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func normalizeLocalSkillInstallRoot(extractRoot, installRoot, skillName string) (string, string, error) {
|
||||
entries, err := os.ReadDir(extractRoot)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
return "", "", fmt.Errorf("skill package is empty")
|
||||
}
|
||||
if len(entries) == 1 && entries[0].IsDir() {
|
||||
return extractRoot, entries[0].Name(), nil
|
||||
}
|
||||
skillRoot := filepath.Join(installRoot, skillName)
|
||||
if err := os.MkdirAll(skillRoot, 0755); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := copyLocalDirContents(extractRoot, skillRoot); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return installRoot, skillName, nil
|
||||
}
|
||||
|
||||
func registerOpenclawLocalSkill(containerName, configPath, skillName string) error {
|
||||
conf, err := readOpenclawConfig(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
skillKey, err := getOpenclawSkillKey(containerName, skillName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setOpenclawSkillEnabled(conf, skillKey, true)
|
||||
return writeOpenclawConfigRaw(configPath, conf)
|
||||
}
|
||||
|
||||
func extractLocalSkillPackage(packagePath, targetDir string) error {
|
||||
format, err := localSkillPackageFormat(packagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch format {
|
||||
case "zip":
|
||||
return unzipLocalSkillPackage(packagePath, targetDir)
|
||||
case "tar", "targz":
|
||||
return untarLocalSkillPackage(packagePath, targetDir, format == "targz")
|
||||
case "7z":
|
||||
return un7zLocalSkillPackage(packagePath, targetDir)
|
||||
default:
|
||||
return fmt.Errorf("unsupported skill package format")
|
||||
}
|
||||
}
|
||||
|
||||
func unzipLocalSkillPackage(packagePath, targetDir string) error {
|
||||
reader, err := zip.OpenReader(packagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
root, err := filepath.Abs(targetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range reader.File {
|
||||
name, err := safeLocalSkillZipEntryName(file.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetPath := filepath.Join(root, name)
|
||||
rel, err := filepath.Rel(root, targetPath)
|
||||
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("invalid zip entry: %s", file.Name)
|
||||
}
|
||||
if file.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(targetPath, file.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if file.Mode()&os.ModeSymlink != 0 {
|
||||
return fmt.Errorf("unsupported zip entry: %s", file.Name)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
rc, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dst, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
_ = rc.Close()
|
||||
return err
|
||||
}
|
||||
_, copyErr := io.Copy(dst, rc)
|
||||
closeErr := dst.Close()
|
||||
_ = rc.Close()
|
||||
if copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
if closeErr != nil {
|
||||
return closeErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func untarLocalSkillPackage(packagePath, targetDir string, gzipped bool) error {
|
||||
src, err := os.Open(packagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
var reader io.Reader = src
|
||||
var gzipReader *gzip.Reader
|
||||
if gzipped {
|
||||
gzipReader, err = gzip.NewReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
reader = gzipReader
|
||||
}
|
||||
root, err := filepath.Abs(targetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tarReader := tar.NewReader(reader)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := safeLocalSkillZipEntryName(header.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetPath := filepath.Join(root, name)
|
||||
rel, err := filepath.Rel(root, targetPath)
|
||||
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("invalid tar entry: %s", header.Name)
|
||||
}
|
||||
info := header.FileInfo()
|
||||
if info.IsDir() {
|
||||
if err := os.MkdirAll(targetPath, info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA {
|
||||
continue
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
dst, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, copyErr := io.Copy(dst, tarReader)
|
||||
closeErr := dst.Close()
|
||||
if copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
if closeErr != nil {
|
||||
return closeErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func un7zLocalSkillPackage(packagePath, targetDir string) error {
|
||||
if _, err := listLocal7zEntries(packagePath); err != nil {
|
||||
return err
|
||||
}
|
||||
root, err := filepath.Abs(targetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bin, err := findLocal7zBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempDir, err := os.MkdirTemp("", "1panel-agent-skill-7z-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
if err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).Run(bin, "x", "-y", "-o"+tempDir, packagePath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateExtractedLocalSkillPackage(tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyLocalDirContents(tempDir, root)
|
||||
}
|
||||
|
||||
func safeLocalSkillZipEntryName(name string) (string, error) {
|
||||
clean := path.Clean(strings.ReplaceAll(name, "\\", "/"))
|
||||
clean = strings.TrimLeft(clean, "/")
|
||||
if clean == "." || clean == "" || clean == ".." || strings.HasPrefix(clean, "../") {
|
||||
return "", fmt.Errorf("invalid zip entry: %s", name)
|
||||
}
|
||||
return clean, nil
|
||||
}
|
||||
|
||||
func copyLocalDirContents(sourceRoot, targetRoot string) error {
|
||||
return filepath.Walk(sourceRoot, func(sourcePath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sourcePath == sourceRoot {
|
||||
return nil
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || (!info.IsDir() && !info.Mode().IsRegular()) {
|
||||
return fmt.Errorf("unsupported skill package entry: %s", sourcePath)
|
||||
}
|
||||
rel, err := filepath.Rel(sourceRoot, sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := safeLocalSkillZipEntryName(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetPath := filepath.Join(targetRoot, name)
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(targetPath, info.Mode())
|
||||
}
|
||||
return copyLocalSkillFile(sourcePath, targetPath)
|
||||
})
|
||||
}
|
||||
|
||||
func copyLocalSkillFile(source, target string) error {
|
||||
src, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
dst, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
_ = dst.Close()
|
||||
return err
|
||||
}
|
||||
return dst.Close()
|
||||
}
|
||||
|
||||
func validateExtractedLocalSkillPackage(root string) error {
|
||||
return filepath.Walk(root, func(currentPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if currentPath == root {
|
||||
return nil
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || (!info.IsDir() && !info.Mode().IsRegular()) {
|
||||
return fmt.Errorf("unsupported skill package entry: %s", currentPath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func localSkillPackageExtension(name string) string {
|
||||
lower := strings.ToLower(filepath.Base(strings.TrimSpace(name)))
|
||||
if lower == "" || lower == "." {
|
||||
return ""
|
||||
}
|
||||
if strings.HasSuffix(lower, ".tar.gz") {
|
||||
return ".tar.gz"
|
||||
}
|
||||
return strings.ToLower(filepath.Ext(lower))
|
||||
}
|
||||
|
||||
func localSkillPackageFormat(name string) (string, error) {
|
||||
switch localSkillPackageExtension(name) {
|
||||
case ".zip":
|
||||
return "zip", nil
|
||||
case ".7z":
|
||||
return "7z", nil
|
||||
case ".tar":
|
||||
return "tar", nil
|
||||
case ".tar.gz":
|
||||
return "targz", nil
|
||||
default:
|
||||
return "", fmt.Errorf("only .zip, .7z, .tar, and .tar.gz skill packages are supported")
|
||||
}
|
||||
}
|
||||
|
||||
func findLocal7zBinary() (string, error) {
|
||||
for _, name := range []string{"7z", "7za", "7zr"} {
|
||||
if path, err := exec.LookPath(name); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("7z command is required to process .7z skill packages")
|
||||
}
|
||||
|
||||
func listLocal7zEntries(packagePath string) ([]string, error) {
|
||||
bin, err := findLocal7zBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output, err := exec.Command(bin, "l", "-slt", packagePath).CombinedOutput()
|
||||
if err != nil {
|
||||
if message := strings.TrimSpace(string(output)); message != "" {
|
||||
return nil, fmt.Errorf("7z failed: %w: %s", err, message)
|
||||
}
|
||||
return nil, fmt.Errorf("7z failed: %w", err)
|
||||
}
|
||||
entries := make([]string, 0)
|
||||
for _, line := range strings.Split(string(output), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "Path = ") {
|
||||
continue
|
||||
}
|
||||
entry := strings.TrimSpace(strings.TrimPrefix(line, "Path = "))
|
||||
if entry == "" || entry == packagePath || entry == filepath.Base(packagePath) {
|
||||
continue
|
||||
}
|
||||
name, err := safeLocalSkillZipEntryName(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, name)
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
return nil, fmt.Errorf("skill package is empty")
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UninstallSkill(req dto.AgentSkillUninstallReq) error {
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
@@ -205,15 +767,19 @@ func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) {
|
||||
func loadOpenclawSkillSearchOutput(containerName, source, keyword string) (string, error) {
|
||||
switch source {
|
||||
case "skillhub":
|
||||
return runDockerExecWithStdout(2*time.Minute, containerName, "skillhub", "search", keyword, "--json")
|
||||
return cmd.RunDockerExecWithStdout(2*time.Minute, containerName, "skillhub", "search", keyword, "--json")
|
||||
default:
|
||||
return runDockerExecWithStdout(
|
||||
2*time.Minute,
|
||||
containerName,
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("CLAWHUB_REGISTRY=%q clawhub search %q", resolveClawhubRegistry(source), keyword),
|
||||
)
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(2*time.Minute)).
|
||||
RunWithStdout(
|
||||
"docker",
|
||||
"exec",
|
||||
"-e",
|
||||
"CLAWHUB_REGISTRY="+resolveClawhubRegistry(source),
|
||||
containerName,
|
||||
"clawhub",
|
||||
"search",
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,22 +827,28 @@ func parseClawhubSearchResult(output, source string) []dto.AgentSkillSearchItem
|
||||
return items
|
||||
}
|
||||
|
||||
func buildOpenclawSkillInstallCommand(source, slug string) string {
|
||||
func installOpenclawSkill(mgr *cmd.CommandHelper, containerName, source, slug string) error {
|
||||
if err := mgr.Run("docker", "exec", containerName, "mkdir", "-p", openclawManagedSkillsDir); err != nil {
|
||||
return err
|
||||
}
|
||||
switch source {
|
||||
case "clawhub-global", "clawhub-cn":
|
||||
return fmt.Sprintf(
|
||||
"mkdir -p %s && CLAWHUB_REGISTRY=%q clawhub --workdir /home/node/.openclaw --dir skills install %q",
|
||||
openclawManagedSkillsDir,
|
||||
resolveClawhubRegistry(source),
|
||||
return mgr.Run(
|
||||
"docker",
|
||||
"exec",
|
||||
"-e",
|
||||
"CLAWHUB_REGISTRY="+resolveClawhubRegistry(source),
|
||||
containerName,
|
||||
"clawhub",
|
||||
"--workdir",
|
||||
"/home/node/.openclaw",
|
||||
"--dir",
|
||||
"skills",
|
||||
"install",
|
||||
slug,
|
||||
)
|
||||
default:
|
||||
return fmt.Sprintf(
|
||||
"mkdir -p %s && skillhub --dir %s install %q",
|
||||
openclawManagedSkillsDir,
|
||||
openclawManagedSkillsDir,
|
||||
slug,
|
||||
)
|
||||
return mgr.Run("docker", "exec", containerName, "skillhub", "--dir", openclawManagedSkillsDir, "install", slug)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +862,7 @@ func resolveClawhubRegistry(source string) string {
|
||||
}
|
||||
|
||||
func getOpenclawSkillKey(containerName, name string) (string, error) {
|
||||
output, err := runDockerExecWithStdout(2*time.Minute, containerName, "sh", "-c", fmt.Sprintf("openclaw skills info %q --json 2>&1", name))
|
||||
output, err := cmd.RunDockerExecWithStdout(2*time.Minute, containerName, "openclaw", "skills", "info", name, "--json")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -307,7 +879,7 @@ func parseOpenclawSkillKey(name, output string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
if payload.SkillKey == "" {
|
||||
return "", fmt.Errorf("skill %s does not have a skillKey", name)
|
||||
return name, nil
|
||||
}
|
||||
return payload.SkillKey, nil
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
agentenv "github.com/1Panel-dev/1Panel/agent/utils/env"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
|
||||
"github.com/joho/godotenv"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -60,11 +61,6 @@ func ensureContainerRunning(containerName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDockerExecWithStdout(timeout time.Duration, containerName string, args ...string) (string, error) {
|
||||
commandArgs := append([]string{"exec", containerName}, args...)
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(timeout)).RunWithStdout("docker", commandArgs...)
|
||||
}
|
||||
|
||||
func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAccountInput, error) {
|
||||
resolvedAPIKey := strings.TrimSpace(apiKey)
|
||||
resolvedBaseURL := strings.TrimSpace(baseURL)
|
||||
@@ -382,6 +378,11 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
|
||||
item.BridgePort = toInt(bridge)
|
||||
}
|
||||
}
|
||||
if agentType == constant.AppHermesAgent {
|
||||
auth := readHermesDashboardAuthFromInstall(appInstall)
|
||||
item.DashboardUsername = auth.Username
|
||||
item.DashboardPassword = auth.Password
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
@@ -584,6 +585,9 @@ func checkAgentUpgradable(install model.AppInstall) bool {
|
||||
if install.App.ID == 0 {
|
||||
return false
|
||||
}
|
||||
if ignoreUpdate(install) {
|
||||
return false
|
||||
}
|
||||
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(install.App.ID))
|
||||
if err != nil || len(details) == 0 {
|
||||
return false
|
||||
@@ -863,13 +867,15 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
envPath := path.Join(confDir, ".env")
|
||||
lines := []string{fmt.Sprintf("OPENCLAW_GATEWAY_TOKEN=%s", token)}
|
||||
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && account.APIKey != "" {
|
||||
lines = append(lines, fmt.Sprintf("%s=%s", envKey, account.APIKey))
|
||||
envMap := map[string]string{
|
||||
"OPENCLAW_GATEWAY_TOKEN": token,
|
||||
}
|
||||
content := strings.Join(lines, "\n") + "\n"
|
||||
return fileOp.SaveFile(envPath, content, 0600)
|
||||
order := []string{"OPENCLAW_GATEWAY_TOKEN"}
|
||||
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && account.APIKey != "" {
|
||||
envMap[envKey] = account.APIKey
|
||||
order = append(order, envKey)
|
||||
}
|
||||
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
||||
}
|
||||
|
||||
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||
@@ -1527,6 +1533,41 @@ func readInstallEnv(envStr string) map[string]interface{} {
|
||||
return data
|
||||
}
|
||||
|
||||
func readAgentEnvMap(envPath string) (map[string]string, error) {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(envPath) {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
envMap, err := godotenv.Read(envPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return envMap, nil
|
||||
}
|
||||
|
||||
func writeAgentEnvMap(envPath string, envMap map[string]string, order []string) error {
|
||||
if len(envMap) == 0 {
|
||||
return files.NewFileOp().SaveFile(envPath, "", 0600)
|
||||
}
|
||||
return agentenv.WriteWithOrder(envMap, envPath, order)
|
||||
}
|
||||
|
||||
func upsertAgentEnv(envPath string, values map[string]string, order []string, overwrite bool) error {
|
||||
envMap, err := readAgentEnvMap(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range values {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if overwrite || strings.TrimSpace(envMap[key]) == "" {
|
||||
envMap[key] = value
|
||||
}
|
||||
}
|
||||
return writeAgentEnvMap(envPath, envMap, order)
|
||||
}
|
||||
|
||||
func maskKey(value string) string {
|
||||
trim := strings.TrimSpace(value)
|
||||
if len(trim) <= 6 {
|
||||
|
||||
@@ -77,7 +77,7 @@ func (u *AIToolService) LoadDetail(name string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama show %s", containerName, name)
|
||||
stdout, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "ollama", "show", name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func (u *AIToolService) Close(name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.RunDefaultBashCf("docker exec %s ollama stop %s", containerName, name); err != nil {
|
||||
if err := cmd.NewCommandMgr().Run("docker", "exec", containerName, "ollama", "stop", name); err != nil {
|
||||
return fmt.Errorf("handle ollama stop %s failed, %v", name, err)
|
||||
}
|
||||
return nil
|
||||
@@ -194,7 +194,7 @@ func (u *AIToolService) Delete(req dto.ForceDelete) error {
|
||||
}
|
||||
for _, item := range ollamaList {
|
||||
if item.Status != constant.StatusDeleted {
|
||||
if err := cmd.RunDefaultBashCf("docker exec %s ollama rm %s", containerName, item.Name); err != nil && !req.ForceDelete {
|
||||
if err := cmd.NewCommandMgr().Run("docker", "exec", containerName, "ollama", "rm", item.Name); err != nil && !req.ForceDelete {
|
||||
return fmt.Errorf("handle ollama rm %s failed, %v", item.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama list", containerName)
|
||||
stdout, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "ollama", "list")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -382,12 +382,15 @@ func LoadContainerName() (string, error) {
|
||||
}
|
||||
|
||||
func loadModelSize(name string, containerName string) (string, error) {
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama list | grep %s", containerName, name)
|
||||
stdout, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "ollama", "list")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if !strings.Contains(line, name) {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
|
||||
+213
-21
@@ -3,6 +3,13 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
@@ -15,24 +22,27 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"mime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AlertService struct{}
|
||||
|
||||
var eeHiddenAlertTypes = []string{"licenseException", "panelUpdate", "panelPwdEndTime"}
|
||||
var communityAlertMethodTypeNames = map[string]string{
|
||||
constant.WeCom: "WeCom",
|
||||
constant.DingTalk: "DingTalk",
|
||||
constant.FeiShu: "FeiShu",
|
||||
constant.SMS: "SMS",
|
||||
}
|
||||
|
||||
type IAlertService interface {
|
||||
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
||||
GetAlerts() ([]dto.AlertDTO, error)
|
||||
CreateAlert(create dto.AlertCreate) error
|
||||
UpdateAlert(req dto.AlertUpdate) error
|
||||
CreateAlert(create dto.AlertCreate, operator string) error
|
||||
UpdateAlert(req dto.AlertUpdate, operator string) error
|
||||
DeleteAlert(id uint) error
|
||||
GetAlert(id uint) (dto.AlertDTO, error)
|
||||
UpdateStatus(id uint, status string) error
|
||||
ExternalUpdateAlert(req dto.AlertCreate) error
|
||||
ExternalUpdateAlert(req dto.AlertCreate, operator string) error
|
||||
|
||||
GetDisks() ([]dto.DiskDTO, error)
|
||||
PageAlertLogs(req dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error)
|
||||
@@ -40,8 +50,9 @@ type IAlertService interface {
|
||||
GetClams() ([]dto.ClamDTO, error)
|
||||
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
||||
|
||||
GetAlertConfig() ([]model.AlertConfig, error)
|
||||
UpdateAlertConfig(req dto.AlertConfigUpdate) error
|
||||
GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, error)
|
||||
PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error)
|
||||
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
||||
DeleteAlertConfig(id uint) error
|
||||
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
||||
}
|
||||
@@ -55,6 +66,9 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
opts []repo.DBOption
|
||||
result []dto.AlertDTO
|
||||
)
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(eeHiddenAlertTypes))
|
||||
}
|
||||
if search.Status != "" {
|
||||
opts = append(opts, repo.WithByStatus(search.Status))
|
||||
}
|
||||
@@ -81,6 +95,8 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
CreateUser: item.CreateUser,
|
||||
UpdateUser: item.UpdateUser,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
@@ -112,6 +128,8 @@ func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
CreateUser: item.CreateUser,
|
||||
UpdateUser: item.UpdateUser,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
@@ -120,7 +138,10 @@ func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error {
|
||||
if err := a.validateCommunityAlertMethod(create.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
var alertID uint
|
||||
var alertInfo model.Alert
|
||||
if create.Project != "" {
|
||||
@@ -137,7 +158,7 @@ func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
}
|
||||
upAlert.ID = alertID
|
||||
err := a.UpdateAlert(upAlert)
|
||||
err := a.UpdateAlert(upAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -146,6 +167,8 @@ func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
}
|
||||
alertInfo.CreateUser = operator
|
||||
alertInfo.UpdateUser = operator
|
||||
|
||||
if err := alertRepo.Create(&alertInfo); err != nil {
|
||||
return err
|
||||
@@ -156,7 +179,10 @@ func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||
if err := a.validateCommunityAlertMethod(req.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["id"] = req.ID
|
||||
@@ -169,6 +195,7 @@ func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
|
||||
upMap["status"] = req.Status
|
||||
upMap["send_count"] = req.SendCount
|
||||
upMap["advanced_params"] = req.AdvancedParams
|
||||
upMap["update_user"] = operator
|
||||
|
||||
if err := alertRepo.Update(upMap, repo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
@@ -308,12 +335,25 @@ func (a AlertService) GetDisks() ([]dto.DiskDTO, error) {
|
||||
|
||||
func executeDiskCommand() (string, error) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashC("df -hT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev")
|
||||
stdout, err := cmdMgr.RunWithStdout("df", "-hT", "-P")
|
||||
if err != nil {
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunWithStdoutBashC("df -lhT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev")
|
||||
stdout, err = cmdMgr2.RunWithStdout("df", "-lhT", "-P")
|
||||
}
|
||||
return stdout, err
|
||||
if err != nil {
|
||||
return stdout, err
|
||||
}
|
||||
var lines []string
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if !strings.Contains(line, "/") || strings.Contains(line, "tmpfs") || strings.Contains(line, "snap/core") || strings.Contains(line, "udev") {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return strings.Join(lines, "\n"), nil
|
||||
}
|
||||
|
||||
func shouldExclude(fields []string, mountPoint string, excludes map[string]struct{}) bool {
|
||||
@@ -441,17 +481,40 @@ func (a AlertService) GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
||||
return cronJobs, err
|
||||
}
|
||||
|
||||
func (a AlertService) GetAlertConfig() ([]model.AlertConfig, error) {
|
||||
func (a AlertService) GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, error) {
|
||||
var (
|
||||
opts []repo.DBOption
|
||||
configs []model.AlertConfig
|
||||
)
|
||||
if len(req.ExcludeTypes) > 0 {
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
||||
}
|
||||
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
||||
configs, err := alertRepo.AlertConfigList(opts...)
|
||||
return configs, err
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error) {
|
||||
opts := []repo.DBOption{
|
||||
alertRepo.WithByTypeNotIn([]string{"common"}),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
}
|
||||
if len(req.ExcludeTypes) > 0 {
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
||||
}
|
||||
return alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||
if err := a.validateCommunityAlertConfigType(req.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.checkAlertConfigDisplayNameUnique(req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.checkAlertConfigSMSPhoneUnique(req); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.ID != 0 {
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["id"] = req.ID
|
||||
@@ -459,6 +522,7 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
upMap["title"] = req.Title
|
||||
upMap["status"] = req.Status
|
||||
upMap["config"] = req.Config
|
||||
upMap["update_user"] = operator
|
||||
if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -467,6 +531,8 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
if err := copier.Copy(&alertConfig, &req); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
}
|
||||
alertConfig.CreateUser = operator
|
||||
alertConfig.UpdateUser = operator
|
||||
if err := alertRepo.CreateAlertConfig(&alertConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -475,7 +541,130 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate) error {
|
||||
if req.Type != constant.SMSConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
phone := alertConfigSMSPhone(req.Config)
|
||||
configs, err := alertRepo.AlertConfigList(alertRepo.WithByType(req.Type))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
if req.ID != 0 && config.ID == req.ID {
|
||||
continue
|
||||
}
|
||||
if alertConfigSMSPhone(config.Config) == phone {
|
||||
return buserr.New("ErrAlertConfigPhoneExist")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdate) error {
|
||||
displayName := alertConfigDisplayName(req.Type, req.Config)
|
||||
if displayName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
configs, err := alertRepo.AlertConfigList(alertRepo.WithByType(req.Type))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
if req.ID != 0 && config.ID == req.ID {
|
||||
continue
|
||||
}
|
||||
if alertConfigDisplayName(config.Type, config.Config) == displayName {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) validateCommunityAlertMethod(method string) error {
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(method) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, item := range strings.Split(method, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if configID, err := strconv.ParseUint(item, 10, 64); err == nil {
|
||||
config, err := alertRepo.GetConfigById(uint(configID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := communityAlertMethodTypeNames[config.Type]; ok {
|
||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, ok := communityAlertMethodTypeNames[item]; ok {
|
||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) validateCommunityAlertConfigType(configType string) error {
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||
return nil
|
||||
}
|
||||
if _, ok := communityAlertMethodTypeNames[configType]; ok {
|
||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func alertConfigDisplayName(configType, configData string) string {
|
||||
switch configType {
|
||||
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark, constant.SMS:
|
||||
var cfg struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(configData), &cfg); err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(cfg.DisplayName)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func alertConfigSMSPhone(configData string) string {
|
||||
var cfg struct {
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(configData), &cfg); err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(cfg.Phone)
|
||||
}
|
||||
|
||||
func (a AlertService) DeleteAlertConfig(id uint) error {
|
||||
_, err := alertRepo.GetConfigById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usedAlerts, err := alertRepo.List(alertRepo.WithByAlertMethodContainsConfigID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(usedAlerts) > 0 {
|
||||
return buserr.New("ErrAlertConfigInUse")
|
||||
}
|
||||
return alertRepo.DeleteAlertConfig(repo.WithByID(id))
|
||||
}
|
||||
|
||||
@@ -501,14 +690,17 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||
Body: i18n.GetMsgByKey("TestAlert"),
|
||||
IsHTML: false,
|
||||
}
|
||||
transport := xpack.LoadRequestTransport()
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
if err := email.SendMail(cfg, msg, transport); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
|
||||
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error {
|
||||
if err := a.validateCommunityAlertMethod(updateAlert.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
var newStatus string
|
||||
if updateAlert.SendCount == 0 {
|
||||
@@ -552,7 +744,7 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
|
||||
} else {
|
||||
if updateAlert.Method != "" && updateAlert.Title != "" {
|
||||
updateAlert.Status = newStatus
|
||||
if err := a.CreateAlert(updateAlert); err != nil {
|
||||
if err := a.CreateAlert(updateAlert, operator); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
+233
-104
@@ -4,9 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
@@ -23,7 +25,7 @@ import (
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
gnet "github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,7 +36,7 @@ const (
|
||||
|
||||
type AlertTaskHelper struct {
|
||||
DiskIO chan []disk.IOCountersStat
|
||||
NetIO chan []net.IOCountersStat
|
||||
NetIO chan []gnet.IOCountersStat
|
||||
}
|
||||
|
||||
type IAlertTaskHelper interface {
|
||||
@@ -46,6 +48,7 @@ type IAlertTaskHelper interface {
|
||||
|
||||
var cpuLoad1, cpuLoad5, cpuLoad15 []float64
|
||||
var memoryLoad1, memoryLoad5, memoryLoad15 []float64
|
||||
var alertTaskMu sync.Mutex
|
||||
|
||||
var baseTypes = map[string]bool{"ssl": true, "siteEndTime": true, "panelPwdEndTime": true, "panelUpdate": true}
|
||||
var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, "load": true, "panelLogin": true, "sshLogin": true, "nodeException": true, "licenseException": true}
|
||||
@@ -53,36 +56,53 @@ var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, "
|
||||
func NewIAlertTaskHelper() IAlertTaskHelper {
|
||||
return &AlertTaskHelper{
|
||||
DiskIO: make(chan []disk.IOCountersStat, 1),
|
||||
NetIO: make(chan []net.IOCountersStat, 1),
|
||||
NetIO: make(chan []gnet.IOCountersStat, 1),
|
||||
}
|
||||
}
|
||||
func (m *AlertTaskHelper) StartTask() {
|
||||
alertTaskMu.Lock()
|
||||
defer alertTaskMu.Unlock()
|
||||
m.startTaskLocked()
|
||||
}
|
||||
|
||||
func (m *AlertTaskHelper) startTaskLocked() {
|
||||
baseAlert, resourceAlert := m.getClassifiedAlerts()
|
||||
if len(baseAlert) == 0 && len(resourceAlert) == 0 {
|
||||
return
|
||||
}
|
||||
handleBaseAlerts(baseAlert)
|
||||
handleResourceAlerts(resourceAlert)
|
||||
handleBaseAlertsLocked(baseAlert)
|
||||
handleResourceAlertsLocked(resourceAlert)
|
||||
}
|
||||
|
||||
func (m *AlertTaskHelper) StopTask() {
|
||||
stopBaseJob()
|
||||
stopResourceJob()
|
||||
alertTaskMu.Lock()
|
||||
defer alertTaskMu.Unlock()
|
||||
stopBaseJobLocked()
|
||||
stopResourceJobLocked()
|
||||
}
|
||||
|
||||
func (m *AlertTaskHelper) ResetTask() {
|
||||
m.StopTask()
|
||||
m.StartTask()
|
||||
alertTaskMu.Lock()
|
||||
defer alertTaskMu.Unlock()
|
||||
stopBaseJobLocked()
|
||||
stopResourceJobLocked()
|
||||
m.startTaskLocked()
|
||||
}
|
||||
|
||||
func (m *AlertTaskHelper) InitTask(alertType string) {
|
||||
alertTaskMu.Lock()
|
||||
defer alertTaskMu.Unlock()
|
||||
m.initTaskLocked(alertType)
|
||||
}
|
||||
|
||||
func (m *AlertTaskHelper) initTaskLocked(alertType string) {
|
||||
resetAlertState(alertType)
|
||||
if baseTypes[alertType] {
|
||||
stopBaseJob()
|
||||
stopBaseJobLocked()
|
||||
} else if resourceTypes[alertType] {
|
||||
stopResourceJob()
|
||||
stopResourceJobLocked()
|
||||
}
|
||||
m.StartTask()
|
||||
m.startTaskLocked()
|
||||
}
|
||||
|
||||
func resetAlertState(alertType string) {
|
||||
@@ -110,14 +130,14 @@ func (m *AlertTaskHelper) getClassifiedAlerts() (baseAlerts, resourceAlerts []dt
|
||||
return
|
||||
}
|
||||
|
||||
func handleBaseAlerts(baseAlerts []dto.AlertDTO) {
|
||||
func handleBaseAlertsLocked(baseAlerts []dto.AlertDTO) {
|
||||
if len(baseAlerts) == 0 {
|
||||
stopBaseJob()
|
||||
stopBaseJobLocked()
|
||||
return
|
||||
}
|
||||
if global.AlertBaseJobID == 0 {
|
||||
baseTask(baseAlerts)
|
||||
jobID, err := global.Cron.AddFunc("*/30 * * * *", func() {
|
||||
jobID, err := global.Cron.AddFunc("@every 30m", func() {
|
||||
baseTask(baseAlerts)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -129,9 +149,9 @@ func handleBaseAlerts(baseAlerts []dto.AlertDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleResourceAlerts(resourceAlerts []dto.AlertDTO) {
|
||||
func handleResourceAlertsLocked(resourceAlerts []dto.AlertDTO) {
|
||||
if len(resourceAlerts) == 0 {
|
||||
stopResourceJob()
|
||||
stopResourceJobLocked()
|
||||
return
|
||||
}
|
||||
if global.AlertResourceJobID == 0 {
|
||||
@@ -147,7 +167,7 @@ func handleResourceAlerts(resourceAlerts []dto.AlertDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func stopBaseJob() {
|
||||
func stopBaseJobLocked() {
|
||||
if global.AlertBaseJobID != 0 {
|
||||
global.Cron.Remove(global.AlertBaseJobID)
|
||||
global.AlertBaseJobID = 0
|
||||
@@ -155,7 +175,7 @@ func stopBaseJob() {
|
||||
}
|
||||
}
|
||||
|
||||
func stopResourceJob() {
|
||||
func stopResourceJobLocked() {
|
||||
if global.AlertResourceJobID != 0 {
|
||||
global.Cron.Remove(global.AlertResourceJobID)
|
||||
global.AlertResourceJobID = 0
|
||||
@@ -174,10 +194,16 @@ func baseTask(baseAlert []dto.AlertDTO) {
|
||||
case "siteEndTime":
|
||||
loadWebsiteInfo(alert)
|
||||
case "panelPwdEndTime":
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
continue
|
||||
}
|
||||
if global.IsMaster {
|
||||
loadPanelPwd(alert)
|
||||
}
|
||||
case "panelUpdate":
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
continue
|
||||
}
|
||||
if global.IsMaster {
|
||||
loadPanelUpdate(alert)
|
||||
}
|
||||
@@ -210,6 +236,9 @@ func resourceTask(resourceAlert []dto.AlertDTO) {
|
||||
loadNodeException(alert)
|
||||
}
|
||||
case "licenseException":
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
continue
|
||||
}
|
||||
if execute && global.IsMaster {
|
||||
loadLicenseException(alert)
|
||||
}
|
||||
@@ -457,6 +486,7 @@ func loadPanelLogin(alert dto.AlertDTO) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to check recent failed ip login logs: %v", err)
|
||||
}
|
||||
records = filterLoginLogsNotInWhitelist(records, whitelist)
|
||||
if len(records) > 0 {
|
||||
quota := strings.Join(func() []string {
|
||||
var ips []string
|
||||
@@ -506,6 +536,7 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to check recent failed ip ssh login logs: %v", err)
|
||||
}
|
||||
records = filterSSHLoginEntriesNotInWhitelist(records, whitelist)
|
||||
if len(records) > 0 {
|
||||
quota := strings.Join(records, "\n")
|
||||
params := []dto.Param{
|
||||
@@ -524,9 +555,63 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
||||
filtered := make([]model.LoginLog, 0, len(records))
|
||||
for _, record := range records {
|
||||
if !isIPInWhitelist(record.IP, whitelist) {
|
||||
filtered = append(filtered, record)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func filterSSHLoginEntriesNotInWhitelist(records []string, whitelist []string) []string {
|
||||
filtered := make([]string, 0, len(records))
|
||||
for _, record := range records {
|
||||
ip := record
|
||||
if idx := strings.Index(record, "-"); idx >= 0 {
|
||||
ip = record[:idx]
|
||||
}
|
||||
if !isIPInWhitelist(ip, whitelist) {
|
||||
filtered = append(filtered, record)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func isIPInWhitelist(ip string, whitelist []string) bool {
|
||||
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
||||
if targetIP == nil {
|
||||
return false
|
||||
}
|
||||
for _, item := range whitelist {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if item == ip {
|
||||
return true
|
||||
}
|
||||
if whiteIP := net.ParseIP(item); whiteIP != nil {
|
||||
if whiteIP.Equal(targetIP) {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ipNet.Contains(targetIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loadNodeException(alert dto.AlertDTO) {
|
||||
// only master alert
|
||||
failCount, err := xpack.GetNodeErrorAlert()
|
||||
failCount, err := xpack.AlertProvider.GetNodeErrorAlert()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting node, err: %s", err)
|
||||
return
|
||||
@@ -555,7 +640,7 @@ func loadNodeException(alert dto.AlertDTO) {
|
||||
|
||||
func loadLicenseException(alert dto.AlertDTO) {
|
||||
// only master alert
|
||||
failCount, err := xpack.GetLicenseErrorAlert()
|
||||
failCount, err := xpack.AlertProvider.GetLicenseErrorAlert()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting license, err: %s", err)
|
||||
return
|
||||
@@ -590,94 +675,138 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
|
||||
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
|
||||
for _, m := range methods {
|
||||
m = strings.TrimSpace(m)
|
||||
switch m {
|
||||
case constant.SMS:
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
continue
|
||||
}
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.SMS)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
alertErr := xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.SMS)
|
||||
case constant.Email:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.Email)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.Email)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, m)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
err := xpack.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, m, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, m, err)
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, m)
|
||||
case constant.Bark:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, m)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, m)
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateBarkAlertLog(create, alertInfo, params, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert %s push failed, err: %v", alertType, m, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, m)
|
||||
default:
|
||||
if configId, err := strconv.ParseUint(m, 10, 64); err == nil {
|
||||
sendAlertsByConfigId(alert, alertType, quota, quotaType, params, uint(configId))
|
||||
} else {
|
||||
sendAlertsByLegacyMethod(alert, alertType, quota, quotaType, params, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendAlertsByConfigId(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, configId uint) {
|
||||
config, err := alertRepo.GetConfigById(configId)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("alert config not found for id %d: %v", configId, err)
|
||||
return
|
||||
}
|
||||
doSendAlert(alert, alertType, quota, quotaType, params, config)
|
||||
}
|
||||
|
||||
func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, method string) {
|
||||
typeMap := map[string]string{
|
||||
"mail": constant.Email,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.SMS: constant.SMS,
|
||||
}
|
||||
configType, ok := typeMap[method]
|
||||
if !ok {
|
||||
configType = method
|
||||
}
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
doSendAlert(alert, alertType, quota, quotaType, params, config)
|
||||
}
|
||||
|
||||
func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, config model.AlertConfig) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
methodStr := strconv.Itoa(int(config.ID))
|
||||
switch config.Type {
|
||||
case constant.SMS:
|
||||
if !alertUtil.CheckSMSSendLimit(config, methodStr) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
alertErr := xpack.AlertProvider.CreateSMSAlertLog(alertType, alert, create, quotaType, params, config, methodStr)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.Email:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo, config)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.Bark:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Bark)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateBarkAlertLog(create, alertInfo, params, transport, agentInfo, config)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert %s push failed, err: %v", alertType, methodStr, alertErr.Error())
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
func getRepoOptionsByProject(project string) []repo.DBOption {
|
||||
var opts []repo.DBOption
|
||||
|
||||
+276
-124
@@ -1,12 +1,16 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AlertSender struct {
|
||||
@@ -22,115 +26,90 @@ func NewAlertSender(alert dto.AlertDTO, quotaType string) *AlertSender {
|
||||
}
|
||||
|
||||
func (s *AlertSender) Send(quota string, params []dto.Param) {
|
||||
methods := strings.Split(s.alert.Method, ",")
|
||||
for _, method := range methods {
|
||||
method = strings.TrimSpace(method)
|
||||
switch method {
|
||||
case constant.SMS:
|
||||
s.sendSMS(quota, params)
|
||||
case constant.Email:
|
||||
s.sendEmail(quota, params)
|
||||
case constant.Bark:
|
||||
s.sendBark(quota, params)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
s.sendWebhook(quota, params, method)
|
||||
}
|
||||
}
|
||||
s.sendByConfigIds(s.alert.Method, quota, params, false)
|
||||
}
|
||||
|
||||
func (s *AlertSender) ResourceSend(quota string, params []dto.Param) {
|
||||
methods := strings.Split(s.alert.Method, ",")
|
||||
for _, method := range methods {
|
||||
method = strings.TrimSpace(method)
|
||||
switch method {
|
||||
case constant.SMS:
|
||||
s.sendResourceSMS(quota, params)
|
||||
case constant.Email:
|
||||
s.sendResourceEmail(quota, params)
|
||||
case constant.Bark:
|
||||
s.sendResourceBark(quota, params)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
s.sendResourceWebhook(quota, params, method)
|
||||
s.sendByConfigIds(s.alert.Method, quota, params, true)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendByConfigIds(methodStr string, quota string, params []dto.Param, isResource bool) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
configIds := strings.Split(methodStr, ",")
|
||||
for _, idStr := range configIds {
|
||||
idStr = strings.TrimSpace(idStr)
|
||||
configId, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
s.sendByLegacyMethod(idStr, quota, params, isResource)
|
||||
continue
|
||||
}
|
||||
config, err := alertRepo.GetConfigById(uint(configId))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("alert config not found for id %d: %v", configId, err)
|
||||
continue
|
||||
}
|
||||
s.sendByConfig(config, quota, params, isResource)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, params []dto.Param, isResource bool) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
switch config.Type {
|
||||
case constant.SMS:
|
||||
if isResource {
|
||||
s.sendResourceSMSWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendSMSWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.Email:
|
||||
if isResource {
|
||||
s.sendResourceEmailWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendEmailWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.Bark:
|
||||
if isResource {
|
||||
s.sendResourceBarkWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendBarkWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
if isResource {
|
||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendWebhookWithConfig(config, quota, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
return
|
||||
func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS}
|
||||
configType := method
|
||||
if mapped, ok := typeMap[method]; ok {
|
||||
configType = mapped
|
||||
}
|
||||
|
||||
totalCount, isValid := s.canSendAlert(constant.SMS)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
}
|
||||
|
||||
err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
s.sendByConfig(config, quota, params, isResource)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
|
||||
totalCount, isValid := s.canSendAlert(constant.Email)
|
||||
if !isValid {
|
||||
func (s *AlertSender) sendSMSWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
method := strconv.Itoa(int(config.ID))
|
||||
if !alertUtil.CheckSMSSendLimit(config, method) {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
|
||||
}
|
||||
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendBark(quota string, params []dto.Param) {
|
||||
totalCount, isValid := s.canSendAlert(constant.Bark)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
|
||||
}
|
||||
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert bark push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Bark)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||
totalCount, isValid := s.canSendAlert(method)
|
||||
if !isValid {
|
||||
return
|
||||
@@ -141,43 +120,60 @@ func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method strin
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: method,
|
||||
}
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo)
|
||||
|
||||
err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, config, method)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
|
||||
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMS))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendSMSWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.SMS)
|
||||
func (s *AlertSender) sendEmailWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
if err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS); err != nil {
|
||||
global.LOG.Errorf("failed to send SMS alert: %v", err)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo, config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.Email)
|
||||
func (s *AlertSender) sendResourceEmailWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
@@ -189,19 +185,70 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo, config); err != nil {
|
||||
global.LOG.Errorf("failed to send Email alert: %v", err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.Bark)
|
||||
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendEmailWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceEmailWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendBarkWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo, config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert bark push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceBarkWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
@@ -213,18 +260,114 @@ func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
if err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo, config); err != nil {
|
||||
global.LOG.Errorf("failed to send Bark alert: %v", err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Bark)
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendBark(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.Bark))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendBarkWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.Bark))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceBarkWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo); err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(method))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendWebhookWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, method string) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(method))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMSWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
method := strconv.Itoa(int(config.ID))
|
||||
if !alertUtil.CheckSMSSendLimit(config, method) {
|
||||
return
|
||||
}
|
||||
|
||||
todayCount, isValid := s.canResourceSendAlert(method)
|
||||
if !isValid {
|
||||
return
|
||||
@@ -235,16 +378,25 @@ func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, meth
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: method,
|
||||
}
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
if err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo); err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
|
||||
|
||||
if err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, config, method); err != nil {
|
||||
global.LOG.Errorf("failed to send SMS alert: %v", err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceSMSWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) canSendAlert(method string) (uint, bool) {
|
||||
todayCount, totalCount, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
|
||||
if err != nil {
|
||||
|
||||
@@ -126,7 +126,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (*response.
|
||||
lang := strings.ToLower(common.GetLang(ctx))
|
||||
for _, ap := range apps {
|
||||
if req.Type == "php" {
|
||||
if !global.CONF.Base.IsOffLine && (ap.RequiredPanelVersion == 0 || !common.CompareAppVersion(common.GetSystemVersion(info.SystemVersion), fmt.Sprintf("%f", ap.RequiredPanelVersion))) {
|
||||
if !global.CONF.Base.IsOffline && (ap.RequiredPanelVersion == 0 || !common.CompareAppVersion(common.GetSystemVersion(info.SystemVersion), fmt.Sprintf("%f", ap.RequiredPanelVersion))) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -966,7 +966,7 @@ func deleteCustomApp() {
|
||||
}
|
||||
|
||||
func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
|
||||
if xpack.IsUseCustomApp() {
|
||||
if xpack.MultiNodeProvider.IsUseCustomApp() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -294,6 +294,7 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
DetailID: req.DetailId,
|
||||
Backup: req.Backup,
|
||||
PullImage: req.PullImage,
|
||||
DeleteImage: req.DeleteImage,
|
||||
DockerCompose: req.DockerCompose,
|
||||
TaskID: req.TaskID,
|
||||
}
|
||||
@@ -589,7 +590,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
||||
if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate {
|
||||
continue
|
||||
}
|
||||
if common.CompareVersion(detail.Version, install.Version) {
|
||||
canUpgrade := common.CompareVersion(detail.Version, install.Version)
|
||||
if app.Key == vllmAppKeyForUpgrade {
|
||||
currentImage := loadVllmImageFromEnv(install.Env)
|
||||
canUpgrade = isVllmUpgradeCandidate(install.Version, detail.Version, currentImage)
|
||||
}
|
||||
if canUpgrade {
|
||||
var newCompose string
|
||||
if req.UpdateVersion != "" && req.UpdateVersion == detail.Version && detail.DockerCompose == "" && !app.IsLocalApp() {
|
||||
filename := filepath.Base(detail.DownloadUrl)
|
||||
|
||||
@@ -82,7 +82,7 @@ func (a AppService) createSyncAppStoreTask(sharedCtx **appSyncContext) func(t *t
|
||||
|
||||
ctx := &appSyncContext{
|
||||
task: t,
|
||||
httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.LoadRequestTransport()},
|
||||
httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.MultiNodeProvider.LoadRequestTransport()},
|
||||
baseRemoteUrl: fmt.Sprintf("%s/%s/1panel", global.AppRepoURL(), global.CONF.Base.Mode),
|
||||
systemVersion: setting.SystemVersion,
|
||||
settingService: settingService,
|
||||
@@ -279,9 +279,9 @@ func (c *appSyncContext) syncAppIconsAndDetails() error {
|
||||
}()
|
||||
|
||||
var (
|
||||
completed int
|
||||
icon200Count int
|
||||
icon304Count int
|
||||
completed int
|
||||
icon200Count int
|
||||
icon304Count int
|
||||
iconFailCount int
|
||||
)
|
||||
milestones := [4]int{totalWork / 4, totalWork / 2, totalWork * 3 / 4, totalWork}
|
||||
|
||||
+230
-62
@@ -89,7 +89,15 @@ func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func isPortInUse(port int, protocol string) bool {
|
||||
return common.ScanPortWithProto(port, normalizeComposeProtocol(protocol))
|
||||
}
|
||||
|
||||
func checkPortExist(port int) error {
|
||||
return checkPortExistWithProtocol(port, "")
|
||||
}
|
||||
|
||||
func checkPortExistWithProtocol(port int, protocol string) error {
|
||||
errMap := make(map[string]interface{})
|
||||
errMap["port"] = port
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
||||
@@ -110,7 +118,7 @@ func checkPortExist(port int) error {
|
||||
errMap["name"] = domain.Domain
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
if common.ScanPort(port) {
|
||||
if isPortInUse(port, protocol) {
|
||||
return buserr.WithDetail("ErrPortInUsed", port, nil)
|
||||
}
|
||||
return nil
|
||||
@@ -357,33 +365,13 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
return err
|
||||
}
|
||||
if deleteReq.DeleteImage {
|
||||
delImageStr := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image")
|
||||
content, err := op.GetContent(install.GetEnvPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
|
||||
if err != nil {
|
||||
if err = deleteAppImagesByCompose(t, content, []byte(install.DockerCompose), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
for _, image := range images {
|
||||
imageID, err := client.GetImageIDByName(image)
|
||||
if err == nil {
|
||||
imgStr := delImageStr + image
|
||||
t.Log(imgStr)
|
||||
|
||||
if err = client.DeleteImage(imageID); err != nil {
|
||||
t.LogFailedWithErr(imgStr, err)
|
||||
continue
|
||||
}
|
||||
t.LogSuccess(delImageStr + image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tx, ctx := helper.GetTxAndContext()
|
||||
@@ -468,6 +456,69 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type appImageID struct {
|
||||
name string
|
||||
id string
|
||||
}
|
||||
|
||||
func getAppImageIDsByCompose(client docker.Client, envContent, composeContent []byte) ([]appImageID, error) {
|
||||
images, err := docker.GetImagesFromDockerCompose(envContent, composeContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageIDs := make([]appImageID, 0, len(images))
|
||||
for _, image := range images {
|
||||
imageID, err := client.GetImageIDByName(image)
|
||||
if err == nil && imageID != "" {
|
||||
imageIDs = append(imageIDs, appImageID{name: image, id: imageID})
|
||||
}
|
||||
}
|
||||
return imageIDs, nil
|
||||
}
|
||||
|
||||
func deleteAppImagesByCompose(t *task.Task, envContent, composeContent []byte, excludeImages []string) error {
|
||||
client, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
imageIDs, err := getAppImageIDsByCompose(client, envContent, composeContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return deleteAppImagesByIDs(t, client, imageIDs, excludeImages)
|
||||
}
|
||||
|
||||
func deleteAppImagesByIDs(t *task.Task, client docker.Client, imageIDs []appImageID, excludeImages []string) error {
|
||||
delImageStr := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image")
|
||||
excludeImageIDs := make(map[string]struct{}, len(excludeImages))
|
||||
for _, image := range excludeImages {
|
||||
imageID, err := client.GetImageIDByName(image)
|
||||
if err == nil && imageID != "" {
|
||||
excludeImageIDs[imageID] = struct{}{}
|
||||
}
|
||||
}
|
||||
deletedImageIDs := make(map[string]struct{}, len(imageIDs))
|
||||
for _, image := range imageIDs {
|
||||
if _, ok := excludeImageIDs[image.id]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := deletedImageIDs[image.id]; ok {
|
||||
continue
|
||||
}
|
||||
deletedImageIDs[image.id] = struct{}{}
|
||||
imgStr := delImageStr + image.name
|
||||
t.Log(imgStr)
|
||||
if err := client.DeleteImage(image.id); err != nil {
|
||||
t.LogFailedWithErr(imgStr, err)
|
||||
continue
|
||||
}
|
||||
t.LogSuccess(imgStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteLink(del dto.DelAppLink) error {
|
||||
install := del.Install
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
@@ -665,7 +716,7 @@ func buildNginx(parentTask *task.Task) error {
|
||||
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image"))
|
||||
parentTask.LogStart(logStr)
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(60*time.Minute))
|
||||
if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil {
|
||||
if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogSuccess(logStr)
|
||||
@@ -682,6 +733,9 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) {
|
||||
return errors.New("vLLM can only upgrade within the same image type")
|
||||
}
|
||||
if install.Version == detail.Version {
|
||||
return errors.New("two version is same")
|
||||
}
|
||||
@@ -748,28 +802,23 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dockerCLi, _ := docker.NewClient()
|
||||
if req.PullImage {
|
||||
composeContent := []byte(detail.DockerCompose)
|
||||
if req.DockerCompose != "" {
|
||||
composeContent = []byte(req.DockerCompose)
|
||||
oldEnvContent := append([]byte(nil), content...)
|
||||
oldDockerCompose := install.DockerCompose
|
||||
if install.App.Key == vllmAppKeyForUpgrade {
|
||||
envs := make(map[string]interface{})
|
||||
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||
return err
|
||||
}
|
||||
images, err := docker.GetImagesFromDockerCompose(content, composeContent)
|
||||
image := buildVllmUpgradeImage(loadVllmImageFromEnv(install.Env), oldVersion, detail.Version)
|
||||
envs[vllmImageEnvKey] = image
|
||||
paramByte, err := json.Marshal(envs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, image := range images {
|
||||
t.Log(i18n.GetWithName("PullImageStart", image))
|
||||
if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
|
||||
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
|
||||
}
|
||||
install.Env = string(paramByte)
|
||||
content = setVllmImageInEnvContent(content, image)
|
||||
}
|
||||
|
||||
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
|
||||
_, _ = command.CombinedOutput()
|
||||
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
|
||||
if install.App.Key == constant.AppOpenresty {
|
||||
installBuildDir := path.Join(install.GetPath(), "build")
|
||||
detailBuildDir := path.Join(detailDir, "build")
|
||||
@@ -808,9 +857,13 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
return err
|
||||
}
|
||||
if req.DockerCompose == "" {
|
||||
newCompose, err = getUpgradeCompose(install, detail)
|
||||
if err != nil {
|
||||
return err
|
||||
if install.App.Key == vllmAppKeyForUpgrade {
|
||||
newCompose = install.DockerCompose
|
||||
} else {
|
||||
newCompose, err = getUpgradeCompose(install, detail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newCompose = req.DockerCompose
|
||||
@@ -820,6 +873,46 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
install.Version = detail.Version
|
||||
install.AppDetailId = req.DetailID
|
||||
|
||||
var oldImageIDs []appImageID
|
||||
if req.DeleteImage {
|
||||
dockerCLi, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldImageIDs, err = getAppImageIDsByCompose(dockerCLi, oldEnvContent, []byte(oldDockerCompose))
|
||||
dockerCLi.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if req.PullImage {
|
||||
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dockerCLi, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dockerCLi.Close()
|
||||
for _, image := range images {
|
||||
t.Log(i18n.GetWithName("PullImageStart", image))
|
||||
if pullErr := dockerCLi.PullImageWithProcess(t, image); pullErr != nil {
|
||||
if exist, _ := dockerCLi.ImageExists(image); exist {
|
||||
t.Log(i18n.GetMsgByKey("UseExistImage"))
|
||||
continue
|
||||
}
|
||||
return buserr.WithNameAndErr("ErrDockerPullImage", "", pullErr)
|
||||
}
|
||||
exist, err := dockerCLi.ImageExists(image)
|
||||
if err != nil || !exist {
|
||||
return buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s does not exist after pull: %v", image, err))
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := compose.Down(install.GetComposePath()); err != nil {
|
||||
if out != "" {
|
||||
upErr = errors.New(out)
|
||||
@@ -870,7 +963,31 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
}
|
||||
t.LogSuccess(logStr)
|
||||
install.Status = constant.StatusRunning
|
||||
return appInstallRepo.Save(context.Background(), &install)
|
||||
if err = appInstallRepo.Save(context.Background(), &install); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.DeleteImage {
|
||||
newEnvContent, err := fileOp.GetContent(install.GetEnvPath())
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
|
||||
return nil
|
||||
}
|
||||
excludeImages, err := docker.GetImagesFromDockerCompose(newEnvContent, []byte(install.DockerCompose))
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
|
||||
return nil
|
||||
}
|
||||
dockerCLi, err := docker.NewClient()
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
|
||||
return nil
|
||||
}
|
||||
defer dockerCLi.Close()
|
||||
if err = deleteAppImagesByIDs(t, dockerCLi, oldImageIDs, excludeImages); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rollBackApp := func(t *task.Task) {
|
||||
@@ -885,7 +1002,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
}
|
||||
}
|
||||
|
||||
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskScopeApp, task.TaskUpgrade), upgradeApp, rollBackApp, 0, 1*time.Hour)
|
||||
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp), upgradeApp, rollBackApp, 0, 1*time.Hour)
|
||||
|
||||
go func() {
|
||||
err = upgradeTask.Execute()
|
||||
@@ -1129,7 +1246,7 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
||||
task.LogStart(logStr)
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir))
|
||||
if err := cmdMgr.RunBashC(scriptPath); err != nil {
|
||||
if err := cmdMgr.Run("bash", scriptPath); err != nil {
|
||||
task.LogFailedWithErr(logStr, err)
|
||||
return err
|
||||
}
|
||||
@@ -1178,11 +1295,12 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imagePrefix := xpack.GetImagePrefix()
|
||||
imagePrefix := xpack.MultiNodeProvider.GetImagePrefix()
|
||||
dockerCLi, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dockerCLi.Close()
|
||||
for _, image := range images {
|
||||
if imagePrefix != "" {
|
||||
lastSlashIndex := strings.LastIndex(image, "/")
|
||||
@@ -1596,6 +1714,9 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
synAppInstall(containersMap, &installed, false)
|
||||
}
|
||||
|
||||
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
||||
envMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
||||
installDTO := response.AppInstallDTO{
|
||||
ID: installed.ID,
|
||||
Name: installed.Name,
|
||||
@@ -1617,14 +1738,16 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
Website: installed.App.Website,
|
||||
Document: installed.App.Document,
|
||||
},
|
||||
Favorite: installed.Favorite,
|
||||
SortOrder: installed.SortOrder,
|
||||
Container: installed.ContainerName,
|
||||
ServiceName: strings.ToLower(installed.ServiceName),
|
||||
Favorite: installed.Favorite,
|
||||
SortOrder: installed.SortOrder,
|
||||
Container: installed.ContainerName,
|
||||
ServiceName: strings.ToLower(installed.ServiceName),
|
||||
ResourceKeys: resourceKeys,
|
||||
Env: envMap,
|
||||
}
|
||||
|
||||
if !updated && !checkUpdate {
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
||||
res = append(res, installDTO)
|
||||
continue
|
||||
}
|
||||
@@ -1632,7 +1755,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
if installed.Version == "latest" {
|
||||
if checkUpdate {
|
||||
installDTO.CanUpdate = false
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
continue
|
||||
@@ -1661,7 +1784,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
if len(versions) == 0 {
|
||||
if checkUpdate {
|
||||
installDTO.CanUpdate = false
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
continue
|
||||
@@ -1693,7 +1816,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
} else if checkUpdate {
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
}
|
||||
@@ -1778,7 +1901,7 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
|
||||
if !serviceValid {
|
||||
return buserr.New("ErrFileParse")
|
||||
}
|
||||
imagePreFix := xpack.GetImagePrefix()
|
||||
imagePreFix := xpack.MultiNodeProvider.GetImagePrefix()
|
||||
if imagePreFix != "" {
|
||||
for _, service := range services {
|
||||
serviceValue := service.(map[string]interface{})
|
||||
@@ -1935,6 +2058,39 @@ func isHostModel(dockerCompose string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func copyAppDetailMissing(fileOp files.FileOp, srcDir, dstDir string) error {
|
||||
entries, err := os.ReadDir(srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if strings.HasPrefix(entry.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
srcPath := path.Join(srcDir, entry.Name())
|
||||
dstPath := path.Join(dstDir, entry.Name())
|
||||
if !fileOp.Stat(dstPath) {
|
||||
if entry.IsDir() {
|
||||
if err := fileOp.CopyDir(srcPath, dstDir); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := fileOp.CopyFile(srcPath, dstDir); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if err := copyAppDetailMissing(fileOp, srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRestartPolicy(yml string) string {
|
||||
var project docker.ComposeProject
|
||||
if err := yaml.Unmarshal([]byte(yml), &project); err != nil {
|
||||
@@ -2121,7 +2277,7 @@ func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRe
|
||||
caRequest := request.WebsiteCAObtain{
|
||||
ID: ca.ID,
|
||||
Domains: "localhost",
|
||||
KeyType: "4096",
|
||||
KeyType: "RSA4096",
|
||||
Time: 99,
|
||||
Unit: "year",
|
||||
Dir: sslDir,
|
||||
@@ -2227,13 +2383,25 @@ func needsUpdate(localTag *model.Tag, remoteTag dto.Tag, translations string) bo
|
||||
}
|
||||
|
||||
func hasLinkDB(installID uint) bool {
|
||||
return hasLinkDBFromKeys(getAppInstallResourceKeys(installID))
|
||||
}
|
||||
|
||||
func getAppInstallResourceKeys(installID uint) []string {
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(installID))
|
||||
keys := make([]string, 0, len(resources))
|
||||
for _, resource := range resources {
|
||||
keys = append(keys, resource.Key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func hasLinkDBFromKeys(resourceKeys []string) bool {
|
||||
hasDB := false
|
||||
if len(resources) > 0 {
|
||||
for _, resource := range resources {
|
||||
if resource.Key == constant.AppPostgres || resource.Key == constant.AppMysql ||
|
||||
resource.Key == constant.AppMariaDB || resource.Key == constant.AppMysqlCluster ||
|
||||
resource.Key == constant.AppPostgresql || resource.Key == constant.AppPostgresqlCluster {
|
||||
if len(resourceKeys) > 0 {
|
||||
for _, resourceKey := range resourceKeys {
|
||||
if resourceKey == constant.AppPostgres || resourceKey == constant.AppMysql ||
|
||||
resourceKey == constant.AppMariaDB || resourceKey == constant.AppMysqlCluster ||
|
||||
resourceKey == constant.AppPostgresql || resourceKey == constant.AppPostgresqlCluster {
|
||||
hasDB = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -63,11 +63,30 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
|
||||
return nil, err
|
||||
if !req.IsImmediate {
|
||||
if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
|
||||
return nil, err
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
backupTask, err := task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeBackup, req.TaskID, install.ID)
|
||||
if err != nil {
|
||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
record.Status = constant.StatusFailed
|
||||
record.Message = err.Error()
|
||||
return nil, err
|
||||
}
|
||||
if err = doAppBackup(&install, backupTask, backupDir, fileName, "", req.Secret); err != nil {
|
||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
record.Status = constant.StatusFailed
|
||||
record.Message = err.Error()
|
||||
return nil, err
|
||||
}
|
||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||
record.Status = constant.StatusSuccess
|
||||
return record, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
@@ -595,35 +596,22 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig, extraNetworks := buildContainerRecoverNetworkConfig(recoverCtx.inspectInfo.NetworkSettings, hostConfig)
|
||||
removeBridgeDriverIPAM(recoverCtx.client, networkConfig, extraNetworks)
|
||||
createRes, err := recoverCtx.client.ContainerCreate(ctx, config, hostConfig, networkConfig, nil, recoverCtx.targetName)
|
||||
createRes, err := createContainerWithOldNetworks(ctx, recoverCtx.client, config, hostConfig, recoverCtx.inspectInfo.NetworkSettings, recoverCtx.targetName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recoverCtx.createdContainerID = createRes.ID
|
||||
|
||||
extraNames := make([]string, 0, len(extraNetworks))
|
||||
for name := range extraNetworks {
|
||||
extraNames = append(extraNames, name)
|
||||
}
|
||||
sort.Strings(extraNames)
|
||||
for _, item := range extraNames {
|
||||
if err := recoverCtx.client.NetworkConnect(ctx, item, recoverCtx.createdContainerID, extraNetworks[item]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeBridgeDriverIPAM(cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||
func removeUnsupportedEndpointStaticIPAM(cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||
if primary != nil {
|
||||
removeBridgeDriverIPAMFromEndpoints(cli, primary.EndpointsConfig)
|
||||
removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, primary.EndpointsConfig)
|
||||
}
|
||||
removeBridgeDriverIPAMFromEndpoints(cli, extras)
|
||||
removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, extras)
|
||||
}
|
||||
|
||||
func removeBridgeDriverIPAMFromEndpoints(cli *client.Client, endpoints map[string]*network.EndpointSettings) {
|
||||
func removeUnsupportedEndpointStaticIPAMFromEndpoints(cli *client.Client, endpoints map[string]*network.EndpointSettings) {
|
||||
for netName, endpoint := range endpoints {
|
||||
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||
continue
|
||||
@@ -632,10 +620,59 @@ func removeBridgeDriverIPAMFromEndpoints(cli *client.Client, endpoints map[strin
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if info.Driver == "bridge" {
|
||||
endpoint.IPAMConfig = nil
|
||||
removeUnsupportedEndpointStaticIP(netName, info, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
func removeUnsupportedEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) {
|
||||
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||
return
|
||||
}
|
||||
if isDefaultBridgeNetwork(netName, info) {
|
||||
endpoint.IPAMConfig = nil
|
||||
return
|
||||
}
|
||||
|
||||
if endpoint.IPAMConfig.IPv4Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv4Address, false) {
|
||||
endpoint.IPAMConfig.IPv4Address = ""
|
||||
}
|
||||
if endpoint.IPAMConfig.IPv6Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv6Address, true) {
|
||||
endpoint.IPAMConfig.IPv6Address = ""
|
||||
}
|
||||
if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" {
|
||||
endpoint.IPAMConfig = nil
|
||||
}
|
||||
}
|
||||
|
||||
func isDefaultBridgeNetwork(netName string, info network.Inspect) bool {
|
||||
return info.Driver == "bridge" && (netName == "bridge" || info.Name == "bridge")
|
||||
}
|
||||
|
||||
func networkSupportsStaticIP(info network.Inspect, ip string, isIPv6 bool) bool {
|
||||
if ip == "" {
|
||||
return true
|
||||
}
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if addr.Is6() != isIPv6 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, item := range info.IPAM.Config {
|
||||
if item.Subnet == "" {
|
||||
continue
|
||||
}
|
||||
subnet, err := netip.ParsePrefix(item.Subnet)
|
||||
if err != nil || subnet.Addr().Is6() != isIPv6 {
|
||||
continue
|
||||
}
|
||||
if subnet.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ensureContainerRecoverNetworks(recoverCtx *containerRecoverContext) error {
|
||||
@@ -745,11 +782,6 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
||||
IPv4Address: endpoint.IPAMConfig.IPv4Address,
|
||||
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
||||
}
|
||||
} else if endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "" {
|
||||
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
||||
IPv4Address: endpoint.IPAddress,
|
||||
IPv6Address: endpoint.GlobalIPv6Address,
|
||||
}
|
||||
}
|
||||
if name == primaryName {
|
||||
config.EndpointsConfig[name] = endpointSetting
|
||||
|
||||
@@ -97,7 +97,8 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, recordID
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.RunDefaultBashCf("docker exec %s redis-cli -a %s --no-auth-warning save", redisInfo.ContainerName, redisInfo.Password); err != nil {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30 * time.Minute))
|
||||
if err := cmdMgr.Run("docker", "exec", redisInfo.ContainerName, "redis-cli", "-a", redisInfo.Password, "--no-auth-warning", "save"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -109,13 +110,13 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, recordID
|
||||
return nil
|
||||
}
|
||||
if strings.HasSuffix(fileName, ".aof") {
|
||||
if err := cmd.RunDefaultBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName); err != nil {
|
||||
if err := cmdMgr.Run("docker", "cp", redisInfo.ContainerName+":/data/appendonly.aof", path.Join(backupDir, fileName)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := cmd.RunDefaultBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName); err != nil {
|
||||
if err := cmdMgr.Run("docker", "cp", redisInfo.ContainerName+":/data/dump.rdb", path.Join(backupDir, fileName)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -178,7 +178,7 @@ func handleWebsiteRecover(website *model.Website, parentTask *task.Task, recover
|
||||
if err = fileOp.TarGzExtractPro(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), GetOpenrestyDir(SitesRootDir), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.RunDefaultBashCf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName); err != nil {
|
||||
if err := cmd.NewCommandMgr().Run("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload"); err != nil {
|
||||
return err
|
||||
}
|
||||
oldWebsite.ID = website.ID
|
||||
|
||||
+13
-12
@@ -36,8 +36,8 @@ type IClamService interface {
|
||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||
Operate(operate string) error
|
||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ClamCreate) error
|
||||
Update(req dto.ClamUpdate) error
|
||||
Create(req dto.ClamCreate, operator string) error
|
||||
Update(req dto.ClamUpdate, operator string) error
|
||||
UpdateStatus(id uint, status string) error
|
||||
Delete(req dto.ClamDelete) error
|
||||
HandleOnce(id uint) error
|
||||
@@ -86,8 +86,9 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
baseInfo.IsActive = false
|
||||
}
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
if baseInfo.IsActive {
|
||||
version, err := cmd.RunDefaultWithStdoutBashC("clamdscan --version")
|
||||
version, err := cmdMgr.RunWithStdout("clamdscan", "--version")
|
||||
if err == nil {
|
||||
if strings.Contains(version, "/") {
|
||||
baseInfo.Version = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
|
||||
@@ -99,7 +100,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
_ = clam.CheckWithStopAll(false, clamRepo)
|
||||
}
|
||||
if baseInfo.FreshIsActive {
|
||||
version, err := cmd.RunDefaultWithStdoutBashC("freshclam --version")
|
||||
version, err := cmdMgr.RunWithStdout("freshclam", "--version")
|
||||
if err == nil {
|
||||
if strings.Contains(version, "/") {
|
||||
baseInfo.FreshVersion = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
|
||||
@@ -164,7 +165,7 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
func (c *ClamService) Create(req dto.ClamCreate, operator string) error {
|
||||
clam, _ := clamRepo.Get(repo.WithByName(req.Name))
|
||||
if clam.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -179,7 +180,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
clam.InfectedDir = ""
|
||||
}
|
||||
if len(req.Spec) != 0 {
|
||||
entryID, err := xpack.StartClam(&clam, false)
|
||||
entryID, err := xpack.MultiNodeProvider.StartClam(&clam, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -198,7 +199,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
Project: strconv.Itoa(int(clam.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert)
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -206,7 +207,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
func (c *ClamService) Update(req dto.ClamUpdate, operator string) error {
|
||||
if cmd.CheckIllegal(req.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
@@ -232,7 +233,7 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
upMap["entry_id"] = 0
|
||||
}
|
||||
if len(req.Spec) != 0 && clam.Status != constant.StatusDisable {
|
||||
newEntryID, err := xpack.StartClam(&clamItem, true)
|
||||
newEntryID, err := xpack.MultiNodeProvider.StartClam(&clamItem, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -259,7 +260,7 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
Type: "clams",
|
||||
Project: strconv.Itoa(int(clam.ID)),
|
||||
}
|
||||
err := NewIAlertService().ExternalUpdateAlert(updateAlert)
|
||||
err := NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -276,7 +277,7 @@ func (c *ClamService) UpdateStatus(id uint, status string) error {
|
||||
err error
|
||||
)
|
||||
if status == constant.StatusEnable {
|
||||
entryID, err = xpack.StartClam(&clam, true)
|
||||
entryID, err = xpack.MultiNodeProvider.StartClam(&clam, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -493,7 +494,7 @@ func (c *ClamService) loadConfigPath(confType string) string {
|
||||
|
||||
func handleAlert(infectedFiles, clamName string, clamId uint) {
|
||||
itemInfected, _ := strconv.Atoi(strings.TrimSpace(infectedFiles))
|
||||
if itemInfected < 0 {
|
||||
if itemInfected <= 0 {
|
||||
return
|
||||
}
|
||||
pushAlert := dto.PushAlert{
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -32,7 +33,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
@@ -53,6 +53,8 @@ import (
|
||||
|
||||
type ContainerService struct{}
|
||||
|
||||
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
||||
|
||||
type IContainerService interface {
|
||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
||||
List() []dto.ContainerOptions
|
||||
@@ -297,14 +299,14 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []dto.ContainerListStats
|
||||
datas := make([]dto.ContainerListStats, len(list))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(list))
|
||||
for i := 0; i < len(list); i++ {
|
||||
go func(item container.Summary) {
|
||||
datas = append(datas, loadCpuAndMem(client, item.ID))
|
||||
go func(index int, item container.Summary) {
|
||||
datas[index] = loadCpuAndMem(client, item.ID)
|
||||
wg.Done()
|
||||
}(list[i])
|
||||
}(i, list[i])
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
@@ -527,6 +529,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
removeUnsupportedEndpointStaticIPAM(client, networkConf, nil)
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
|
||||
@@ -675,6 +678,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
||||
return err
|
||||
}
|
||||
removeUnsupportedEndpointStaticIPAM(client, networkConf, nil)
|
||||
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
@@ -738,20 +742,13 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
config := oldContainer.Config
|
||||
config.Image = req.Image
|
||||
hostConf := oldContainer.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
if oldContainer.NetworkSettings != nil {
|
||||
for networkKey := range oldContainer.NetworkSettings.Networks {
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
}
|
||||
err := client.ContainerRemove(ctx, item, container.RemoveOptions{Force: true})
|
||||
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", item), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, item)
|
||||
con, err := createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item)
|
||||
if err != nil {
|
||||
taskItem.Log(i18n.GetMsgByKey("ContainerRecreate"))
|
||||
reCreateAfterUpdate(item, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
||||
@@ -1041,6 +1038,7 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
|
||||
if cmd.CheckIllegal(container, since, tail) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
ctx := c.Request.Context()
|
||||
commandArg := []string{"logs", container}
|
||||
dockerCommand := global.CONF.DockerConfig.Command
|
||||
if containerType == "compose" {
|
||||
@@ -1070,10 +1068,11 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
|
||||
}
|
||||
var dockerCmd *exec.Cmd
|
||||
if containerType == "compose" && dockerCommand == "docker-compose" {
|
||||
dockerCmd = exec.Command("docker-compose", commandArg...)
|
||||
dockerCmd = exec.CommandContext(ctx, "docker-compose", commandArg...)
|
||||
} else {
|
||||
dockerCmd = exec.Command("docker", commandArg...)
|
||||
dockerCmd = exec.CommandContext(ctx, "docker", commandArg...)
|
||||
}
|
||||
dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
stdout, err := dockerCmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1082,15 +1081,20 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
|
||||
if err := dockerCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if dockerCmd.Process != nil {
|
||||
_ = dockerCmd.Process.Kill()
|
||||
_ = dockerCmd.Wait()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
killContainerLogProcess(dockerCmd)
|
||||
case <-done:
|
||||
}
|
||||
}()
|
||||
defer close(done)
|
||||
|
||||
tempFile, err := os.CreateTemp("", "cmd_output_*.txt")
|
||||
if err != nil {
|
||||
killContainerLogProcess(dockerCmd)
|
||||
_ = dockerCmd.Wait()
|
||||
return err
|
||||
}
|
||||
defer tempFile.Close()
|
||||
@@ -1099,31 +1103,19 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
|
||||
global.LOG.Errorf("os.Remove() failed: %v", err)
|
||||
}
|
||||
}()
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
var ansiRegex = re.GetRegex(re.AnsiEscapePattern)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
cleanLine := ansiRegex.ReplaceAllString(line, "")
|
||||
if _, err := tempFile.WriteString(cleanLine + "\n"); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
copyErr := copyContainerLogOutput(tempFile, stdout)
|
||||
waitErr := dockerCmd.Wait()
|
||||
if copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
if waitErr != nil {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Error: %v", err)
|
||||
}
|
||||
case <-time.After(40 * time.Second):
|
||||
global.LOG.Errorf("Download container logs timeout reached")
|
||||
return waitErr
|
||||
}
|
||||
if _, err := tempFile.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
info, _ := tempFile.Stat()
|
||||
|
||||
@@ -1133,6 +1125,36 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyContainerLogOutput(dst io.Writer, src io.Reader) error {
|
||||
reader := bufio.NewReader(src)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if len(line) > 0 {
|
||||
cleanLine := containerLogAnsiRegex.ReplaceAllString(line, "")
|
||||
if _, writeErr := io.WriteString(dst, cleanLine); writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func killContainerLogProcess(command *exec.Cmd) {
|
||||
if command == nil || command.Process == nil {
|
||||
return
|
||||
}
|
||||
if pgid, err := syscall.Getpgid(command.Process.Pid); err == nil {
|
||||
_ = syscall.Kill(-pgid, syscall.SIGKILL)
|
||||
return
|
||||
}
|
||||
_ = command.Process.Kill()
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
@@ -1167,7 +1189,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
|
||||
|
||||
func (u *ContainerService) LoadUsers(req dto.OperationWithName) []string {
|
||||
var users []string
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("docker", "exec", req.Name, "cat", "/etc/passwd")
|
||||
std, err := cmd.RunDockerExecWithStdout(20*time.Second, req.Name, "cat", "/etc/passwd")
|
||||
if err != nil {
|
||||
return users
|
||||
}
|
||||
@@ -1440,7 +1462,7 @@ func (u *ContainerService) DownloadContainerFile(req dto.ContainerFileReq) (io.R
|
||||
fileName = "container-file"
|
||||
}
|
||||
if stat.Mode.IsDir() {
|
||||
if _, err := runContainerCommand(cli, req.ContainerID, []string{"sh", "-c", "command -v tar >/dev/null 2>&1"}); err != nil {
|
||||
if _, err := runContainerCommand(cli, req.ContainerID, []string{"tar", "--help"}); err != nil {
|
||||
_ = cli.Close()
|
||||
return nil, "", "", fmt.Errorf("tar command not found in container")
|
||||
}
|
||||
@@ -1620,9 +1642,8 @@ func toContainerFileInfo(filePath string, stat container.PathStat, isDir bool) d
|
||||
}
|
||||
|
||||
func isContainerDir(cli *client.Client, containerID, targetPath string) (bool, error) {
|
||||
_, err := runContainerCommand(cli, containerID, []string{
|
||||
"sh", "-c", "[ -d \"$1\" ]", "sh", targetPath,
|
||||
})
|
||||
checkPath := strings.TrimSuffix(targetPath, "/") + "/."
|
||||
_, err := runContainerCommand(cli, containerID, []string{"ls", "-d", "--", checkPath})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -1952,15 +1973,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *conta
|
||||
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *container.NetworkSettings) {
|
||||
ctx := context.Background()
|
||||
|
||||
var oldNetworkConf network.NetworkingConfig
|
||||
if networkConf != nil {
|
||||
for networkKey := range networkConf.Networks {
|
||||
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
|
||||
oldContainer, err := createContainerWithOldNetworks(ctx, client, config, hostConf, networkConf, name)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("recreate after container update failed, err: %v", err)
|
||||
return
|
||||
@@ -1971,6 +1984,29 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
|
||||
global.LOG.Info("recreate after container update successful")
|
||||
}
|
||||
|
||||
func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) {
|
||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf)
|
||||
removeUnsupportedEndpointStaticIPAM(client, networkConf, extraNetworks)
|
||||
|
||||
created, err := client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
extraNames := make([]string, 0, len(extraNetworks))
|
||||
for item := range extraNetworks {
|
||||
extraNames = append(extraNames, item)
|
||||
}
|
||||
sort.Strings(extraNames)
|
||||
for _, item := range extraNames {
|
||||
if err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item]); err != nil {
|
||||
_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
|
||||
return created, err
|
||||
}
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func loadVolumeBinds(binds []container.MountPoint) []dto.VolumeHelper {
|
||||
var datas []dto.VolumeHelper
|
||||
for _, bind := range binds {
|
||||
|
||||
@@ -270,6 +270,10 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
return fmt.Errorf("docker-compose up failed, std: %s, err: %v", stdout, err)
|
||||
}
|
||||
} else if req.Operation == "rebuild" {
|
||||
if stdout, err := compose.DownAndUp(req.Path); err != nil {
|
||||
return fmt.Errorf("docker-compose rebuild failed, std: %s, err: %v", stdout, err)
|
||||
}
|
||||
} else {
|
||||
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
|
||||
return fmt.Errorf("docker-compose %s failed, std: %s, err: %v", req.Operation, stdout, err)
|
||||
|
||||
@@ -27,10 +27,10 @@ type CronjobService struct{}
|
||||
type ICronjobService interface {
|
||||
SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
|
||||
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
|
||||
Create(cronjobDto dto.CronjobOperate) error
|
||||
Create(cronjobDto dto.CronjobOperate, operator string) error
|
||||
LoadNextHandle(spec string) ([]string, error)
|
||||
HandleOnce(id uint) error
|
||||
Update(id uint, req dto.CronjobOperate) error
|
||||
Update(id uint, req dto.CronjobOperate, operator string) error
|
||||
UpdateStatus(id uint, status string) error
|
||||
UpdateGroup(req dto.ChangeGroup) error
|
||||
Delete(req dto.CronjobBatchDelete) error
|
||||
@@ -39,7 +39,7 @@ type ICronjobService interface {
|
||||
HandleStop(id uint) error
|
||||
|
||||
Export(req dto.OperateByIDs) (string, error)
|
||||
Import(req []dto.CronjobTrans) error
|
||||
Import(req []dto.CronjobTrans, operator string) error
|
||||
LoadScriptOptions() []dto.ScriptOptions
|
||||
|
||||
LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
|
||||
@@ -212,7 +212,7 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
return string(itemJson), nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans) error {
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
for _, item := range req {
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||
if cronjobItem.ID != 0 {
|
||||
@@ -405,7 +405,7 @@ func (u *CronjobService) Import(req []dto.CronjobTrans) error {
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
}
|
||||
_ = NewIAlertService().CreateAlert(createAlert)
|
||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -558,7 +558,7 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate) error {
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||
if cronjob.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -607,7 +607,7 @@ func (u *CronjobService) Create(req dto.CronjobOperate) error {
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert)
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -678,7 +678,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
||||
var cronjob model.Cronjob
|
||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
@@ -756,7 +756,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
}
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert)
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||
|
||||
func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error {
|
||||
cmdMgr := cmd.NewCommandMgr()
|
||||
if err := cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, websiteLogDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil {
|
||||
if err := cmdMgr.Run("tar", "-czf", dstFilePath, "-C", websiteLogDir, "access.log", "error.log"); err != nil {
|
||||
dstDir := pathUtils.Dir(dstFilePath)
|
||||
if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "access.log"), dstDir); err != nil {
|
||||
return err
|
||||
@@ -399,7 +399,7 @@ func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error
|
||||
if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "error.log"), dstDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, dstDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil {
|
||||
if err = cmdMgr.Run("tar", "-czf", dstFilePath, "-C", dstDir, "access.log", "error.log"); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = fileOp.DeleteFile(pathUtils.Join(dstDir, "access.log"))
|
||||
|
||||
@@ -60,7 +60,9 @@ func (u *DashboardService) Restart(operation string) error {
|
||||
case "system":
|
||||
{
|
||||
go func() {
|
||||
if err := cmd.RunDefaultBashCf("%s reboot", cmd.SudoHandleCmd()); err != nil {
|
||||
cmdMgr := cmd.NewCommandMgr()
|
||||
err := cmdMgr.RunWithOptionalSudo("reboot")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle reboot failed, %v", err)
|
||||
}
|
||||
}()
|
||||
@@ -185,6 +187,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
||||
hostInfo, _ := psutil.HOST.GetHostInfo(false)
|
||||
currentInfo.Uptime = hostInfo.Uptime
|
||||
currentInfo.TimeSinceUptime = time.Unix(int64(hostInfo.BootTime), 0).Format(constant.DateTimeLayout)
|
||||
currentInfo.RunningTime = loadRunningTime(hostInfo.Uptime)
|
||||
currentInfo.Procs = hostInfo.Procs
|
||||
currentInfo.CPUTotal, _ = psutil.CPUInfo.GetLogicalCores(false)
|
||||
|
||||
@@ -265,6 +268,15 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
||||
return ¤tInfo
|
||||
}
|
||||
|
||||
func loadRunningTime(uptime uint64) dto.RunningTime {
|
||||
return dto.RunningTime{
|
||||
Days: uptime / 86400,
|
||||
Hours: (uptime % 86400) / 3600,
|
||||
Minutes: (uptime % 3600) / 60,
|
||||
Seconds: uptime % 60,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadTopCPU() []dto.Process {
|
||||
return loadTopCPU()
|
||||
}
|
||||
@@ -429,11 +441,17 @@ type diskInfo struct {
|
||||
func loadDiskInfo() []dto.DiskInfo {
|
||||
var datas []dto.DiskInfo
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
format := `awk 'NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}'`
|
||||
stdout, err := cmdMgr.RunWithStdout("bash", "-c", `timeout 2 df -hT -P | `+format)
|
||||
format := `NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}`
|
||||
stdout, err := cmdMgr.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-hT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunWithStdout("bash", "-c", `timeout 1 df -lhT -P | `+format)
|
||||
stdout, err = cmdMgr2.RunPipe(
|
||||
cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}},
|
||||
cmd.PipeCommand{Name: "awk", Args: []string{format}},
|
||||
)
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/jinzhu/copier"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
type MongodbService struct{}
|
||||
|
||||
@@ -82,7 +82,7 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
|
||||
}
|
||||
|
||||
func (u *DeviceService) LoadTimeZone() ([]string, error) {
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Minute)).RunWithStdoutBashC("timedatectl list-timezones")
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunWithStdout("timedatectl", "list-timezones")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
@@ -223,7 +223,9 @@ func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error {
|
||||
if cmd.CheckIllegal(req.User, req.Passwd) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if err := cmd.RunDefaultBashCf("%s echo '%s:%s' | %s chpasswd", cmd.SudoHandleCmd(), req.User, req.Passwd, cmd.SudoHandleCmd()); err != nil {
|
||||
cmdItem := cmd.ExecCommandWithOptionalSudo("chpasswd")
|
||||
cmdItem.Stdin = strings.NewReader(req.User + ":" + req.Passwd)
|
||||
if err := cmdItem.Run(); err != nil {
|
||||
if strings.Contains(err.Error(), "does not exist") {
|
||||
return buserr.New("ErrNotExistUser")
|
||||
}
|
||||
@@ -393,7 +395,7 @@ func loadHosts() []dto.HostHelper {
|
||||
}
|
||||
|
||||
func loadHostname() string {
|
||||
std, err := cmd.RunDefaultWithStdoutBashC("hostname")
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("hostname")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -401,7 +403,7 @@ func loadHostname() string {
|
||||
}
|
||||
|
||||
func loadUser() string {
|
||||
std, err := cmd.RunDefaultWithStdoutBashC("whoami")
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("whoami")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -410,7 +412,8 @@ func loadUser() string {
|
||||
|
||||
func loadSwap() []dto.SwapHelper {
|
||||
var data []dto.SwapHelper
|
||||
std, err := cmd.RunDefaultWithStdoutBashCf("%s swapon --summary", cmd.SudoHandleCmd())
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
std, err := cmdMgr.RunWithOptionalSudoAndStdout("swapon", "--summary")
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
|
||||
}
|
||||
}
|
||||
|
||||
dropWithExclude(path.Join(global.Dir.BaseDir, uploadPath), []string{"theme"}, taskItem, &size, &fileCount)
|
||||
dropWithExclude(path.Join(global.Dir.BaseDir, uploadPath), []string{"theme", "skills-hub"}, taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, downloadPath), taskItem, &size, &fileCount)
|
||||
|
||||
logFiles, _ := os.ReadDir(global.Dir.LogDir)
|
||||
@@ -863,7 +863,7 @@ func loadTreeWithAllFile(isCheck bool, originalPath, treeType, pathItem string,
|
||||
return lists
|
||||
}
|
||||
for _, file := range files {
|
||||
if treeType == "upload" && (file.Name() == "theme" && file.IsDir()) {
|
||||
if treeType == "upload" && ((file.Name() == "theme" || file.Name() == "skills-hub") && file.IsDir()) {
|
||||
continue
|
||||
}
|
||||
if treeType == "system_log" && (file.Name() == "1Panel-Core.log" || file.Name() == "1Panel.log" || file.IsDir()) {
|
||||
|
||||
@@ -30,7 +30,8 @@ func NewIDiskService() IDiskService {
|
||||
|
||||
func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) {
|
||||
var diskInfos []response.DiskBasicInfo
|
||||
output, err := cmd.RunDefaultWithStdoutBashC("lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
output, err := cmdMgr.RunWithStdout("lsblk", "-J", "-o", "NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
|
||||
if err == nil {
|
||||
diskInfos, err = parseLsblkJsonOutput(output)
|
||||
if err == nil {
|
||||
@@ -38,7 +39,7 @@ func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error)
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
output, err = cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
|
||||
output, err = cmdMgr.RunWithStdout("lsblk", "-P", "-o", "NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run lsblk command: %v", err)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
@@ -386,10 +388,14 @@ func getParentDevice(device string) string {
|
||||
}
|
||||
|
||||
func getDiskUsageInfo(device string) (size, used, avail string, usePercent int, err error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("df -h %s | tail -1", device))
|
||||
output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("df", "-h", device)
|
||||
if err != nil {
|
||||
return "", "", "", 0, nil
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(lines) > 1 {
|
||||
output = lines[len(lines)-1]
|
||||
}
|
||||
|
||||
fields := strings.Fields(output)
|
||||
if len(fields) >= 5 {
|
||||
@@ -515,7 +521,7 @@ func removeFromFstab(mountPoint string) error {
|
||||
}
|
||||
|
||||
func getFilesystemType(device string) (string, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -o value -s TYPE %s", device))
|
||||
output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("blkid", "-o", "value", "-s", "TYPE", device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -575,7 +581,7 @@ func parseSizeToBytes(sizeStr string) int64 {
|
||||
}
|
||||
|
||||
func getDeviceUUID(device string) (string, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -s UUID -o value %s", device))
|
||||
output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("blkid", "-s", "UUID", "-o", "value", device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
@@ -89,8 +90,8 @@ func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) {
|
||||
data.Version = itemVersion.Version
|
||||
}
|
||||
data.IsSwarm = false
|
||||
stdout2, _ := cmd.RunDefaultWithStdoutBashC("docker info | grep Swarm")
|
||||
if string(stdout2) == " Swarm: active\n" {
|
||||
stdout2, _ := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("docker", "info")
|
||||
if strings.Contains(stdout2, "Swarm: active") {
|
||||
data.IsSwarm = true
|
||||
}
|
||||
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
|
||||
@@ -423,7 +424,7 @@ func validateDockerConfig() error {
|
||||
if !cmd.Which("dockerd") {
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashC("dockerd --validate")
|
||||
stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("dockerd", "--validate")
|
||||
if strings.Contains(stdout, "unknown flag: --validate") {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
)
|
||||
|
||||
func composePortEnvKeys(index int) (containerPort, hostPort, hostIP, protocol string) {
|
||||
return fmt.Sprintf("CONTAINER_PORT_%d", index),
|
||||
fmt.Sprintf("HOST_PORT_%d", index),
|
||||
fmt.Sprintf("HOST_IP_%d", index),
|
||||
fmt.Sprintf("PORT_PROTOCOL_%d", index)
|
||||
}
|
||||
|
||||
func isComposePortEnvKey(key string) bool {
|
||||
return strings.HasPrefix(key, "CONTAINER_PORT_") ||
|
||||
strings.HasPrefix(key, "HOST_PORT_") ||
|
||||
strings.HasPrefix(key, "HOST_IP_") ||
|
||||
strings.HasPrefix(key, "PORT_PROTOCOL_")
|
||||
}
|
||||
|
||||
func formatComposePortMapping(hostIP, hostPort, containerPort, protocol string) string {
|
||||
return fmt.Sprintf("${%s}:${%s}:${%s}/%s", hostIP, hostPort, containerPort, normalizeComposeProtocol(protocol))
|
||||
}
|
||||
|
||||
func normalizeComposeProtocol(protocol string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(protocol)) {
|
||||
case "udp":
|
||||
return "udp"
|
||||
default:
|
||||
return "tcp"
|
||||
}
|
||||
}
|
||||
|
||||
func formatComposeVolume(source, target, mode string) string {
|
||||
return fmt.Sprintf("%s:%s:%s", source, target, normalizeComposeVolumeMode(mode))
|
||||
}
|
||||
|
||||
func normalizeComposeVolumeMode(mode string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||
case "ro":
|
||||
return "ro"
|
||||
default:
|
||||
return "rw"
|
||||
}
|
||||
}
|
||||
|
||||
func loadComposeExposedPortsFromEnv(envs map[string]string, defaultHostIP string, strict bool) ([]request.ExposedPort, error) {
|
||||
var ports []request.ExposedPort
|
||||
for key, value := range envs {
|
||||
if !strings.HasPrefix(key, "CONTAINER_PORT_") {
|
||||
continue
|
||||
}
|
||||
index := strings.TrimPrefix(key, "CONTAINER_PORT_")
|
||||
containerPort, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
if strict {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
hostPort, err := strconv.Atoi(envs["HOST_PORT_"+index])
|
||||
if err != nil {
|
||||
if strict {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
hostIP := envs["HOST_IP_"+index]
|
||||
if hostIP == "" {
|
||||
hostIP = defaultHostIP
|
||||
}
|
||||
ports = append(ports, request.ExposedPort{
|
||||
ContainerPort: containerPort,
|
||||
HostPort: hostPort,
|
||||
HostIP: hostIP,
|
||||
Protocol: normalizeComposeProtocol(envs["PORT_PROTOCOL_"+index]),
|
||||
})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
+42
-13
@@ -76,7 +76,6 @@ type IFileService interface {
|
||||
BatchChangeModeAndOwner(op request.FileRoleReq) error
|
||||
ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error)
|
||||
|
||||
GetPathByType(pathType string) string
|
||||
BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo
|
||||
GetHostMount() []dto.DiskInfo
|
||||
GetUsersAndGroups() (*response.UserGroupResponse, error)
|
||||
@@ -670,6 +669,9 @@ func applyDecompressOwnership(srcPath, dstPath string) error {
|
||||
}
|
||||
|
||||
func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo, error) {
|
||||
if files.ShouldDenySensitiveFileRead(op.Path) {
|
||||
return response.FileInfo{}, buserr.New("ErrSensitiveFileRead")
|
||||
}
|
||||
info, err := files.NewFileInfo(files.FileOption{
|
||||
Path: op.Path,
|
||||
Expand: true,
|
||||
@@ -706,6 +708,9 @@ func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo,
|
||||
}
|
||||
|
||||
func (f *FileService) GetPreviewContent(op request.FileContentReq) (response.FileInfo, error) {
|
||||
if files.ShouldDenySensitiveFileRead(op.Path) {
|
||||
return response.FileInfo{}, buserr.New("ErrSensitiveFileRead")
|
||||
}
|
||||
info, err := files.NewFileInfo(files.FileOption{
|
||||
Path: op.Path,
|
||||
Expand: false,
|
||||
@@ -715,6 +720,9 @@ func (f *FileService) GetPreviewContent(op request.FileContentReq) (response.Fil
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
|
||||
if files.IsBinaryPreviewFile(info.MimeType, info.Extension) {
|
||||
return response.FileInfo{}, buserr.New("ErrFileCanNotRead")
|
||||
}
|
||||
if files.IsBlockDevice(info.FileMode) {
|
||||
return response.FileInfo{FileInfo: *info}, nil
|
||||
}
|
||||
@@ -825,7 +833,23 @@ func (f *FileService) ChangeName(req request.FileRename) error {
|
||||
func (f *FileService) Wget(w request.FileWget) (string, error) {
|
||||
fo := files.NewFileOp()
|
||||
key := "file-wget-" + common.GetUuid()
|
||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, w.IgnoreCertificate)
|
||||
options := files.DownloadOptions{
|
||||
IgnoreCertificate: w.IgnoreCertificate,
|
||||
}
|
||||
if w.UseProxy {
|
||||
systemProxy, err := NewISettingService().GetSystemProxy()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
options.Proxy = &files.DownloadProxyConfig{
|
||||
Type: systemProxy.Type,
|
||||
URL: systemProxy.URL,
|
||||
Port: systemProxy.Port,
|
||||
User: systemProxy.User,
|
||||
Password: systemProxy.Password,
|
||||
}
|
||||
}
|
||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, options)
|
||||
}
|
||||
|
||||
func (f *FileService) MvFile(m request.FileMove) error {
|
||||
@@ -911,6 +935,9 @@ func readEditableFileHistoryContent(filePath string) ([]byte, os.FileMode, bool)
|
||||
if mode.IsDir() || mode&os.ModeSymlink != 0 || !mode.IsRegular() || files.IsBlockDevice(mode) || info.Size() > fileHistorySnapshotMaxSize {
|
||||
return nil, mode, false
|
||||
}
|
||||
if files.IsBinaryPreviewFile(files.GetMimeType(filePath), filepath.Ext(info.Name())) {
|
||||
return nil, mode, false
|
||||
}
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, mode, false
|
||||
@@ -946,6 +973,11 @@ func buildHistoryMoveTargetPath(dst, name, sourcePath string, sourceCount int) s
|
||||
}
|
||||
|
||||
func (f *FileService) FileDownload(d request.FileDownload) (string, error) {
|
||||
for _, p := range d.Paths {
|
||||
if files.ShouldDenySensitiveFileRead(p) {
|
||||
return "", buserr.New("ErrSensitiveFileRead")
|
||||
}
|
||||
}
|
||||
filePath := d.Paths[0]
|
||||
if d.Compress {
|
||||
tempPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||
@@ -1077,6 +1109,14 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
logFilePath, _ = ini_conf.GetIniValue(configPath, "supervisord", "logfile")
|
||||
case constant.Supervisor:
|
||||
logFilePath = path.Join(global.Dir.DataDir, "tools", "supervisord", "log", req.Name)
|
||||
case "ai-proxy":
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return nil, buserr.New("ErrInvalidParams")
|
||||
}
|
||||
logFilePath = path.Join(global.Dir.LogDir, "ai", safeName)
|
||||
default:
|
||||
return nil, buserr.WithName("ErrNotSupportType", req.Type)
|
||||
}
|
||||
|
||||
file, err := os.Open(logFilePath)
|
||||
@@ -1122,17 +1162,6 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (f *FileService) GetPathByType(pathType string) string {
|
||||
if pathType == "websiteDir" {
|
||||
value, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
if value == "" {
|
||||
return path.Join(global.Dir.BaseDir, "1panel", "www")
|
||||
}
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo {
|
||||
fileList := make([]response.ExistFileInfo, 0, len(req.Paths))
|
||||
for _, filePath := range req.Paths {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -131,6 +132,9 @@ func (s *FileShareService) Create(req request.FileShareCreate) (*response.FileSh
|
||||
if path == "" || strings.Contains(path, "..") {
|
||||
return nil, buserr.New("ErrFileSharePath")
|
||||
}
|
||||
if files.ShouldDenySensitiveFileRead(path) {
|
||||
return nil, buserr.New("ErrSensitiveFileRead")
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil || info.IsDir() {
|
||||
return nil, buserr.New("ErrFileSharePath")
|
||||
|
||||
@@ -186,6 +186,9 @@ func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
|
||||
if err := client.Restart(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := u.addPortsBeforeStart(client); err != nil {
|
||||
return err
|
||||
}
|
||||
needRestartDocker = true
|
||||
case "disableBanPing":
|
||||
if err := firewall.UpdatePingStatus("0"); err != nil {
|
||||
@@ -219,7 +222,7 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
||||
req.Chain = iptables.Chain1PanelBasic
|
||||
}
|
||||
protos := strings.Split(req.Protocol, "/")
|
||||
itemAddress := strings.Split(strings.TrimSuffix(req.Address, ","), ",")
|
||||
itemAddress := splitFirewallRuleAddresses(req.Address)
|
||||
|
||||
if client.Name() == "ufw" {
|
||||
if strings.Contains(req.Port, ",") || strings.Contains(req.Port, "-") {
|
||||
@@ -512,6 +515,7 @@ func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.Po
|
||||
if err := copier.Copy(&fireInfo, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
fireInfo.Address = normalizeFirewallRuleAddress(fireInfo.Address)
|
||||
|
||||
if client.Name() == "ufw" {
|
||||
if len(fireInfo.Address) != 0 && !strings.EqualFold(fireInfo.Address, "Anywhere") {
|
||||
@@ -526,6 +530,26 @@ func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.Po
|
||||
return client.Port(fireInfo, req.Operation)
|
||||
}
|
||||
|
||||
func splitFirewallRuleAddresses(address string) []string {
|
||||
parts := strings.Split(strings.TrimSuffix(address, ","), ",")
|
||||
addresses := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
addresses = append(addresses, normalizeFirewallRuleAddress(part))
|
||||
}
|
||||
if len(addresses) == 0 {
|
||||
return []string{""}
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func normalizeFirewallRuleAddress(address string) string {
|
||||
address = strings.TrimSpace(address)
|
||||
if strings.EqualFold(address, "Anywhere") {
|
||||
return ""
|
||||
}
|
||||
return address
|
||||
}
|
||||
|
||||
type portOfApp struct {
|
||||
AppName string
|
||||
HttpPort string
|
||||
@@ -579,30 +603,21 @@ func (u *FirewallService) cleanUnUsedData(client firewall.FirewallClient) {
|
||||
}
|
||||
|
||||
func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) error {
|
||||
if !global.IsMaster {
|
||||
if err := client.Port(fireClient.FireInfo{Port: global.CONF.Base.Port, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
if client.Name() == "iptables" {
|
||||
isInit, _ := iptables.LoadInitStatus("iptables", "base")
|
||||
if !isInit {
|
||||
return nil
|
||||
}
|
||||
return syncIptablesFirewallPortWhiteList(true)
|
||||
}
|
||||
portWhiteList, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range portWhiteList {
|
||||
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var portSetting model.Setting
|
||||
_ = global.CoreDB.Where("key = ?", "ServerPort").First(&portSetting).Error
|
||||
if len(portSetting.Value) != 0 {
|
||||
if err := client.Port(fireClient.FireInfo{Port: portSetting.Value, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: loadSSHPort(), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: "80", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "udp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Reload()
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
fireClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/client"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
|
||||
)
|
||||
|
||||
type firewallPortWhitelist struct {
|
||||
Port string
|
||||
Protocol string
|
||||
}
|
||||
|
||||
func loadConfiguredFirewallPortWhiteList() ([]firewallPortWhitelist, error) {
|
||||
value, err := settingRepo.GetValueByKey(constant.FirewallPortWhiteList)
|
||||
if err != nil {
|
||||
value = constant.FirewallPortWhiteListValue
|
||||
if err := settingRepo.UpdateOrCreate(constant.FirewallPortWhiteList, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return parseFirewallPortWhiteList(value)
|
||||
}
|
||||
|
||||
func loadFirewallPortWhiteList() ([]firewallPortWhitelist, error) {
|
||||
portWhiteList, err := loadConfiguredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requiredPorts, err := loadRequiredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return normalizeFirewallPortWhiteList(append(portWhiteList, requiredPorts...)), nil
|
||||
}
|
||||
|
||||
func loadRequiredFirewallPortWhiteList() ([]firewallPortWhitelist, error) {
|
||||
panelPort := LoadPanelPort()
|
||||
if panelPort == "" {
|
||||
return nil, fmt.Errorf("find 1panel service port failed")
|
||||
}
|
||||
return normalizeFirewallPortWhiteList([]firewallPortWhitelist{
|
||||
{Port: panelPort, Protocol: "tcp"},
|
||||
{Port: loadSSHPort(), Protocol: "tcp"},
|
||||
}), nil
|
||||
}
|
||||
|
||||
func parseFirewallPortWhiteList(value string) ([]firewallPortWhitelist, error) {
|
||||
items := strings.FieldsFunc(value, func(r rune) bool {
|
||||
return r == ',' || r == '\n' || r == ';' || r == ' '
|
||||
})
|
||||
ports := make([]firewallPortWhitelist, 0, len(items))
|
||||
exists := make(map[string]struct{})
|
||||
for _, item := range items {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
port, protocol, ok := strings.Cut(item, "/")
|
||||
if !ok {
|
||||
protocol = "tcp"
|
||||
}
|
||||
port = strings.TrimSpace(port)
|
||||
protocol = strings.ToLower(strings.TrimSpace(protocol))
|
||||
if protocol != "tcp" && protocol != "udp" {
|
||||
return nil, fmt.Errorf("invalid firewall port whitelist protocol: %s", item)
|
||||
}
|
||||
portNum, err := strconv.Atoi(port)
|
||||
if err != nil || portNum < 1 || portNum > 65535 {
|
||||
return nil, fmt.Errorf("invalid firewall port whitelist: %s", item)
|
||||
}
|
||||
key := fmt.Sprintf("%d/%s", portNum, protocol)
|
||||
if _, ok := exists[key]; ok {
|
||||
continue
|
||||
}
|
||||
exists[key] = struct{}{}
|
||||
ports = append(ports, firewallPortWhitelist{Port: strconv.Itoa(portNum), Protocol: protocol})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func normalizeFirewallPortWhiteList(portWhiteList []firewallPortWhitelist) []firewallPortWhitelist {
|
||||
ports := make([]firewallPortWhitelist, 0, len(portWhiteList))
|
||||
exists := make(map[string]struct{})
|
||||
for _, item := range portWhiteList {
|
||||
if item.Port == "" {
|
||||
continue
|
||||
}
|
||||
key := fmt.Sprintf("%s/%s", item.Port, item.Protocol)
|
||||
if _, ok := exists[key]; ok {
|
||||
continue
|
||||
}
|
||||
exists[key] = struct{}{}
|
||||
ports = append(ports, item)
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
func syncFirewallPortWhiteListAfterUpdate(oldValue string) error {
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if client.Name() == "iptables" {
|
||||
isInit, _ := iptables.LoadInitStatus("iptables", "base")
|
||||
if !isInit {
|
||||
return nil
|
||||
}
|
||||
oldPortWhiteList, err := parseFirewallPortWhiteList(oldValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syncIptablesFirewallPortWhiteList(true, oldPortWhiteList)
|
||||
}
|
||||
|
||||
isActive, _ := client.Status()
|
||||
if !isActive {
|
||||
return nil
|
||||
}
|
||||
portWhiteList, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldPortWhiteList, err := parseFirewallPortWhiteList(oldValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requiredPorts, err := loadRequiredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldPortWhiteList = normalizeFirewallPortWhiteList(append(oldPortWhiteList, requiredPorts...))
|
||||
return syncFirewallClientPortWhiteList(client, oldPortWhiteList, portWhiteList)
|
||||
}
|
||||
|
||||
func syncFirewallClientPortWhiteList(client firewall.FirewallClient, oldPortWhiteList, portWhiteList []firewallPortWhitelist) error {
|
||||
oldPorts := firewallPortWhiteListMap(oldPortWhiteList)
|
||||
newPorts := firewallPortWhiteListMap(portWhiteList)
|
||||
for _, item := range oldPortWhiteList {
|
||||
key := firewallPortWhiteListKey(item)
|
||||
if _, ok := newPorts[key]; ok {
|
||||
continue
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "remove"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, item := range portWhiteList {
|
||||
key := firewallPortWhiteListKey(item)
|
||||
if _, ok := oldPorts[key]; ok {
|
||||
continue
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return client.Reload()
|
||||
}
|
||||
|
||||
func firewallPortWhiteListMap(portWhiteList []firewallPortWhitelist) map[string]struct{} {
|
||||
ports := make(map[string]struct{})
|
||||
for _, item := range portWhiteList {
|
||||
ports[firewallPortWhiteListKey(item)] = struct{}{}
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
func firewallPortWhiteListKey(item firewallPortWhitelist) string {
|
||||
return item.Port + "/" + item.Protocol
|
||||
}
|
||||
@@ -27,12 +27,14 @@ import (
|
||||
type HostToolService struct{}
|
||||
|
||||
type IHostToolService interface {
|
||||
GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error)
|
||||
GetToolStatus(req request.HostToolTypeReq) (*response.HostToolRes, error)
|
||||
CreateToolConfig(req request.HostToolCreate) error
|
||||
OperateTool(req request.HostToolReq) error
|
||||
OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error)
|
||||
OperateTool(req request.HostToolOperateReq) error
|
||||
GetToolConfig(req request.HostToolTypeReq) (*response.HostToolConfig, error)
|
||||
UpdateToolConfig(req request.HostToolConfigUpdate) error
|
||||
OperateSupervisorProcess(req request.SupervisorProcessConfig) error
|
||||
GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error)
|
||||
GetSupervisorProcessFile(req request.HostSupervisorProcessFileGetReq) (string, error)
|
||||
OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error)
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ func NewIHostToolService() IHostToolService {
|
||||
return &HostToolService{}
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
|
||||
func (h *HostToolService) GetToolStatus(req request.HostToolTypeReq) (*response.HostToolRes, error) {
|
||||
res := &response.HostToolRes{}
|
||||
res.Type = req.Type
|
||||
switch req.Type {
|
||||
@@ -71,7 +73,7 @@ func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.Host
|
||||
supervisorConfig.ServiceName = serviceNameSet.Value
|
||||
}
|
||||
|
||||
versionRes, _ := cmd.RunDefaultWithStdoutBashC("supervisord -v")
|
||||
versionRes, _ := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("supervisord", "-v")
|
||||
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
|
||||
_, ctlRrr := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = ctlRrr == nil
|
||||
@@ -201,7 +203,7 @@ func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
|
||||
func (h *HostToolService) OperateTool(req request.HostToolOperateReq) error {
|
||||
serviceName := req.Type
|
||||
if req.Type == constant.Supervisord {
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
@@ -212,12 +214,10 @@ func (h *HostToolService) OperateTool(req request.HostToolReq) error {
|
||||
return controller.Handle(req.Operate, serviceName)
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
|
||||
fileOp := files.NewFileOp()
|
||||
res := &response.HostToolConfig{}
|
||||
func (h *HostToolService) loadToolConfigInfo(toolType string) (string, string) {
|
||||
configPath := ""
|
||||
serviceName := "supervisord"
|
||||
switch req.Type {
|
||||
switch toolType {
|
||||
case constant.Supervisord:
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if pathSet.ID != 0 || pathSet.Value != "" {
|
||||
@@ -227,37 +227,48 @@ func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*respon
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
serviceName = serviceNameSet.Value
|
||||
}
|
||||
default:
|
||||
return "", ""
|
||||
}
|
||||
switch req.Operate {
|
||||
case "get":
|
||||
content, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Content = string(content)
|
||||
case "set":
|
||||
file, err := fileOp.OpenFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldContent, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(configPath, strings.NewReader(req.Content), fileInfo.Mode()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = controller.HandleRestart(serviceName); err != nil {
|
||||
_ = fileOp.WriteFile(configPath, bytes.NewReader(oldContent), fileInfo.Mode())
|
||||
return nil, err
|
||||
}
|
||||
return configPath, serviceName
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetToolConfig(req request.HostToolTypeReq) (*response.HostToolConfig, error) {
|
||||
fileOp := files.NewFileOp()
|
||||
res := &response.HostToolConfig{}
|
||||
configPath, _ := h.loadToolConfigInfo(req.Type)
|
||||
content, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Content = string(content)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) UpdateToolConfig(req request.HostToolConfigUpdate) error {
|
||||
fileOp := files.NewFileOp()
|
||||
configPath, serviceName := h.loadToolConfigInfo(req.Type)
|
||||
file, err := fileOp.OpenFile(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldContent, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.WriteFile(configPath, strings.NewReader(req.Content), fileInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = controller.HandleRestart(serviceName); err != nil {
|
||||
_ = fileOp.WriteFile(configPath, bytes.NewReader(oldContent), fileInfo.Mode())
|
||||
return err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcessConfig) error {
|
||||
@@ -451,7 +462,19 @@ func (h *HostToolService) OperateSupervisorProcessFile(req request.SupervisorPro
|
||||
return handleSupervisorFile(req, includeDir, "", "")
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetSupervisorProcessFile(req request.HostSupervisorProcessFileGetReq) (string, error) {
|
||||
return h.OperateSupervisorProcessFile(request.SupervisorProcessFileReq{
|
||||
Name: req.Name,
|
||||
Operate: "get",
|
||||
File: req.File,
|
||||
})
|
||||
}
|
||||
|
||||
func handleSupervisorFile(req request.SupervisorProcessFileReq, includeDir, containerName, logFile string) (string, error) {
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return "", buserr.New("ErrInvalidParams")
|
||||
}
|
||||
var (
|
||||
fileOp = files.NewFileOp()
|
||||
group = fmt.Sprintf("program:%s", req.Name)
|
||||
@@ -550,8 +573,8 @@ func operateSupervisorCtl(operate, name, group, includeDir, containerName string
|
||||
err error
|
||||
)
|
||||
if containerName != "" {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30 * time.Second))
|
||||
output, err = cmdMgr.RunWithStdoutBashCf("docker exec %s supervisorctl %s", containerName, strings.Join(processNames, " "))
|
||||
args := append([]string{"supervisorctl"}, processNames...)
|
||||
output, err = cmd.RunDockerExecWithStdout(30*time.Second, containerName, args...)
|
||||
} else {
|
||||
var out []byte
|
||||
out, err = exec.Command("supervisorctl", processNames...).Output()
|
||||
@@ -593,8 +616,8 @@ func getProcessStatus(config *response.SupervisorProcessConfig, containerName st
|
||||
)
|
||||
processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...)
|
||||
if containerName != "" {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(3 * time.Second))
|
||||
output, err = cmdMgr.RunWithStdoutBashCf("docker exec %s supervisorctl %s", containerName, strings.Join(processNames, " "))
|
||||
args := append([]string{"supervisorctl"}, processNames...)
|
||||
output, err = cmd.RunDockerExecWithStdout(3*time.Second, containerName, args...)
|
||||
} else {
|
||||
var out []byte
|
||||
out, err = exec.Command("supervisorctl", processNames...).Output()
|
||||
@@ -613,8 +636,7 @@ func getProcessStatus(config *response.SupervisorProcessConfig, containerName st
|
||||
Status: fields[1],
|
||||
}
|
||||
if fields[1] == "RUNNING" {
|
||||
status.PID = strings.TrimSuffix(fields[3], ",")
|
||||
status.Uptime = fields[5]
|
||||
status.PID, status.Uptime = parseSupervisorRunningDetails(fields)
|
||||
} else {
|
||||
status.Msg = strings.Join(fields[2:], " ")
|
||||
}
|
||||
@@ -623,3 +645,19 @@ func getProcessStatus(config *response.SupervisorProcessConfig, containerName st
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSupervisorRunningDetails(fields []string) (string, string) {
|
||||
var pid, uptime string
|
||||
if len(fields) > 3 && fields[2] == "pid" {
|
||||
pid = strings.TrimSuffix(fields[3], ",")
|
||||
}
|
||||
for i := 4; i < len(fields); i++ {
|
||||
if strings.TrimSuffix(fields[i], ",") == "uptime" {
|
||||
if i+1 < len(fields) {
|
||||
uptime = strings.Join(fields[i+1:], " ")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return pid, uptime
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user