mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
306
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04848a92a | ||
|
|
748f5f070a | ||
|
|
2dcb367622 | ||
|
|
26bd2d08be | ||
|
|
739686ff89 | ||
|
|
68a871a5d7 | ||
|
|
b47bba4827 | ||
|
|
3b91859a49 | ||
|
|
c417bc9c2b | ||
|
|
7c95becfeb | ||
|
|
6b1480c5fa | ||
|
|
7f450544e1 | ||
|
|
0c046edfe5 | ||
|
|
2a2e6607b8 | ||
|
|
f6ed11ac55 | ||
|
|
d330f60b2d | ||
|
|
10c8cafcee | ||
|
|
d1534c88aa | ||
|
|
eb6b4c7cb7 | ||
|
|
c77ddcfebe | ||
|
|
af12526a6c | ||
|
|
b347abe100 | ||
|
|
092b57413b | ||
|
|
e8f35e4e06 | ||
|
|
b019e7526d | ||
|
|
1b06467521 | ||
|
|
7f3fd0cb1d | ||
|
|
3e9848554b | ||
|
|
b02cdbc5f9 | ||
|
|
d7723fd42a | ||
|
|
8b265bb81e | ||
|
|
89b6c94e42 | ||
|
|
219ac160b6 | ||
|
|
51ca1eab18 | ||
|
|
1ab3da1fab | ||
|
|
1d1b12c2fa | ||
|
|
07a658582d | ||
|
|
61da74ae50 | ||
|
|
90334f3cb0 | ||
|
|
fb9278bfc6 | ||
|
|
7750936af6 | ||
|
|
7b695af996 | ||
|
|
2874849557 | ||
|
|
9fd1f3e661 | ||
|
|
85182d9164 | ||
|
|
d9b2ea051f | ||
|
|
097fe6dcfb | ||
|
|
d5beca6d96 | ||
|
|
e3a356f893 | ||
|
|
8be2a9ab02 | ||
|
|
3d7f0f5143 | ||
|
|
1fd62fbaf7 | ||
|
|
f1372dda85 | ||
|
|
64c354adb2 | ||
|
|
a379e1f840 | ||
|
|
270ba799f7 | ||
|
|
60dcfacb88 | ||
|
|
b474d720e9 | ||
|
|
b2192b7252 | ||
|
|
6a988ad96a | ||
|
|
029ac5efee | ||
|
|
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 |
@@ -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
|
||||
@@ -1,25 +0,0 @@
|
||||
name: LLM Code Review
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
jobs:
|
||||
llm-code-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fit2cloud/LLM-CodeReview-Action@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
|
||||
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
||||
LANGUAGE: English
|
||||
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
MODEL: qwen2.5-coder-3b-instruct
|
||||
PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English."
|
||||
top_p: 1
|
||||
temperature: 1
|
||||
# max_tokens: 10000
|
||||
MAX_PATCH_LENGTH: 10000
|
||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||
@@ -1,21 +0,0 @@
|
||||
name: SonarCloud Scan
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
sonarcloud:
|
||||
name: SonarCloud
|
||||
if: github.repository == '1Panel-dev/1Panel'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: SonarCloud Scan
|
||||
uses: SonarSource/sonarcloud-github-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror the Github organization repos to Gitee.
|
||||
uses: Yikun/hub-mirror-action@master
|
||||
uses: Yikun/hub-mirror-action@ba51c01b28a6c9f95a25d4f1bcf6af2a147c0e18 # master
|
||||
with:
|
||||
src: 'github/1Panel-dev'
|
||||
dst: 'gitee/fit2cloud-feizhiyun'
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
name: Typos Check
|
||||
on: pull_request
|
||||
jobs:
|
||||
run:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@master
|
||||
+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,8 @@
|
||||
<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>
|
||||
<a href="/docs/README.lo.md"><img alt="ພາສາລາວ" src="https://img.shields.io/badge/%E0%BA%9E%E0%BA%B2%E0%BA%AA%E0%BA%B2%E0%BA%A5%E0%BA%B2%E0%BA%A7-d9d9d9"></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
+146
-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,34 @@ func (b *BaseApi) GetAgentAccountModels(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Create Agent account model
|
||||
// @Summary Discover custom provider models
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelDiscoverReq true "request"
|
||||
// @Success 200 {array} dto.AgentAccountModel
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/accounts/models/discover [post]
|
||||
func (b *BaseApi) DiscoverAgentAccountModels(c *gin.Context) {
|
||||
var req dto.AgentAccountModelDiscoverReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list, err := agentService.DiscoverAccountModels(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @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 +542,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 +562,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 +582,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 +602,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")
|
||||
|
||||
@@ -41,20 +41,40 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Bind user of mysql database
|
||||
// @Summary List mysql users
|
||||
// @Accept json
|
||||
// @Param request body dto.BindUser true "request"
|
||||
// @Success 200
|
||||
// @Param request body dto.MysqlUserSearch true "request"
|
||||
// @Success 200 {array} dto.MysqlUser
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/bind [post]
|
||||
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 mysql 数据库名 [database] [username]","formatEN":"bind mysql database [database] [username]"}
|
||||
func (b *BaseApi) BindUser(c *gin.Context) {
|
||||
var req dto.BindUser
|
||||
// @Router /databases/users/search [post]
|
||||
func (b *BaseApi) ListMysqlUsers(c *gin.Context) {
|
||||
var req dto.MysqlUserSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := mysqlService.ListUsers(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Create mysql user
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlUserCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/users [post]
|
||||
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 mysql 数据库 [database] 用户 [username]@[host]","formatEN":"create mysql database [database] user [username]@[host]"}
|
||||
func (b *BaseApi) CreateMysqlUser(c *gin.Context) {
|
||||
var req dto.MysqlUserCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
@@ -63,8 +83,191 @@ func (b *BaseApi) BindUser(c *gin.Context) {
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
if err := mysqlService.CreateUser(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
if err := mysqlService.BindUser(req); err != nil {
|
||||
// @Tags Database Mysql
|
||||
// @Summary Delete mysql user
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlUserDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/users/del [post]
|
||||
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 mysql 数据库 [database] 用户 [username]@[host]","formatEN":"delete mysql database [database] user [username]@[host]"}
|
||||
func (b *BaseApi) DeleteMysqlUser(c *gin.Context) {
|
||||
var req dto.MysqlUserDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := mysqlService.DeleteUser(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Update mysql user
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlUserUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/users/update [post]
|
||||
// @x-panel-log {"bodyKeys":["database","username","host","newHost","description"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] => [newHost] 描述 [description]","formatEN":"update mysql database [database] user [username] access [host] => [newHost] description [description]"}
|
||||
func (b *BaseApi) UpdateMysqlUser(c *gin.Context) {
|
||||
var req dto.MysqlUserUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := mysqlService.UpdateUser(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Change mysql user password
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlUserPassword true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/users/password [post]
|
||||
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 mysql 数据库 [database] 用户 [username]@[host] 密码","formatEN":"update mysql database [database] user [username]@[host] password"}
|
||||
func (b *BaseApi) ChangeMysqlUserPassword(c *gin.Context) {
|
||||
var req dto.MysqlUserPassword
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
if err := mysqlService.ChangeUserPassword(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Save mysql user password locally
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlUserPassword true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/users/password/save [post]
|
||||
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"补充 mysql 数据库 [database] 用户 [username]@[host] 密码","formatEN":"save mysql database [database] user [username]@[host] password locally"}
|
||||
func (b *BaseApi) SaveMysqlUserPassword(c *gin.Context) {
|
||||
var req dto.MysqlUserPassword
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
if err := mysqlService.SaveUserPassword(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary List mysql grants
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlUserSearch true "request"
|
||||
// @Success 200 {array} dto.MysqlGrant
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/grants/search [post]
|
||||
func (b *BaseApi) ListMysqlGrants(c *gin.Context) {
|
||||
var req dto.MysqlUserSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := mysqlService.ListGrants(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary List mysql grant summary
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlGrantSummarySearch true "request"
|
||||
// @Success 200 {object} map[string][]dto.MysqlUser
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/grants/summary [post]
|
||||
func (b *BaseApi) ListMysqlGrantSummary(c *gin.Context) {
|
||||
var req dto.MysqlGrantSummarySearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := mysqlService.ListGrantSummary(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Grant mysql user
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlGrantCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/grants [post]
|
||||
// @x-panel-log {"bodyKeys":["database","db","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"授权 mysql 数据库 [database] 用户 [username]@[host] 访问 [db]","formatEN":"grant mysql database [database] user [username]@[host] access to [db]"}
|
||||
func (b *BaseApi) GrantMysqlUser(c *gin.Context) {
|
||||
var req dto.MysqlGrantCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := mysqlService.GrantUser(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Revoke mysql grant
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlGrantDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/grants/del [post]
|
||||
// @x-panel-log {"bodyKeys":["database","db","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"取消 mysql 数据库 [database] 用户 [username]@[host] 对 [db] 的授权","formatEN":"revoke mysql database [database] user [username]@[host] access to [db]"}
|
||||
func (b *BaseApi) RevokeMysqlGrant(c *gin.Context) {
|
||||
var req dto.MysqlGrantDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := mysqlService.RevokeGrant(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -94,14 +297,14 @@ func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Change mysql password
|
||||
// @Summary Change mysql root password
|
||||
// @Accept json
|
||||
// @Param request body dto.ChangeDBInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/change/password [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"}
|
||||
// @x-panel-log {"bodyKeys":["database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] root 密码","formatEN":"Update database [database] root password"}
|
||||
func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
||||
var req dto.ChangeDBInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -125,14 +328,14 @@ func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Change mysql access
|
||||
// @Summary Change mysql root access
|
||||
// @Accept json
|
||||
// @Param request body dto.ChangeDBInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/change/access [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"}
|
||||
// @x-panel-log {"bodyKeys":["database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] root 访问权限","formatEN":"Update database [database] root access"}
|
||||
func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) {
|
||||
var req dto.ChangeDBInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -37,14 +37,15 @@ var (
|
||||
|
||||
cronjobService = service.NewICronjobService()
|
||||
|
||||
fileService = service.NewIFileService()
|
||||
fileHistoryService = service.NewIFileHistoryService()
|
||||
fileShareService = service.NewIFileShareService()
|
||||
sshService = service.NewISSHService()
|
||||
firewallService = service.NewIFirewallService()
|
||||
iptablesService = service.NewIIptablesService()
|
||||
monitorService = service.NewIMonitorService()
|
||||
systemService = service.NewISystemService()
|
||||
fileService = service.NewIFileService()
|
||||
fileHistoryService = service.NewIFileHistoryService()
|
||||
fileShareService = service.NewIFileShareService()
|
||||
sshService = service.NewISSHService()
|
||||
firewallService = service.NewIFirewallService()
|
||||
iptablesService = service.NewIIptablesService()
|
||||
monitorService = service.NewIMonitorService()
|
||||
systemService = service.NewISystemService()
|
||||
runtimeDiagnosticsService = service.NewIRuntimeDiagnosticsService()
|
||||
|
||||
deviceService = service.NewIDeviceService()
|
||||
fail2banService = service.NewIFail2BanService()
|
||||
|
||||
+104
-15
@@ -12,7 +12,9 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
@@ -28,6 +30,11 @@ import (
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
var cancelledChunkUploads = struct {
|
||||
sync.RWMutex
|
||||
ids map[string]struct{}
|
||||
}{ids: make(map[string]struct{})}
|
||||
|
||||
// @Tags File
|
||||
// @Summary List files
|
||||
// @Accept json
|
||||
@@ -193,7 +200,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 +222,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 +647,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 +686,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 {
|
||||
@@ -865,7 +880,24 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
filename := c.PostForm("filename")
|
||||
fileDir := filepath.Join(tmpDir, filename)
|
||||
if filename == "" || filepath.Base(filename) != filename || strings.ContainsAny(filename, `/\\`) {
|
||||
helper.BadRequest(c, errors.New("invalid filename"))
|
||||
return
|
||||
}
|
||||
uploadID := strings.TrimSpace(c.PostForm("uploadID"))
|
||||
cancellable := uploadID != ""
|
||||
if cancellable && (filepath.Base(uploadID) != uploadID || strings.ContainsAny(uploadID, `/\\`)) {
|
||||
helper.BadRequest(c, errors.New("invalid upload ID"))
|
||||
return
|
||||
}
|
||||
if !cancellable {
|
||||
uploadID = filename
|
||||
}
|
||||
fileDir := filepath.Join(tmpDir, uploadID)
|
||||
if cancellable && chunkUploadCancelled(uploadID) {
|
||||
helper.BadRequest(c, errors.New("upload cancelled"))
|
||||
return
|
||||
}
|
||||
if chunkIndex == 0 {
|
||||
if fileOp.Stat(fileDir) {
|
||||
_ = fileOp.DeleteDir(fileDir)
|
||||
@@ -876,7 +908,7 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = os.Remove(fileDir)
|
||||
_ = os.RemoveAll(fileDir)
|
||||
}
|
||||
}()
|
||||
var (
|
||||
@@ -896,6 +928,11 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
||||
return
|
||||
}
|
||||
if cancellable && chunkUploadCancelled(uploadID) {
|
||||
err = errors.New("upload cancelled")
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
|
||||
err = os.WriteFile(chunkPath, chunkData, constant.DirPerm)
|
||||
@@ -914,19 +951,67 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
||||
return
|
||||
}
|
||||
if cancellable {
|
||||
cancelledChunkUploads.Lock()
|
||||
delete(cancelledChunkUploads.ids, uploadID)
|
||||
cancelledChunkUploads.Unlock()
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// StopChunkUpload removes temporary chunks left by a cancelled upload.
|
||||
func (b *BaseApi) StopChunkUpload(c *gin.Context) {
|
||||
var req request.FileProcessReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
uploadID := strings.TrimSpace(req.Key)
|
||||
if uploadID == "" || filepath.Base(uploadID) != uploadID || strings.ContainsAny(uploadID, `/\\`) {
|
||||
helper.BadRequest(c, errors.New("invalid upload ID"))
|
||||
return
|
||||
}
|
||||
cancelledChunkUploads.Lock()
|
||||
cancelledChunkUploads.ids[uploadID] = struct{}{}
|
||||
cancelledChunkUploads.Unlock()
|
||||
time.AfterFunc(10*time.Minute, func() {
|
||||
cancelledChunkUploads.Lock()
|
||||
delete(cancelledChunkUploads.ids, uploadID)
|
||||
cancelledChunkUploads.Unlock()
|
||||
})
|
||||
if err := os.RemoveAll(filepath.Join(global.Dir.TmpDir, "upload", uploadID)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func chunkUploadCancelled(uploadID string) bool {
|
||||
cancelledChunkUploads.RLock()
|
||||
_, ok := cancelledChunkUploads.ids[uploadID]
|
||||
cancelledChunkUploads.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
var wsUpgrade = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// @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 +1021,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 +1046,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 +1092,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 +1423,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
|
||||
|
||||
@@ -2,6 +2,7 @@ package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -20,3 +21,57 @@ func (b *BaseApi) GetSystemFiles(c *gin.Context) {
|
||||
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary Get host system log status
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.SystemLogStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/system/status [get]
|
||||
func (b *BaseApi) GetSystemLogStatus(c *gin.Context) {
|
||||
data, err := logService.GetSystemLogStatus()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary Read host logs
|
||||
// @Accept json
|
||||
// @Param request body dto.SystemLogReq true "request"
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.SystemLogRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/system/read [post]
|
||||
func (b *BaseApi) ReadSystemLog(c *gin.Context) {
|
||||
var req dto.SystemLogReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := logService.ReadSystemLog(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary List running host services
|
||||
// @Produce json
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/system/services [get]
|
||||
func (b *BaseApi) ListRunningServices(c *gin.Context) {
|
||||
data, err := logService.ListRunningServices()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags RuntimeDiagnostics
|
||||
// @Summary Load runtime diagnostics summary
|
||||
// @Success 200 {object} dto.RuntimeDiagnosticsSummary
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/diagnostics/summary [get]
|
||||
func (b *BaseApi) LoadRuntimeDiagnosticsSummary(c *gin.Context) {
|
||||
data, err := runtimeDiagnosticsService.Summary()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags RuntimeDiagnostics
|
||||
// @Summary Load grouped goroutine snapshot
|
||||
// @Success 200 {object} dto.RuntimeGoroutineSnapshot
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/diagnostics/goroutines [get]
|
||||
func (b *BaseApi) LoadRuntimeGoroutines(c *gin.Context) {
|
||||
data, err := runtimeDiagnosticsService.Goroutines()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithDataGzipped(c, data)
|
||||
}
|
||||
|
||||
// @Tags RuntimeDiagnostics
|
||||
// @Summary Capture runtime profile
|
||||
// @Param request body dto.RuntimeProfileCreate true "request"
|
||||
// @Success 200 {file} file
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/diagnostics/profiles [post]
|
||||
func (b *BaseApi) CreateRuntimeProfile(c *gin.Context) {
|
||||
var req dto.RuntimeProfileCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
profile, err := runtimeDiagnosticsService.CreateProfile(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(profile.Path)
|
||||
c.Header("Content-Disposition", `attachment; filename="`+profile.Name+`"`)
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.File(profile.Path)
|
||||
c.Abort()
|
||||
}
|
||||
+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
|
||||
|
||||
@@ -224,6 +224,21 @@ func (b *BaseApi) ExportSSHLogs(c *gin.Context) {
|
||||
helper.SuccessWithData(c, tmpFile)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Clean host SSH logs
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/ssh/log/clean [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空 SSH 登录日志","formatEN":"clean SSH login logs"}
|
||||
func (b *BaseApi) CleanSSHLogs(c *gin.Context) {
|
||||
if err := sshService.CleanLog(); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Load host SSH conf
|
||||
// @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 {
|
||||
|
||||
+172
-68
@@ -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 {
|
||||
@@ -36,11 +118,11 @@ type AgentItem struct {
|
||||
ProviderName string `json:"providerName"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
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"`
|
||||
@@ -208,19 +290,22 @@ type AgentOverviewSnapshot struct {
|
||||
}
|
||||
|
||||
type AgentAccountModel struct {
|
||||
RecordID uint `json:"recordId"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Input []string `json:"input"`
|
||||
RecordID uint `json:"recordId"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AgentAccountModelReq struct {
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountModelDiscoverReq struct {
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
BaseURL string `json:"baseURL" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountModelCreateReq struct {
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model AgentAccountModel `json:"model" validate:"required"`
|
||||
@@ -244,6 +329,8 @@ type AgentAccountCreateReq struct {
|
||||
BaseURL string `json:"baseURL"`
|
||||
Models []AgentAccountModel `json:"models"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
AuthMode string `json:"authMode"`
|
||||
VerifyModel string `json:"verifyModel"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
@@ -254,6 +341,8 @@ type AgentAccountUpdateReq struct {
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
AuthMode string `json:"authMode"`
|
||||
VerifyModel string `json:"verifyModel"`
|
||||
Remark string `json:"remark"`
|
||||
SyncAgents bool `json:"syncAgents"`
|
||||
}
|
||||
@@ -262,6 +351,9 @@ type AgentAccountVerifyReq struct {
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
AuthMode string `json:"authMode"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
type AgentAccountDeleteReq struct {
|
||||
@@ -274,36 +366,49 @@ 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"`
|
||||
AuthMode string `json:"authMode"`
|
||||
VerifyModel string `json:"verifyModel"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ProviderModelInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Input []string `json:"input"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ProviderAPIInfo struct {
|
||||
APIType string `json:"apiType"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
EditableBaseURL bool `json:"editableBaseUrl"`
|
||||
DefaultAuthMode string `json:"defaultAuthMode"`
|
||||
AuthModes []string `json:"authModes"`
|
||||
}
|
||||
|
||||
type ProviderInfo struct {
|
||||
Sort uint `json:"-"`
|
||||
Provider string `json:"provider"`
|
||||
DisplayName string `json:"displayName"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Models []ProviderModelInfo `json:"models"`
|
||||
Sort uint `json:"-"`
|
||||
Provider string `json:"provider"`
|
||||
DisplayName string `json:"displayName"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
DefaultAPIType string `json:"defaultApiType"`
|
||||
APITypes []ProviderAPIInfo `json:"apiTypes"`
|
||||
Models []ProviderModelInfo `json:"models"`
|
||||
}
|
||||
|
||||
type AgentFeishuConfigReq struct {
|
||||
@@ -324,11 +429,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 +677,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 +708,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 +745,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 {
|
||||
|
||||
@@ -2,6 +2,7 @@ package dto
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
)
|
||||
@@ -132,6 +133,8 @@ type Locale struct {
|
||||
Ko string `json:"ko"`
|
||||
Tr string `json:"tr"`
|
||||
Es string `json:"es-es" yaml:"es-es"`
|
||||
Fa string `json:"fa"`
|
||||
Lo string `json:"lo"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -134,10 +134,15 @@ type ExtraHost struct {
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
type ContainerNetwork struct {
|
||||
Network string `json:"network"`
|
||||
Ipv4 string `json:"ipv4"`
|
||||
Ipv6 string `json:"ipv6"`
|
||||
MacAddr string `json:"macAddr"`
|
||||
Network string `json:"network"`
|
||||
Ipv4 string `json:"ipv4"`
|
||||
Ipv6 string `json:"ipv6"`
|
||||
MacAddr string `json:"macAddr"`
|
||||
Links []string `json:"links"`
|
||||
Aliases []string `json:"aliases"`
|
||||
DriverOpts map[string]string `json:"driverOpts"`
|
||||
GwPriority int `json:"gwPriority"`
|
||||
LinkLocalIPs []string `json:"linkLocalIPs"`
|
||||
}
|
||||
|
||||
type ContainerCreateByCommand struct {
|
||||
@@ -319,7 +324,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"`
|
||||
}
|
||||
|
||||
+69
-10
@@ -60,25 +60,84 @@ type MysqlDBCreate struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
Format string `json:"format" validate:"required"`
|
||||
Collation string `json:"collation"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type MysqlUser struct {
|
||||
Username string `json:"username"`
|
||||
Host string `json:"host"`
|
||||
Password string `json:"password"`
|
||||
Description string `json:"description"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
}
|
||||
|
||||
type MysqlGrant struct {
|
||||
Database string `json:"database"`
|
||||
Username string `json:"username"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
type MysqlGrantSummarySearch struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
DBs []string `json:"dbs" validate:"required"`
|
||||
}
|
||||
|
||||
type MysqlUserSearch struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
}
|
||||
|
||||
type MysqlUserCreate struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
DBs []string `json:"dbs"`
|
||||
}
|
||||
|
||||
type MysqlUserDelete struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
}
|
||||
|
||||
type MysqlUserUpdate struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
NewHost string `json:"newHost" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type MysqlUserPassword struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
type MysqlGrantCreate struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
DB string `json:"db" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
}
|
||||
|
||||
type MysqlGrantDelete struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
DB string `json:"db" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
}
|
||||
|
||||
type MysqlFormatCollationOption struct {
|
||||
Format string `json:"format"`
|
||||
Collations []string `json:"collations"`
|
||||
}
|
||||
|
||||
type BindUser struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
DB string `json:"db" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Permission string `json:"permission" validate:"required"`
|
||||
}
|
||||
|
||||
type MysqlLoadDB struct {
|
||||
From string `json:"from" validate:"required,oneof=local remote"`
|
||||
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
@@ -14,3 +16,37 @@ type SearchTaskLogReq struct {
|
||||
type TaskDTO struct {
|
||||
model.Task
|
||||
}
|
||||
|
||||
type SystemLogReq struct {
|
||||
PageSize int `json:"pageSize" validate:"omitempty,min=1,max=500"`
|
||||
Cursor string `json:"cursor"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
Keyword string `json:"keyword"`
|
||||
Priority string `json:"priority"`
|
||||
Service string `json:"service"`
|
||||
}
|
||||
|
||||
type SystemLogRes struct {
|
||||
Source string `json:"source"`
|
||||
Items []SystemLogItem `json:"items"`
|
||||
HasMore bool `json:"hasMore"`
|
||||
NextCursor string `json:"nextCursor"`
|
||||
}
|
||||
|
||||
type SystemLogStatus struct {
|
||||
Source string `json:"source"`
|
||||
Version string `json:"version"`
|
||||
KeywordFilterSupported bool `json:"keywordFilterSupported"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type SystemLogItem struct {
|
||||
Timestamp int64 `json:"-"`
|
||||
Cursor string `json:"-"`
|
||||
Time string `json:"time"`
|
||||
Priority string `json:"priority"`
|
||||
Service string `json:"service"`
|
||||
Message string `json:"message"`
|
||||
Raw string `json:"raw"`
|
||||
}
|
||||
|
||||
+39
-5
@@ -1,6 +1,8 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
)
|
||||
@@ -87,9 +89,41 @@ var LBAlgorithms = map[string]struct{}{"ip_hash": {}, "least_conn": {}}
|
||||
var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF-Connecting-IP": {}}
|
||||
|
||||
type NginxModule struct {
|
||||
Name string `json:"name"`
|
||||
Script string `json:"script"`
|
||||
Packages []string `json:"packages"`
|
||||
Params string `json:"params"`
|
||||
Enable bool `json:"enable"`
|
||||
Name string `json:"name"`
|
||||
Custom bool `json:"custom,omitempty"`
|
||||
Script string `json:"script"`
|
||||
Packages []string `json:"packages"`
|
||||
Params string `json:"params"`
|
||||
Enable bool `json:"enable"`
|
||||
BuildMode string `json:"buildMode,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
LoadOrder int `json:"loadOrder,omitempty"`
|
||||
Builds []NginxModuleBuild `json:"builds,omitempty"`
|
||||
LastError string `json:"lastError,omitempty"`
|
||||
}
|
||||
|
||||
type NginxModuleBuild struct {
|
||||
Provider string `json:"provider"`
|
||||
BuildMode string `json:"buildMode"`
|
||||
Status string `json:"status"`
|
||||
Hash string `json:"hash"`
|
||||
Target NginxModuleTarget `json:"target"`
|
||||
Artifacts []NginxModuleArtifact `json:"artifacts,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
BuiltAt time.Time `json:"builtAt,omitempty"`
|
||||
}
|
||||
|
||||
type NginxModuleTarget struct {
|
||||
Key string `json:"key"`
|
||||
OpenRestyVersion string `json:"openrestyVersion"`
|
||||
Architecture string `json:"architecture"`
|
||||
Image string `json:"image,omitempty"`
|
||||
ImageDigest string `json:"imageDigest,omitempty"`
|
||||
BuilderDigest string `json:"builderDigest,omitempty"`
|
||||
}
|
||||
|
||||
type NginxModuleArtifact struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Checksum string `json:"checksum"`
|
||||
}
|
||||
|
||||
@@ -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,10 @@ 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"`
|
||||
GatewayArgs string `json:"gatewayArgs" validate:"max=4096"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type McpServerUpdate struct {
|
||||
@@ -32,11 +36,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"`
|
||||
|
||||
@@ -114,17 +114,22 @@ type NginxRedirectUpdate struct {
|
||||
}
|
||||
|
||||
type NginxBuildReq struct {
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
Mirror string `json:"mirror" validate:"required"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
Mirror string `json:"mirror" validate:"required"`
|
||||
Modules []string `json:"modules"`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
|
||||
type NginxModuleUpdate struct {
|
||||
Operate string `json:"operate" validate:"required,oneof=create delete update"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Script string `json:"script"`
|
||||
Packages string `json:"packages"`
|
||||
Enable bool `json:"enable"`
|
||||
Params string `json:"params"`
|
||||
Operate string `json:"operate" validate:"required,oneof=create delete update"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Script string `json:"script"`
|
||||
Packages string `json:"packages"`
|
||||
Enable bool `json:"enable"`
|
||||
Params string `json:"params"`
|
||||
BuildMode string `json:"buildMode" validate:"omitempty,oneof=dynamic static"`
|
||||
Provider string `json:"provider" validate:"omitempty,oneof=local prebuilt"`
|
||||
LoadOrder int `json:"loadOrder" validate:"omitempty,min=0,max=9999"`
|
||||
}
|
||||
|
||||
type NginxOperateReq struct {
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -69,16 +69,25 @@ type NginxProxyCache struct {
|
||||
}
|
||||
|
||||
type NginxModule struct {
|
||||
Name string `json:"name"`
|
||||
Script string `json:"script"`
|
||||
Packages string `json:"packages"`
|
||||
Params string `json:"params"`
|
||||
Enable bool `json:"enable"`
|
||||
Name string `json:"name"`
|
||||
Custom bool `json:"custom"`
|
||||
Script string `json:"script"`
|
||||
Packages string `json:"packages"`
|
||||
Params string `json:"params"`
|
||||
Enable bool `json:"enable"`
|
||||
BuildMode string `json:"buildMode"`
|
||||
Provider string `json:"provider"`
|
||||
LoadOrder int `json:"loadOrder"`
|
||||
BuildStatus string `json:"buildStatus"`
|
||||
LoadStatus string `json:"loadStatus"`
|
||||
Artifacts []dto.NginxModuleArtifact `json:"artifacts"`
|
||||
LastError string `json:"lastError"`
|
||||
}
|
||||
|
||||
type NginxBuildConfig struct {
|
||||
Mirror string `json:"mirror"`
|
||||
Modules []NginxModule `json:"modules"`
|
||||
Mirror string `json:"mirror"`
|
||||
DynamicSupported bool `json:"dynamicSupported"`
|
||||
Modules []NginxModule `json:"modules"`
|
||||
}
|
||||
|
||||
type NginxConfigRes struct {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type RuntimeDiagnosticsSummary struct {
|
||||
RSS uint64 `json:"rss"`
|
||||
HeapAlloc uint64 `json:"heapAlloc"`
|
||||
HeapObjects uint64 `json:"heapObjects"`
|
||||
Goroutines int `json:"goroutines"`
|
||||
}
|
||||
|
||||
type RuntimeGoroutineGroup struct {
|
||||
State string `json:"state"`
|
||||
Top string `json:"top"`
|
||||
Count int `json:"count"`
|
||||
Stack []string `json:"stack"`
|
||||
}
|
||||
|
||||
type RuntimeGoroutineSnapshot struct {
|
||||
Total int `json:"total"`
|
||||
GroupCount int `json:"groupCount"`
|
||||
Truncated bool `json:"truncated"`
|
||||
CapturedAt time.Time `json:"capturedAt"`
|
||||
Goroutines []RuntimeGoroutineGroup `json:"goroutines"`
|
||||
}
|
||||
|
||||
type RuntimeProfileCreate struct {
|
||||
Type string `json:"type" validate:"required,oneof=cpu heap goroutine mutex block"`
|
||||
Duration int `json:"duration" validate:"omitempty,min=5,max=30"`
|
||||
}
|
||||
@@ -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,17 @@ 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"`
|
||||
AuthMode string `json:"authMode"`
|
||||
VerifyModel string `json:"verifyModel"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
MasterAccountID uint `json:"masterAccountId" gorm:"index"`
|
||||
}
|
||||
|
||||
func (AgentAccount) TableName() string {
|
||||
|
||||
@@ -2,14 +2,10 @@ package model
|
||||
|
||||
type AgentAccountModel struct {
|
||||
BaseModel
|
||||
AccountID uint `json:"accountId" gorm:"index"`
|
||||
Model string `json:"model" gorm:"index"`
|
||||
Name string `json:"name"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Input string `json:"input" gorm:"type:text"`
|
||||
SortOrder int `json:"sortOrder" gorm:"index"`
|
||||
AccountID uint `json:"accountId" gorm:"index"`
|
||||
Model string `json:"model" gorm:"index"`
|
||||
Name string `json:"name"`
|
||||
SortOrder int `json:"sortOrder" gorm:"index"`
|
||||
}
|
||||
|
||||
func (AgentAccountModel) 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 {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
type DatabaseUser struct {
|
||||
BaseModel
|
||||
Type string `json:"type" gorm:"not null;uniqueIndex:idx_database_user"`
|
||||
Database string `json:"database" gorm:"not null;uniqueIndex:idx_database_user"`
|
||||
Username string `json:"username" gorm:"not null;uniqueIndex:idx_database_user"`
|
||||
Host string `json:"host" gorm:"uniqueIndex:idx_database_user"`
|
||||
Password string `json:"password"`
|
||||
Description string `json:"description"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
}
|
||||
|
||||
type DatabaseUserGrant struct {
|
||||
BaseModel
|
||||
Type string `json:"type" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||
Database string `json:"database" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||
DBName string `json:"dbName" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||
Username string `json:"username" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||
Host string `json:"host" gorm:"not null;uniqueIndex:idx_database_user_grant"`
|
||||
}
|
||||
@@ -18,4 +18,7 @@ type McpServer struct {
|
||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||
OutputTransport string `json:"outputTransport"`
|
||||
Type string `json:"type"`
|
||||
GatewayImage string `json:"gatewayImage"`
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
GatewayArgs string `json:"gatewayArgs"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
+242
-330
@@ -1,321 +1,181 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
ID string
|
||||
Name string
|
||||
ContextWindow int
|
||||
MaxTokens int
|
||||
Reasoning bool
|
||||
Input []string
|
||||
type APIConfig struct {
|
||||
APIType string
|
||||
BaseURL string
|
||||
EditableBaseURL bool
|
||||
DefaultAuthMode string
|
||||
AuthModes []string
|
||||
}
|
||||
|
||||
type RuntimeDefault struct {
|
||||
APIType string
|
||||
ContextWindow int
|
||||
MaxTokens int
|
||||
Input []string
|
||||
const (
|
||||
AuthModeBearer = "bearer"
|
||||
AuthModeXAPIKey = "x-api-key"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
Key string
|
||||
DisplayName string
|
||||
Sort uint
|
||||
DefaultBaseURL string
|
||||
DefaultAPIType string
|
||||
APIConfigs []APIConfig
|
||||
EnvKey string
|
||||
Default RuntimeDefault
|
||||
Models []Model
|
||||
}
|
||||
|
||||
var catalog = map[string]Meta{
|
||||
"custom": {
|
||||
Key: "custom",
|
||||
DisplayName: "Custom",
|
||||
Sort: 10,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "CUSTOM_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{},
|
||||
Key: "custom", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY",
|
||||
APIConfigs: editableAPIConfigs("openai-completions", "openai-responses", "anthropic-messages"),
|
||||
},
|
||||
"ollama": {
|
||||
Key: "ollama",
|
||||
DisplayName: "Ollama",
|
||||
Sort: 15,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-responses",
|
||||
ContextWindow: 160000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
||||
APIConfigs: editableAPIConfigs("openai-responses", "openai-completions"),
|
||||
},
|
||||
"vllm": {
|
||||
Key: "vllm",
|
||||
DisplayName: "vLLM",
|
||||
Sort: 20,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "VLLM_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{},
|
||||
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
||||
APIConfigs: editableAPIConfigs("openai-completions", "openai-responses", "anthropic-messages"),
|
||||
},
|
||||
"deepseek": {
|
||||
Key: "deepseek",
|
||||
DisplayName: "DeepSeek",
|
||||
Sort: 25,
|
||||
DefaultBaseURL: "https://api.deepseek.com/v1",
|
||||
EnvKey: "DEEPSEEK_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 131072,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "deepseek/deepseek-v4-flash", Name: "deepseek-v4-flash"},
|
||||
{ID: "deepseek/deepseek-v4-pro", Name: "deepseek-v4-pro"},
|
||||
Key: "deepseek", DisplayName: "DeepSeek", Sort: 25, DefaultAPIType: "openai-completions", EnvKey: "DEEPSEEK_API_KEY",
|
||||
APIConfigs: []APIConfig{
|
||||
{APIType: "openai-completions", BaseURL: "https://api.deepseek.com/v1"},
|
||||
anthropicAPIConfig("https://api.deepseek.com/anthropic", AuthModeXAPIKey),
|
||||
},
|
||||
Models: []Model{{ID: "deepseek-v4-flash", Name: "deepseek-v4-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}},
|
||||
},
|
||||
"bailian-coding-plan": {
|
||||
Key: "bailian-coding-plan",
|
||||
DisplayName: "阿里云百炼 Coding Plan",
|
||||
Sort: 30,
|
||||
DefaultBaseURL: "https://coding.dashscope.aliyuncs.com/v1",
|
||||
EnvKey: "QWEN_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
Key: "bailian-coding-plan", DisplayName: "阿里云百炼 Coding Plan", Sort: 30, DefaultAPIType: "openai-completions", EnvKey: "QWEN_API_KEY",
|
||||
APIConfigs: []APIConfig{
|
||||
{APIType: "openai-completions", BaseURL: "https://coding.dashscope.aliyuncs.com/v1"},
|
||||
anthropicAPIConfig("https://coding.dashscope.aliyuncs.com/apps/anthropic", AuthModeBearer),
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "bailian-coding-plan/qwen3.5-plus", Name: "Qwen3.5-Plus", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-max", Name: "Qwen3-Max", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-next", Name: "Qwen3-Coder-Next", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-plus", Name: "Qwen3-Coder-Plus", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/minimax-m2.5", Name: "MiniMax M2.5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/glm-5", Name: "GLM-5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/kimi-k2.5", Name: "Kimi-k2.5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
||||
{ID: "qwen3-coder-plus", Name: "Qwen3-Coder-Plus"},
|
||||
{ID: "qwen3-max-2026-01-23", Name: "Qwen3-Max-2026-01-23"},
|
||||
{ID: "qwen3-coder-next", Name: "Qwen3-Coder-Next"},
|
||||
{ID: "glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "qwen3.5-plus", Name: "Qwen3.5-Plus"},
|
||||
{ID: "glm-5", Name: "GLM-5"},
|
||||
{ID: "MiniMax-M2.5", Name: "MiniMax M2.5"},
|
||||
{ID: "qwen3.6-plus", Name: "Qwen3.6-Plus"},
|
||||
{ID: "qwen3.7-plus", Name: "Qwen3.7-Plus"},
|
||||
},
|
||||
},
|
||||
"ark-coding-plan": {
|
||||
Key: "ark-coding-plan",
|
||||
DisplayName: "方舟 Coding Plan",
|
||||
Sort: 35,
|
||||
DefaultBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
||||
EnvKey: "ARK_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 4096,
|
||||
Input: []string{"text"},
|
||||
Key: "ark-coding-plan", DisplayName: "方舟 Coding Plan", Sort: 35, DefaultAPIType: "openai-completions", EnvKey: "ARK_API_KEY",
|
||||
APIConfigs: []APIConfig{
|
||||
{APIType: "openai-completions", BaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3"},
|
||||
anthropicAPIConfig("https://ark.cn-beijing.volces.com/api/coding", AuthModeBearer),
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "ark-coding-plan/ark-code-latest", Name: "Ark Coding Plan", ContextWindow: 256000, MaxTokens: 4096},
|
||||
{ID: "ark-coding-plan/doubao-seed-code", Name: "Doubao Seed Code", ContextWindow: 256000, MaxTokens: 4096},
|
||||
{ID: "ark-coding-plan/glm-4.7", Name: "GLM 4.7 Coding", ContextWindow: 200000, MaxTokens: 4096},
|
||||
{ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi K2 Thinking", ContextWindow: 256000, MaxTokens: 4096},
|
||||
{ID: "ark-coding-plan/kimi-k2.5", Name: "Kimi K2.5 Coding", ContextWindow: 256000, MaxTokens: 4096},
|
||||
{ID: "ark-coding-plan/doubao-seed-code-preview-251028", Name: "Doubao Seed Code Preview", ContextWindow: 256000, MaxTokens: 4096},
|
||||
{ID: "ark-code-latest", Name: "Ark Coding Plan"}, {ID: "doubao-seed-code", Name: "Doubao Seed Code"},
|
||||
{ID: "glm-4.7", Name: "GLM 4.7 Coding"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||
{ID: "kimi-k2.5", Name: "Kimi K2.5 Coding"}, {ID: "doubao-seed-code-preview-251028", Name: "Doubao Seed Code Preview"},
|
||||
},
|
||||
},
|
||||
"zai": {
|
||||
Key: "zai",
|
||||
DisplayName: "Z.ai",
|
||||
Sort: 40,
|
||||
DefaultBaseURL: "https://open.bigmodel.cn/api/paas/v4",
|
||||
EnvKey: "ZAI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 204800,
|
||||
MaxTokens: 131072,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "zai/glm-5", Name: "GLM-5", Reasoning: true},
|
||||
{ID: "zai/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
||||
{ID: "zai/glm-4.7-flash", Name: "GLM-4.7-Flash", Reasoning: true},
|
||||
{ID: "zai/glm-4.7-flashx", Name: "GLM-4.7-FlashX", Reasoning: true},
|
||||
},
|
||||
Key: "zai", DisplayName: "Z.ai", Sort: 40, DefaultAPIType: "openai-completions", EnvKey: "ZAI_API_KEY",
|
||||
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://open.bigmodel.cn/api/paas/v4", EditableBaseURL: true}},
|
||||
Models: []Model{{ID: "glm-5", Name: "GLM-5"}, {ID: "glm-4.7", Name: "GLM-4.7"}, {ID: "glm-4.7-flash", Name: "GLM-4.7-Flash"}, {ID: "glm-4.7-flashx", Name: "GLM-4.7-FlashX"}},
|
||||
},
|
||||
"minimax": {
|
||||
Key: "minimax",
|
||||
DisplayName: "MiniMax (CN)",
|
||||
Sort: 45,
|
||||
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
||||
EnvKey: "MINIMAX_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "anthropic-messages",
|
||||
ContextWindow: 200000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{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"},
|
||||
Key: "minimax", DisplayName: "MiniMax (CN)", Sort: 45, DefaultAPIType: "anthropic-messages", EnvKey: "MINIMAX_API_KEY",
|
||||
APIConfigs: []APIConfig{
|
||||
anthropicAPIConfig("https://api.minimaxi.com/anthropic", AuthModeXAPIKey, AuthModeBearer),
|
||||
{APIType: "openai-completions", BaseURL: "https://api.minimaxi.com/v1"},
|
||||
},
|
||||
Models: []Model{{ID: "MiniMax-M3", Name: "MiniMax M3"}, {ID: "MiniMax-M2.7", Name: "MiniMax M2.7"}, {ID: "MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"}},
|
||||
},
|
||||
"xiaomi": {
|
||||
Key: "xiaomi",
|
||||
DisplayName: "Xiaomi",
|
||||
Sort: 46,
|
||||
DefaultBaseURL: "https://api.xiaomimimo.com/v1",
|
||||
EnvKey: "XIAOMI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 262144,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "xiaomi/mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash", ContextWindow: 262144, MaxTokens: 8192, Input: []string{"text"}},
|
||||
{ID: "xiaomi/mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro", ContextWindow: 1048576, MaxTokens: 32000, Reasoning: true, Input: []string{"text"}},
|
||||
{ID: "xiaomi/mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni", ContextWindow: 262144, MaxTokens: 32000, Reasoning: true, Input: []string{"text", "image"}},
|
||||
Key: "xiaomi", DisplayName: "Xiaomi", Sort: 46, DefaultAPIType: "openai-completions", EnvKey: "XIAOMI_API_KEY",
|
||||
APIConfigs: []APIConfig{
|
||||
{APIType: "openai-completions", BaseURL: "https://api.xiaomimimo.com/v1"},
|
||||
anthropicAPIConfig("https://api.xiaomimimo.com/anthropic", AuthModeBearer),
|
||||
},
|
||||
Models: []Model{{ID: "mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash"}, {ID: "mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro"}, {ID: "mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni"}},
|
||||
},
|
||||
"kimi": {
|
||||
Key: "kimi",
|
||||
DisplayName: "Kimi (CN)",
|
||||
Sort: 50,
|
||||
DefaultBaseURL: "https://api.moonshot.cn/v1",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
||||
{ID: "kimi/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
||||
},
|
||||
Key: "kimi", DisplayName: "Kimi (CN)", Sort: 50, DefaultAPIType: "openai-completions", EnvKey: "KIMI_API_KEY",
|
||||
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://api.moonshot.cn/v1"}},
|
||||
Models: []Model{{ID: "kimi-k2.5", Name: "Kimi K2.5"}, {ID: "kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"}},
|
||||
},
|
||||
"kimi-coding": {
|
||||
Key: "kimi-coding",
|
||||
DisplayName: "Kimi Coding",
|
||||
Sort: 51,
|
||||
DefaultBaseURL: "https://api.kimi.com/coding/",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "anthropic-messages",
|
||||
ContextWindow: 262144,
|
||||
MaxTokens: 32768,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "kimi-coding/kimi-code", Name: "Kimi Code", ContextWindow: 262144, MaxTokens: 32768, Reasoning: true, Input: []string{"text", "image"}},
|
||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5", ContextWindow: 262144, MaxTokens: 32768, Reasoning: true, Input: []string{"text", "image"}},
|
||||
},
|
||||
Key: "kimi-coding", DisplayName: "Kimi Coding", Sort: 51, DefaultAPIType: "anthropic-messages", EnvKey: "KIMI_API_KEY",
|
||||
APIConfigs: []APIConfig{anthropicAPIConfig("https://api.kimi.com/coding/", AuthModeXAPIKey)},
|
||||
Models: []Model{{ID: "kimi-code", Name: "Kimi Code"}, {ID: "k2p5", Name: "Kimi K2.5"}},
|
||||
},
|
||||
"openai": {
|
||||
Key: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
Sort: 55,
|
||||
DefaultBaseURL: "https://api.openai.com/v1",
|
||||
EnvKey: "OPENAI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-responses",
|
||||
ContextWindow: 272000,
|
||||
MaxTokens: 128000,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "openai/gpt-5.4", Name: "gpt-5.4", ContextWindow: 272000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
||||
{ID: "openai/gpt-5.4-pro", Name: "gpt-5.4-pro", ContextWindow: 1050000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
||||
{ID: "openai/gpt-5.4-mini", Name: "gpt-5.4-mini", ContextWindow: 400000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
||||
{ID: "openai/gpt-5.4-nano", Name: "gpt-5.4-nano", ContextWindow: 400000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}},
|
||||
Key: "openai", DisplayName: "OpenAI", Sort: 55, DefaultAPIType: "openai-responses", EnvKey: "OPENAI_API_KEY",
|
||||
APIConfigs: []APIConfig{
|
||||
{APIType: "openai-responses", BaseURL: "https://api.openai.com/v1"},
|
||||
{APIType: "openai-completions", BaseURL: "https://api.openai.com/v1"},
|
||||
},
|
||||
Models: []Model{{ID: "gpt-5.4", Name: "gpt-5.4"}, {ID: "gpt-5.4-pro", Name: "gpt-5.4-pro"}, {ID: "gpt-5.4-mini", Name: "gpt-5.4-mini"}, {ID: "gpt-5.4-nano", Name: "gpt-5.4-nano"}},
|
||||
},
|
||||
"openrouter": {
|
||||
Key: "openrouter",
|
||||
DisplayName: "OpenRouter",
|
||||
Sort: 56,
|
||||
DefaultBaseURL: "https://openrouter.ai/api/v1",
|
||||
EnvKey: "OPENROUTER_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "openrouter/free", Name: "openrouter/free"},
|
||||
{ID: "openrouter/auto", Name: "openrouter/auto"},
|
||||
},
|
||||
Key: "openrouter", DisplayName: "OpenRouter", Sort: 56, DefaultAPIType: "openai-completions", EnvKey: "OPENROUTER_API_KEY",
|
||||
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://openrouter.ai/api/v1"}},
|
||||
Models: []Model{{ID: "openrouter/free", Name: "openrouter/free"}, {ID: "openrouter/auto", Name: "openrouter/auto"}},
|
||||
},
|
||||
"anthropic": {
|
||||
Key: "anthropic",
|
||||
DisplayName: "Anthropic",
|
||||
Sort: 60,
|
||||
DefaultBaseURL: "https://api.anthropic.com",
|
||||
EnvKey: "ANTHROPIC_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "anthropic-messages",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "anthropic/claude-sonnet-4-6", Name: "Claude Sonnet 4.6", Reasoning: true},
|
||||
{ID: "anthropic/claude-opus-4-6", Name: "Claude Opus 4.6", Reasoning: true},
|
||||
{ID: "anthropic/claude-opus-4-5", Name: "Claude Opus 4.5"},
|
||||
{ID: "anthropic/claude-sonnet-4-5", Name: "Claude Sonnet 4.5"},
|
||||
{ID: "anthropic/claude-haiku-4-5", Name: "Claude Haiku 4.5"},
|
||||
},
|
||||
Key: "anthropic", DisplayName: "Anthropic", Sort: 60, DefaultAPIType: "anthropic-messages", EnvKey: "ANTHROPIC_API_KEY",
|
||||
APIConfigs: []APIConfig{anthropicAPIConfig("https://api.anthropic.com", AuthModeXAPIKey)},
|
||||
Models: []Model{{ID: "claude-sonnet-4-6", Name: "Claude Sonnet 4.6"}, {ID: "claude-opus-4-6", Name: "Claude Opus 4.6"}, {ID: "claude-opus-4-5", Name: "Claude Opus 4.5"}, {ID: "claude-sonnet-4-5", Name: "Claude Sonnet 4.5"}, {ID: "claude-haiku-4-5", Name: "Claude Haiku 4.5"}},
|
||||
},
|
||||
"gemini": {
|
||||
Key: "gemini",
|
||||
DisplayName: "Gemini",
|
||||
Sort: 65,
|
||||
DefaultBaseURL: "https://generativelanguage.googleapis.com",
|
||||
EnvKey: "GEMINI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview", Reasoning: true},
|
||||
{ID: "google/gemini-flash-latest", Name: "Gemini Flash Latest"},
|
||||
{ID: "google/gemini-3-pro-preview", Name: "Gemini 3 Pro Preview", Reasoning: true},
|
||||
},
|
||||
Key: "gemini", DisplayName: "Gemini", Sort: 65, DefaultAPIType: "gemini-generate-content", EnvKey: "GEMINI_API_KEY",
|
||||
APIConfigs: []APIConfig{{APIType: "gemini-generate-content", BaseURL: "https://generativelanguage.googleapis.com"}},
|
||||
Models: []Model{{ID: "gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"}, {ID: "gemini-flash-latest", Name: "Gemini Flash Latest"}, {ID: "gemini-3-pro-preview", Name: "Gemini 3 Pro Preview"}},
|
||||
},
|
||||
"moonshot": {
|
||||
Key: "moonshot",
|
||||
DisplayName: "Moonshot (Global)",
|
||||
Sort: 70,
|
||||
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
||||
EnvKey: "MOONSHOT_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
||||
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
||||
},
|
||||
Key: "moonshot", DisplayName: "Moonshot (Global)", Sort: 70, DefaultAPIType: "openai-completions", EnvKey: "MOONSHOT_API_KEY",
|
||||
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://api.moonshot.ai/v1"}},
|
||||
Models: []Model{{ID: "kimi-k2.5", Name: "Kimi K2.5"}, {ID: "kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"}},
|
||||
},
|
||||
}
|
||||
|
||||
func editableAPIConfigs(apiTypes ...string) []APIConfig {
|
||||
configs := make([]APIConfig, 0, len(apiTypes))
|
||||
for _, apiType := range apiTypes {
|
||||
if apiType == "anthropic-messages" {
|
||||
config := anthropicAPIConfig("", AuthModeXAPIKey, AuthModeBearer)
|
||||
config.EditableBaseURL = true
|
||||
configs = append(configs, config)
|
||||
continue
|
||||
}
|
||||
configs = append(configs, APIConfig{APIType: apiType, EditableBaseURL: true})
|
||||
}
|
||||
return configs
|
||||
}
|
||||
|
||||
func anthropicAPIConfig(baseURL, defaultAuthMode string, additionalAuthModes ...string) APIConfig {
|
||||
authModes := []string{defaultAuthMode}
|
||||
for _, authMode := range additionalAuthModes {
|
||||
if authMode != defaultAuthMode {
|
||||
authModes = append(authModes, authMode)
|
||||
}
|
||||
}
|
||||
return APIConfig{
|
||||
APIType: "anthropic-messages",
|
||||
BaseURL: baseURL,
|
||||
DefaultAuthMode: defaultAuthMode,
|
||||
AuthModes: authModes,
|
||||
}
|
||||
}
|
||||
|
||||
func Get(key string) (Meta, bool) {
|
||||
meta, ok := catalog[key]
|
||||
if !ok {
|
||||
@@ -332,12 +192,101 @@ func All() map[string]Meta {
|
||||
return result
|
||||
}
|
||||
|
||||
func DefaultBaseURL(key string) (string, bool) {
|
||||
func FindAPIConfig(key, apiType string) (APIConfig, bool) {
|
||||
meta, ok := catalog[key]
|
||||
if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" {
|
||||
if !ok {
|
||||
return APIConfig{}, false
|
||||
}
|
||||
target := strings.TrimSpace(apiType)
|
||||
if target == "" {
|
||||
target = meta.DefaultAPIType
|
||||
}
|
||||
for _, config := range meta.APIConfigs {
|
||||
if config.APIType == target {
|
||||
return config, true
|
||||
}
|
||||
}
|
||||
return APIConfig{}, false
|
||||
}
|
||||
|
||||
func ResolveAuthMode(provider, apiType, requested string) (string, error) {
|
||||
config, ok := FindAPIConfig(provider, apiType)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("provider %s does not support api type %s", provider, apiType)
|
||||
}
|
||||
if config.APIType != "anthropic-messages" {
|
||||
return "", nil
|
||||
}
|
||||
authMode := strings.TrimSpace(requested)
|
||||
if authMode == "" {
|
||||
authMode = config.DefaultAuthMode
|
||||
}
|
||||
for _, allowed := range config.AuthModes {
|
||||
if authMode == allowed {
|
||||
return authMode, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("provider %s does not support auth mode %s", provider, authMode)
|
||||
}
|
||||
|
||||
func DefaultBaseURL(key string) (string, bool) {
|
||||
config, ok := FindAPIConfig(key, "")
|
||||
if !ok || strings.TrimSpace(config.BaseURL) == "" {
|
||||
return "", false
|
||||
}
|
||||
return meta.DefaultBaseURL, true
|
||||
return config.BaseURL, true
|
||||
}
|
||||
|
||||
func DefaultAPIType(key string) string {
|
||||
meta, ok := catalog[key]
|
||||
if !ok || strings.TrimSpace(meta.DefaultAPIType) == "" {
|
||||
return "openai-completions"
|
||||
}
|
||||
return meta.DefaultAPIType
|
||||
}
|
||||
|
||||
func ResolveBaseURL(key, apiType, requested string) (string, error) {
|
||||
config, ok := FindAPIConfig(key, apiType)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("provider %s does not support api type %s", key, apiType)
|
||||
}
|
||||
if !config.EditableBaseURL {
|
||||
return strings.TrimRight(config.BaseURL, "/"), nil
|
||||
}
|
||||
baseURL := strings.TrimSpace(requested)
|
||||
if baseURL == "" {
|
||||
baseURL = config.BaseURL
|
||||
}
|
||||
if baseURL == "" {
|
||||
return "", fmt.Errorf("base url is required")
|
||||
}
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return "", fmt.Errorf("invalid base url")
|
||||
}
|
||||
parsed.Path = normalizeEndpointPath(config.APIType, parsed.Path)
|
||||
parsed.RawQuery = ""
|
||||
parsed.Fragment = ""
|
||||
return strings.TrimRight(parsed.String(), "/"), nil
|
||||
}
|
||||
|
||||
func normalizeEndpointPath(apiType, value string) string {
|
||||
path := strings.TrimRight(value, "/")
|
||||
suffixes := []string{}
|
||||
switch apiType {
|
||||
case "openai-completions":
|
||||
suffixes = []string{"/chat/completions"}
|
||||
case "openai-responses":
|
||||
suffixes = []string{"/responses"}
|
||||
case "anthropic-messages":
|
||||
suffixes = []string{"/v1/messages", "/messages"}
|
||||
}
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(strings.ToLower(path), suffix) {
|
||||
return path[:len(path)-len(suffix)]
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func EnvKey(key string) string {
|
||||
@@ -350,89 +299,52 @@ func EnvKey(key string) string {
|
||||
|
||||
func DisplayName(key string) string {
|
||||
meta, ok := catalog[key]
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
if strings.TrimSpace(meta.DisplayName) == "" {
|
||||
if !ok || strings.TrimSpace(meta.DisplayName) == "" {
|
||||
return key
|
||||
}
|
||||
return meta.DisplayName
|
||||
}
|
||||
|
||||
func FindModel(key, modelID string) (Model, bool) {
|
||||
meta, ok := Get(key)
|
||||
if !ok {
|
||||
return Model{}, false
|
||||
}
|
||||
for _, item := range meta.Models {
|
||||
if item.ID == modelID {
|
||||
return item, true
|
||||
func NormalizeModelID(provider, modelID string) string {
|
||||
target := strings.TrimLeft(strings.TrimSpace(modelID), "/")
|
||||
for _, prefix := range legacyModelPrefixes[provider] {
|
||||
legacyPrefix := prefix + "/"
|
||||
if !strings.HasPrefix(target, legacyPrefix) {
|
||||
continue
|
||||
}
|
||||
candidate := strings.TrimSpace(strings.TrimPrefix(target, legacyPrefix))
|
||||
if candidate != "" {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return Model{}, false
|
||||
return target
|
||||
}
|
||||
|
||||
var legacyModelPrefixes = map[string][]string{
|
||||
"custom": {"custom"},
|
||||
"vllm": {"custom"},
|
||||
"ollama": {"ollama"},
|
||||
"deepseek": {"deepseek"},
|
||||
"bailian-coding-plan": {"bailian-coding-plan"},
|
||||
"ark-coding-plan": {"ark-coding-plan"},
|
||||
"zai": {"zai"},
|
||||
"minimax": {"minimax"},
|
||||
"xiaomi": {"xiaomi"},
|
||||
"kimi": {"kimi", "moonshot"},
|
||||
"kimi-coding": {"kimi-coding"},
|
||||
"openai": {"openai"},
|
||||
"anthropic": {"anthropic"},
|
||||
"gemini": {"google", "gemini"},
|
||||
"moonshot": {"moonshot"},
|
||||
}
|
||||
|
||||
func cloneMeta(meta Meta) Meta {
|
||||
clone := meta
|
||||
if len(meta.Default.Input) > 0 {
|
||||
clone.Default.Input = make([]string, len(meta.Default.Input))
|
||||
copy(clone.Default.Input, meta.Default.Input)
|
||||
}
|
||||
if len(meta.Models) > 0 {
|
||||
clone.Models = make([]Model, len(meta.Models))
|
||||
for i, item := range meta.Models {
|
||||
clone.Models[i] = normalizeModel(meta, item)
|
||||
}
|
||||
clone.APIConfigs = make([]APIConfig, len(meta.APIConfigs))
|
||||
for index, config := range meta.APIConfigs {
|
||||
clone.APIConfigs[index] = config
|
||||
clone.APIConfigs[index].AuthModes = append([]string(nil), config.AuthModes...)
|
||||
}
|
||||
clone.Models = append([]Model(nil), meta.Models...)
|
||||
return clone
|
||||
}
|
||||
|
||||
func normalizeModel(meta Meta, model Model) Model {
|
||||
clone := model
|
||||
clone.ID = strings.TrimSpace(clone.ID)
|
||||
clone.Name = strings.TrimSpace(clone.Name)
|
||||
if clone.Name == "" {
|
||||
clone.Name = clone.ID
|
||||
}
|
||||
if clone.MaxTokens <= 0 {
|
||||
clone.MaxTokens = meta.Default.MaxTokens
|
||||
}
|
||||
if clone.ContextWindow <= 0 {
|
||||
clone.ContextWindow = meta.Default.ContextWindow
|
||||
}
|
||||
if len(clone.Input) == 0 && len(meta.Default.Input) > 0 {
|
||||
clone.Input = make([]string, len(meta.Default.Input))
|
||||
copy(clone.Input, meta.Default.Input)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func ResolveRuntimeParams(provider, apiType string, maxTokens, contextWindow int) (string, int, int) {
|
||||
defaultAPIType := "openai-completions"
|
||||
defaultMaxTokens := 8192
|
||||
defaultContextWindow := 256000
|
||||
if meta, ok := Get(provider); ok {
|
||||
if meta.Default.APIType != "" {
|
||||
defaultAPIType = meta.Default.APIType
|
||||
}
|
||||
if meta.Default.MaxTokens > 0 {
|
||||
defaultMaxTokens = meta.Default.MaxTokens
|
||||
}
|
||||
if meta.Default.ContextWindow > 0 {
|
||||
defaultContextWindow = meta.Default.ContextWindow
|
||||
}
|
||||
}
|
||||
resolvedAPI := apiType
|
||||
if strings.TrimSpace(apiType) == "" {
|
||||
resolvedAPI = defaultAPIType
|
||||
}
|
||||
resolvedMaxTokens := defaultMaxTokens
|
||||
resolvedContextWindow := defaultContextWindow
|
||||
if maxTokens > 0 {
|
||||
resolvedMaxTokens = maxTokens
|
||||
}
|
||||
if contextWindow > 0 {
|
||||
resolvedContextWindow = contextWindow
|
||||
}
|
||||
return resolvedAPI, resolvedMaxTokens, resolvedContextWindow
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DiscoverModels(baseURL, apiKey string) ([]string, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, buildModelDiscoveryURL(baseURL), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
resp, err := (&http.Client{Timeout: defaultVerifyTimeout}).Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("request failed: %s", resp.Status)
|
||||
}
|
||||
var payload struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models := make([]string, 0, len(payload.Data))
|
||||
seen := make(map[string]struct{}, len(payload.Data))
|
||||
for _, item := range payload.Data {
|
||||
id := strings.TrimSpace(item.ID)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
models = append(models, id)
|
||||
}
|
||||
if len(models) == 0 {
|
||||
return nil, fmt.Errorf("no models found")
|
||||
}
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func buildModelDiscoveryURL(baseURL string) string {
|
||||
base := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
for _, apiType := range []string{"openai-completions", "openai-responses", "anthropic-messages"} {
|
||||
base = normalizeEndpointPath(apiType, base)
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(base, "/v1/models"):
|
||||
return base
|
||||
case strings.HasSuffix(base, "/v1"):
|
||||
return base + "/models"
|
||||
default:
|
||||
return base + "/v1/models"
|
||||
}
|
||||
}
|
||||
@@ -15,72 +15,49 @@ type OpenClawProviderPatch struct {
|
||||
AuthHeader bool
|
||||
}
|
||||
|
||||
func BuildOpenClawProviderPatch(provider, modelName, apiType, baseURL, apiKey string) (*OpenClawProviderPatch, error) {
|
||||
func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL, apiKey string) (*OpenClawProviderPatch, error) {
|
||||
if modelName == "" {
|
||||
return nil, fmt.Errorf("model is required")
|
||||
}
|
||||
resolvedAPIType, _, _ := ResolveRuntimeParams(provider, apiType, 0, 0)
|
||||
modelID := resolveOpenClawModelID(provider, modelName)
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
return newOpenClawProviderPatch(modelName, "deepseek", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||
case "gemini":
|
||||
return newOpenClawProviderPatch("google/"+modelID, "google", modelID, apiKey, baseURL, resolvedAPIType, false), nil
|
||||
case "moonshot", "kimi":
|
||||
return buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey), nil
|
||||
case "bailian-coding-plan":
|
||||
return newOpenClawProviderPatch("bailian-coding-plan/"+modelID, "bailian-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||
case "ark-coding-plan":
|
||||
return newOpenClawProviderPatch("ark-coding-plan/"+modelID, "ark-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||
case "minimax":
|
||||
return newOpenClawProviderPatch("minimax/"+modelID, "minimax", modelID, apiKey, baseURL, "anthropic-messages", true), nil
|
||||
case "xiaomi":
|
||||
return newOpenClawProviderPatch("xiaomi/"+modelID, "xiaomi", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||
case "custom", "vllm":
|
||||
return newOpenClawProviderPatch(provider+"/"+modelID, provider, modelID, apiKey, baseURL, resolvedAPIType, false), nil
|
||||
case "ollama":
|
||||
return newOpenClawProviderPatch(modelName, "ollama", modelID, "ollama", baseURL, resolvedAPIType, false), nil
|
||||
case "kimi-coding":
|
||||
return newOpenClawProviderPatch(modelName, "kimi-coding", modelID, apiKey, baseURL, "anthropic-messages", false), nil
|
||||
case "zai":
|
||||
return newOpenClawProviderPatch("zai/"+modelID, "zai", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||
default:
|
||||
return newOpenClawProviderPatch(modelName, provider, modelID, apiKey, baseURL, resolvedAPIType, false), nil
|
||||
resolvedAPIType := apiType
|
||||
if _, ok := FindAPIConfig(provider, resolvedAPIType); !ok {
|
||||
resolvedAPIType = DefaultAPIType(provider)
|
||||
}
|
||||
}
|
||||
|
||||
func buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey string) *OpenClawProviderPatch {
|
||||
resolvedAuthMode, err := ResolveAuthMode(provider, resolvedAPIType, authMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usesBearer := resolvedAuthMode == AuthModeBearer
|
||||
modelID := NormalizeModelID(provider, modelName)
|
||||
providerKey := provider
|
||||
primaryModel := modelName
|
||||
if provider == "kimi" {
|
||||
preserveQualifiedModel := false
|
||||
switch provider {
|
||||
case "gemini":
|
||||
providerKey = "google"
|
||||
resolvedAPIType = "google-generative-ai"
|
||||
usesBearer = false
|
||||
case "moonshot", "kimi":
|
||||
providerKey = "moonshot"
|
||||
primaryModel = "moonshot/" + modelID
|
||||
resolvedAPIType = "openai-completions"
|
||||
usesBearer = false
|
||||
case "ollama":
|
||||
apiKey = "ollama"
|
||||
usesBearer = false
|
||||
case "openai", "openrouter", "anthropic":
|
||||
preserveQualifiedModel = strings.Contains(modelName, "/")
|
||||
}
|
||||
return newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, "openai-completions", false)
|
||||
}
|
||||
|
||||
func newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, apiType string, authHeader bool) *OpenClawProviderPatch {
|
||||
primaryModel := providerKey + "/" + modelID
|
||||
if preserveQualifiedModel {
|
||||
primaryModel = modelName
|
||||
}
|
||||
return &OpenClawProviderPatch{
|
||||
PrimaryModel: primaryModel,
|
||||
ProviderKey: providerKey,
|
||||
ModelID: modelID,
|
||||
APIKey: apiKey,
|
||||
BaseURL: baseURL,
|
||||
APIType: apiType,
|
||||
AuthHeader: authHeader,
|
||||
}
|
||||
}
|
||||
|
||||
func resolveOpenClawModelID(provider, modelName string) string {
|
||||
if provider == "custom" || provider == "vllm" {
|
||||
target := strings.TrimLeft(modelName, "/")
|
||||
if parts := strings.SplitN(target, "/", 2); len(parts) == 2 && parts[0] == "custom" {
|
||||
return strings.TrimLeft(parts[1], "/")
|
||||
}
|
||||
return target
|
||||
}
|
||||
if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 {
|
||||
return parts[1]
|
||||
}
|
||||
return modelName
|
||||
APIType: resolvedAPIType,
|
||||
AuthHeader: usesBearer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
+69
-119
@@ -3,7 +3,9 @@ package provider
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -18,160 +20,108 @@ type VerifyRequest struct {
|
||||
Body []byte
|
||||
}
|
||||
|
||||
const (
|
||||
defaultVerifyTimeout = 30 * time.Second
|
||||
)
|
||||
type verifyErrorResponse struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func SkipVerification(key string) bool {
|
||||
switch key {
|
||||
case "custom", "vllm", "ollama", "kimi-coding":
|
||||
const defaultVerifyTimeout = 30 * time.Second
|
||||
|
||||
func SkipVerification(provider string) bool {
|
||||
switch provider {
|
||||
case "vllm", "ollama", "kimi-coding":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func VerifyAccount(provider, baseURL, apiKey string) error {
|
||||
req := BuildVerifyRequest(provider, baseURL, apiKey)
|
||||
var body *bytes.Buffer
|
||||
if len(req.Body) > 0 {
|
||||
body = bytes.NewBuffer(req.Body)
|
||||
} else {
|
||||
body = bytes.NewBuffer(nil)
|
||||
}
|
||||
httpReq, err := http.NewRequest(req.Method, req.URL, body)
|
||||
func VerifyAccount(provider, apiType, authMode, baseURL, apiKey, model string) error {
|
||||
req := BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model)
|
||||
httpReq, err := http.NewRequest(req.Method, req.URL, bytes.NewReader(req.Body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range req.Headers {
|
||||
httpReq.Header.Set(key, value)
|
||||
}
|
||||
resp, err := (&http.Client{Timeout: verifyTimeout()}).Do(httpReq)
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
resp, err := (&http.Client{Timeout: defaultVerifyTimeout}).Do(httpReq)
|
||||
if err != nil {
|
||||
return buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 400 {
|
||||
return buserr.WithErr("ErrAgentAccountUnavailable", fmt.Errorf("verify failed: %s", resp.Status))
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
|
||||
if readErr != nil {
|
||||
return buserr.WithErr("ErrAgentAccountUnavailable", readErr)
|
||||
}
|
||||
return buserr.WithErr("ErrAgentAccountUnavailable", errors.New(verifyHTTPError(resp.StatusCode, body)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyTimeout() time.Duration {
|
||||
return defaultVerifyTimeout
|
||||
}
|
||||
func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model string) VerifyRequest {
|
||||
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
headers := map[string]string{"Content-Type": "application/json"}
|
||||
request := VerifyRequest{Method: http.MethodPost, Headers: headers}
|
||||
|
||||
func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
|
||||
base := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
headers := map[string]string{}
|
||||
request := VerifyRequest{Method: http.MethodGet, Headers: headers}
|
||||
|
||||
switch provider {
|
||||
case "anthropic", "kimi-coding":
|
||||
headers["x-api-key"] = apiKey
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/models"
|
||||
} else {
|
||||
request.URL = base + "/v1/models"
|
||||
}
|
||||
case "gemini":
|
||||
request.Method = http.MethodPost
|
||||
if strings.Contains(base, "/v1beta") {
|
||||
request.URL = base + "/models/gemini-3-flash-preview:generateContent"
|
||||
} else {
|
||||
request.URL = base + "/v1beta/models/gemini-3-flash-preview:generateContent"
|
||||
}
|
||||
if provider == "gemini" {
|
||||
request.URL = baseURL + "/v1beta/models/" + strings.TrimSpace(model) + ":generateContent"
|
||||
headers["x-goog-api-key"] = apiKey
|
||||
headers["Content-Type"] = "application/json"
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"contents": []map[string]interface{}{{
|
||||
"parts": []map[string]string{{
|
||||
"text": "Explain how AI works in a few words",
|
||||
}},
|
||||
}},
|
||||
"contents": []map[string]interface{}{{"parts": []map[string]string{{"text": "test"}}}},
|
||||
})
|
||||
case "zai":
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
request.URL = base + "/models"
|
||||
case "bailian-coding-plan":
|
||||
request.Method = http.MethodPost
|
||||
if !strings.Contains(base, "/v1") {
|
||||
base = base + "/v1"
|
||||
return request
|
||||
}
|
||||
|
||||
switch apiType {
|
||||
case "anthropic-messages":
|
||||
request.URL = baseURL + "/v1/messages"
|
||||
if authMode == AuthModeBearer {
|
||||
headers["Authorization"] = "Bearer " + apiKey
|
||||
} else {
|
||||
headers["x-api-key"] = apiKey
|
||||
}
|
||||
request.URL = base + "/chat/completions"
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
headers["Content-Type"] = "application/json"
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": "qwen3.5-plus",
|
||||
"messages": []map[string]string{{"role": "user", "content": "test"}},
|
||||
"max_tokens": 1,
|
||||
})
|
||||
case "ark-coding-plan":
|
||||
request.Method = http.MethodPost
|
||||
if !strings.Contains(base, "/api/coding/v3") {
|
||||
base = "https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
}
|
||||
request.URL = base + "/chat/completions"
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
headers["Content-Type"] = "application/json"
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": "ark-code-latest",
|
||||
"messages": []map[string]string{{"role": "user", "content": "test"}},
|
||||
"max_tokens": 1,
|
||||
})
|
||||
case "minimax":
|
||||
request.Method = http.MethodPost
|
||||
headers["x-api-key"] = apiKey
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
headers["Content-Type"] = "application/json"
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/messages"
|
||||
} else {
|
||||
request.URL = base + "/v1/messages"
|
||||
}
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": "MiniMax-M2.5",
|
||||
"max_tokens": 1,
|
||||
"messages": []map[string]interface{}{{
|
||||
"role": "user",
|
||||
"content": []map[string]string{{
|
||||
"type": "text",
|
||||
"text": "test",
|
||||
}},
|
||||
}},
|
||||
"model": model, "max_tokens": 1, "stream": false,
|
||||
"messages": []map[string]interface{}{{"role": "user", "content": []map[string]string{{"type": "text", "text": "test"}}}},
|
||||
})
|
||||
case "xiaomi":
|
||||
request.Method = http.MethodPost
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
headers["Content-Type"] = "application/json"
|
||||
if !strings.Contains(base, "/v1") {
|
||||
base = base + "/v1"
|
||||
}
|
||||
request.URL = base + "/chat/completions"
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": "mimo-v2-flash",
|
||||
"max_tokens": 1,
|
||||
"messages": []map[string]string{{"role": "user", "content": "test"}},
|
||||
})
|
||||
case "openrouter":
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/key"
|
||||
} else {
|
||||
request.URL = base + "/v1/key"
|
||||
}
|
||||
case "openai-responses":
|
||||
request.URL = baseURL + "/responses"
|
||||
headers["Authorization"] = "Bearer " + apiKey
|
||||
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "test", "max_output_tokens": 1, "stream": false})
|
||||
default:
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/models"
|
||||
} else {
|
||||
request.URL = base + "/v1/models"
|
||||
request.URL = baseURL + "/chat/completions"
|
||||
if provider != "ollama" || strings.TrimSpace(apiKey) != "" {
|
||||
headers["Authorization"] = "Bearer " + apiKey
|
||||
}
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": 1, "stream": false,
|
||||
})
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
func verifyHTTPError(statusCode int, body []byte) string {
|
||||
message := strings.TrimSpace(string(body))
|
||||
var payload verifyErrorResponse
|
||||
if err := json.Unmarshal(body, &payload); err == nil {
|
||||
if value := strings.TrimSpace(payload.Error.Message); value != "" {
|
||||
message = value
|
||||
} else if value := strings.TrimSpace(payload.Message); value != "" {
|
||||
message = value
|
||||
}
|
||||
}
|
||||
if message == "" {
|
||||
return fmt.Sprintf("validation request returned status %d", statusCode)
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func mustJSON(value interface{}) []byte {
|
||||
payload, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type IAppInstallResourceRpo interface {
|
||||
WithAppInstallId(appInstallId uint) DBOption
|
||||
WithLinkId(linkId uint) DBOption
|
||||
WithResourceId(resourceId uint) DBOption
|
||||
WithResourceIds(resourceIds []uint) DBOption
|
||||
GetBy(opts ...DBOption) ([]model.AppInstallResource, error)
|
||||
GetFirst(opts ...DBOption) (model.AppInstallResource, error)
|
||||
Create(ctx context.Context, resource *model.AppInstallResource) error
|
||||
@@ -44,6 +45,12 @@ func (a AppInstallResourceRpo) WithResourceId(resourceId uint) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppInstallResourceRpo) WithResourceIds(resourceIds []uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("resource_id IN ?", resourceIds)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppInstallResourceRpo) GetBy(opts ...DBOption) ([]model.AppInstallResource, error) {
|
||||
db := global.DB.Model(&model.AppInstallResource{})
|
||||
var resources []model.AppInstallResource
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DatabaseUserRepo struct{}
|
||||
|
||||
type IDatabaseUserRepo interface {
|
||||
Get(opts ...DBOption) (model.DatabaseUser, error)
|
||||
List(opts ...DBOption) ([]model.DatabaseUser, error)
|
||||
Save(user *model.DatabaseUser) error
|
||||
Delete(opts ...DBOption) error
|
||||
DeleteBy(ctx context.Context, opts ...DBOption) error
|
||||
Update(vars map[string]interface{}, opts ...DBOption) error
|
||||
WithByDatabase(database string) DBOption
|
||||
WithByUser(username, host string) DBOption
|
||||
WithByUserList(users [][2]string) DBOption
|
||||
}
|
||||
|
||||
func NewIDatabaseUserRepo() IDatabaseUserRepo {
|
||||
return &DatabaseUserRepo{}
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) Get(opts ...DBOption) (model.DatabaseUser, error) {
|
||||
var user model.DatabaseUser
|
||||
db := global.DB.Model(&model.DatabaseUser{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
if err := db.First(&user).Error; err != nil {
|
||||
return user, err
|
||||
}
|
||||
password, err := encrypt.StringDecrypt(user.Password)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("decrypt database user %s password failed, err: %v", user.Username, err)
|
||||
}
|
||||
user.Password = password
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) List(opts ...DBOption) ([]model.DatabaseUser, error) {
|
||||
var users []model.DatabaseUser
|
||||
db := global.DB.Model(&model.DatabaseUser{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
if err := db.Find(&users).Error; err != nil {
|
||||
return users, err
|
||||
}
|
||||
for i := 0; i < len(users); i++ {
|
||||
password, err := encrypt.StringDecrypt(users[i].Password)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("decrypt database user %s password failed, err: %v", users[i].Username, err)
|
||||
}
|
||||
users[i].Password = password
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) Save(user *model.DatabaseUser) error {
|
||||
if len(user.Password) != 0 {
|
||||
password, err := encrypt.StringEncrypt(user.Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt database user %s password failed, err: %v", user.Username, err)
|
||||
}
|
||||
user.Password = password
|
||||
}
|
||||
return global.DB.Save(user).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.DatabaseUser{}).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
|
||||
return getTx(ctx, opts...).Delete(&model.DatabaseUser{}).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) Update(vars map[string]interface{}, opts ...DBOption) error {
|
||||
db := global.DB.Model(&model.DatabaseUser{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Updates(vars).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) WithByDatabase(database string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("database = ?", database)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) WithByUser(username, host string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("username = ? AND host = ?", username, host)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DatabaseUserRepo) WithByUserList(users [][2]string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(users) == 0 {
|
||||
return g.Where("1 = 0")
|
||||
}
|
||||
values := make([][]interface{}, 0, len(users))
|
||||
for _, user := range users {
|
||||
values = append(values, []interface{}{user[0], user[1]})
|
||||
}
|
||||
return g.Where("(username, host) IN ?", values)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DatabaseUserGrantRepo struct{}
|
||||
|
||||
type IDatabaseUserGrantRepo interface {
|
||||
Get(opts ...DBOption) (model.DatabaseUserGrant, error)
|
||||
List(opts ...DBOption) ([]model.DatabaseUserGrant, error)
|
||||
Save(grant *model.DatabaseUserGrant) error
|
||||
Replace(dbType, database string, grants []model.DatabaseUserGrant) error
|
||||
Delete(opts ...DBOption) error
|
||||
DeleteBy(ctx context.Context, opts ...DBOption) error
|
||||
Update(vars map[string]interface{}, opts ...DBOption) error
|
||||
WithByDatabase(database string) DBOption
|
||||
WithByDBName(dbName string) DBOption
|
||||
WithByDBNames(dbNames []string) DBOption
|
||||
WithByUser(username, host string) DBOption
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) Get(opts ...DBOption) (model.DatabaseUserGrant, error) {
|
||||
var grant model.DatabaseUserGrant
|
||||
db := global.DB.Model(&model.DatabaseUserGrant{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&grant).Error
|
||||
return grant, err
|
||||
}
|
||||
|
||||
func NewIDatabaseUserGrantRepo() IDatabaseUserGrantRepo {
|
||||
return &DatabaseUserGrantRepo{}
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) List(opts ...DBOption) ([]model.DatabaseUserGrant, error) {
|
||||
var grants []model.DatabaseUserGrant
|
||||
db := global.DB.Model(&model.DatabaseUserGrant{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&grants).Error
|
||||
return grants, err
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) Save(grant *model.DatabaseUserGrant) error {
|
||||
return global.DB.Save(grant).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) Replace(dbType, database string, grants []model.DatabaseUserGrant) error {
|
||||
return global.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("`type` = ? AND database = ?", dbType, database).Delete(&model.DatabaseUserGrant{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(grants) != 0 {
|
||||
return tx.Create(&grants).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.DatabaseUserGrant{}).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
|
||||
return getTx(ctx, opts...).Delete(&model.DatabaseUserGrant{}).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) Update(vars map[string]interface{}, opts ...DBOption) error {
|
||||
db := global.DB.Model(&model.DatabaseUserGrant{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Updates(vars).Error
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) WithByDatabase(database string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("database = ?", database)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) WithByDBName(dbName string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("db_name = ?", dbName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) WithByDBNames(dbNames []string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("db_name IN ?", dbNames)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DatabaseUserGrantRepo) WithByUser(username, host string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("username = ? AND host = ?", username, host)
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,12 @@ 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
|
||||
WithDBType(dbType string) DBOption
|
||||
WithDBTypes(dbTypes []string) DBOption
|
||||
WithDBID(dbID uint) DBOption
|
||||
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
||||
@@ -76,6 +78,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)
|
||||
@@ -118,6 +133,12 @@ func (w *WebsiteRepo) WithDBType(dbType string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithDBTypes(dbTypes []string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("db_type IN ?", dbTypes)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithDBID(dbID uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("db_id = ?", dbID)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+646
-106
File diff suppressed because it is too large
Load Diff
@@ -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"`
|
||||
@@ -71,7 +76,7 @@ func writeHermesConfig(confDir string, account *model.AgentAccount, modelName st
|
||||
}
|
||||
}
|
||||
|
||||
provider := resolveHermesProvider(account.Provider)
|
||||
provider := resolveHermesProvider(account.Provider, account.APIType)
|
||||
configPath := path.Join(confDir, "config.yaml")
|
||||
cfg, err := readHermesConfigMap(configPath)
|
||||
if err != nil {
|
||||
@@ -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 {
|
||||
@@ -407,11 +458,18 @@ func normalizeHermesTimezone(timezone string) string {
|
||||
return timezone
|
||||
}
|
||||
|
||||
func resolveHermesProvider(provider string) string {
|
||||
func resolveHermesProvider(provider, apiType string) string {
|
||||
if apiType == "anthropic-messages" && (provider == "deepseek" || provider == "bailian-coding-plan" || provider == "ark-coding-plan" || provider == "xiaomi") {
|
||||
return "anthropic"
|
||||
}
|
||||
switch provider {
|
||||
case "":
|
||||
return "custom"
|
||||
case "openrouter", "anthropic", "gemini", "zai", "kimi-coding", "xiaomi":
|
||||
case "moonshot":
|
||||
return "kimi-coding"
|
||||
case "kimi":
|
||||
return "kimi-coding-cn"
|
||||
case "openrouter", "anthropic", "deepseek", "gemini", "zai", "kimi-coding", "xiaomi":
|
||||
return provider
|
||||
case "minimax":
|
||||
return "minimax-cn"
|
||||
@@ -428,17 +486,7 @@ func resolveHermesModel(sourceProvider, targetProvider, modelName string) string
|
||||
if targetProvider != "custom" {
|
||||
return target
|
||||
}
|
||||
if sourceProvider == "custom" || sourceProvider == "vllm" {
|
||||
return normalizeCustomModel(target)
|
||||
}
|
||||
if strings.Contains(target, "/") {
|
||||
parts := strings.SplitN(target, "/", 2)
|
||||
model := strings.TrimSpace(parts[1])
|
||||
if model != "" {
|
||||
return model
|
||||
}
|
||||
}
|
||||
return target
|
||||
return providercatalog.NormalizeModelID(sourceProvider, target)
|
||||
}
|
||||
|
||||
func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) {
|
||||
@@ -449,7 +497,7 @@ func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels [
|
||||
if configuredModel == "" {
|
||||
return "", buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
provider := resolveHermesProvider(account.Provider)
|
||||
provider := resolveHermesProvider(account.Provider, account.APIType)
|
||||
for _, item := range accountModels {
|
||||
if resolveHermesModel(account.Provider, provider, item.ID) == configuredModel {
|
||||
return item.ID, nil
|
||||
@@ -462,7 +510,8 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if resolveHermesProvider(account.Provider) == "custom" {
|
||||
resolvedProvider := resolveHermesProvider(account.Provider, account.APIType)
|
||||
if resolvedProvider == "custom" {
|
||||
if account.APIKey == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -480,6 +529,11 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
||||
}
|
||||
entries = append(entries, hermesEnvEntry{Key: key, Value: value})
|
||||
}
|
||||
if resolvedProvider == "anthropic" {
|
||||
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
||||
appendEntry("ANTHROPIC_BASE_URL", baseURL)
|
||||
return entries
|
||||
}
|
||||
|
||||
switch account.Provider {
|
||||
case "openrouter":
|
||||
@@ -487,6 +541,7 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
||||
appendEntry("OPENROUTER_BASE_URL", baseURL)
|
||||
case "anthropic":
|
||||
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
||||
appendEntry("ANTHROPIC_BASE_URL", baseURL)
|
||||
case "gemini":
|
||||
appendEntry("GOOGLE_API_KEY", apiKey)
|
||||
appendEntry("GEMINI_API_KEY", apiKey)
|
||||
@@ -532,7 +587,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 +603,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 {
|
||||
@@ -556,6 +611,7 @@ func hermesManagedModelEnvKeys() []string {
|
||||
"OPENROUTER_API_KEY",
|
||||
"OPENROUTER_BASE_URL",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_BASE_URL",
|
||||
"GOOGLE_API_KEY",
|
||||
"GEMINI_API_KEY",
|
||||
"GEMINI_BASE_URL",
|
||||
@@ -598,46 +654,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
|
||||
}
|
||||
|
||||
+183
-274
@@ -4,10 +4,12 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -19,10 +21,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"
|
||||
)
|
||||
|
||||
@@ -36,6 +39,39 @@ type resolvedAgentAccountInput struct {
|
||||
Provider string
|
||||
APIKey string
|
||||
BaseURL string
|
||||
APIType string
|
||||
AuthMode string
|
||||
}
|
||||
|
||||
func ensureAgentAccountNameAvailable(provider, name string, excludeID uint) error {
|
||||
opts := []repo.DBOption{repo.WithByProvider(provider), repo.WithByName(name)}
|
||||
if excludeID > 0 {
|
||||
opts = append(opts, repo.WithByNOTID(excludeID))
|
||||
}
|
||||
account, err := agentAccountRepo.GetFirst(opts...)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if account != nil && account.ID > 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func agentAccountUsedBySetting(tx *gorm.DB, accountID uint, statusKey, accountIDKey string) (bool, error) {
|
||||
var settings []model.Setting
|
||||
if err := tx.Where("key IN ?", []string{statusKey, accountIDKey}).Find(&settings).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
values := make(map[string]string, len(settings))
|
||||
for _, setting := range settings {
|
||||
values[setting.Key] = setting.Value
|
||||
}
|
||||
return strings.EqualFold(strings.TrimSpace(values[statusKey]), constant.StatusEnable) &&
|
||||
strings.TrimSpace(values[accountIDKey]) == strconv.FormatUint(uint64(accountID), 10), nil
|
||||
}
|
||||
|
||||
func loadOpenclawAgentByID(agentID uint) (*model.Agent, error) {
|
||||
@@ -60,24 +96,26 @@ 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) {
|
||||
func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, modelID string) (resolvedAgentAccountInput, error) {
|
||||
resolvedAPIKey := strings.TrimSpace(apiKey)
|
||||
resolvedBaseURL := strings.TrimSpace(baseURL)
|
||||
if resolvedBaseURL == "" {
|
||||
if requiresInitialAgentAccountModels(provider) {
|
||||
resolvedAPIType := strings.TrimSpace(apiType)
|
||||
resolvedAuthMode, err := providercatalog.ResolveAuthMode(provider, resolvedAPIType, authMode)
|
||||
if err != nil {
|
||||
return resolvedAgentAccountInput{}, err
|
||||
}
|
||||
resolvedBaseURL, err := providercatalog.ResolveBaseURL(provider, resolvedAPIType, baseURL)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "base url is required") {
|
||||
return resolvedAgentAccountInput{}, buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
if defaultURL, ok := providercatalog.DefaultBaseURL(provider); ok {
|
||||
resolvedBaseURL = defaultURL
|
||||
}
|
||||
return resolvedAgentAccountInput{}, err
|
||||
}
|
||||
modelID = strings.TrimSpace(modelID)
|
||||
if modelID == "" {
|
||||
return resolvedAgentAccountInput{}, buserr.New("ErrAgentAccountModelsRequired")
|
||||
}
|
||||
if !providercatalog.SkipVerification(provider) {
|
||||
if err := providercatalog.VerifyAccount(provider, resolvedBaseURL, resolvedAPIKey); err != nil {
|
||||
if err := providercatalog.VerifyAccount(provider, resolvedAPIType, resolvedAuthMode, resolvedBaseURL, resolvedAPIKey, modelID); err != nil {
|
||||
return resolvedAgentAccountInput{}, err
|
||||
}
|
||||
}
|
||||
@@ -85,6 +123,8 @@ func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAc
|
||||
Provider: provider,
|
||||
APIKey: resolvedAPIKey,
|
||||
BaseURL: resolvedBaseURL,
|
||||
APIType: resolvedAPIType,
|
||||
AuthMode: resolvedAuthMode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -341,26 +381,24 @@ func setOtherConfig(conf map[string]interface{}, config dto.AgentOtherConfig) {
|
||||
func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map[string]interface{}) dto.AgentItem {
|
||||
agentType := agent.AgentType
|
||||
item := dto.AgentItem{
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
Remark: agent.Remark,
|
||||
AgentType: agentType,
|
||||
Provider: agent.Provider,
|
||||
ProviderName: providercatalog.DisplayName(agent.Provider),
|
||||
Model: agent.Model,
|
||||
APIType: agent.APIType,
|
||||
MaxTokens: agent.MaxTokens,
|
||||
ContextWindow: agent.ContextWindow,
|
||||
BaseURL: agent.BaseURL,
|
||||
APIKey: maskKey(agent.APIKey),
|
||||
Token: agent.Token,
|
||||
Status: agent.Status,
|
||||
Message: agent.Message,
|
||||
AppInstallID: agent.AppInstallID,
|
||||
WebsiteID: agent.WebsiteID,
|
||||
AccountID: agent.AccountID,
|
||||
ConfigPath: agent.ConfigPath,
|
||||
CreatedAt: agent.CreatedAt,
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
Remark: agent.Remark,
|
||||
AgentType: agentType,
|
||||
Provider: agent.Provider,
|
||||
ProviderName: providercatalog.DisplayName(agent.Provider),
|
||||
Model: agent.Model,
|
||||
APIType: agent.APIType,
|
||||
BaseURL: agent.BaseURL,
|
||||
APIKey: maskKey(agent.APIKey),
|
||||
Token: agent.Token,
|
||||
Status: agent.Status,
|
||||
Message: agent.Message,
|
||||
AppInstallID: agent.AppInstallID,
|
||||
WebsiteID: agent.WebsiteID,
|
||||
AccountID: agent.AccountID,
|
||||
ConfigPath: agent.ConfigPath,
|
||||
CreatedAt: agent.CreatedAt,
|
||||
}
|
||||
if appInstall != nil && appInstall.ID > 0 {
|
||||
item.Container = appInstall.ContainerName
|
||||
@@ -382,6 +420,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 +627,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
|
||||
@@ -680,13 +726,9 @@ type modelProvider struct {
|
||||
}
|
||||
|
||||
type modelEntry struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Input []string `json:"input"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Cost modelCost `json:"cost"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Input []string `json:"input,omitempty"`
|
||||
}
|
||||
|
||||
func requiresOpenclawProviderModels(provider string) bool {
|
||||
@@ -706,13 +748,6 @@ func applyOpenclawModelsConfig(conf map[string]interface{}, models *modelsConfig
|
||||
return nil
|
||||
}
|
||||
|
||||
type modelCost struct {
|
||||
Input float64 `json:"input"`
|
||||
Output float64 `json:"output"`
|
||||
CacheRead float64 `json:"cacheRead"`
|
||||
CacheWrite float64 `json:"cacheWrite"`
|
||||
}
|
||||
|
||||
type browserConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ExecutablePath string `json:"executablePath"`
|
||||
@@ -863,13 +898,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) {
|
||||
@@ -1056,7 +1093,7 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s
|
||||
}
|
||||
|
||||
func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) {
|
||||
providerPatch, err := providercatalog.BuildOpenClawProviderPatch(account.Provider, model.ID, account.APIType, account.BaseURL, account.APIKey)
|
||||
providerPatch, err := providercatalog.BuildOpenClawProviderPatch(account.Provider, model.ID, account.APIType, account.AuthMode, account.BaseURL, account.APIKey)
|
||||
if err != nil {
|
||||
return "", modelEntry{}, "", modelProvider{}, err
|
||||
}
|
||||
@@ -1068,28 +1105,24 @@ func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.Agen
|
||||
}, nil
|
||||
}
|
||||
|
||||
var openclawVisionModelPattern = regexp.MustCompile(`(?i)(\b(gpt-4o|gpt-4\.1|gpt-[5-9]|o[134])\b|\bclaude-(3|4|sonnet|opus|haiku)\b|\bgemini\b|\b(qwen[\w.-]*-?vl|qwen-vl|qwen3\.[5-9]-plus)\b|\b(kimi-k2\.(5|6)|kimi-k2\.7-code|minimax-m3)\b|\b(vision|llava|pixtral|internvl|mllama|minicpm-v|glm-4v|omni)\b|(^|[-_/])vl([-_/]|$))`)
|
||||
|
||||
func buildOpenclawModelEntry(modelID string, model dto.AgentAccountModel) modelEntry {
|
||||
name := strings.TrimSpace(model.Name)
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(modelID)
|
||||
}
|
||||
return modelEntry{
|
||||
ID: strings.TrimSpace(modelID),
|
||||
Name: name,
|
||||
Reasoning: model.Reasoning,
|
||||
Input: sanitizeAgentAccountModelInputs(model.Input),
|
||||
ContextWindow: model.ContextWindow,
|
||||
MaxTokens: model.MaxTokens,
|
||||
Cost: modelCost{},
|
||||
entry := modelEntry{ID: strings.TrimSpace(modelID), Name: name}
|
||||
if openclawVisionModelPattern.MatchString(modelID) {
|
||||
entry.Input = []string{"text", "image"}
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
type openclawAccountModelRuntime struct {
|
||||
StoredModel string
|
||||
PrimaryModel string
|
||||
APIType string
|
||||
MaxTokens int
|
||||
ContextWindow int
|
||||
StoredModel string
|
||||
PrimaryModel string
|
||||
APIType string
|
||||
}
|
||||
|
||||
func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.AgentAccountModel) (openclawAccountModelRuntime, error) {
|
||||
@@ -1098,11 +1131,9 @@ func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.Age
|
||||
return openclawAccountModelRuntime{}, err
|
||||
}
|
||||
return openclawAccountModelRuntime{
|
||||
StoredModel: model.ID,
|
||||
PrimaryModel: primaryModel,
|
||||
APIType: account.APIType,
|
||||
MaxTokens: model.MaxTokens,
|
||||
ContextWindow: model.ContextWindow,
|
||||
StoredModel: model.ID,
|
||||
PrimaryModel: primaryModel,
|
||||
APIType: account.APIType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1122,15 +1153,11 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto
|
||||
if account == nil {
|
||||
return nil, fmt.Errorf("account is required")
|
||||
}
|
||||
if requiresInitialAgentAccountModels(account.Provider) && len(requested) > 1 {
|
||||
if account.Provider != "custom" && requiresInitialAgentAccountModels(account.Provider) && len(requested) > 1 {
|
||||
return nil, buserr.New("ErrAgentAccountSingleInitialModel")
|
||||
}
|
||||
if len(requested) > 0 {
|
||||
models := make([]dto.AgentAccountModel, 0, len(requested))
|
||||
for _, item := range requested {
|
||||
models = append(models, cloneAgentAccountModel(item))
|
||||
}
|
||||
return models, nil
|
||||
return normalizeAgentAccountModels(account, requested)
|
||||
}
|
||||
meta, ok := providercatalog.Get(account.Provider)
|
||||
if !ok || len(meta.Models) == 0 {
|
||||
@@ -1142,15 +1169,22 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto
|
||||
requested = make([]dto.AgentAccountModel, 0, len(meta.Models))
|
||||
for _, item := range meta.Models {
|
||||
requested = append(requested, dto.AgentAccountModel{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
ContextWindow: item.ContextWindow,
|
||||
MaxTokens: item.MaxTokens,
|
||||
Reasoning: item.Reasoning,
|
||||
Input: append([]string(nil), item.Input...),
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
})
|
||||
}
|
||||
return requested, nil
|
||||
return normalizeAgentAccountModels(account, requested)
|
||||
}
|
||||
|
||||
func buildDiscoveredAgentAccountModels(modelIDs []string) []dto.AgentAccountModel {
|
||||
models := make([]dto.AgentAccountModel, 0, len(modelIDs))
|
||||
for _, modelID := range modelIDs {
|
||||
models = append(models, dto.AgentAccountModel{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
})
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
||||
func compactPersistedAgentAccountModelSortOrder(accountID uint) error {
|
||||
@@ -1178,18 +1212,6 @@ func loadAgentAccountModels(account *model.AgentAccount) ([]dto.AgentAccountMode
|
||||
return listPersistedAgentAccountModels(account.ID)
|
||||
}
|
||||
|
||||
func cloneAgentAccountModel(model dto.AgentAccountModel) dto.AgentAccountModel {
|
||||
return dto.AgentAccountModel{
|
||||
RecordID: model.RecordID,
|
||||
ID: model.ID,
|
||||
Name: model.Name,
|
||||
ContextWindow: model.ContextWindow,
|
||||
MaxTokens: model.MaxTokens,
|
||||
Reasoning: model.Reasoning,
|
||||
Input: append([]string(nil), model.Input...),
|
||||
}
|
||||
}
|
||||
|
||||
func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, existing []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) {
|
||||
if account == nil {
|
||||
return nil, fmt.Errorf("account is required")
|
||||
@@ -1213,12 +1235,8 @@ func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, exi
|
||||
continue
|
||||
}
|
||||
requested = append(requested, dto.AgentAccountModel{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
ContextWindow: item.ContextWindow,
|
||||
MaxTokens: item.MaxTokens,
|
||||
Reasoning: item.Reasoning,
|
||||
Input: append([]string(nil), item.Input...),
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
})
|
||||
}
|
||||
if len(requested) == len(existing) {
|
||||
@@ -1237,18 +1255,10 @@ func listPersistedAgentAccountModels(accountID uint) ([]dto.AgentAccountModel, e
|
||||
}
|
||||
result := make([]dto.AgentAccountModel, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
inputs := []string{}
|
||||
if strings.TrimSpace(row.Input) != "" {
|
||||
_ = json.Unmarshal([]byte(row.Input), &inputs)
|
||||
}
|
||||
result = append(result, dto.AgentAccountModel{
|
||||
RecordID: row.ID,
|
||||
ID: strings.TrimSpace(row.Model),
|
||||
Name: strings.TrimSpace(row.Name),
|
||||
ContextWindow: row.ContextWindow,
|
||||
MaxTokens: row.MaxTokens,
|
||||
Reasoning: row.Reasoning,
|
||||
Input: sanitizeAgentAccountModelInputs(inputs),
|
||||
RecordID: row.ID,
|
||||
ID: strings.TrimSpace(row.Model),
|
||||
Name: strings.TrimSpace(row.Name),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
@@ -1259,19 +1269,11 @@ func replacePersistedAgentAccountModelsWithTx(tx *gorm.DB, accountID uint, model
|
||||
return err
|
||||
}
|
||||
for index, item := range models {
|
||||
inputPayload, err := json.Marshal(sanitizeAgentAccountModelInputs(item.Input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record := &model.AgentAccountModel{
|
||||
AccountID: accountID,
|
||||
Model: strings.TrimSpace(item.ID),
|
||||
Name: strings.TrimSpace(item.Name),
|
||||
ContextWindow: item.ContextWindow,
|
||||
MaxTokens: item.MaxTokens,
|
||||
Reasoning: item.Reasoning,
|
||||
Input: string(inputPayload),
|
||||
SortOrder: index + 1,
|
||||
AccountID: accountID,
|
||||
Model: strings.TrimSpace(item.ID),
|
||||
Name: strings.TrimSpace(item.Name),
|
||||
SortOrder: index + 1,
|
||||
}
|
||||
if err := tx.Create(record).Error; err != nil {
|
||||
return err
|
||||
@@ -1312,86 +1314,17 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco
|
||||
if modelID == "" {
|
||||
return dto.AgentAccountModel{}, fmt.Errorf("model is required")
|
||||
}
|
||||
modelID = providercatalog.NormalizeModelID(account.Provider, modelID)
|
||||
name := strings.TrimSpace(model.Name)
|
||||
if name == "" {
|
||||
name = modelID
|
||||
}
|
||||
inputs := sanitizeAgentAccountModelInputs(model.Input)
|
||||
return dto.AgentAccountModel{
|
||||
ID: normalizeAgentAccountModelID(account.Provider, modelID),
|
||||
Name: name,
|
||||
ContextWindow: model.ContextWindow,
|
||||
MaxTokens: model.MaxTokens,
|
||||
Reasoning: model.Reasoning,
|
||||
Input: inputs,
|
||||
ID: modelID,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeAgentAccountModelID(provider, requestedID string) string {
|
||||
switch provider {
|
||||
case "custom", "vllm":
|
||||
return normalizeCustomModel(requestedID)
|
||||
case "ollama":
|
||||
target := strings.TrimSpace(requestedID)
|
||||
if strings.HasPrefix(target, "ollama/") {
|
||||
return target
|
||||
}
|
||||
target = strings.TrimLeft(strings.TrimSpace(target), "/")
|
||||
if target == "" {
|
||||
return ""
|
||||
}
|
||||
return "ollama/" + target
|
||||
default:
|
||||
target := strings.TrimSpace(requestedID)
|
||||
if target == "" {
|
||||
return ""
|
||||
}
|
||||
prefix := poolModelPrefix(provider)
|
||||
if strings.Contains(target, "/") {
|
||||
parts := strings.SplitN(target, "/", 2)
|
||||
targetPrefix := parts[0]
|
||||
targetModel := strings.TrimSpace(parts[1])
|
||||
if targetModel == "" {
|
||||
return strings.TrimSpace(target)
|
||||
}
|
||||
for _, item := range supportedProviderModelPrefixes(provider) {
|
||||
if item == targetPrefix {
|
||||
if prefix != "" {
|
||||
return prefix + "/" + targetModel
|
||||
}
|
||||
return strings.TrimSpace(target)
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(target)
|
||||
}
|
||||
target = strings.TrimLeft(strings.TrimSpace(target), "/")
|
||||
if prefix == "" {
|
||||
return target
|
||||
}
|
||||
return prefix + "/" + target
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeAgentAccountModelInputs(values []string) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
for _, value := range values {
|
||||
normalized := value
|
||||
if normalized != "text" && normalized != "image" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[normalized]; ok {
|
||||
continue
|
||||
}
|
||||
seen[normalized] = struct{}{}
|
||||
result = append(result, normalized)
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return []string{"text"}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func requiresInitialAgentAccountModels(provider string) bool {
|
||||
switch provider {
|
||||
case "custom", "vllm", "ollama":
|
||||
@@ -1401,36 +1334,14 @@ func requiresInitialAgentAccountModels(provider string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeComparableProviderModelID(provider, modelID string) string {
|
||||
target := strings.TrimSpace(modelID)
|
||||
if target == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.Contains(target, "/") {
|
||||
return target
|
||||
}
|
||||
parts := strings.SplitN(target, "/", 2)
|
||||
prefix := parts[0]
|
||||
model := strings.TrimSpace(parts[1])
|
||||
if model == "" {
|
||||
return target
|
||||
}
|
||||
for _, item := range supportedProviderModelPrefixes(provider) {
|
||||
if item == prefix {
|
||||
return model
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func sameProviderModelID(provider, left, right string) bool {
|
||||
leftTrimmed := strings.TrimSpace(left)
|
||||
rightTrimmed := strings.TrimSpace(right)
|
||||
if leftTrimmed == rightTrimmed {
|
||||
return true
|
||||
}
|
||||
leftComparable := normalizeComparableProviderModelID(provider, leftTrimmed)
|
||||
rightComparable := normalizeComparableProviderModelID(provider, rightTrimmed)
|
||||
leftComparable := providercatalog.NormalizeModelID(provider, leftTrimmed)
|
||||
rightComparable := providercatalog.NormalizeModelID(provider, rightTrimmed)
|
||||
return leftComparable != "" && leftComparable == rightComparable
|
||||
}
|
||||
|
||||
@@ -1451,6 +1362,20 @@ func requireAgentAccountModelForProvider(provider string, models []dto.AgentAcco
|
||||
return selectedAccountModel, nil
|
||||
}
|
||||
|
||||
func resolveAgentAccountVerifyModel(provider, requested string, models []dto.AgentAccountModel) (string, error) {
|
||||
if len(models) == 0 {
|
||||
return "", buserr.New("ErrAgentAccountModelsRequired")
|
||||
}
|
||||
if strings.TrimSpace(requested) == "" {
|
||||
return models[0].ID, nil
|
||||
}
|
||||
selected, ok := findAgentAccountModelForProvider(provider, models, requested)
|
||||
if !ok {
|
||||
return "", buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
return selected.ID, nil
|
||||
}
|
||||
|
||||
func ensureAccountModelsNotBound(account *model.AgentAccount, models []dto.AgentAccountModel) error {
|
||||
if account == nil || account.ID == 0 {
|
||||
return nil
|
||||
@@ -1527,6 +1452,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 {
|
||||
@@ -1554,57 +1514,6 @@ func toInt(value interface{}) int {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeCustomModel(modelName string) string {
|
||||
trim := strings.TrimSpace(modelName)
|
||||
trim = strings.TrimLeft(trim, "/")
|
||||
if parts := strings.SplitN(trim, "/", 2); len(parts) == 2 {
|
||||
if strings.EqualFold(parts[0], "custom") {
|
||||
return strings.TrimLeft(strings.TrimSpace(parts[1]), "/")
|
||||
}
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func runtimeProviderModelPrefix(provider string) string {
|
||||
switch provider {
|
||||
case "gemini":
|
||||
return "google"
|
||||
case "kimi":
|
||||
return "moonshot"
|
||||
default:
|
||||
return provider
|
||||
}
|
||||
}
|
||||
|
||||
func poolModelPrefix(provider string) string {
|
||||
meta, ok := providercatalog.Get(provider)
|
||||
if ok && len(meta.Models) > 0 {
|
||||
parts := strings.SplitN(strings.TrimSpace(meta.Models[0].ID), "/", 2)
|
||||
if len(parts) == 2 && strings.TrimSpace(parts[0]) != "" {
|
||||
return parts[0]
|
||||
}
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
func supportedProviderModelPrefixes(provider string) []string {
|
||||
values := []string{poolModelPrefix(provider), runtimeProviderModelPrefix(provider)}
|
||||
result := make([]string, 0, len(values))
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
for _, value := range values {
|
||||
target := value
|
||||
if target == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[target]; ok {
|
||||
continue
|
||||
}
|
||||
seen[target] = struct{}{}
|
||||
result = append(result, target)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func generateToken() string {
|
||||
bytes := make([]byte, 24)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
|
||||
@@ -162,7 +162,7 @@ func fillAgentWebsiteItems(items []dto.AgentItem, explicitWebsiteMap map[uint]mo
|
||||
}
|
||||
}
|
||||
|
||||
func uniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]model.Website {
|
||||
func UniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]model.Website {
|
||||
websiteMap := make(map[uint]model.Website)
|
||||
duplicateAppInstallIDs := make(map[uint]struct{})
|
||||
for _, website := range websites {
|
||||
@@ -182,10 +182,6 @@ func uniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]m
|
||||
return websiteMap
|
||||
}
|
||||
|
||||
func UniqueDeploymentWebsiteMapForMigration(websites []model.Website) map[uint]model.Website {
|
||||
return uniqueDeploymentWebsiteMapByAppInstall(websites)
|
||||
}
|
||||
|
||||
func uniqueUintList(items []uint) []uint {
|
||||
itemMap := make(map[uint]struct{}, len(items))
|
||||
uniq := make([]uint, 0, len(items))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
)
|
||||
|
||||
const (
|
||||
vllmAppKeyForUpgrade = "vllm"
|
||||
vllmImageEnvKey = "IMAGE"
|
||||
vllmImageTypeNvidia = "nvidia"
|
||||
vllmImageTypeIntel = "intel"
|
||||
vllmImageTypeAscend = "ascend"
|
||||
)
|
||||
|
||||
func resolveVllmVersionFamily(version, image string) string {
|
||||
normalizedVersion := strings.ToLower(strings.TrimSpace(version))
|
||||
if strings.HasPrefix(normalizedVersion, vllmImageTypeIntel+"-") {
|
||||
return vllmImageTypeIntel
|
||||
}
|
||||
if strings.HasPrefix(normalizedVersion, vllmImageTypeAscend+"-") {
|
||||
return vllmImageTypeAscend
|
||||
}
|
||||
if strings.HasPrefix(normalizedVersion, vllmImageTypeNvidia+"-") {
|
||||
return vllmImageTypeNvidia
|
||||
}
|
||||
normalizedImage := strings.ToLower(strings.TrimSpace(image))
|
||||
if strings.Contains(normalizedImage, "intel/") || strings.Contains(normalizedImage, "llm-scaler-vllm") {
|
||||
return vllmImageTypeIntel
|
||||
}
|
||||
if strings.Contains(normalizedImage, "ascend/") || strings.Contains(normalizedImage, "vllm-ascend") {
|
||||
return vllmImageTypeAscend
|
||||
}
|
||||
return vllmImageTypeNvidia
|
||||
}
|
||||
|
||||
func trimVllmVersionFamily(version string) string {
|
||||
trimmed := strings.TrimSpace(version)
|
||||
normalized := strings.ToLower(trimmed)
|
||||
for _, family := range []string{vllmImageTypeNvidia, vllmImageTypeIntel, vllmImageTypeAscend} {
|
||||
prefix := family + "-"
|
||||
if strings.HasPrefix(normalized, prefix) {
|
||||
return strings.TrimSpace(trimmed[len(prefix):])
|
||||
}
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func buildDefaultVllmImageByVersion(version string) string {
|
||||
tag := trimVllmVersionFamily(version)
|
||||
family := resolveVllmVersionFamily(version, "")
|
||||
if family == vllmImageTypeIntel {
|
||||
return "intel/llm-scaler-vllm:" + tag
|
||||
}
|
||||
if tag != "" && !strings.HasPrefix(strings.ToLower(tag), "v") {
|
||||
tag = "v" + tag
|
||||
}
|
||||
if family == vllmImageTypeAscend {
|
||||
return "quay.io/ascend/vllm-ascend:" + tag
|
||||
}
|
||||
return "vllm/vllm-openai:" + tag
|
||||
}
|
||||
|
||||
func isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage string) bool {
|
||||
currentFamily := resolveVllmVersionFamily(currentVersion, currentImage)
|
||||
targetFamily := resolveVllmVersionFamily(targetVersion, "")
|
||||
return currentFamily == targetFamily
|
||||
}
|
||||
|
||||
func hasVllmVersionFamilyPrefix(version string) bool {
|
||||
normalized := strings.ToLower(strings.TrimSpace(version))
|
||||
return strings.HasPrefix(normalized, vllmImageTypeNvidia+"-") ||
|
||||
strings.HasPrefix(normalized, vllmImageTypeIntel+"-") ||
|
||||
strings.HasPrefix(normalized, vllmImageTypeAscend+"-")
|
||||
}
|
||||
|
||||
func isVllmUpgradeCandidate(currentVersion, targetVersion, currentImage string) bool {
|
||||
if strings.TrimSpace(currentVersion) == strings.TrimSpace(targetVersion) {
|
||||
return false
|
||||
}
|
||||
if !isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage) {
|
||||
return false
|
||||
}
|
||||
if common.CompareVersion(targetVersion, currentVersion) {
|
||||
return true
|
||||
}
|
||||
return !hasVllmVersionFamilyPrefix(currentVersion) &&
|
||||
resolveVllmVersionFamily(targetVersion, "") == vllmImageTypeNvidia &&
|
||||
trimVllmVersionFamily(currentVersion) == trimVllmVersionFamily(targetVersion)
|
||||
}
|
||||
|
||||
func buildVllmUpgradeImage(currentImage, currentVersion, targetVersion string) string {
|
||||
trimmedImage := strings.TrimSpace(currentImage)
|
||||
if trimmedImage == "" || trimmedImage == buildDefaultVllmImageByVersion(currentVersion) {
|
||||
return buildDefaultVllmImageByVersion(targetVersion)
|
||||
}
|
||||
return trimmedImage
|
||||
}
|
||||
|
||||
func loadVllmImageFromEnv(raw string) string {
|
||||
envs := make(map[string]interface{})
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return ""
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &envs); err != nil {
|
||||
return ""
|
||||
}
|
||||
if image, ok := envs[vllmImageEnvKey].(string); ok {
|
||||
return strings.TrimSpace(image)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func setVllmImageInEnvContent(content []byte, image string) []byte {
|
||||
normalizedImage := strings.TrimSpace(image)
|
||||
if normalizedImage == "" {
|
||||
return content
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
replaced := false
|
||||
for index, line := range lines {
|
||||
if strings.HasPrefix(line, vllmImageEnvKey+"=") {
|
||||
lines[index] = vllmImageEnvKey + "=" + normalizedImage
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
if len(lines) > 0 && lines[len(lines)-1] == "" {
|
||||
lines[len(lines)-1] = vllmImageEnvKey + "=" + normalizedImage
|
||||
lines = append(lines, "")
|
||||
} else {
|
||||
lines = append(lines, vllmImageEnvKey+"="+normalizedImage)
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
+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
|
||||
}
|
||||
}
|
||||
@@ -303,6 +303,9 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
||||
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if statusCode == http.StatusNotFound {
|
||||
return appDetailDTO, buserr.New("ErrAppVersionUnavailable")
|
||||
}
|
||||
if err != nil {
|
||||
return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err)
|
||||
}
|
||||
@@ -426,7 +429,12 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
} else {
|
||||
if appDetail.DockerCompose == "" {
|
||||
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
|
||||
_, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
var statusCode int
|
||||
statusCode, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if statusCode == http.StatusNotFound {
|
||||
err = buserr.New("ErrAppVersionUnavailable")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -966,7 +974,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,12 +590,20 @@ 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)
|
||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
|
||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if statusCode == http.StatusNotFound {
|
||||
return versions, buserr.New("ErrAppVersionUnavailable")
|
||||
}
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user