mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-10 08:00:32 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a888d289f |
@@ -0,0 +1,25 @@
|
|||||||
|
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"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Mirror the Github organization repos to Gitee.
|
- name: Mirror the Github organization repos to Gitee.
|
||||||
uses: Yikun/hub-mirror-action@ba51c01b28a6c9f95a25d4f1bcf6af2a147c0e18 # master
|
uses: Yikun/hub-mirror-action@master
|
||||||
with:
|
with:
|
||||||
src: 'github/1Panel-dev'
|
src: 'github/1Panel-dev'
|
||||||
dst: 'gitee/fit2cloud-feizhiyun'
|
dst: 'gitee/fit2cloud-feizhiyun'
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
||||||
|
|
||||||
|
<h3 align="center">The open-source VPS control panel with native AI agent support</h3>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Loved by a global community of <strong>2.5M+</strong> self-hosters.
|
Trusted by <strong>2,000,000+</strong> self-hosters worldwide
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -9,6 +12,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel?color=%231890FF" alt="License: GPL v3"></a>
|
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel?color=%231890FF" alt="License: GPL v3"></a>
|
||||||
|
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
|
||||||
<a href="https://discord.gg/bUpUqWqdRr"><img src="https://img.shields.io/discord/1318846410149335080?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb" alt="Discord"></a>
|
<a href="https://discord.gg/bUpUqWqdRr"><img src="https://img.shields.io/discord/1318846410149335080?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb" alt="Discord"></a>
|
||||||
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
|
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
|
||||||
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a>
|
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a>
|
||||||
@@ -21,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.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.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.de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-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.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.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.ko.md"><img alt="한국어" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||||
<a href="/docs/README.id.md"><img alt="Bahasa Indonesia" src="https://img.shields.io/badge/Bahasa Indonesia-d9d9d9"></a>
|
<a href="/docs/README.id.md"><img alt="Bahasa Indonesia" src="https://img.shields.io/badge/Bahasa Indonesia-d9d9d9"></a>
|
||||||
@@ -29,36 +33,40 @@
|
|||||||
<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.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.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.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>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What is 1Panel?
|
## What is 1Panel?
|
||||||
|
|
||||||
1Panel is a modern, open-source Linux server management panel and a lightweight AI management platform. Through an intuitive web interface, it provides users with comprehensive, one-stop server management capabilities:
|
1Panel is a modern, open-source VPS control panel — and the only one with **native AI agent support**. Run Ollama models, deploy OpenClaw agents, and manage your entire server stack from one clean web interface. No CLI memorization required.
|
||||||
- **AI Management**: Offers a unified management platform from bare metal to agents (Metal-to-Agent). It integrates an AI gateway, and Skills Hub, while supporting centralized management of agents and models.
|
|
||||||
- **Efficient Visual Operations**: Easily manage Linux servers through a web-based GUI, streamlining tasks such as host monitoring, file management, database management, and container management.
|
👉 Watch the [2-minute introduction](https://www.youtube.com/watch?v=Jl_wqp-XA08)
|
||||||
- **Rapid Website Deployment**: Deeply integrates with popular website builders like WordPress and Halo. It enables one-click domain binding and SSL certificate configuration, significantly lowering the barrier to website creation.
|
|
||||||
- **Curated App Store**: Features a built-in store of high-quality open-source applications, providing one-click installation and upgrade services to effortlessly extend server capabilities.
|
|
||||||
- **Enterprise-Grade Security**: Deploys applications based on container technology to effectively minimize vulnerability exposure. It also provides security features such as WAF and log auditing to ensure comprehensive server protection.
|
|
||||||
- **One-Click Data Backup**: Supports one-click backup and restoration, and integrates with various cloud storage solutions to ensure data security and prevent loss.
|
|
||||||
|
|
||||||
## Why 1Panel?
|
## Why 1Panel?
|
||||||
|
|
||||||
| | 1Panel | cPanel / Plesk | aaPanel | Webmin |
|
| | 1Panel | cPanel / Plesk | aaPanel | Webmin |
|
||||||
|--|--------|----------------|---------|--------|
|
|--|--------|----------------|---------|--------|
|
||||||
| Free & open source | ✅ | ❌ | Partial | ✅ |
|
| Free & open source | ✅ | ❌ | Partial | ✅ |
|
||||||
| AI management | ✅ | ❌ | ❌ | ❌ |
|
| Native AI agent runtime | ✅ | ❌ | ❌ | ❌ |
|
||||||
| One-click app marketplace | ✅ 165+ apps | ❌ | ✅ | ❌ |
|
| One-click app marketplace | ✅ 165+ apps | ❌ | ✅ | ❌ |
|
||||||
| Modern UI (post-2020) | ✅ | ❌ | Partial | ❌ |
|
| Modern UI (post-2020) | ✅ | ❌ | Partial | ❌ |
|
||||||
| Docker / container management | ✅ | ❌ | ❌ | ❌ |
|
| Docker / container management | ✅ | ❌ | ❌ | ❌ |
|
||||||
| Active development | ✅ | ✅ | ✅ | Slow |
|
| Active development | ✅ | ✅ | ✅ | Slow |
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
- **AI Agent Runtime**: Deploy Ollama LLMs, spin up OpenClaw personal agents, and monitor GPU utilization — all from the dashboard. No separate AI stack to manage.
|
||||||
|
- **One-Click Website Deployment**: Launch production-ready websites with automatic domain binding, SSL provisioning, and Nginx config — zero manual setup.
|
||||||
|
- **App Marketplace**: 165+ trusted open-source apps (Nextcloud, Bitwarden, Umami, NocoBase, and more) installed and updated with a single click.
|
||||||
|
- **Docker & Container Management**: Create, start, stop, and inspect containers, images, networks, and volumes through a visual UI — no CLI juggling.
|
||||||
|
- **Security Out of the Box**: Firewall rules, fail2ban, container isolation, WAF, and audit logs — configured and running from day one.
|
||||||
|
- **Backup & Restore**: Schedule automated backups to AWS S3, Cloudflare R2, or local storage. Restore any snapshot in one click.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Prepare your Linux server and run the following script:
|
> **Requirements:** Linux VPS (Debian / Ubuntu / CentOS / Rocky), 1 GB RAM, internet access.
|
||||||
|
> Takes ~60 seconds.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash -c "$(curl -sSL https://resource.1panel.pro/v2/quick_start.sh)"
|
bash -c "$(curl -sSL https://resource.1panel.pro/v2/quick_start.sh)"
|
||||||
@@ -73,20 +81,24 @@ Run `1pctl user-info` via SSH if you need to retrieve your access credentials.
|
|||||||
|
|
||||||
## Pro Edition
|
## Pro Edition
|
||||||
|
|
||||||
1Panel OSS is free forever. 1Panel Pro and Ent adds features built for teams and production workloads:
|
1Panel OSS is free forever. Pro adds features built for teams and production workloads:
|
||||||
|
|
||||||
| Feature | OSS | Pro | Ent |
|
| Feature | OSS | Pro |
|
||||||
|---------|:---:|:---:|:---:|
|
|---------|:---:|:---:|
|
||||||
| One-click app installs | ✅ | ✅ | ✅ |
|
| One-click app installs | ✅ | ✅ |
|
||||||
| AI agents (OpenClaw) | 5 agent | Unlimited | ✅ |
|
| AI agents (OpenClaw) | 1 agent | Unlimited |
|
||||||
| WAF & advanced security | Basic | ✅ | ✅ |
|
| WAF & advanced security | Basic | ✅ |
|
||||||
| Website tamper protection | ❌ | ✅ | ✅ |
|
| Website tamper protection | ❌ | ✅ |
|
||||||
| Website uptime monitoring | ❌ | ✅ | ✅ |
|
| Website uptime monitoring | ❌ | ✅ |
|
||||||
| Multi-node management | ❌ | ✅ | ✅ |
|
| Multi-node management | ❌ | ✅ |
|
||||||
| Custom logo & theme | ❌ | ✅ | ✅ |
|
| Custom logo & theme | ❌ | ✅ |
|
||||||
| KVM Web UI | ❌ | ❌ | ✅ |
|
| Priority support | ❌ | ✅ |
|
||||||
| AI Gateway | ❌ | ❌ | ✅ |
|
|
||||||
| Priority support | ❌ | ❌ | ✅ |
|
**From $80/year.** [Compare plans & start 30-day free trial →](https://1panel.pro/pricing)
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#1Panel-dev/1Panel&Date)
|
||||||
|
|
||||||
## Community & Support
|
## Community & Support
|
||||||
|
|
||||||
|
|||||||
@@ -500,27 +500,6 @@ func (b *BaseApi) GetAgentAccountModels(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags AI
|
|
||||||
// @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
|
// @Tags AI
|
||||||
// @Summary Create model account model
|
// @Summary Create model account model
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -1378,88 +1357,6 @@ func (b *BaseApi) UninstallAgentSkill(c *gin.Context) {
|
|||||||
helper.Success(c)
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags AI
|
|
||||||
// @Summary List OpenClaw plugins
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.AgentPluginsReq true "request"
|
|
||||||
// @Success 200 {array} dto.AgentPluginItem
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /ai/agents/plugins/list [post]
|
|
||||||
func (b *BaseApi) ListAgentPlugins(c *gin.Context) {
|
|
||||||
var req dto.AgentPluginsReq
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data, err := agentService.ListPlugins(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags AI
|
|
||||||
// @Summary Search OpenClaw plugins
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.AgentPluginSearchReq true "request"
|
|
||||||
// @Success 200 {array} dto.AgentPluginSearchItem
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /ai/agents/plugins/search [post]
|
|
||||||
func (b *BaseApi) SearchAgentPlugins(c *gin.Context) {
|
|
||||||
var req dto.AgentPluginSearchReq
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data, err := agentService.SearchPlugins(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags AI
|
|
||||||
// @Summary Install an OpenClaw marketplace plugin
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.AgentPluginMarketInstallReq true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /ai/agents/plugins/install [post]
|
|
||||||
func (b *BaseApi) InstallAgentMarketPlugin(c *gin.Context) {
|
|
||||||
var req dto.AgentPluginMarketInstallReq
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := agentService.InstallMarketPlugin(req); err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags AI
|
|
||||||
// @Summary Operate an OpenClaw plugin
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.AgentPluginOperateReq true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /ai/agents/plugins/operate [post]
|
|
||||||
func (b *BaseApi) OperateAgentPlugin(c *gin.Context) {
|
|
||||||
var req dto.AgentPluginOperateReq
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := agentService.OperatePlugin(req); err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags AI
|
// @Tags AI
|
||||||
// @Summary Login Agent Weixin channel
|
// @Summary Login Agent Weixin channel
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"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"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -297,34 +294,6 @@ func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := alertService.UpdateAlertConfig(req, loadAuditUser(c)); err != nil {
|
if err := alertService.UpdateAlertConfig(req, loadAuditUser(c)); err != nil {
|
||||||
switch {
|
|
||||||
case errors.Is(err, repo.ErrAlertConfigRevisionConflict):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "ALERT_CONFIG_REVISION_CONFLICT", "ErrInvalidParams", err)
|
|
||||||
case errors.Is(err, repo.ErrAlertConfigRevisionRequired):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "ALERT_CONFIG_REVISION_REQUIRED", "ErrInvalidParams", err)
|
|
||||||
default:
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Alert
|
|
||||||
// @Summary Update alert config status
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.AlertConfigStatusUpdate true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /alert/config/status [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置状态 [id][status]","formatEN":"update alert config status [id][status]"}
|
|
||||||
func (b *BaseApi) UpdateAlertConfigStatus(c *gin.Context) {
|
|
||||||
var req dto.AlertConfigStatusUpdate
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := alertService.UpdateAlertConfigStatus(req, loadAuditUser(c)); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -377,15 +346,6 @@ func (b *BaseApi) TestAlertConfig(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Type == constant.Custom {
|
|
||||||
result, err := alertService.TestCustomAlertConfig(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
flag, err := alertService.TestAlertConfig(req)
|
flag, err := alertService.TestAlertConfig(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
|
|||||||
@@ -413,15 +413,10 @@ func (b *BaseApi) Backup(c *gin.Context) {
|
|||||||
|
|
||||||
switch req.Type {
|
switch req.Type {
|
||||||
case "app":
|
case "app":
|
||||||
record, err := backupService.AppBackup(req)
|
if _, err := backupService.AppBackup(req); err != nil {
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.IsImmediate {
|
|
||||||
helper.SuccessWithData(c, record)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "mysql", "mariadb", constant.AppMysqlCluster:
|
case "mysql", "mariadb", constant.AppMysqlCluster:
|
||||||
if err := backupService.MysqlBackup(req); err != nil {
|
if err := backupService.MysqlBackup(req); err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ func (b *BaseApi) ContainerItemStats(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := containerService.ContainerItemStats(c.Request.Context(), req)
|
data, err := containerService.ContainerItemStats(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -881,26 +881,6 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
|
|||||||
helper.Success(c)
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Container Compose
|
|
||||||
// @Summary Pin compose
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.ComposePin true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /containers/compose/pin [post]
|
|
||||||
func (b *BaseApi) ComposePin(c *gin.Context) {
|
|
||||||
var req dto.ComposePin
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := containerService.ComposePin(req); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Container Compose
|
// @Tags Container Compose
|
||||||
// @Summary Load compose environment variables
|
// @Summary Load compose environment variables
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
|||||||
@@ -41,40 +41,20 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Mysql
|
// @Tags Database Mysql
|
||||||
// @Summary List mysql users
|
// @Summary Bind user of mysql database
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.MysqlUserSearch true "request"
|
// @Param request body dto.BindUser true "request"
|
||||||
// @Success 200 {array} dto.MysqlUser
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @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
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/users [post]
|
// @Router /databases/bind [post]
|
||||||
// @x-panel-log {"bodyKeys":["database","username","host"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 mysql 数据库 [database] 用户 [username]@[host]","formatEN":"create mysql database [database] user [username]@[host]"}
|
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 mysql 数据库名 [database] [username]","formatEN":"bind mysql database [database] [username]"}
|
||||||
func (b *BaseApi) CreateMysqlUser(c *gin.Context) {
|
func (b *BaseApi) BindUser(c *gin.Context) {
|
||||||
var req dto.MysqlUserCreate
|
var req dto.BindUser
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Password) != 0 {
|
if len(req.Password) != 0 {
|
||||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -83,191 +63,8 @@ func (b *BaseApi) CreateMysqlUser(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
req.Password = string(password)
|
req.Password = string(password)
|
||||||
}
|
}
|
||||||
if err := mysqlService.CreateUser(req); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Database Mysql
|
if err := mysqlService.BindUser(req); err != nil {
|
||||||
// @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)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -297,14 +94,14 @@ func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Mysql
|
// @Tags Database Mysql
|
||||||
// @Summary Change mysql root password
|
// @Summary Change mysql password
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.ChangeDBInfo true "request"
|
// @Param request body dto.ChangeDBInfo true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/change/password [post]
|
// @Router /databases/change/password [post]
|
||||||
// @x-panel-log {"bodyKeys":["database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] root 密码","formatEN":"Update database [database] root password"}
|
// @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"}
|
||||||
func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
||||||
var req dto.ChangeDBInfo
|
var req dto.ChangeDBInfo
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
@@ -328,14 +125,14 @@ func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Mysql
|
// @Tags Database Mysql
|
||||||
// @Summary Change mysql root access
|
// @Summary Change mysql access
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.ChangeDBInfo true "request"
|
// @Param request body dto.ChangeDBInfo true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/change/access [post]
|
// @Router /databases/change/access [post]
|
||||||
// @x-panel-log {"bodyKeys":["database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] root 访问权限","formatEN":"Update database [database] root access"}
|
// @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"}
|
||||||
func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) {
|
func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) {
|
||||||
var req dto.ChangeDBInfo
|
var req dto.ChangeDBInfo
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
|||||||
@@ -37,17 +37,14 @@ var (
|
|||||||
|
|
||||||
cronjobService = service.NewICronjobService()
|
cronjobService = service.NewICronjobService()
|
||||||
|
|
||||||
fileService = service.NewIFileService()
|
fileService = service.NewIFileService()
|
||||||
fileHistoryService = service.NewIFileHistoryService()
|
fileHistoryService = service.NewIFileHistoryService()
|
||||||
fileShareService = service.NewIFileShareService()
|
fileShareService = service.NewIFileShareService()
|
||||||
sshService = service.NewISSHService()
|
sshService = service.NewISSHService()
|
||||||
firewallService = service.NewIFirewallService()
|
firewallService = service.NewIFirewallService()
|
||||||
firewallSettingService = service.NewIFirewallSettingService()
|
iptablesService = service.NewIIptablesService()
|
||||||
forwardingService = service.NewIForwardingService()
|
monitorService = service.NewIMonitorService()
|
||||||
dockerPortGuardService = service.NewIDockerPortGuardService()
|
systemService = service.NewISystemService()
|
||||||
monitorService = service.NewIMonitorService()
|
|
||||||
systemService = service.NewISystemService()
|
|
||||||
runtimeDiagnosticsService = service.NewIRuntimeDiagnosticsService()
|
|
||||||
|
|
||||||
deviceService = service.NewIDeviceService()
|
deviceService = service.NewIDeviceService()
|
||||||
fail2banService = service.NewIFail2BanService()
|
fail2banService = service.NewIFail2BanService()
|
||||||
@@ -62,7 +59,6 @@ var (
|
|||||||
websiteDnsAccountService = service.NewIWebsiteDnsAccountService()
|
websiteDnsAccountService = service.NewIWebsiteDnsAccountService()
|
||||||
websiteSSLService = service.NewIWebsiteSSLService()
|
websiteSSLService = service.NewIWebsiteSSLService()
|
||||||
websiteAcmeAccountService = service.NewIWebsiteAcmeAccountService()
|
websiteAcmeAccountService = service.NewIWebsiteAcmeAccountService()
|
||||||
websiteTemplateService = service.NewIWebsiteTemplateService()
|
|
||||||
|
|
||||||
nginxService = service.NewINginxService()
|
nginxService = service.NewINginxService()
|
||||||
|
|
||||||
|
|||||||
+21
-535
@@ -12,9 +12,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"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"
|
||||||
@@ -30,86 +28,6 @@ import (
|
|||||||
qrcode "github.com/skip2/go-qrcode"
|
qrcode "github.com/skip2/go-qrcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cancelledChunkUploads = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
ids map[string]struct{}
|
|
||||||
}{ids: make(map[string]struct{})}
|
|
||||||
|
|
||||||
type chunkUploadLock struct {
|
|
||||||
mutex sync.Mutex
|
|
||||||
refs int
|
|
||||||
}
|
|
||||||
|
|
||||||
var chunkUploadLocks = struct {
|
|
||||||
sync.Mutex
|
|
||||||
items map[string]*chunkUploadLock
|
|
||||||
}{items: make(map[string]*chunkUploadLock)}
|
|
||||||
|
|
||||||
type completedChunkUpload struct {
|
|
||||||
dstDir string
|
|
||||||
filename string
|
|
||||||
fileSize int64
|
|
||||||
}
|
|
||||||
|
|
||||||
var completedChunkUploads = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
items map[string]completedChunkUpload
|
|
||||||
}{items: make(map[string]completedChunkUpload)}
|
|
||||||
|
|
||||||
var activeChunkUploadTTL = 24 * time.Hour
|
|
||||||
|
|
||||||
var (
|
|
||||||
errChunkUploadCancelled = errors.New("upload cancelled")
|
|
||||||
errInvalidChunkUpload = errors.New("invalid chunk upload")
|
|
||||||
)
|
|
||||||
|
|
||||||
type activeChunkUpload struct {
|
|
||||||
upload completedChunkUpload
|
|
||||||
expiresAt time.Time
|
|
||||||
timer *time.Timer
|
|
||||||
}
|
|
||||||
|
|
||||||
var activeChunkUploads = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
items map[string]activeChunkUpload
|
|
||||||
}{items: make(map[string]activeChunkUpload)}
|
|
||||||
|
|
||||||
type resumableUploadChunk struct {
|
|
||||||
UploadID string
|
|
||||||
Filename string
|
|
||||||
DstDir string
|
|
||||||
ChunkIndex int
|
|
||||||
ChunkCount int
|
|
||||||
Offset int64
|
|
||||||
FileSize int64
|
|
||||||
Overwrite bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func invalidChunkUploadError(message string) error {
|
|
||||||
return fmt.Errorf("%w: %s", errInvalidChunkUpload, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRetryableChunkUploadError(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if errors.Is(err, errChunkUploadCancelled) ||
|
|
||||||
errors.Is(err, errInvalidChunkUpload) ||
|
|
||||||
errors.Is(err, os.ErrExist) ||
|
|
||||||
errors.Is(err, os.ErrPermission) ||
|
|
||||||
errors.Is(err, os.ErrInvalid) ||
|
|
||||||
errors.Is(err, syscall.ENOSPC) ||
|
|
||||||
errors.Is(err, syscall.EDQUOT) ||
|
|
||||||
errors.Is(err, syscall.EROFS) ||
|
|
||||||
errors.Is(err, syscall.EFBIG) ||
|
|
||||||
errors.Is(err, syscall.ENAMETOOLONG) ||
|
|
||||||
errors.Is(err, syscall.ENOTDIR) ||
|
|
||||||
errors.Is(err, syscall.EISDIR) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags File
|
// @Tags File
|
||||||
// @Summary List files
|
// @Summary List files
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -549,7 +467,11 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dstInfo, statErr := os.Stat(dstFilename)
|
dstInfo, statErr := os.Stat(dstFilename)
|
||||||
err = finalizeUploadedFile(tmpFilename, dstFilename, overwrite)
|
if overwrite {
|
||||||
|
_ = os.Remove(dstFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Rename(tmpFilename, dstFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(tmpFilename)
|
_ = os.Remove(tmpFilename)
|
||||||
e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err)
|
e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err)
|
||||||
@@ -684,35 +606,10 @@ func (b *BaseApi) StopWget(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := files.CancelDownload(req.Key); err != nil {
|
files.CancelDownload(req.Key)
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags File
|
|
||||||
// @Summary Remove finished download progress records without deleting files
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.FileProcessRemoveReq true "request"
|
|
||||||
// @Success 200 {object} response.FileProcessKeys
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /files/wget/process/remove [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["keys"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"移除已结束下载记录 [keys]","formatEN":"Remove finished download records [keys]"}
|
|
||||||
func (b *BaseApi) RemoveWgetRecords(c *gin.Context) {
|
|
||||||
var req request.FileProcessRemoveReq
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
keys, err := files.RemoveDownloadRecords(req.Keys)
|
|
||||||
if err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, response.FileProcessKeys{Keys: keys})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags File
|
// @Tags File
|
||||||
// @Summary Move file
|
// @Summary Move file
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -734,26 +631,6 @@ func (b *BaseApi) MoveFile(c *gin.Context) {
|
|||||||
helper.Success(c)
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags File
|
|
||||||
// @Summary Stop file move task
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.FileMoveStopReq true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /files/move/stop [post]
|
|
||||||
func (b *BaseApi) StopMoveFile(c *gin.Context) {
|
|
||||||
var req request.FileMoveStopReq
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fileService.StopMvFile(req.TaskID); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags File
|
// @Tags File
|
||||||
// @Summary Download file
|
// @Summary Download file
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -908,289 +785,6 @@ func (b *BaseApi) DepthDirSize(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, res)
|
helper.SuccessWithData(c, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lockChunkUpload(uploadID string) func() {
|
|
||||||
chunkUploadLocks.Lock()
|
|
||||||
lock, ok := chunkUploadLocks.items[uploadID]
|
|
||||||
if !ok {
|
|
||||||
lock = &chunkUploadLock{}
|
|
||||||
chunkUploadLocks.items[uploadID] = lock
|
|
||||||
}
|
|
||||||
lock.refs++
|
|
||||||
chunkUploadLocks.Unlock()
|
|
||||||
|
|
||||||
lock.mutex.Lock()
|
|
||||||
return func() {
|
|
||||||
lock.mutex.Unlock()
|
|
||||||
chunkUploadLocks.Lock()
|
|
||||||
lock.refs--
|
|
||||||
if lock.refs == 0 {
|
|
||||||
delete(chunkUploadLocks.items, uploadID)
|
|
||||||
}
|
|
||||||
chunkUploadLocks.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resumableUploadPartPath(dstDir, uploadID string) string {
|
|
||||||
return filepath.Join(dstDir, fmt.Sprintf(".1panel-upload-%s.part", uploadID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func finalizeUploadedFile(tmpFile, dstFile string, overwrite bool) error {
|
|
||||||
if overwrite {
|
|
||||||
return os.Rename(tmpFile, dstFile)
|
|
||||||
}
|
|
||||||
if err := os.Link(tmpFile, dstFile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.Remove(tmpFile); err != nil {
|
|
||||||
if rollbackErr := os.Remove(dstFile); rollbackErr != nil {
|
|
||||||
return fmt.Errorf("remove upload temporary file failed: %v, rollback destination failed: %w", err, rollbackErr)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerActiveChunkUpload(uploadID string, upload completedChunkUpload) error {
|
|
||||||
activeChunkUploads.Lock()
|
|
||||||
defer activeChunkUploads.Unlock()
|
|
||||||
if active, ok := activeChunkUploads.items[uploadID]; ok {
|
|
||||||
if active.upload != upload {
|
|
||||||
return invalidChunkUploadError("upload ID is already used by another file")
|
|
||||||
}
|
|
||||||
active.timer.Stop()
|
|
||||||
}
|
|
||||||
expiresAt := time.Now().Add(activeChunkUploadTTL)
|
|
||||||
timer := time.AfterFunc(activeChunkUploadTTL, func() {
|
|
||||||
expireActiveChunkUpload(uploadID, expiresAt)
|
|
||||||
})
|
|
||||||
activeChunkUploads.items[uploadID] = activeChunkUpload{
|
|
||||||
upload: upload,
|
|
||||||
expiresAt: expiresAt,
|
|
||||||
timer: timer,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadActiveChunkUpload(uploadID string) (completedChunkUpload, bool) {
|
|
||||||
activeChunkUploads.RLock()
|
|
||||||
active, ok := activeChunkUploads.items[uploadID]
|
|
||||||
activeChunkUploads.RUnlock()
|
|
||||||
return active.upload, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteActiveChunkUpload(uploadID string) {
|
|
||||||
activeChunkUploads.Lock()
|
|
||||||
if active, ok := activeChunkUploads.items[uploadID]; ok {
|
|
||||||
active.timer.Stop()
|
|
||||||
}
|
|
||||||
delete(activeChunkUploads.items, uploadID)
|
|
||||||
activeChunkUploads.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func discardActiveChunkUpload(uploadID, partFile string) error {
|
|
||||||
deleteActiveChunkUpload(uploadID)
|
|
||||||
if err := os.Remove(partFile); err != nil && !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("remove upload temporary file failed: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func finalizeActiveChunkUpload(uploadID, partFile, dstFile string, overwrite bool) error {
|
|
||||||
if err := finalizeUploadedFile(partFile, dstFile, overwrite); err != nil {
|
|
||||||
if removeErr := discardActiveChunkUpload(uploadID, partFile); removeErr != nil {
|
|
||||||
return errors.Join(err, removeErr)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expireActiveChunkUpload(uploadID string, expiresAt time.Time) {
|
|
||||||
unlock := lockChunkUpload(uploadID)
|
|
||||||
defer unlock()
|
|
||||||
activeChunkUploads.Lock()
|
|
||||||
active, ok := activeChunkUploads.items[uploadID]
|
|
||||||
if !ok || !active.expiresAt.Equal(expiresAt) {
|
|
||||||
activeChunkUploads.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete(activeChunkUploads.items, uploadID)
|
|
||||||
activeChunkUploads.Unlock()
|
|
||||||
partFile := resumableUploadPartPath(active.upload.dstDir, uploadID)
|
|
||||||
if err := os.Remove(partFile); err != nil && !os.IsNotExist(err) {
|
|
||||||
global.LOG.Warnf("remove inactive upload part [%s] failed: %v", partFile, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeActiveResumableUploadPart(uploadID string) error {
|
|
||||||
unlock := lockChunkUpload(uploadID)
|
|
||||||
defer unlock()
|
|
||||||
upload, ok := loadActiveChunkUpload(uploadID)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := os.Remove(resumableUploadPartPath(upload.dstDir, uploadID))
|
|
||||||
if err == nil || os.IsNotExist(err) {
|
|
||||||
deleteActiveChunkUpload(uploadID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadCompletedChunkUpload(uploadID string) (completedChunkUpload, bool) {
|
|
||||||
completedChunkUploads.RLock()
|
|
||||||
completed, ok := completedChunkUploads.items[uploadID]
|
|
||||||
completedChunkUploads.RUnlock()
|
|
||||||
return completed, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func markChunkUploadCompleted(uploadID string, completed completedChunkUpload) {
|
|
||||||
completedChunkUploads.Lock()
|
|
||||||
completedChunkUploads.items[uploadID] = completed
|
|
||||||
completedChunkUploads.Unlock()
|
|
||||||
time.AfterFunc(10*time.Minute, func() {
|
|
||||||
completedChunkUploads.Lock()
|
|
||||||
delete(completedChunkUploads.items, uploadID)
|
|
||||||
completedChunkUploads.Unlock()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeResumableUploadChunk(chunk resumableUploadChunk, chunkData []byte) error {
|
|
||||||
unlock := lockChunkUpload(chunk.UploadID)
|
|
||||||
defer unlock()
|
|
||||||
if chunkUploadCancelled(chunk.UploadID) {
|
|
||||||
return errChunkUploadCancelled
|
|
||||||
}
|
|
||||||
if chunk.UploadID == "" || filepath.Base(chunk.UploadID) != chunk.UploadID || strings.ContainsAny(chunk.UploadID, `/\`) {
|
|
||||||
return invalidChunkUploadError("invalid upload ID")
|
|
||||||
}
|
|
||||||
if chunk.Filename == "" || filepath.Base(chunk.Filename) != chunk.Filename || strings.ContainsAny(chunk.Filename, `/\`) {
|
|
||||||
return invalidChunkUploadError("invalid filename")
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(chunk.DstDir) == "" {
|
|
||||||
return invalidChunkUploadError("upload destination is required")
|
|
||||||
}
|
|
||||||
dstDir := filepath.Clean(strings.TrimSpace(chunk.DstDir))
|
|
||||||
|
|
||||||
if chunk.ChunkCount <= 0 || chunk.ChunkIndex < 0 || chunk.ChunkIndex >= chunk.ChunkCount {
|
|
||||||
return invalidChunkUploadError("invalid chunk index")
|
|
||||||
}
|
|
||||||
if chunk.FileSize <= 0 || chunk.Offset < 0 || chunk.Offset > chunk.FileSize {
|
|
||||||
return invalidChunkUploadError("invalid upload offset")
|
|
||||||
}
|
|
||||||
chunkEnd := chunk.Offset + int64(len(chunkData))
|
|
||||||
if chunkEnd > chunk.FileSize {
|
|
||||||
return invalidChunkUploadError("chunk exceeds file size")
|
|
||||||
}
|
|
||||||
if chunk.ChunkIndex+1 == chunk.ChunkCount {
|
|
||||||
if chunkEnd != chunk.FileSize {
|
|
||||||
return invalidChunkUploadError("final chunk does not match file size")
|
|
||||||
}
|
|
||||||
} else if chunkEnd >= chunk.FileSize {
|
|
||||||
return invalidChunkUploadError("non-final chunk reaches file size")
|
|
||||||
}
|
|
||||||
if completed, ok := loadCompletedChunkUpload(chunk.UploadID); ok {
|
|
||||||
if completed.dstDir == dstDir && completed.filename == chunk.Filename && completed.fileSize == chunk.FileSize {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return invalidChunkUploadError("upload ID has already completed another file")
|
|
||||||
}
|
|
||||||
upload := completedChunkUpload{dstDir: dstDir, filename: chunk.Filename, fileSize: chunk.FileSize}
|
|
||||||
if err := registerActiveChunkUpload(chunk.UploadID, upload); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mode, err := files.GetParentMode(dstDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = os.MkdirAll(dstDir, mode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstDirInfo, err := os.Stat(dstDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !dstDirInfo.IsDir() {
|
|
||||||
return invalidChunkUploadError(fmt.Sprintf("upload destination [%s] is not a directory", dstDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
dstFile := filepath.Join(dstDir, chunk.Filename)
|
|
||||||
partFile := resumableUploadPartPath(dstDir, chunk.UploadID)
|
|
||||||
if dstFile == partFile {
|
|
||||||
return invalidChunkUploadError("filename conflicts with upload temporary file")
|
|
||||||
}
|
|
||||||
fileMode := dstDirInfo.Mode().Perm()
|
|
||||||
ownerInfo := dstDirInfo
|
|
||||||
if dstInfo, statErr := os.Stat(dstFile); statErr == nil {
|
|
||||||
if !chunk.Overwrite {
|
|
||||||
if err := discardActiveChunkUpload(chunk.UploadID, partFile); err != nil {
|
|
||||||
return errors.Join(os.ErrExist, err)
|
|
||||||
}
|
|
||||||
return os.ErrExist
|
|
||||||
}
|
|
||||||
fileMode = dstInfo.Mode().Perm()
|
|
||||||
ownerInfo = dstInfo
|
|
||||||
} else if !os.IsNotExist(statErr) {
|
|
||||||
return statErr
|
|
||||||
}
|
|
||||||
|
|
||||||
part, err := os.OpenFile(partFile, os.O_CREATE|os.O_RDWR, fileMode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
partClosed := false
|
|
||||||
defer func() {
|
|
||||||
if !partClosed {
|
|
||||||
_ = part.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if stat, statErr := part.Stat(); statErr != nil {
|
|
||||||
return statErr
|
|
||||||
} else if chunk.Offset > stat.Size() {
|
|
||||||
return invalidChunkUploadError(fmt.Sprintf("unexpected upload offset %d, current size is %d", chunk.Offset, stat.Size()))
|
|
||||||
} else if chunk.Offset < stat.Size() && chunkEnd > stat.Size() {
|
|
||||||
if err = part.Truncate(chunk.Offset); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err = part.WriteAt(chunkData, chunk.Offset); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if chunk.ChunkIndex+1 != chunk.ChunkCount {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
partInfo, err := part.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if partInfo.Size() != chunk.FileSize {
|
|
||||||
return invalidChunkUploadError(fmt.Sprintf("uploaded file size mismatch: expected %d, got %d", chunk.FileSize, partInfo.Size()))
|
|
||||||
}
|
|
||||||
if err = part.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
partClosed = true
|
|
||||||
if err = os.Chmod(partFile, fileMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if stat, ok := ownerInfo.Sys().(*syscall.Stat_t); ok {
|
|
||||||
if err = os.Chown(partFile, int(stat.Uid), int(stat.Gid)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if chunkUploadCancelled(chunk.UploadID) {
|
|
||||||
return errChunkUploadCancelled
|
|
||||||
}
|
|
||||||
if err = finalizeActiveChunkUpload(chunk.UploadID, partFile, dstFile, chunk.Overwrite); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
markChunkUploadCompleted(chunk.UploadID, upload)
|
|
||||||
deleteActiveChunkUpload(chunk.UploadID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int, overwrite bool) error {
|
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int, overwrite bool) error {
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = os.RemoveAll(fileDir)
|
_ = os.RemoveAll(fileDir)
|
||||||
@@ -1270,10 +864,6 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if chunkCount <= 0 || chunkIndex < 0 || chunkIndex >= chunkCount {
|
|
||||||
helper.BadRequest(c, errors.New("invalid chunk index"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
tmpDir := path.Join(global.Dir.TmpDir, "upload")
|
tmpDir := path.Join(global.Dir.TmpDir, "upload")
|
||||||
if !fileOp.Stat(tmpDir) {
|
if !fileOp.Stat(tmpDir) {
|
||||||
@@ -1283,30 +873,8 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
filename := c.PostForm("filename")
|
filename := c.PostForm("filename")
|
||||||
if filename == "" || filepath.Base(filename) != filename || strings.ContainsAny(filename, `/\\`) {
|
fileDir := filepath.Join(tmpDir, filename)
|
||||||
helper.BadRequest(c, errors.New("invalid filename"))
|
if chunkIndex == 0 {
|
||||||
return
|
|
||||||
}
|
|
||||||
uploadID := strings.TrimSpace(c.PostForm("uploadID"))
|
|
||||||
resumable := c.PostForm("fileSize") != "" || c.PostForm("offset") != ""
|
|
||||||
cancellable := uploadID != ""
|
|
||||||
if cancellable && (filepath.Base(uploadID) != uploadID || strings.ContainsAny(uploadID, `/\\`)) {
|
|
||||||
helper.BadRequest(c, errors.New("invalid upload ID"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if resumable && !cancellable {
|
|
||||||
helper.BadRequest(c, errors.New("upload ID is required"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !cancellable {
|
|
||||||
uploadID = filename
|
|
||||||
}
|
|
||||||
fileDir := filepath.Join(tmpDir, uploadID)
|
|
||||||
if cancellable && chunkUploadCancelled(uploadID) {
|
|
||||||
helper.BadRequest(c, errChunkUploadCancelled)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !resumable && chunkIndex == 0 {
|
|
||||||
if fileOp.Stat(fileDir) {
|
if fileOp.Stat(fileDir) {
|
||||||
_ = fileOp.DeleteDir(fileDir)
|
_ = fileOp.DeleteDir(fileDir)
|
||||||
}
|
}
|
||||||
@@ -1315,68 +883,28 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
filePath := filepath.Join(fileDir, filename)
|
filePath := filepath.Join(fileDir, filename)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !resumable && err != nil {
|
if err != nil {
|
||||||
_ = os.RemoveAll(fileDir)
|
_ = os.Remove(fileDir)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
chunkData, err := io.ReadAll(uploadFile)
|
var (
|
||||||
if err != nil {
|
emptyFile *os.File
|
||||||
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
chunkData []byte
|
||||||
return
|
)
|
||||||
}
|
|
||||||
if cancellable && chunkUploadCancelled(uploadID) {
|
|
||||||
err = errChunkUploadCancelled
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if resumable {
|
|
||||||
offset, parseErr := strconv.ParseInt(c.PostForm("offset"), 10, 64)
|
|
||||||
if parseErr != nil {
|
|
||||||
helper.BadRequest(c, parseErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fileSize, parseErr := strconv.ParseInt(c.PostForm("fileSize"), 10, 64)
|
|
||||||
if parseErr != nil {
|
|
||||||
helper.BadRequest(c, parseErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
overwrite := true
|
|
||||||
if ow := c.PostForm("overwrite"); ow != "" {
|
|
||||||
overwrite, _ = strconv.ParseBool(ow)
|
|
||||||
}
|
|
||||||
err = writeResumableUploadChunk(resumableUploadChunk{
|
|
||||||
UploadID: uploadID,
|
|
||||||
Filename: filename,
|
|
||||||
DstDir: c.PostForm("path"),
|
|
||||||
ChunkIndex: chunkIndex,
|
|
||||||
ChunkCount: chunkCount,
|
|
||||||
Offset: offset,
|
|
||||||
FileSize: fileSize,
|
|
||||||
Overwrite: overwrite,
|
|
||||||
}, chunkData)
|
|
||||||
if err != nil {
|
|
||||||
uploadErr := buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err)
|
|
||||||
helper.ErrorWithDetailAndData(c, http.StatusInternalServerError, "ErrInternalServer", uploadErr, gin.H{
|
|
||||||
"retryable": isRetryableChunkUploadError(err),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if chunkIndex+1 == chunkCount {
|
|
||||||
cancelledChunkUploads.Lock()
|
|
||||||
delete(cancelledChunkUploads.ids, uploadID)
|
|
||||||
cancelledChunkUploads.Unlock()
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
emptyFile, err := os.Create(filePath)
|
emptyFile, err = os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer emptyFile.Close()
|
defer emptyFile.Close()
|
||||||
|
|
||||||
|
chunkData, err = io.ReadAll(uploadFile)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
|
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
|
||||||
err = os.WriteFile(chunkPath, chunkData, constant.DirPerm)
|
err = os.WriteFile(chunkPath, chunkData, constant.DirPerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1394,54 +922,12 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cancellable {
|
|
||||||
cancelledChunkUploads.Lock()
|
|
||||||
delete(cancelledChunkUploads.ids, uploadID)
|
|
||||||
cancelledChunkUploads.Unlock()
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, true)
|
helper.SuccessWithData(c, true)
|
||||||
} else {
|
} else {
|
||||||
return
|
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
|
|
||||||
}
|
|
||||||
if err := removeActiveResumableUploadPart(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{
|
var wsUpgrade = websocket.Upgrader{
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
|
|||||||
+224
-569
@@ -1,53 +1,26 @@
|
|||||||
package v2
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"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"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *BaseApi) UpdatePanelFirewallPort(c *gin.Context) {
|
|
||||||
if !global.IsMaster {
|
|
||||||
c.AbortWithStatus(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var request struct {
|
|
||||||
OldPort uint `json:"oldPort" validate:"required,min=1,max=65535"`
|
|
||||||
NewPort uint `json:"newPort" validate:"required,min=1,max=65535"`
|
|
||||||
}
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := firewallService.UpdatePanelPort(c.Request.Context(), request.OldPort, request.NewPort); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Load firewall base info
|
// @Summary Load firewall base info
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.OperationWithName true "request"
|
// @Param request body dto.OperationWithName true "request"
|
||||||
// @Success 200 {object} dto.FirewallSubsystemStatus
|
// @Success 200 {object} dto.FirewallBaseInfo
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/base [post]
|
// @Router /hosts/firewall/base [post]
|
||||||
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||||
var request dto.OperationWithName
|
var req dto.OperationWithName
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := firewallService.LoadBaseInfo(request.Name)
|
data, err := firewallService.LoadBaseInfo(req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -56,22 +29,48 @@ func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, data)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Page firewall rules
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.RuleSearch true "request"
|
||||||
|
// @Success 200 {object} dto.PageResult
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/search [post]
|
||||||
|
func (b *BaseApi) SearchFirewallRule(c *gin.Context) {
|
||||||
|
var req dto.RuleSearch
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total, list, err := firewallService.SearchWithPage(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Operate firewall
|
// @Summary Operate firewall
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.FirewallLifecycleOperation true "request"
|
// @Param request body dto.FirewallOperation true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/operate [post]
|
// @Router /hosts/firewall/operate [post]
|
||||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"}
|
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"}
|
||||||
func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
||||||
var request dto.FirewallLifecycleOperation
|
var req dto.FirewallOperation
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := firewallService.OperateFirewall(request); err != nil {
|
if err := firewallService.OperateFirewall(req); err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -80,605 +79,261 @@ func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Load forwarding base info
|
// @Summary Create group
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Success 200 {object} dto.FirewallSubsystemStatus
|
// @Param request body dto.PortRuleOperate true "request"
|
||||||
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/forward/base [post]
|
// @Router /hosts/firewall/port [post]
|
||||||
func (b *BaseApi) LoadForwardingBaseInfo(c *gin.Context) {
|
// @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加端口规则 [strategy] [port]","formatEN":"create port rules [strategy][port]"}
|
||||||
data, err := forwardingService.LoadBaseInfo()
|
func (b *BaseApi) OperatePortRule(c *gin.Context) {
|
||||||
if err != nil {
|
var req dto.PortRuleOperate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.OperatePortRule(req, true); err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, data)
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperateForwardRule
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Operate forward rule
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ForwardRuleOperate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/forward [post]
|
||||||
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"}
|
||||||
|
func (b *BaseApi) OperateForwardRule(c *gin.Context) {
|
||||||
|
var req dto.ForwardRuleOperate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.OperateForwardRule(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Page forwarding rules
|
// @Summary Operate Ip rule
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.ForwardRuleSearch true "request"
|
// @Param request body dto.AddrRuleOperate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/ip [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["strategy","address"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 ip 规则 [strategy] [address]","formatEN":"create address rules [strategy][address]"}
|
||||||
|
func (b *BaseApi) OperateIPRule(c *gin.Context) {
|
||||||
|
var req dto.AddrRuleOperate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.OperateAddressRule(req, true); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Batch operate rule
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.BatchRuleOperate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/batch [post]
|
||||||
|
func (b *BaseApi) BatchOperateRule(c *gin.Context) {
|
||||||
|
var req dto.BatchRuleOperate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.BatchOperateRule(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Update rule description
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.UpdateFirewallDescription true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/update/description [post]
|
||||||
|
func (b *BaseApi) UpdateFirewallDescription(c *gin.Context) {
|
||||||
|
var req dto.UpdateFirewallDescription
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.UpdateDescription(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Update port rule
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.PortRuleUpdate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/update/port [post]
|
||||||
|
func (b *BaseApi) UpdatePortRule(c *gin.Context) {
|
||||||
|
var req dto.PortRuleUpdate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.UpdatePortRule(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Update Ip rule
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.AddrRuleUpdate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /hosts/firewall/update/addr [post]
|
||||||
|
func (b *BaseApi) UpdateAddrRule(c *gin.Context) {
|
||||||
|
var req dto.AddrRuleUpdate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.UpdateAddrRule(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary search iptables filter rules
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.SearchPageWithType true "request"
|
||||||
// @Success 200 {object} dto.PageResult
|
// @Success 200 {object} dto.PageResult
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/forward/search [post]
|
// @Router /hosts/firewall/filter/rule/search [post]
|
||||||
func (b *BaseApi) SearchForwardingRules(c *gin.Context) {
|
func (b *BaseApi) SearchFilterRules(c *gin.Context) {
|
||||||
var request dto.ForwardRuleSearch
|
var req dto.SearchPageWithType
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, items, err := forwardingService.SearchRules(request)
|
|
||||||
|
total, list, err := iptablesService.Search(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.SuccessWithData(c, dto.PageResult{Items: items, Total: total})
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Operate forwarding rules
|
// @Summary Operate iptables filter rule
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.ForwardRuleOperate true "request"
|
// @Param request body dto.IptablesRuleOp true "request"
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/forward/operate [post]
|
// @Router /hosts/firewall/filter/rule/operate [post]
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"}
|
// @x-panel-log {"bodyKeys":["operation","chain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] filter规则到 [chain]","formatEN":"[operation] filter rule to [chain]"}
|
||||||
func (b *BaseApi) OperateForwardingRules(c *gin.Context) {
|
func (b *BaseApi) OperateFilterRule(c *gin.Context) {
|
||||||
var request dto.ForwardRuleOperate
|
var req dto.IptablesRuleOp
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := iptablesService.OperateRule(req, true); err != nil {
|
||||||
result, err := forwardingService.OperateRules(request)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Enable forwarding
|
// @Summary Batch operate iptables filter rules
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.FirewallInitializationTask true "request"
|
// @Param request body dto.IptablesBatchOperate true "request"
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/forward/enable [post]
|
// @Router /hosts/firewall/filter/rule/batch [post]
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"初始化并启用端口转发","formatEN":"initialize and enable port forwarding"}
|
func (b *BaseApi) BatchOperateFilterRule(c *gin.Context) {
|
||||||
func (b *BaseApi) EnableForwarding(c *gin.Context) {
|
var req dto.IptablesBatchOperate
|
||||||
var request dto.FirewallInitializationTask
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result, err := forwardingService.QueueInitialization(request)
|
|
||||||
if err != nil {
|
if err := iptablesService.BatchOperate(req); err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Apply/Unload/Init firewall filter chain
|
// @Summary Apply/Unload/Init iptables filter
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.FilterChainOperation true "request"
|
// @Param request body dto.IptablesOp true "request"
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/filter/operate [post]
|
// @Router /hosts/firewall/filter/operate [post]
|
||||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 防火墙过滤链","formatEN":"[operate] firewall filter chain"}
|
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] iptables filter 防火墙","formatEN":"[operate] iptables filter firewall"}
|
||||||
func (b *BaseApi) OperateFilterChain(c *gin.Context) {
|
func (b *BaseApi) OperateFilterChain(c *gin.Context) {
|
||||||
var request dto.FilterChainOperation
|
var req dto.IptablesOp
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if request.Operate == "init-base" {
|
if err := iptablesService.Operate(req); err != nil {
|
||||||
result, err := firewallService.QueueFilterChainInitialization(request)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := firewallService.OperateFilterChain(request); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, dto.FilterChainOperationResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary List unified firewall v2 rules
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleInventory true "request"
|
|
||||||
// @Success 200 {object} dto.FirewallRuleInventoryResponse
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/search [post]
|
|
||||||
func (b *BaseApi) SearchFirewallRules(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleInventory
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
inventory, err := firewallService.Inventory(c.Request.Context(), request)
|
|
||||||
if err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, inventory)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Reset firewall rules
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleReset true "request"
|
|
||||||
// @Success 200 {object} dto.FirewallRuleResetResponse
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/reset [post]
|
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重置防火墙规则","formatEN":"reset firewall rules"}
|
|
||||||
func (b *BaseApi) ResetFirewallRules(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleReset
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := firewallService.Reset(c.Request.Context(), request)
|
|
||||||
if err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Load one provider-native firewall object definition
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallNativeDetail true "request"
|
|
||||||
// @Success 200 {string} string
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/native/detail [post]
|
|
||||||
func (b *BaseApi) LoadFirewallNativeDetail(c *gin.Context) {
|
|
||||||
var request dto.FirewallNativeDetail
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info, err := firewallService.LoadFirewallNativeDetail(c.Request.Context(), request)
|
|
||||||
if err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Adopt an external firewall rule
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleAdopt true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/adopt [post]
|
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"纳管防火墙规则","formatEN":"adopt firewall rule"}
|
|
||||||
func (b *BaseApi) AdoptFirewallRule(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleAdopt
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := firewallService.Adopt(c.Request.Context(), request); err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Queue firewall rule creation
|
// @Summary load chain status with name
|
||||||
// @Description Creation and import return a taskID immediately; validation and execution results are written to the task log.
|
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.FirewallRuleCreate true "request"
|
// @Param request body dto.OperationWithName true "request"
|
||||||
// @Success 200 {object} dto.FirewallRuleCreateResponse
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Failure 409 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules [post]
|
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加防火墙规则","formatEN":"create firewall rules"}
|
|
||||||
func (b *BaseApi) CreateFirewallRules(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleCreate
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := firewallService.Create(c.Request.Context(), request)
|
|
||||||
if err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Preview firewall rule synchronization
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleSyncRequest true "request"
|
|
||||||
// @Success 200 {object} dto.FirewallRuleSyncPreview
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/sync/preview [post]
|
|
||||||
func (b *BaseApi) PreviewFirewallRuleSync(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleSyncRequest
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := firewallService.PreviewRuleSync(c.Request.Context(), c.ClientIP(), request)
|
|
||||||
if err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Load the currently executing firewall rule synchronization task
|
|
||||||
// @Success 200 {object} dto.FirewallRuleSyncTask
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/sync/task [get]
|
|
||||||
func (b *BaseApi) LoadFirewallRuleSyncTask(c *gin.Context) {
|
|
||||||
result, err := firewallService.CurrentRuleSyncTask()
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Synchronize firewall rules to a target backend
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleSyncRequest true "request"
|
|
||||||
// @Success 200 {object} dto.FirewallRuleSyncResult
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/sync [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["subsystem","sourceProvider","targetProvider"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 [subsystem] 防火墙规则到 [targetProvider]","formatEN":"sync [subsystem] firewall rules to [targetProvider]"}
|
|
||||||
func (b *BaseApi) SyncFirewallRules(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleSyncRequest
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := firewallService.SyncRules(c.Request.Context(), c.ClientIP(), request)
|
|
||||||
if err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Delete managed unified firewall v2 rules
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleDelete true "request"
|
|
||||||
// @Success 200 {object} dto.FirewallRuleDeleteResponse
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/delete [post]
|
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除防火墙规则","formatEN":"delete firewall rules"}
|
|
||||||
func (b *BaseApi) DeleteFirewallRules(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleDelete
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := firewallService.Delete(c.Request.Context(), request)
|
|
||||||
if err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Update a managed unified firewall v2 rule
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleUpdate true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/update [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["uuid"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙规则 [uuid]","formatEN":"update firewall rule [uuid]"}
|
|
||||||
func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleUpdate
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !normalizeFirewallRuleUUID(c, &request.UUID) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := firewallService.Update(c.Request.Context(), c.ClientIP(), request); err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Reorder a managed unified firewall v2 rule
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallRuleReorder true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Failure 400 {object} dto.Response
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/rules/reorder [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["uuid"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整防火墙规则顺序 [uuid]","formatEN":"reorder firewall rule [uuid]"}
|
|
||||||
func (b *BaseApi) ReorderFirewallRule(c *gin.Context) {
|
|
||||||
var request dto.FirewallRuleReorder
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !normalizeFirewallRuleUUID(c, &request.UUID) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := firewallService.Reorder(c.Request.Context(), c.ClientIP(), request); err != nil {
|
|
||||||
handleFirewallRuleError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeFirewallRuleUUID(c *gin.Context, value *string) bool {
|
|
||||||
if value == nil {
|
|
||||||
helper.BadRequest(c, repo.ErrFirewallPersistenceInvalid)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
*value = strings.TrimSpace(*value)
|
|
||||||
if *value == "" {
|
|
||||||
helper.BadRequest(c, repo.ErrFirewallPersistenceInvalid)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleFirewallRuleError(c *gin.Context, err error) {
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, filter.ErrProtectedRule):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_LOCKOUT_RISK", "ErrInvalidParams", err)
|
|
||||||
case errors.Is(err, filter.ErrRuleStale):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err)
|
|
||||||
case errors.Is(err, repo.ErrFirewallRuleRevisionConflict):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_REVISION_CONFLICT", "ErrInvalidParams", err)
|
|
||||||
case errors.Is(err, filter.ErrManagedScopeChange):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrFirewallRuleScopeChange", err)
|
|
||||||
case errors.Is(err, filter.ErrUnsupportedScope), errors.Is(err, filter.ErrInvalidScope),
|
|
||||||
errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrInvalidParams", err)
|
|
||||||
case errors.Is(err, filter.ErrInvalidRule), errors.Is(err, filter.ErrRuleOperation), errors.Is(err, filter.ErrRuleConflict),
|
|
||||||
errors.Is(err, repo.ErrFirewallPersistenceInvalid):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_RULE_UNSUPPORTED", "ErrInvalidParams", err)
|
|
||||||
case errors.Is(err, filter.ErrVerificationFailed):
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_VERIFY_FAILED", "ErrInternalServer", err)
|
|
||||||
default:
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_APPLY_FAILED", "ErrInternalServer", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Load firewall settings
|
|
||||||
// @Success 200 {object} dto.FirewallSettings
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/settings [get]
|
|
||||||
func (b *BaseApi) LoadFirewallSettings(c *gin.Context) {
|
|
||||||
data, err := firewallSettingService.Load(c.Request.Context())
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Queue firewall port whitelist update
|
|
||||||
// @Description Returns a taskID; configuration save and per-rule results are recorded in the task log.
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallPortWhitelistUpdate true "request"
|
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/settings/whitelist [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙端口白名单 [value]","formatEN":"update firewall port whitelist [value]"}
|
|
||||||
func (b *BaseApi) UpdateFirewallPortWhitelist(c *gin.Context) {
|
|
||||||
var request dto.FirewallPortWhitelistUpdate
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := firewallSettingService.QueuePortWhitelist(request.Value)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Operate firewall backend
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.FirewallBackendOperation true "request"
|
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/settings/operate [post]
|
// @Router /hosts/firewall/filter/chain/status [post]
|
||||||
// @x-panel-log {"bodyKeys":["subsystem","backend","operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"防火墙子系统 [subsystem] 后端 [operation] [backend]","formatEN":"[operation] firewall [subsystem] backend [backend]"}
|
func (b *BaseApi) LoadChainStatus(c *gin.Context) {
|
||||||
func (b *BaseApi) OperateFirewallBackend(c *gin.Context) {
|
var req dto.OperationWithName
|
||||||
var request dto.FirewallBackendOperation
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil {
|
|
||||||
if errors.Is(err, service.ErrFirewallBackendCleanupRequired) {
|
|
||||||
helper.ErrorWithBusinessCode(
|
|
||||||
c,
|
|
||||||
http.StatusConflict,
|
|
||||||
"FW_BACKEND_CLEANUP_REQUIRED",
|
|
||||||
"ErrInvalidParams",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
helper.SuccessWithData(c, iptablesService.LoadChainStatus(req))
|
||||||
// @Summary List Docker port guard status and policies
|
|
||||||
// @Success 200 {object} dto.DockerPortGuardList
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/docker/ports [get]
|
|
||||||
func (b *BaseApi) ListDockerPortGuard(c *gin.Context) {
|
|
||||||
data, err := dockerPortGuardService.LoadOverview(c.Request.Context())
|
|
||||||
if err != nil {
|
|
||||||
handleDockerPortGuardError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary List Docker published ports
|
|
||||||
// @Success 200 {array} dto.DockerPortGuardContainer
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/docker/endpoints [get]
|
|
||||||
func (b *BaseApi) ListDockerPublishedPorts(c *gin.Context) {
|
|
||||||
data, err := dockerPortGuardService.LoadPublishedPorts(c.Request.Context())
|
|
||||||
if err != nil {
|
|
||||||
handleDockerPortGuardError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Sync Docker port guard rules
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/docker/sync [post]
|
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Docker 端口防护规则","formatEN":"sync Docker port guard rules"}
|
|
||||||
func (b *BaseApi) SyncDockerPortGuard(c *gin.Context) {
|
|
||||||
if err := dockerPortGuardService.Reconcile(c.Request.Context()); err != nil {
|
|
||||||
handleDockerPortGuardError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Operate Docker port guard
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.DockerPortGuardOperation true "request"
|
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/docker/operate [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Docker 端口防护","formatEN":"[operation] Docker port guard"}
|
|
||||||
func (b *BaseApi) OperateDockerPortGuard(c *gin.Context) {
|
|
||||||
var request dto.DockerPortGuardOperation
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if request.Operation == "initialize" {
|
|
||||||
result, err := dockerPortGuardService.QueueInitialization(request)
|
|
||||||
if err != nil {
|
|
||||||
handleDockerPortGuardError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := dockerPortGuardService.Operate(c.Request.Context(), request); err != nil {
|
|
||||||
handleDockerPortGuardError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Delete Docker port guard policies
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.DockerPortGuardPolicyBatchDelete true "request"
|
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/docker/policies/delete/batch [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["uuids"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 Docker 端口防护策略 [uuids]","formatEN":"delete Docker port guard policies [uuids]"}
|
|
||||||
func (b *BaseApi) DeleteDockerPortGuardPolicies(c *gin.Context) {
|
|
||||||
var request dto.DockerPortGuardPolicyBatchDelete
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := dockerPortGuardService.DeletePolicies(request)
|
|
||||||
if err != nil {
|
|
||||||
handleDockerPortGuardError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Firewall
|
|
||||||
// @Summary Batch upsert Docker port guard policies
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.DockerPortGuardPolicyBatch true "request"
|
|
||||||
// @Success 200 {object} dto.FilterChainOperationResponse
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/firewall/docker/policies/batch [post]
|
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"批量更新 Docker 端口防护策略","formatEN":"batch update Docker port guard policies"}
|
|
||||||
func (b *BaseApi) UpsertDockerPortGuardPolicies(c *gin.Context) {
|
|
||||||
var request dto.DockerPortGuardPolicyBatch
|
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result, err := dockerPortGuardService.UpsertPolicies(request)
|
|
||||||
if err != nil {
|
|
||||||
handleDockerPortGuardError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleDockerPortGuardError(c *gin.Context, err error) {
|
|
||||||
if errors.Is(err, service.ErrDockerIptablesChainUnavailable) {
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE", "ErrDockerIptablesChainUnavailable", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, service.ErrDockerNftablesChainUnavailable) {
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_NFTABLES_CHAIN_UNAVAILABLE", "ErrDockerNftablesChainUnavailable", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, service.ErrDockerGuardInvalid) {
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_DOCKER_GUARD_INVALID", "ErrInvalidParams", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, service.ErrDockerUnavailable) {
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_UNAVAILABLE", "ErrDockerFailed", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.ErrorWithBusinessCode(c, http.StatusInternalServerError, "FW_DOCKER_GUARD_FAILED", "ErrInternalServer", err)
|
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-9
@@ -3,8 +3,9 @@ package v2
|
|||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"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"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
"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"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,20 +17,27 @@ import (
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /ai/gpu/load [get]
|
// @Router /ai/gpu/load [get]
|
||||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||||
ok, client := accelerator.New()
|
ok, client := gpu.New()
|
||||||
if ok {
|
if ok {
|
||||||
snapshot, err := client.Collect(c.Request.Context())
|
info, err := client.LoadGpuInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if warning := snapshot.Warning(); warning != nil {
|
helper.SuccessWithData(c, info)
|
||||||
global.LOG.Warnf("load realtime accelerator data partially failed, err: %v", warning)
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, &snapshot.Info)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, &accelerator.Info{})
|
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
|
// @Tags AI
|
||||||
|
|||||||
@@ -30,26 +30,6 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
|||||||
ctx.Abort()
|
ctx.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrorWithBusinessCode(ctx *gin.Context, code int, businessCode, msgKey string, err error) {
|
|
||||||
res := dto.Response{
|
|
||||||
Code: code,
|
|
||||||
ErrorCode: businessCode,
|
|
||||||
Message: i18n.GetMsgWithDetail(msgKey, err.Error()),
|
|
||||||
}
|
|
||||||
ctx.JSON(http.StatusOK, res)
|
|
||||||
ctx.Abort()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ErrorWithDetailAndData(ctx *gin.Context, code int, msgKey string, err error, data interface{}) {
|
|
||||||
res := dto.Response{
|
|
||||||
Code: code,
|
|
||||||
Data: data,
|
|
||||||
}
|
|
||||||
res.Message = i18n.GetMsgWithDetail(msgKey, err.Error())
|
|
||||||
ctx.JSON(http.StatusOK, res)
|
|
||||||
ctx.Abort()
|
|
||||||
}
|
|
||||||
|
|
||||||
func InternalServer(ctx *gin.Context, err error) {
|
func InternalServer(ctx *gin.Context, err error) {
|
||||||
ErrorWithDetail(ctx, http.StatusInternalServerError, "ErrInternalServer", err)
|
ErrorWithDetail(ctx, http.StatusInternalServerError, "ErrInternalServer", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,57 +20,3 @@ func (b *BaseApi) GetSystemFiles(c *gin.Context) {
|
|||||||
|
|
||||||
helper.SuccessWithData(c, data)
|
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,27 +23,6 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, list)
|
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
|
// @Tags McpServer
|
||||||
// @Summary Create mcp server
|
// @Summary Create mcp server
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -128,48 +107,6 @@ func (b *BaseApi) OperateMcpServer(c *gin.Context) {
|
|||||||
helper.Success(c)
|
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
|
// @Tags McpServer
|
||||||
// @Summary Bind Domain for mcp server
|
// @Summary Bind Domain for mcp server
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -224,21 +224,6 @@ func (b *BaseApi) ExportSSHLogs(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, tmpFile)
|
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
|
// @Tags SSH
|
||||||
// @Summary Load host SSH conf
|
// @Summary Load host SSH conf
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
|||||||
+31
-140
@@ -1,14 +1,11 @@
|
|||||||
package v2
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||||
@@ -22,33 +19,29 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
gossh "golang.org/x/crypto/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Tags Terminal
|
// @Tags Terminal
|
||||||
// @Summary Ws local terminal
|
// @Summary Ws local terminal
|
||||||
// @Param command query string false "command"
|
// @Param command query string false "command"
|
||||||
// @Param session query string false "session id to reattach"
|
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/terminal/local [get]
|
// @Router /hosts/terminal/local [get]
|
||||||
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
|
||||||
b.runSSHSession(c, "local", loadLocalConn, c.DefaultQuery("command", ""))
|
b.runSSHSession(c, loadLocalConn, c.DefaultQuery("command", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Terminal
|
// @Tags Terminal
|
||||||
// @Summary Ws host SSH
|
// @Summary Ws host SSH
|
||||||
// @Param id query integer false "id"
|
// @Param id query integer false "id"
|
||||||
// @Param command query string false "command"
|
// @Param command query string false "command"
|
||||||
// @Param session query string false "session id to reattach"
|
|
||||||
// @Param title query string false "session title shown in the session list"
|
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/terminal/ssh [get]
|
// @Router /hosts/terminal/ssh [get]
|
||||||
func (b *BaseApi) WsHostSSH(c *gin.Context) {
|
func (b *BaseApi) WsHostSSH(c *gin.Context) {
|
||||||
b.runSSHSession(c, "ssh", func() (*ssh.SSHClient, error) {
|
b.runSSHSession(c, func() (*ssh.SSHClient, error) {
|
||||||
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||||
if hostID <= 0 {
|
if hostID <= 0 {
|
||||||
return nil, errors.New("missing host id")
|
return nil, errors.New("missing host id")
|
||||||
@@ -72,33 +65,26 @@ func (b *BaseApi) WsContainerTerminal(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
identity, ok := loadTerminalIdentity(c)
|
|
||||||
if !ok {
|
slave, err := loadContainerTerminalCommand(c)
|
||||||
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
if wshandleError(wsConn, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer slave.Close()
|
||||||
|
|
||||||
|
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||||
|
if wshandleError(wsConn, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := terminal.SessionOptions{
|
quitChan := make(chan bool, 3)
|
||||||
Identity: identity,
|
tty.Start(quitChan)
|
||||||
Kind: "container",
|
go slave.Wait(quitChan)
|
||||||
Target: containerTerminalTarget(c),
|
|
||||||
Cols: cols,
|
|
||||||
Rows: rows,
|
|
||||||
}
|
|
||||||
if err := terminal.ServeCommand(wsConn, strings.TrimSpace(c.Query("session")), opts, func() (*terminal.LocalCommand, error) {
|
|
||||||
return loadContainerTerminalCommand(c)
|
|
||||||
}); err != nil {
|
|
||||||
_ = wshandleError(wsConn, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func containerTerminalTarget(c *gin.Context) string {
|
<-quitChan
|
||||||
query := c.Request.URL.Query()
|
|
||||||
for _, key := range []string{"cols", "rows", "session", "terminalRevalidate"} {
|
global.LOG.Info("websocket finished")
|
||||||
query.Del(key)
|
closeTerminalConn(wsConn)
|
||||||
}
|
|
||||||
sum := sha256.Sum256([]byte(query.Encode()))
|
|
||||||
return hex.EncodeToString(sum[:])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
||||||
@@ -129,127 +115,32 @@ func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
|||||||
return wsConn, cols, rows, true
|
return wsConn, cols, rows, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseApi) runSSHSession(c *gin.Context, kind string, connect func() (*ssh.SSHClient, error), command string) {
|
func (b *BaseApi) runSSHSession(c *gin.Context, connect func() (*ssh.SSHClient, error), command string) {
|
||||||
wsConn, cols, rows, ok := prepareTerminalSession(c)
|
wsConn, cols, rows, ok := prepareTerminalSession(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
identity, ok := loadTerminalIdentity(c)
|
|
||||||
if !ok {
|
|
||||||
_ = wshandleError(wsConn, errors.New("missing terminal identity"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hostID := 0
|
client, clientErr := connect()
|
||||||
if kind == "ssh" {
|
if wshandleError(wsConn, errors.WithMessage(clientErr, "failed to set up the connection. Please check the host information")) {
|
||||||
hostID, _ = strconv.Atoi(c.DefaultQuery("id", "0"))
|
return
|
||||||
}
|
}
|
||||||
opts := terminal.SessionOptions{
|
defer client.Close()
|
||||||
Identity: identity,
|
|
||||||
Kind: kind,
|
|
||||||
Title: sanitizeTerminalTitle(c.Query("title")),
|
|
||||||
HostID: uint(max(hostID, 0)),
|
|
||||||
Cols: cols,
|
|
||||||
Rows: rows,
|
|
||||||
InitCmd: command,
|
|
||||||
}
|
|
||||||
err := terminal.Serve(wsConn, strings.TrimSpace(c.Query("session")), opts, func() (*gossh.Client, error) {
|
|
||||||
client, err := connect()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to set up the connection. Please check the host information")
|
|
||||||
}
|
|
||||||
return client.Client, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
_ = wshandleError(wsConn, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Terminal
|
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
|
||||||
// @Summary List the caller's live terminal sessions
|
if wshandleError(wsConn, err) {
|
||||||
// @Success 200 {array} terminal.Info
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/terminal/sessions/search [post]
|
|
||||||
func (b *BaseApi) SearchTerminalSessions(c *gin.Context) {
|
|
||||||
identity, ok := loadTerminalIdentity(c)
|
|
||||||
if !ok {
|
|
||||||
helper.BadRequest(c, errors.New("missing terminal identity"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, terminal.List(identity))
|
defer sws.Close()
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Terminal
|
quitChan := make(chan bool, 3)
|
||||||
// @Summary Close a terminal session
|
sws.Start(quitChan)
|
||||||
// @Accept json
|
go sws.Wait(quitChan)
|
||||||
// @Param request body dto.TerminalSessionClose true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/terminal/sessions/close [post]
|
|
||||||
func (b *BaseApi) CloseTerminalSession(c *gin.Context) {
|
|
||||||
var req dto.TerminalSessionClose
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
identity, ok := loadTerminalIdentity(c)
|
|
||||||
if !ok {
|
|
||||||
helper.BadRequest(c, errors.New("missing terminal identity"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := terminal.CloseSession(req.ID, identity); err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Terminal
|
<-quitChan
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /hosts/terminal/sessions/closeAll [post]
|
|
||||||
func (b *BaseApi) CloseAllTerminalSessions(c *gin.Context) {
|
|
||||||
identity, ok := loadTerminalIdentity(c)
|
|
||||||
if !ok {
|
|
||||||
helper.BadRequest(c, errors.New("missing terminal identity"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
terminal.Revoke("auth_session", identity.UserID, identity.AuthSessionID)
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BaseApi) RevokeTerminalSessions(c *gin.Context) {
|
closeTerminalConn(wsConn)
|
||||||
var req dto.TerminalSessionRevoke
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (req.Scope == "auth_session" && (req.UserID == "" || req.AuthSessionID == "")) ||
|
|
||||||
(req.Scope == "user" && req.UserID == "") {
|
|
||||||
helper.BadRequest(c, errors.New("missing terminal revocation identity"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
terminal.Revoke(req.Scope, req.UserID, req.AuthSessionID)
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadTerminalIdentity(c *gin.Context) (terminal.Identity, bool) {
|
|
||||||
identity := terminal.Identity{
|
|
||||||
UserID: strings.TrimSpace(c.GetHeader(terminal.HeaderUserID)),
|
|
||||||
AuthSessionID: strings.TrimSpace(c.GetHeader(terminal.HeaderAuthSessionID)),
|
|
||||||
}
|
|
||||||
return identity, identity.Valid()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitizeTerminalTitle keeps the title a short single line.
|
|
||||||
func sanitizeTerminalTitle(title string) string {
|
|
||||||
title = strings.Join(strings.Fields(title), " ")
|
|
||||||
if r := []rune(title); len(r) > 64 {
|
|
||||||
title = string(r[:64])
|
|
||||||
}
|
|
||||||
return title
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeTerminalConn(wsConn *websocket.Conn) {
|
func closeTerminalConn(wsConn *websocket.Conn) {
|
||||||
|
|||||||
@@ -210,27 +210,6 @@ func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) {
|
|||||||
helper.Success(c)
|
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
|
// @Tags Website SSL
|
||||||
// @Summary Upload ssl
|
// @Summary Upload ssl
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -269,8 +248,6 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
|
|||||||
var req request.WebsiteSSLFileUpload
|
var req request.WebsiteSSLFileUpload
|
||||||
|
|
||||||
req.Description = c.PostForm("description")
|
req.Description = c.PostForm("description")
|
||||||
req.Nodes = c.PostForm("nodes")
|
|
||||||
req.PushNode, _ = strconv.ParseBool(c.PostForm("pushNode"))
|
|
||||||
sslID := c.PostForm("sslID")
|
sslID := c.PostForm("sslID")
|
||||||
if sslID != "" {
|
if sslID != "" {
|
||||||
req.SSLID, _ = strconv.ParseUint(sslID, 10, 64)
|
req.SSLID, _ = strconv.ParseUint(sslID, 10, 64)
|
||||||
@@ -306,8 +283,6 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
|
|||||||
Certificate: string(certificateContent),
|
Certificate: string(certificateContent),
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
SSLID: uint(req.SSLID),
|
SSLID: uint(req.SSLID),
|
||||||
PushNode: req.PushNode,
|
|
||||||
Nodes: req.Nodes,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := websiteSSLService.Upload(uploadReq); err != nil {
|
if err := websiteSSLService.Upload(uploadReq); err != nil {
|
||||||
|
|||||||
@@ -1,259 +0,0 @@
|
|||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"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/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Page website templates
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteTemplateSearch true "request"
|
|
||||||
// @Success 200 {object} dto.PageResult
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/search [post]
|
|
||||||
func (b *BaseApi) PageWebsiteTemplate(c *gin.Context) {
|
|
||||||
var req request.WebsiteTemplateSearch
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
total, templates, err := websiteTemplateService.PageTemplate(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, dto.PageResult{
|
|
||||||
Total: total,
|
|
||||||
Items: templates,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Create website template
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteTemplateCreate true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站模板 [name]","formatEN":"Create website template [name]"}
|
|
||||||
func (b *BaseApi) CreateWebsiteTemplate(c *gin.Context) {
|
|
||||||
var req request.WebsiteTemplateCreate
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := websiteTemplateService.CreateTemplate(req); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Update website template
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteTemplateUpdate true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/update [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新网站模板 [name]","formatEN":"Update website template [name]"}
|
|
||||||
func (b *BaseApi) UpdateWebsiteTemplate(c *gin.Context) {
|
|
||||||
var req request.WebsiteTemplateUpdate
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := websiteTemplateService.UpdateTemplate(req); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Delete website template
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.OperateByID true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/del [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_templates","output_column":"name","output_value":"name"}],"formatZH":"删除网站模板 [name]","formatEN":"Delete website template [name]"}
|
|
||||||
func (b *BaseApi) DeleteWebsiteTemplate(c *gin.Context) {
|
|
||||||
var req dto.OperateByID
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := websiteTemplateService.DeleteTemplate(req.ID); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Get website template
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.OperateByID true "request"
|
|
||||||
// @Success 200 {object} response.WebsiteTemplateDTO
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/get [post]
|
|
||||||
func (b *BaseApi) GetWebsiteTemplate(c *gin.Context) {
|
|
||||||
var req dto.OperateByID
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
template, err := websiteTemplateService.GetTemplate(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, template)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Upload website template zip
|
|
||||||
// @Accept multipart/form-data
|
|
||||||
// @Param file formData file true "file"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/upload [post]
|
|
||||||
func (b *BaseApi) UploadTemplateZip(c *gin.Context) {
|
|
||||||
fileHeader, err := c.FormFile("file")
|
|
||||||
if err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, err := fileHeader.Open()
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
content, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
filePath, variables, err := websiteTemplateService.SaveUploadZip(fileHeader.Filename, content)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, gin.H{"filePath": filePath, "variables": variables})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Preview website template
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsitePreviewReq true "request"
|
|
||||||
// @Success 200 {object} response.WebsitePreviewDTO
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/preview [post]
|
|
||||||
func (b *BaseApi) PreviewWebsiteTemplate(c *gin.Context) {
|
|
||||||
var req request.WebsitePreviewReq
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
preview, err := websiteTemplateService.Preview(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, preview)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Page website template outputs
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteTemplateOutputSearch true "request"
|
|
||||||
// @Success 200 {object} dto.PageResult
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/outputs/search [post]
|
|
||||||
func (b *BaseApi) PageWebsiteTemplateOutput(c *gin.Context) {
|
|
||||||
var req request.WebsiteTemplateOutputSearch
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
total, outputs, err := websiteTemplateService.PageOutput(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, dto.PageResult{
|
|
||||||
Total: total,
|
|
||||||
Items: outputs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Create website template output
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteTemplateOutputCreate true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/outputs [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成模板产物 [name]","formatEN":"Generate template output [name]"}
|
|
||||||
func (b *BaseApi) CreateWebsiteTemplateOutput(c *gin.Context) {
|
|
||||||
var req request.WebsiteTemplateOutputCreate
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := websiteTemplateService.CreateOutput(req); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Delete website template output
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.OperateByID true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/outputs/del [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_template_outputs","output_column":"name","output_value":"name"}],"formatZH":"删除模板产物 [name]","formatEN":"Delete template output [name]"}
|
|
||||||
func (b *BaseApi) DeleteWebsiteTemplateOutput(c *gin.Context) {
|
|
||||||
var req dto.OperateByID
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := websiteTemplateService.DeleteOutput(req.ID); err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.Success(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website Template
|
|
||||||
// @Summary Get website template output
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.OperateByID true "request"
|
|
||||||
// @Success 200 {object} response.WebsiteTemplateOutputDTO
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Security Timestamp
|
|
||||||
// @Router /websites/templates/outputs/get [post]
|
|
||||||
func (b *BaseApi) GetWebsiteTemplateOutput(c *gin.Context) {
|
|
||||||
var req dto.OperateByID
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output, err := websiteTemplateService.GetOutput(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, output)
|
|
||||||
}
|
|
||||||
+70
-143
@@ -3,30 +3,28 @@ package dto
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type AgentCreateReq struct {
|
type AgentCreateReq struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
AppVersion string `json:"appVersion" validate:"required"`
|
AppVersion string `json:"appVersion" validate:"required"`
|
||||||
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
|
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
|
||||||
BridgePort int `json:"bridgePort"`
|
BridgePort int `json:"bridgePort"`
|
||||||
AllowedOrigins []string `json:"allowedOrigins"`
|
AllowedOrigins []string `json:"allowedOrigins"`
|
||||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
AccountID uint `json:"accountId"`
|
AccountID uint `json:"accountId"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
DashboardUsername string `json:"dashboardUsername"`
|
TaskID string `json:"taskID"`
|
||||||
DashboardPassword string `json:"dashboardPassword"`
|
Advanced bool `json:"advanced"`
|
||||||
TaskID string `json:"taskID"`
|
ContainerName string `json:"containerName"`
|
||||||
Advanced bool `json:"advanced"`
|
AllowPort bool `json:"allowPort"`
|
||||||
ContainerName string `json:"containerName"`
|
SpecifyIP string `json:"specifyIP"`
|
||||||
AllowPort bool `json:"allowPort"`
|
RestartPolicy string `json:"restartPolicy"`
|
||||||
SpecifyIP string `json:"specifyIP"`
|
CpuQuota float64 `json:"cpuQuota"`
|
||||||
RestartPolicy string `json:"restartPolicy"`
|
MemoryLimit float64 `json:"memoryLimit"`
|
||||||
CpuQuota float64 `json:"cpuQuota"`
|
MemoryUnit string `json:"memoryUnit"`
|
||||||
MemoryLimit float64 `json:"memoryLimit"`
|
PullImage bool `json:"pullImage"`
|
||||||
MemoryUnit string `json:"memoryUnit"`
|
EditCompose bool `json:"editCompose"`
|
||||||
PullImage bool `json:"pullImage"`
|
DockerCompose string `json:"dockerCompose"`
|
||||||
EditCompose bool `json:"editCompose"`
|
|
||||||
DockerCompose string `json:"dockerCompose"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentBatchInstallReq struct {
|
type AgentBatchInstallReq struct {
|
||||||
@@ -40,8 +38,6 @@ type AgentBatchInstallReq struct {
|
|||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
AccountID uint `json:"accountId"`
|
AccountID uint `json:"accountId"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
DashboardUsername string `json:"dashboardUsername"`
|
|
||||||
DashboardPassword string `json:"dashboardPassword"`
|
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
Advanced bool `json:"advanced"`
|
Advanced bool `json:"advanced"`
|
||||||
ContainerName string `json:"containerName"`
|
ContainerName string `json:"containerName"`
|
||||||
@@ -118,11 +114,11 @@ type AgentItem struct {
|
|||||||
ProviderName string `json:"providerName"`
|
ProviderName string `json:"providerName"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
APIType string `json:"apiType"`
|
APIType string `json:"apiType"`
|
||||||
|
MaxTokens int `json:"maxTokens"`
|
||||||
|
ContextWindow int `json:"contextWindow"`
|
||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
APIKey string `json:"apiKey"`
|
APIKey string `json:"apiKey"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
DashboardUsername string `json:"dashboardUsername"`
|
|
||||||
DashboardPassword string `json:"dashboardPassword"`
|
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
AppInstallID uint `json:"appInstallId"`
|
AppInstallID uint `json:"appInstallId"`
|
||||||
@@ -290,22 +286,19 @@ type AgentOverviewSnapshot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountModel struct {
|
type AgentAccountModel struct {
|
||||||
RecordID uint `json:"recordId"`
|
RecordID uint `json:"recordId"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
ContextWindow int `json:"contextWindow"`
|
||||||
|
MaxTokens int `json:"maxTokens"`
|
||||||
|
Reasoning bool `json:"reasoning"`
|
||||||
|
Input []string `json:"input"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountModelReq struct {
|
type AgentAccountModelReq struct {
|
||||||
AccountID uint `json:"accountId" validate:"required"`
|
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 {
|
type AgentAccountModelCreateReq struct {
|
||||||
AccountID uint `json:"accountId" validate:"required"`
|
AccountID uint `json:"accountId" validate:"required"`
|
||||||
Model AgentAccountModel `json:"model" validate:"required"`
|
Model AgentAccountModel `json:"model" validate:"required"`
|
||||||
@@ -322,40 +315,31 @@ type AgentAccountModelDeleteReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountCreateReq struct {
|
type AgentAccountCreateReq struct {
|
||||||
Provider string `json:"provider" validate:"required"`
|
Provider string `json:"provider" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
APIKey string `json:"apiKey" validate:"required"`
|
APIKey string `json:"apiKey" validate:"required"`
|
||||||
RememberAPIKey bool `json:"rememberApiKey"`
|
RememberAPIKey bool `json:"rememberApiKey"`
|
||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
Models []AgentAccountModel `json:"models"`
|
Models []AgentAccountModel `json:"models"`
|
||||||
APIType string `json:"apiType" validate:"required"`
|
APIType string `json:"apiType" validate:"required"`
|
||||||
AuthMode string `json:"authMode"`
|
Remark string `json:"remark"`
|
||||||
VerifyModel string `json:"verifyModel"`
|
|
||||||
ValidateAvailability *bool `json:"validateAvailability"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountUpdateReq struct {
|
type AgentAccountUpdateReq struct {
|
||||||
ID uint `json:"id" validate:"required"`
|
ID uint `json:"id" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
APIKey string `json:"apiKey" validate:"required"`
|
APIKey string `json:"apiKey" validate:"required"`
|
||||||
RememberAPIKey bool `json:"rememberApiKey"`
|
RememberAPIKey bool `json:"rememberApiKey"`
|
||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
APIType string `json:"apiType" validate:"required"`
|
APIType string `json:"apiType" validate:"required"`
|
||||||
AuthMode string `json:"authMode"`
|
Remark string `json:"remark"`
|
||||||
VerifyModel string `json:"verifyModel"`
|
SyncAgents bool `json:"syncAgents"`
|
||||||
ValidateAvailability *bool `json:"validateAvailability"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
SyncAgents bool `json:"syncAgents"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountVerifyReq struct {
|
type AgentAccountVerifyReq struct {
|
||||||
Provider string `json:"provider" validate:"required"`
|
Provider string `json:"provider" validate:"required"`
|
||||||
APIKey string `json:"apiKey" validate:"required"`
|
APIKey string `json:"apiKey" validate:"required"`
|
||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
APIType string `json:"apiType" validate:"required"`
|
|
||||||
AuthMode string `json:"authMode"`
|
|
||||||
Model string `json:"model"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentAccountDeleteReq struct {
|
type AgentAccountDeleteReq struct {
|
||||||
@@ -365,8 +349,6 @@ type AgentAccountDeleteReq struct {
|
|||||||
type AgentAccountSearch struct {
|
type AgentAccountSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
APIType string `json:"apiType"`
|
|
||||||
TextOnly bool `json:"textOnly"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,36 +367,26 @@ type AgentAccountInfo struct {
|
|||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
Models []AgentAccountModel `json:"models"`
|
Models []AgentAccountModel `json:"models"`
|
||||||
APIType string `json:"apiType"`
|
APIType string `json:"apiType"`
|
||||||
AuthMode string `json:"authMode"`
|
|
||||||
VerifyModel string `json:"verifyModel"`
|
|
||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderModelInfo struct {
|
type ProviderModelInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
ContextWindow int `json:"contextWindow"`
|
||||||
|
MaxTokens int `json:"maxTokens"`
|
||||||
type ProviderAPIInfo struct {
|
Reasoning bool `json:"reasoning"`
|
||||||
APIType string `json:"apiType"`
|
Input []string `json:"input"`
|
||||||
BaseURL string `json:"baseUrl"`
|
|
||||||
EditableBaseURL bool `json:"editableBaseUrl"`
|
|
||||||
SupportsModelDiscovery bool `json:"supportsModelDiscovery"`
|
|
||||||
DefaultAuthMode string `json:"defaultAuthMode"`
|
|
||||||
AuthModes []string `json:"authModes"`
|
|
||||||
Models []ProviderModelInfo `json:"models"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderInfo struct {
|
type ProviderInfo struct {
|
||||||
Sort uint `json:"-"`
|
Sort uint `json:"-"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
DefaultAPIType string `json:"defaultApiType"`
|
Models []ProviderModelInfo `json:"models"`
|
||||||
APITypes []ProviderAPIInfo `json:"apiTypes"`
|
|
||||||
Models []ProviderModelInfo `json:"models"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentFeishuConfigReq struct {
|
type AgentFeishuConfigReq struct {
|
||||||
@@ -435,6 +407,11 @@ type AgentFeishuConfigUpdateReq struct {
|
|||||||
Bots []AgentFeishuBot `json:"bots" validate:"required,min=1"`
|
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 {
|
type AgentFeishuConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
ThreadSession bool `json:"threadSession"`
|
ThreadSession bool `json:"threadSession"`
|
||||||
@@ -604,52 +581,6 @@ type AgentPluginStatus struct {
|
|||||||
Upgradable bool `json:"upgradable"`
|
Upgradable bool `json:"upgradable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentPluginsReq struct {
|
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentPluginSearchReq struct {
|
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
|
||||||
Keyword string `json:"keyword" validate:"required,max=100"`
|
|
||||||
Limit int `json:"limit" validate:"omitempty,min=1,max=100"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentPluginMarketInstallReq struct {
|
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
|
||||||
Package string `json:"package" validate:"required,max=200"`
|
|
||||||
Version string `json:"version" validate:"required,max=100"`
|
|
||||||
TaskID string `json:"taskID" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentPluginOperateReq struct {
|
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
|
||||||
PluginID string `json:"pluginId" validate:"required,max=200"`
|
|
||||||
Operate string `json:"operate" validate:"required,oneof=enable disable update uninstall"`
|
|
||||||
TaskID string `json:"taskID" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentPluginItem struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Origin string `json:"origin"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentPluginSearchItem struct {
|
|
||||||
Package string `json:"package"`
|
|
||||||
PluginID string `json:"pluginId"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Channel string `json:"channel"`
|
|
||||||
VerificationTier string `json:"verificationTier"`
|
|
||||||
Categories []string `json:"categories"`
|
|
||||||
Official bool `json:"official"`
|
|
||||||
Downloads int64 `json:"downloads"`
|
|
||||||
Score float64 `json:"score"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentDiscordConfigUpdateReq struct {
|
type AgentDiscordConfigUpdateReq struct {
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
AgentID uint `json:"agentId" validate:"required"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
@@ -729,20 +660,16 @@ type AgentSecurityConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentOtherConfigUpdateReq struct {
|
type AgentOtherConfigUpdateReq struct {
|
||||||
AgentID uint `json:"agentId" validate:"required"`
|
AgentID uint `json:"agentId" validate:"required"`
|
||||||
UserTimezone string `json:"userTimezone"`
|
UserTimezone string `json:"userTimezone" validate:"required"`
|
||||||
BrowserEnabled bool `json:"browserEnabled"`
|
BrowserEnabled bool `json:"browserEnabled"`
|
||||||
NPMRegistry string `json:"npmRegistry"`
|
NPMRegistry string `json:"npmRegistry" validate:"required"`
|
||||||
DashboardUsername string `json:"dashboardUsername"`
|
|
||||||
DashboardPassword string `json:"dashboardPassword"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentOtherConfig struct {
|
type AgentOtherConfig struct {
|
||||||
UserTimezone string `json:"userTimezone"`
|
UserTimezone string `json:"userTimezone"`
|
||||||
BrowserEnabled bool `json:"browserEnabled"`
|
BrowserEnabled bool `json:"browserEnabled"`
|
||||||
NPMRegistry string `json:"npmRegistry"`
|
NPMRegistry string `json:"npmRegistry"`
|
||||||
DashboardUsername string `json:"dashboardUsername"`
|
|
||||||
DashboardPassword string `json:"dashboardPassword"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentConfigFileReq struct {
|
type AgentConfigFileReq struct {
|
||||||
|
|||||||
+18
-33
@@ -113,10 +113,8 @@ type DiskDTO struct {
|
|||||||
|
|
||||||
type AlertLogSearch struct {
|
type AlertLogSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
Count uint `json:"count"`
|
Count uint `json:"count"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
StartTime time.Time `json:"startTime"`
|
|
||||||
EndTime time.Time `json:"endTime"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertLogDTO struct {
|
type AlertLogDTO struct {
|
||||||
@@ -151,25 +149,16 @@ type AlertLog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertDetail struct {
|
type AlertDetail struct {
|
||||||
LicenseId string `json:"licenseId"`
|
LicenseId string `json:"licenseId"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SubType string `json:"subType"`
|
SubType string `json:"subType"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
LicenseCode string `json:"licenseCode"`
|
LicenseCode string `json:"licenseCode"`
|
||||||
DeviceId string `json:"deviceId"`
|
DeviceId string `json:"deviceId"`
|
||||||
Project string `json:"project"`
|
Project string `json:"project"`
|
||||||
Params []Param `json:"params"`
|
Params []Param `json:"params"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Task *AlertTaskMetadata `json:"task,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertTaskMetadata struct {
|
|
||||||
AlertID uint `json:"alertId"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Quota string `json:"quota"`
|
|
||||||
QuotaType string `json:"quotaType"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertRule struct {
|
type AlertRule struct {
|
||||||
@@ -304,19 +293,15 @@ type OfflineQueryRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertConfigUpdate struct {
|
type AlertConfigUpdate struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Config string `json:"config"`
|
Config string `json:"config"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
Revision *time.Time `json:"revision"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertConfigTest struct {
|
type AlertConfigTest struct {
|
||||||
ID uint `json:"id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Config string `json:"config"`
|
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Sender string `json:"sender"`
|
Sender string `json:"sender"`
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package dto
|
|
||||||
|
|
||||||
const AlertCustomWebhookSchemaVersion = 1
|
|
||||||
|
|
||||||
type AlertConfigStatusUpdate struct {
|
|
||||||
ID uint `json:"id" validate:"required"`
|
|
||||||
Status string `json:"status" validate:"required,oneof=Enable Disable"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookSecretMutation struct {
|
|
||||||
Action string `json:"action,omitempty"`
|
|
||||||
Value string `json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookURL struct {
|
|
||||||
AlertCustomWebhookSecretMutation
|
|
||||||
Configured bool `json:"configured"`
|
|
||||||
Masked string `json:"masked,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookBody struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Template string `json:"template,omitempty"`
|
|
||||||
Fields []AlertCustomWebhookFormField `json:"fields,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookFormField struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookHeader struct {
|
|
||||||
UID string `json:"uid"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
Secret bool `json:"secret"`
|
|
||||||
Action string `json:"action,omitempty"`
|
|
||||||
Value string `json:"value,omitempty"`
|
|
||||||
Configured bool `json:"configured,omitempty"`
|
|
||||||
Masked string `json:"masked,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookConfig struct {
|
|
||||||
SchemaVersion int `json:"schemaVersion"`
|
|
||||||
State string `json:"state,omitempty"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
Preset string `json:"preset"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
URL AlertCustomWebhookURL `json:"url"`
|
|
||||||
Body AlertCustomWebhookBody `json:"body"`
|
|
||||||
Headers []AlertCustomWebhookHeader `json:"headers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookSecretConfig struct {
|
|
||||||
SchemaVersion int `json:"schemaVersion"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Headers map[string]string `json:"headers,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookResolvedConfig struct {
|
|
||||||
SchemaVersion int
|
|
||||||
DisplayName string
|
|
||||||
Preset string
|
|
||||||
Method string
|
|
||||||
URL string
|
|
||||||
Body AlertCustomWebhookBody
|
|
||||||
Headers []AlertCustomWebhookResolvedHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertCustomWebhookResolvedHeader struct {
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertConfigTestResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
StatusCode int `json:"statusCode,omitempty"`
|
|
||||||
Duration int64 `json:"duration,omitempty"` // milliseconds
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
Response string `json:"response,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
)
|
)
|
||||||
@@ -133,8 +132,6 @@ type Locale struct {
|
|||||||
Ko string `json:"ko"`
|
Ko string `json:"ko"`
|
||||||
Tr string `json:"tr"`
|
Tr string `json:"tr"`
|
||||||
Es string `json:"es-es" yaml:"es-es"`
|
Es string `json:"es-es" yaml:"es-es"`
|
||||||
Fa string `json:"fa"`
|
|
||||||
Lo string `json:"lo"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppForm struct {
|
type AppForm struct {
|
||||||
|
|||||||
@@ -65,15 +65,14 @@ type UploadForRecover struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CommonBackup 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"`
|
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"`
|
Name string `json:"name"`
|
||||||
DetailName string `json:"detailName"`
|
DetailName string `json:"detailName"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
IsImmediate bool `json:"isImmediate"`
|
StopBefore bool `json:"stopBefore"`
|
||||||
StopBefore bool `json:"stopBefore"`
|
TaskID string `json:"taskID"`
|
||||||
TaskID string `json:"taskID"`
|
FileName string `json:"fileName"`
|
||||||
FileName string `json:"fileName"`
|
Args []string `json:"args"`
|
||||||
Args []string `json:"args"`
|
|
||||||
|
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ package dto
|
|||||||
|
|
||||||
type SearchWithPage struct {
|
type SearchWithPage struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
Info string `json:"info"`
|
Info string `json:"info"`
|
||||||
ExcludeAppStore bool `json:"excludeAppStore"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchPageWithType struct {
|
type SearchPageWithType struct {
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ type PageResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
ErrorCode string `json:"errorCode,omitempty"`
|
Message string `json:"message"`
|
||||||
Message string `json:"message"`
|
Data interface{} `json:"data"`
|
||||||
Data interface{} `json:"data"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
|||||||
@@ -134,15 +134,10 @@ type ExtraHost struct {
|
|||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
type ContainerNetwork struct {
|
type ContainerNetwork struct {
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Ipv4 string `json:"ipv4"`
|
Ipv4 string `json:"ipv4"`
|
||||||
Ipv6 string `json:"ipv6"`
|
Ipv6 string `json:"ipv6"`
|
||||||
MacAddr string `json:"macAddr"`
|
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 {
|
type ContainerCreateByCommand struct {
|
||||||
@@ -300,7 +295,6 @@ type ComposeInfo struct {
|
|||||||
ConfigFile string `json:"configFile"`
|
ConfigFile string `json:"configFile"`
|
||||||
Workdir string `json:"workdir"`
|
Workdir string `json:"workdir"`
|
||||||
ComposeFileExists bool `json:"composeFileExists"`
|
ComposeFileExists bool `json:"composeFileExists"`
|
||||||
IsPinned bool `json:"isPinned"`
|
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Containers []ComposeContainer `json:"containers"`
|
Containers []ComposeContainer `json:"containers"`
|
||||||
Env string `json:"env"`
|
Env string `json:"env"`
|
||||||
@@ -315,7 +309,6 @@ type ComposeContainer struct {
|
|||||||
type ComposeCreate struct {
|
type ComposeCreate struct {
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
DirName string `json:"dirName"`
|
|
||||||
From string `json:"from" validate:"required,oneof=edit path template"`
|
From string `json:"from" validate:"required,oneof=edit path template"`
|
||||||
File string `json:"file"`
|
File string `json:"file"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
@@ -326,7 +319,7 @@ type ComposeCreate struct {
|
|||||||
type ComposeOperation struct {
|
type ComposeOperation struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete rebuild"`
|
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"`
|
||||||
WithFile bool `json:"withFile"`
|
WithFile bool `json:"withFile"`
|
||||||
Force bool `json:"force"`
|
Force bool `json:"force"`
|
||||||
}
|
}
|
||||||
@@ -339,10 +332,6 @@ type ComposeUpdate struct {
|
|||||||
Env string `json:"env"`
|
Env string `json:"env"`
|
||||||
ForcePull bool `json:"forcePull"`
|
ForcePull bool `json:"forcePull"`
|
||||||
}
|
}
|
||||||
type ComposePin struct {
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
IsPinned bool `json:"isPinned"`
|
|
||||||
}
|
|
||||||
type ComposeLogClean struct {
|
type ComposeLogClean struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Path string `json:"path" validate:"required"`
|
Path string `json:"path" validate:"required"`
|
||||||
|
|||||||
@@ -197,7 +197,6 @@ type SearchRecord struct {
|
|||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
CronjobID uint `json:"cronjobID"`
|
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
StartTime string `json:"startTime"`
|
StartTime string `json:"startTime"`
|
||||||
Records string `json:"records"`
|
Records string `json:"records"`
|
||||||
|
|||||||
@@ -79,9 +79,8 @@ type NodeCurrent struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DashboardCurrent struct {
|
type DashboardCurrent struct {
|
||||||
Uptime uint64 `json:"uptime"`
|
Uptime uint64 `json:"uptime"`
|
||||||
TimeSinceUptime string `json:"timeSinceUptime"`
|
TimeSinceUptime string `json:"timeSinceUptime"`
|
||||||
RunningTime RunningTime `json:"runningTime"`
|
|
||||||
|
|
||||||
Procs uint64 `json:"procs"`
|
Procs uint64 `json:"procs"`
|
||||||
|
|
||||||
@@ -121,7 +120,6 @@ type DashboardCurrent struct {
|
|||||||
NetBytesRecv uint64 `json:"netBytesRecv"`
|
NetBytesRecv uint64 `json:"netBytesRecv"`
|
||||||
|
|
||||||
GPUData []GPUInfo `json:"gpuData"`
|
GPUData []GPUInfo `json:"gpuData"`
|
||||||
NPUData []NPUInfo `json:"npuData"`
|
|
||||||
XPUData []XPUInfo `json:"xpuData"`
|
XPUData []XPUInfo `json:"xpuData"`
|
||||||
|
|
||||||
TopCPUItems []Process `json:"topCPUItems"`
|
TopCPUItems []Process `json:"topCPUItems"`
|
||||||
@@ -130,13 +128,6 @@ type DashboardCurrent struct {
|
|||||||
ShotTime time.Time `json:"shotTime"`
|
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 {
|
type AppLauncherSync struct {
|
||||||
Keys []string `json:"keys"`
|
Keys []string `json:"keys"`
|
||||||
}
|
}
|
||||||
@@ -157,12 +148,8 @@ type DiskInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GPUInfo struct {
|
type GPUInfo struct {
|
||||||
Type string `json:"type"`
|
|
||||||
Index uint `json:"index"`
|
Index uint `json:"index"`
|
||||||
NPUIndex uint `json:"npuIndex"`
|
|
||||||
ChipIndex uint `json:"chipIndex"`
|
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
BusID string `json:"busID"`
|
|
||||||
GPUUtil string `json:"gpuUtil"`
|
GPUUtil string `json:"gpuUtil"`
|
||||||
Temperature string `json:"temperature"`
|
Temperature string `json:"temperature"`
|
||||||
PerformanceState string `json:"performanceState"`
|
PerformanceState string `json:"performanceState"`
|
||||||
@@ -175,27 +162,6 @@ type GPUInfo struct {
|
|||||||
FanSpeed string `json:"fanSpeed"`
|
FanSpeed string `json:"fanSpeed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NPUInfo struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Index uint `json:"index"`
|
|
||||||
NPUIndex uint `json:"npuIndex"`
|
|
||||||
ChipIndex uint `json:"chipIndex"`
|
|
||||||
ProductName string `json:"productName"`
|
|
||||||
BusID string `json:"busID"`
|
|
||||||
Health string `json:"health"`
|
|
||||||
Temperature string `json:"temperature"`
|
|
||||||
PowerDraw string `json:"powerDraw"`
|
|
||||||
AICore string `json:"aiCore"`
|
|
||||||
MemUsed string `json:"memUsed"`
|
|
||||||
MemTotal string `json:"memTotal"`
|
|
||||||
MemoryUsed string `json:"memoryUsed"`
|
|
||||||
MemoryTotal string `json:"memoryTotal"`
|
|
||||||
HBMUsed string `json:"hbmUsed"`
|
|
||||||
HBMTotal string `json:"hbmTotal"`
|
|
||||||
HugepagesUsed string `json:"hugepagesUsed"`
|
|
||||||
HugepagesTotal string `json:"hugepagesTotal"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppLauncher struct {
|
type AppLauncher struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -228,13 +194,11 @@ type LauncherOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type XPUInfo struct {
|
type XPUInfo struct {
|
||||||
DeviceID int `json:"deviceID"`
|
DeviceID int `json:"deviceID"`
|
||||||
DeviceName string `json:"deviceName"`
|
DeviceName string `json:"deviceName"`
|
||||||
PciBdfAddress string `json:"pciBdfAddress"`
|
Memory string `json:"memory"`
|
||||||
Memory string `json:"memory"`
|
Temperature string `json:"temperature"`
|
||||||
Temperature string `json:"temperature"`
|
MemoryUsed string `json:"memoryUsed"`
|
||||||
GPUUtil string `json:"gpuUtil"`
|
Power string `json:"power"`
|
||||||
MemoryUsed string `json:"memoryUsed"`
|
MemoryUtil string `json:"memoryUtil"`
|
||||||
Power string `json:"power"`
|
|
||||||
MemoryUtil string `json:"memoryUtil"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-69
@@ -60,84 +60,25 @@ type MysqlDBCreate struct {
|
|||||||
Database string `json:"database" validate:"required"`
|
Database string `json:"database" validate:"required"`
|
||||||
Format string `json:"format" validate:"required"`
|
Format string `json:"format" validate:"required"`
|
||||||
Collation string `json:"collation"`
|
Collation string `json:"collation"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username" validate:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password" validate:"required"`
|
||||||
Permission string `json:"permission" validate:"required"`
|
Permission string `json:"permission" validate:"required"`
|
||||||
Description string `json:"description"`
|
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 {
|
type MysqlFormatCollationOption struct {
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
Collations []string `json:"collations"`
|
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 {
|
type MysqlLoadDB struct {
|
||||||
From string `json:"from" validate:"required,oneof=local remote"`
|
From string `json:"from" validate:"required,oneof=local remote"`
|
||||||
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
|
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
|
||||||
|
|||||||
+84
-333
@@ -1,362 +1,113 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
type FirewallBaseInfo struct {
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
Name string `json:"name"`
|
||||||
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
|
IsExist bool `json:"isExist"`
|
||||||
)
|
IsActive bool `json:"isActive"`
|
||||||
|
IsInit bool `json:"isInit"`
|
||||||
type FirewallSubsystemStatus struct {
|
IsBind bool `json:"isBind"`
|
||||||
Name string `json:"name"`
|
Version string `json:"version"`
|
||||||
Backend string `json:"backend"`
|
PingStatus string `json:"pingStatus"`
|
||||||
ConflictBackend string `json:"conflictBackend,omitempty"`
|
|
||||||
IsExist bool `json:"isExist"`
|
|
||||||
IsActive bool `json:"isActive"`
|
|
||||||
IsInit bool `json:"isInit"`
|
|
||||||
IsBind bool `json:"isBind"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
PingStatus string `json:"pingStatus"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
Reason string `json:"reason,omitempty"`
|
|
||||||
SyncError string `json:"syncError,omitempty"`
|
|
||||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
|
||||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallLifecycleOperation struct {
|
type RuleSearch struct {
|
||||||
|
PageInfo
|
||||||
|
Info string `json:"info"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
Type string `json:"type" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirewallOperation struct {
|
||||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
||||||
WithDockerRestart bool `json:"withDockerRestart"`
|
WithDockerRestart bool `json:"withDockerRestart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendOption struct {
|
type PortRuleOperate struct {
|
||||||
Name string `json:"name"`
|
ID uint `json:"id"`
|
||||||
Installed bool `json:"installed"`
|
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||||
Active bool `json:"active"`
|
Chain string `json:"chain"`
|
||||||
Initialized bool `json:"initialized"`
|
Address string `json:"address"`
|
||||||
Bound bool `json:"bound"`
|
Port string `json:"port" validate:"required"`
|
||||||
Supported bool `json:"supported"`
|
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||||
SupportReason string `json:"supportReason,omitempty"`
|
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||||
Implementation string `json:"implementation,omitempty"`
|
|
||||||
Message string `json:"message,omitempty"`
|
Description string `json:"description"`
|
||||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
|
||||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendFamilyStatus struct {
|
type ForwardRuleOperate struct {
|
||||||
Available bool `json:"available"`
|
ForceDelete bool `json:"forceDelete"`
|
||||||
Initialized bool `json:"initialized"`
|
Rules []struct {
|
||||||
Bound bool `json:"bound"`
|
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Num string `json:"num"`
|
||||||
|
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||||
|
Interface string `json:"interface"`
|
||||||
|
Port string `json:"port" validate:"required"`
|
||||||
|
TargetIP string `json:"targetIP"`
|
||||||
|
TargetPort string `json:"targetPort" validate:"required"`
|
||||||
|
} `json:"rules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendGroup struct {
|
type UpdateFirewallDescription struct {
|
||||||
Selected string `json:"selected"`
|
Type string `json:"type"`
|
||||||
Current string `json:"current,omitempty"`
|
Chain string `json:"chain"`
|
||||||
Options []FirewallBackendOption `json:"options"`
|
SrcIP string `json:"srcIP"`
|
||||||
|
DstIP string `json:"dstIP"`
|
||||||
|
SrcPort string `json:"srcPort"`
|
||||||
|
DstPort string `json:"dstPort"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||||
|
|
||||||
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallSettings struct {
|
type AddrRuleOperate struct {
|
||||||
System FirewallBackendGroup `json:"system"`
|
ID uint `json:"id"`
|
||||||
Forwarding FirewallBackendGroup `json:"forwarding"`
|
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||||
Docker FirewallBackendGroup `json:"docker"`
|
Address string `json:"address" validate:"required"`
|
||||||
PingStatus string `json:"pingStatus"`
|
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||||
PortWhitelist string `json:"portWhiteList"`
|
|
||||||
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallPortWhitelistUpdate struct {
|
type PortRuleUpdate struct {
|
||||||
Value string `json:"value" validate:"required"`
|
OldRule PortRuleOperate `json:"oldRule"`
|
||||||
|
NewRule PortRuleOperate `json:"newRule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendOperation struct {
|
type AddrRuleUpdate struct {
|
||||||
Subsystem string `json:"subsystem" validate:"required,oneof=system forwarding docker"`
|
OldRule AddrRuleOperate `json:"oldRule"`
|
||||||
Backend string `json:"backend" validate:"required,oneof=firewalld ufw iptables nftables"`
|
NewRule AddrRuleOperate `json:"newRule"`
|
||||||
Operation string `json:"operation" validate:"required,oneof=select initialize cleanup"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterChainOperation struct {
|
type BatchRuleOperate struct {
|
||||||
Name string `json:"name" validate:"required,eq=1PANEL_BASIC"`
|
Type string `json:"type" validate:"required"`
|
||||||
Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"`
|
Rules []PortRuleOperate `json:"rules"`
|
||||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterChainOperationResponse struct {
|
type IptablesOp struct {
|
||||||
TaskID string `json:"taskID"`
|
Name string `json:"name" validate:"required,oneof=1PANEL_INPUT 1PANEL_OUTPUT 1PANEL_BASIC"`
|
||||||
Queued bool `json:"queued"`
|
Operate string `json:"operate" validate:"required,oneof=init-base init-forward init-advance bind-base unbind-base bind unbind"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallInitializationTask struct {
|
type IptablesRuleOp struct {
|
||||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Chain string `json:"chain" validate:"required,oneof=1PANEL_BASIC 1PANEL_BASIC_BEFORE 1PANEL_INPUT 1PANEL_OUTPUT"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
SrcIP string `json:"srcIP"`
|
||||||
|
SrcPort uint `json:"srcPort"`
|
||||||
|
DstIP string `json:"dstIP"`
|
||||||
|
DstPort uint `json:"dstPort"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=accept drop reject"`
|
||||||
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallSystemPort struct {
|
type IptablesBatchOperate struct {
|
||||||
Family string
|
Rules []IptablesRuleOp `json:"rules"`
|
||||||
Port string
|
|
||||||
Protocol string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRuleInventoryResponse struct {
|
type IptablesChainStatus struct {
|
||||||
IPv4Range filter.PositionRange `json:"ipv4Range"`
|
IsBind bool `json:"isBind"`
|
||||||
IPv6Range filter.PositionRange `json:"ipv6Range"`
|
DefaultStrategy string `json:"defaultStrategy"`
|
||||||
Total int64 `json:"total"`
|
|
||||||
AllTotal int64 `json:"allTotal"`
|
|
||||||
ManagedTotal int64 `json:"managedTotal"`
|
|
||||||
Items []filter.InventoryItem `json:"items"`
|
|
||||||
Notices []filter.ScopeNotice `json:"notices,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleResetResponse struct {
|
|
||||||
Removed int `json:"removed"`
|
|
||||||
Disabled bool `json:"disabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleReset struct {
|
|
||||||
Provider filter.Provider `json:"provider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
|
||||||
WithDockerRestart bool `json:"withDockerRestart"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleInventory struct {
|
|
||||||
PageInfo
|
|
||||||
Scope filter.Scope `json:"scope,omitempty"`
|
|
||||||
Scopes []filter.Scope `json:"scopes,omitempty" validate:"max=16"`
|
|
||||||
All bool `json:"all,omitempty"`
|
|
||||||
Info string `json:"info"`
|
|
||||||
Families []filter.Family `json:"families,omitempty" validate:"omitempty,dive,oneof=ipv4 ipv6"`
|
|
||||||
Actions []string `json:"actions,omitempty" validate:"omitempty,dive,oneof=accept deny"`
|
|
||||||
States []filter.InventoryState `json:"states,omitempty" validate:"omitempty,dive,oneof=managed adopted external drifted protected"`
|
|
||||||
ExcludeChains []string `json:"excludeChains,omitempty" validate:"omitempty,dive,oneof=1PANEL_BASIC_BEFORE 1PANEL_BASIC 1PANEL_BASIC_AFTER"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallNativeDetail struct {
|
|
||||||
Provider filter.Provider `json:"provider" validate:"required,oneof=firewalld ufw"`
|
|
||||||
NativeKind filter.NativeKind `json:"nativeKind" validate:"required,oneof=zone_service ufw_application"`
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Permanent bool `json:"permanent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardBase struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
IsExist bool `json:"isExist"`
|
|
||||||
Initialized bool `json:"initialized"`
|
|
||||||
Bound bool `json:"bound"`
|
|
||||||
IPv4 DockerPortGuardFamilyStatus `json:"ipv4"`
|
|
||||||
IPv6 DockerPortGuardFamilyStatus `json:"ipv6"`
|
|
||||||
Backend string `json:"backend"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardFamilyStatus struct {
|
|
||||||
State string `json:"state"`
|
|
||||||
Reason string `json:"reason,omitempty"`
|
|
||||||
Initialized bool `json:"initialized"`
|
|
||||||
Bound bool `json:"bound"`
|
|
||||||
Effective bool `json:"effective"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardEndpoint struct {
|
|
||||||
Family string `json:"family"`
|
|
||||||
HostIP string `json:"hostIP"`
|
|
||||||
HostPort uint16 `json:"hostPort"`
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
ContainerID string `json:"containerID"`
|
|
||||||
ContainerName string `json:"containerName"`
|
|
||||||
ContainerState string `json:"containerState,omitempty"`
|
|
||||||
ContainerPort uint16 `json:"containerPort"`
|
|
||||||
Compose string `json:"compose,omitempty"`
|
|
||||||
Application string `json:"application,omitempty"`
|
|
||||||
PolicyUUID string `json:"policyUUID,omitempty"`
|
|
||||||
Mode string `json:"mode,omitempty"`
|
|
||||||
NativeAction string `json:"nativeAction,omitempty"`
|
|
||||||
ReadOnly bool `json:"readOnly,omitempty"`
|
|
||||||
Sources []string `json:"sources"`
|
|
||||||
Effective bool `json:"effective"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
TrafficPath string `json:"trafficPath"`
|
|
||||||
ManagementTarget string `json:"managementTarget"`
|
|
||||||
ManagementReason string `json:"managementReason,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardPortGroup struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Endpoint DockerPortGuardEndpoint `json:"endpoint"`
|
|
||||||
Endpoints []DockerPortGuardEndpoint `json:"endpoints"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardContainer struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Compose string `json:"compose,omitempty"`
|
|
||||||
Application string `json:"application,omitempty"`
|
|
||||||
Endpoints []DockerPortGuardEndpoint `json:"endpoints"`
|
|
||||||
PortGroups []DockerPortGuardPortGroup `json:"portGroups"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardList struct {
|
|
||||||
Base DockerPortGuardBase `json:"base"`
|
|
||||||
Containers []DockerPortGuardContainer `json:"containers"`
|
|
||||||
OrphanPolicies []DockerPortGuardEndpoint `json:"orphanPolicies"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardEndpointIdentity struct {
|
|
||||||
Family string `json:"family" validate:"required,oneof=ipv4 ipv6"`
|
|
||||||
HostIP string `json:"hostIP" validate:"required,max=45"`
|
|
||||||
HostPort uint16 `json:"hostPort" validate:"required,min=1"`
|
|
||||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardPolicyBatch struct {
|
|
||||||
Policies []DockerPortGuardPolicy `json:"policies" validate:"required,min=1,dive"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardPolicyBatchDelete struct {
|
|
||||||
UUIDs []string `json:"uuids" validate:"required,min=1,dive,required,max=64"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardPolicy struct {
|
|
||||||
DockerPortGuardEndpointIdentity
|
|
||||||
Mode string `json:"mode" validate:"required,oneof=deny_sources allow_sources deny_all"`
|
|
||||||
Sources []string `json:"sources" validate:"dive,required,max=64"`
|
|
||||||
Description string `json:"description" validate:"max=256"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardOperation struct {
|
|
||||||
Operation string `json:"operation" validate:"required,oneof=initialize bind unbind"`
|
|
||||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleAdopt struct {
|
|
||||||
Scope filter.Scope `json:"scope" validate:"required"`
|
|
||||||
InstanceKey string `json:"instanceKey" validate:"required,max=128"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleCreateItem struct {
|
|
||||||
Rule filter.FirewallRule `json:"rule" validate:"required"`
|
|
||||||
SourceKind string `json:"sourceKind" validate:"omitempty,oneof=user imported"`
|
|
||||||
SourceID string `json:"sourceID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleCreate struct {
|
|
||||||
Items []FirewallRuleCreateItem `json:"items" validate:"required,min=1,dive"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleCreateResponse struct {
|
|
||||||
TaskID string `json:"taskID,omitempty"`
|
|
||||||
Queued bool `json:"queued,omitempty"`
|
|
||||||
Succeeded int `json:"succeeded"`
|
|
||||||
Failed int `json:"failed"`
|
|
||||||
Skipped int `json:"skipped"`
|
|
||||||
Errors []FirewallRuleCreateFailure `json:"errors,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleCreateFailure struct {
|
|
||||||
Index int `json:"index"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Rule filter.FirewallRule `json:"rule"`
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleSyncRequest struct {
|
|
||||||
Subsystem string `json:"subsystem" validate:"omitempty,oneof=system forwarding docker"`
|
|
||||||
SourceProvider filter.Provider `json:"sourceProvider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
|
||||||
TargetProvider filter.Provider `json:"targetProvider" validate:"required,oneof=firewalld ufw iptables nftables"`
|
|
||||||
ResetSource bool `json:"resetSource"`
|
|
||||||
TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleSyncItem struct {
|
|
||||||
SourceUUID string `json:"sourceUUID"`
|
|
||||||
Rule *filter.FirewallRule `json:"rule,omitempty"`
|
|
||||||
ForwardRule *ForwardRule `json:"forwardRule,omitempty"`
|
|
||||||
DockerRule *DockerPortGuardEndpoint `json:"dockerRule,omitempty"`
|
|
||||||
Status firewallsync.Status `json:"status"`
|
|
||||||
ReasonCode firewallsync.ReasonCode `json:"reasonCode,omitempty"`
|
|
||||||
Reason string `json:"reason,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleSyncPreview struct {
|
|
||||||
Subsystem string `json:"subsystem"`
|
|
||||||
SourceProvider filter.Provider `json:"sourceProvider,omitempty"`
|
|
||||||
TargetProvider filter.Provider `json:"targetProvider"`
|
|
||||||
Total int `json:"total"`
|
|
||||||
Ready int `json:"ready"`
|
|
||||||
Existing int `json:"existing"`
|
|
||||||
Removed int `json:"removed"`
|
|
||||||
Blocked int `json:"blocked"`
|
|
||||||
Items []FirewallRuleSyncItem `json:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleSyncResult struct {
|
|
||||||
Subsystem string `json:"subsystem"`
|
|
||||||
SourceProvider filter.Provider `json:"sourceProvider,omitempty"`
|
|
||||||
TargetProvider filter.Provider `json:"targetProvider"`
|
|
||||||
Total int `json:"total"`
|
|
||||||
Succeeded int `json:"succeeded"`
|
|
||||||
Skipped int `json:"skipped"`
|
|
||||||
Removed int `json:"removed"`
|
|
||||||
Failed int `json:"failed"`
|
|
||||||
Errors []FirewallRuleSyncFailure `json:"errors,omitempty"`
|
|
||||||
TaskID string `json:"taskID,omitempty"`
|
|
||||||
Queued bool `json:"queued,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleSyncTask struct {
|
|
||||||
TaskID string `json:"taskID,omitempty"`
|
|
||||||
Executing bool `json:"executing"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleSyncFailure struct {
|
|
||||||
SourceUUID string `json:"sourceUUID"`
|
|
||||||
Rule *filter.FirewallRule `json:"rule,omitempty"`
|
|
||||||
ForwardRule *ForwardRule `json:"forwardRule,omitempty"`
|
|
||||||
DockerRule *DockerPortGuardEndpoint `json:"dockerRule,omitempty"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleDelete struct {
|
|
||||||
UUIDs []string `json:"uuids" validate:"required,min=1,dive,required,max=64"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleDeleteResponse struct {
|
|
||||||
Succeeded int `json:"succeeded"`
|
|
||||||
Failed int `json:"failed"`
|
|
||||||
Errors []FirewallRuleDeleteFailure `json:"errors,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleDeleteFailure struct {
|
|
||||||
Index int `json:"index"`
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleUpdate struct {
|
|
||||||
UUID string `json:"uuid" validate:"required,max=64"`
|
|
||||||
Rule *filter.FirewallRule `json:"rule,omitempty" validate:"required_without_all=Description OrderIndex Priority,excluded_with=Description OrderIndex Priority"`
|
|
||||||
Description *string `json:"description,omitempty" validate:"excluded_with=Rule"`
|
|
||||||
OrderIndex *int64 `json:"orderIndex,omitempty" validate:"excluded_with=Rule Priority"`
|
|
||||||
Priority *int `json:"priority,omitempty" validate:"excluded_with=Rule OrderIndex"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleReorder struct {
|
|
||||||
UUID string `json:"uuid" validate:"required,max=64"`
|
|
||||||
TargetPosition *int64 `json:"targetPosition"`
|
|
||||||
Priority *int `json:"priority"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *FirewallRuleSyncPreview) Add(item FirewallRuleSyncItem) {
|
|
||||||
p.Items = append(p.Items, item)
|
|
||||||
switch item.Status {
|
|
||||||
case firewallsync.StatusReady:
|
|
||||||
p.Ready++
|
|
||||||
p.Total++
|
|
||||||
case firewallsync.StatusExisting:
|
|
||||||
p.Existing++
|
|
||||||
p.Total++
|
|
||||||
case firewallsync.StatusRemove:
|
|
||||||
p.Removed++
|
|
||||||
case firewallsync.StatusBlocked:
|
|
||||||
p.Blocked++
|
|
||||||
if item.ReasonCode != firewallsync.ReasonReadOnlyRule {
|
|
||||||
p.Total++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package dto
|
|
||||||
|
|
||||||
type ForwardRuleSearch struct {
|
|
||||||
PageInfo
|
|
||||||
All bool `json:"all,omitempty"`
|
|
||||||
Info string `json:"info"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Strategy string `json:"strategy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ForwardRule struct {
|
|
||||||
ID uint `json:"id"`
|
|
||||||
Chain string `json:"chain"`
|
|
||||||
Family string `json:"family"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
Port string `json:"port"`
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
Strategy string `json:"strategy"`
|
|
||||||
|
|
||||||
Num string `json:"num"`
|
|
||||||
TargetIP string `json:"targetIP"`
|
|
||||||
TargetPort string `json:"targetPort"`
|
|
||||||
Interface string `json:"interface"`
|
|
||||||
|
|
||||||
UsedStatus string `json:"usedStatus"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
|
|
||||||
IsDesired bool `json:"isDesired"`
|
|
||||||
IsRuntime bool `json:"isRuntime"`
|
|
||||||
SyncStatus string `json:"syncStatus"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ForwardRuleOperate struct {
|
|
||||||
ForceDelete bool `json:"forceDelete"`
|
|
||||||
Rules []ForwardRuleOperation `json:"rules" validate:"required,min=1,dive"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ForwardRuleOperation struct {
|
|
||||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
|
||||||
Num string `json:"num"`
|
|
||||||
Family string `json:"family" validate:"omitempty,oneof=ipv4 ipv6"`
|
|
||||||
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
|
||||||
Interface string `json:"interface"`
|
|
||||||
Port string `json:"port" validate:"required"`
|
|
||||||
TargetIP string `json:"targetIP"`
|
|
||||||
TargetPort string `json:"targetPort" validate:"required"`
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,37 +14,3 @@ type SearchTaskLogReq struct {
|
|||||||
type TaskDTO struct {
|
type TaskDTO struct {
|
||||||
model.Task
|
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"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -45,12 +45,10 @@ type MonitorGPUOptions struct {
|
|||||||
}
|
}
|
||||||
type GPUChartHide struct {
|
type GPUChartHide struct {
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
Type string `json:"type"`
|
|
||||||
Process bool `json:"process"`
|
Process bool `json:"process"`
|
||||||
GPU bool `json:"gpu"`
|
GPU bool `json:"gpu"`
|
||||||
Memory bool `json:"memory"`
|
Memory bool `json:"memory"`
|
||||||
Power bool `json:"power"`
|
Power bool `json:"power"`
|
||||||
PowerLimit bool `json:"powerLimit"`
|
|
||||||
Temperature bool `json:"temperature"`
|
Temperature bool `json:"temperature"`
|
||||||
Speed bool `json:"speed"`
|
Speed bool `json:"speed"`
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-46
@@ -1,8 +1,6 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||||
)
|
)
|
||||||
@@ -51,19 +49,13 @@ const (
|
|||||||
CACHE NginxKey = "cache"
|
CACHE NginxKey = "cache"
|
||||||
HttpPer NginxKey = "http-per"
|
HttpPer NginxKey = "http-per"
|
||||||
ProxyCache NginxKey = "proxy-cache"
|
ProxyCache NginxKey = "proxy-cache"
|
||||||
Brotli NginxKey = "brotli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BrotliKeys are served from the panel-managed http.d file rather than
|
|
||||||
// nginx.conf, because the module is optional: its directives must disappear
|
|
||||||
// together with the module, otherwise nginx refuses to start.
|
|
||||||
var BrotliKeys = []string{"brotli", "brotli_comp_level", "brotli_min_length", "brotli_types"}
|
|
||||||
|
|
||||||
var ScopeKeyMap = map[NginxKey][]string{
|
var ScopeKeyMap = map[NginxKey][]string{
|
||||||
Index: {"index"},
|
Index: {"index"},
|
||||||
LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"},
|
LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"},
|
||||||
SSL: {"ssl_certificate", "ssl_certificate_key"},
|
SSL: {"ssl_certificate", "ssl_certificate_key"},
|
||||||
HttpPer: {"server_names_hash_bucket_size", "client_header_buffer_size", "client_max_body_size", "keepalive_timeout", "gzip", "gzip_min_length", "gzip_comp_level", "gzip_types", "gzip_vary", "gzip_proxied"},
|
HttpPer: {"server_names_hash_bucket_size", "client_header_buffer_size", "client_max_body_size", "keepalive_timeout", "gzip", "gzip_min_length", "gzip_comp_level"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var StaticFileKeyMap = map[NginxKey]struct {
|
var StaticFileKeyMap = map[NginxKey]struct {
|
||||||
@@ -95,41 +87,9 @@ var LBAlgorithms = map[string]struct{}{"ip_hash": {}, "least_conn": {}}
|
|||||||
var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF-Connecting-IP": {}}
|
var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF-Connecting-IP": {}}
|
||||||
|
|
||||||
type NginxModule struct {
|
type NginxModule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Custom bool `json:"custom,omitempty"`
|
Script string `json:"script"`
|
||||||
Script string `json:"script"`
|
Packages []string `json:"packages"`
|
||||||
Packages []string `json:"packages"`
|
Params string `json:"params"`
|
||||||
Params string `json:"params"`
|
Enable bool `json:"enable"`
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ type AppContainerConfig struct {
|
|||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SpecifyIP string `json:"specifyIP"`
|
SpecifyIP string `json:"specifyIP"`
|
||||||
RestartPolicy string `json:"restartPolicy" validate:"omitempty,oneof=always unless-stopped no on-failure"`
|
RestartPolicy string `json:"restartPolicy" validate:"omitempty,oneof=always unless-stopped no on-failure"`
|
||||||
|
|
||||||
KeepServiceName bool `json:"-"`
|
|
||||||
SkipComposeCommonConfig bool `json:"-"`
|
|
||||||
UseLifecycleScripts bool `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstalledSearch struct {
|
type AppInstalledSearch struct {
|
||||||
@@ -96,8 +92,6 @@ type AppInstalledOperate struct {
|
|||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
DeleteImage bool `json:"deleteImage"`
|
DeleteImage bool `json:"deleteImage"`
|
||||||
Favorite bool `json:"favorite"`
|
Favorite bool `json:"favorite"`
|
||||||
|
|
||||||
UseLifecycleScripts bool `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstallUpgrade struct {
|
type AppInstallUpgrade struct {
|
||||||
@@ -105,7 +99,6 @@ type AppInstallUpgrade struct {
|
|||||||
DetailID uint `json:"detailId"`
|
DetailID uint `json:"detailId"`
|
||||||
Backup bool `json:"backup"`
|
Backup bool `json:"backup"`
|
||||||
PullImage bool `json:"pullImage"`
|
PullImage bool `json:"pullImage"`
|
||||||
DeleteImage bool `json:"deleteImage"`
|
|
||||||
DockerCompose string `json:"dockerCompose"`
|
DockerCompose string `json:"dockerCompose"`
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
}
|
}
|
||||||
@@ -117,14 +110,11 @@ type AppInstallDelete struct {
|
|||||||
DeleteDB bool `json:"deleteDB"`
|
DeleteDB bool `json:"deleteDB"`
|
||||||
DeleteImage bool `json:"deleteImage"`
|
DeleteImage bool `json:"deleteImage"`
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
|
|
||||||
UseLifecycleScripts bool `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstalledUpdate struct {
|
type AppInstalledUpdate struct {
|
||||||
InstallId uint `json:"installId" validate:"required"`
|
InstallId uint `json:"installId" validate:"required"`
|
||||||
Params map[string]interface{} `json:"params" validate:"required"`
|
Params map[string]interface{} `json:"params" validate:"required"`
|
||||||
TaskID string `json:"-"`
|
|
||||||
AppContainerConfig
|
AppContainerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,11 +131,6 @@ type FileMove struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Cover bool `json:"cover"`
|
Cover bool `json:"cover"`
|
||||||
CoverPaths []string `json:"coverPaths"`
|
CoverPaths []string `json:"coverPaths"`
|
||||||
TaskID string `json:"taskID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileMoveStopReq struct {
|
|
||||||
TaskID string `json:"taskID" validate:"required"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileDownload struct {
|
type FileDownload struct {
|
||||||
@@ -158,10 +153,6 @@ type FileProcessReq struct {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileProcessRemoveReq struct {
|
|
||||||
Keys []string `json:"keys" validate:"required,min=1,max=1000"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileRoleUpdate struct {
|
type FileRoleUpdate struct {
|
||||||
Path string `json:"path" validate:"required"`
|
Path string `json:"path" validate:"required"`
|
||||||
User string `json:"user" validate:"required"`
|
User string `json:"user" validate:"required"`
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ type McpServerCreate struct {
|
|||||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||||
OutputTransport string `json:"outputTransport" validate:"required"`
|
OutputTransport string `json:"outputTransport" validate:"required"`
|
||||||
Type string `json:"type" 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 {
|
type McpServerUpdate struct {
|
||||||
@@ -36,23 +32,11 @@ type McpServerDelete struct {
|
|||||||
ID uint `json:"id" validate:"required"`
|
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 {
|
type McpServerOperate struct {
|
||||||
ID uint `json:"id" validate:"required"`
|
ID uint `json:"id" validate:"required"`
|
||||||
Operate string `json:"operate" validate:"required"`
|
Operate string `json:"operate" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type McpServerConnectionTest struct {
|
|
||||||
ID uint `json:"id" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type McpBindDomain struct {
|
type McpBindDomain struct {
|
||||||
Domain string `json:"domain" validate:"required"`
|
Domain string `json:"domain" validate:"required"`
|
||||||
SSLID uint `json:"sslID"`
|
SSLID uint `json:"sslID"`
|
||||||
|
|||||||
@@ -114,22 +114,17 @@ type NginxRedirectUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NginxBuildReq struct {
|
type NginxBuildReq struct {
|
||||||
TaskID string `json:"taskID" validate:"required"`
|
TaskID string `json:"taskID" validate:"required"`
|
||||||
Mirror string `json:"mirror" validate:"required"`
|
Mirror string `json:"mirror" validate:"required"`
|
||||||
Modules []string `json:"modules"`
|
|
||||||
Force bool `json:"force"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NginxModuleUpdate struct {
|
type NginxModuleUpdate struct {
|
||||||
Operate string `json:"operate" validate:"required,oneof=create delete update"`
|
Operate string `json:"operate" validate:"required,oneof=create delete update"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Packages string `json:"packages"`
|
Packages string `json:"packages"`
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Params string `json:"params"`
|
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 {
|
type NginxOperateReq struct {
|
||||||
|
|||||||
@@ -21,14 +21,13 @@ type RuntimeCreate struct {
|
|||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
CodeDir string `json:"codeDir"`
|
CodeDir string `json:"codeDir"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
TaskID string `json:"taskID"`
|
|
||||||
|
|
||||||
Params map[string]interface{} `json:"params"`
|
Params map[string]interface{} `json:"params"`
|
||||||
NodeConfig
|
NodeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeConfig struct {
|
type NodeConfig struct {
|
||||||
Install *bool `json:"install"`
|
Install bool `json:"install"`
|
||||||
Clean bool `json:"clean"`
|
Clean bool `json:"clean"`
|
||||||
ExposedPorts []ExposedPort `json:"exposedPorts"`
|
ExposedPorts []ExposedPort `json:"exposedPorts"`
|
||||||
Environments []Environment `json:"environments"`
|
Environments []Environment `json:"environments"`
|
||||||
@@ -43,14 +42,12 @@ type Environment struct {
|
|||||||
type Volume struct {
|
type Volume struct {
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
Mode string `json:"mode"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExposedPort struct {
|
type ExposedPort struct {
|
||||||
HostPort int `json:"hostPort"`
|
HostPort int `json:"hostPort"`
|
||||||
ContainerPort int `json:"containerPort"`
|
ContainerPort int `json:"containerPort"`
|
||||||
HostIP string `json:"hostIP"`
|
HostIP string `json:"hostIP"`
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraHost struct {
|
type ExtraHost struct {
|
||||||
@@ -59,22 +56,19 @@ type ExtraHost struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RuntimeDelete struct {
|
type RuntimeDelete struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
ForceDelete bool `json:"forceDelete"`
|
ForceDelete bool `json:"forceDelete"`
|
||||||
DeleteImage bool `json:"deleteImage"`
|
|
||||||
TaskID string `json:"taskID"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuntimeUpdate struct {
|
type RuntimeUpdate struct {
|
||||||
AppDetailID uint `json:"appDetailId"`
|
Name string `json:"name"`
|
||||||
Name string `json:"name"`
|
ID uint `json:"id"`
|
||||||
ID uint `json:"id"`
|
Image string `json:"image"`
|
||||||
Image string `json:"image"`
|
Version string `json:"version"`
|
||||||
Version string `json:"version"`
|
Rebuild bool `json:"rebuild"`
|
||||||
Rebuild bool `json:"rebuild"`
|
Source string `json:"source"`
|
||||||
Source string `json:"source"`
|
CodeDir string `json:"codeDir"`
|
||||||
CodeDir string `json:"codeDir"`
|
Remark string `json:"remark"`
|
||||||
Remark string `json:"remark"`
|
|
||||||
|
|
||||||
Params map[string]interface{} `json:"params"`
|
Params map[string]interface{} `json:"params"`
|
||||||
NodeConfig
|
NodeConfig
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ type WebsiteCreate struct {
|
|||||||
|
|
||||||
SiteDir string `json:"siteDir"`
|
SiteDir string `json:"siteDir"`
|
||||||
|
|
||||||
TemplateOutputID uint `json:"templateOutputID"`
|
|
||||||
|
|
||||||
RuntimeConfig
|
RuntimeConfig
|
||||||
FtpConfig
|
FtpConfig
|
||||||
DataBaseConfig
|
DataBaseConfig
|
||||||
|
|||||||
@@ -126,16 +126,6 @@ type WebsiteSSLUpload struct {
|
|||||||
Type string `json:"type" validate:"required,oneof=paste local"`
|
Type string `json:"type" validate:"required,oneof=paste local"`
|
||||||
SSLID uint `json:"sslID"`
|
SSLID uint `json:"sslID"`
|
||||||
Description string `json:"description"`
|
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 {
|
type WebsiteCASearch struct {
|
||||||
@@ -167,8 +157,6 @@ type WebsiteCAObtain struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ExecShell bool `json:"execShell"`
|
ExecShell bool `json:"execShell"`
|
||||||
Shell string `json:"shell"`
|
Shell string `json:"shell"`
|
||||||
PushNode bool `json:"pushNode"`
|
|
||||||
Nodes string `json:"nodes"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsiteCARenew struct {
|
type WebsiteCARenew struct {
|
||||||
@@ -179,6 +167,4 @@ type WebsiteSSLFileUpload struct {
|
|||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
SSLID uint64 `json:"sslID"`
|
SSLID uint64 `json:"sslID"`
|
||||||
PushNode bool `json:"pushNode"`
|
|
||||||
Nodes string `json:"nodes"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package request
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WebsiteTemplateSearch struct {
|
|
||||||
dto.PageInfo
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateCreate struct {
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Type string `json:"type" validate:"required,oneof=single multi"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
FilePath string `json:"filePath"`
|
|
||||||
Variables string `json:"variables"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateUpdate struct {
|
|
||||||
ID uint `json:"id" validate:"required"`
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Type string `json:"type" validate:"required,oneof=single multi"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
FilePath string `json:"filePath"`
|
|
||||||
Variables string `json:"variables"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateOutputSearch struct {
|
|
||||||
dto.PageInfo
|
|
||||||
TemplateID uint `json:"templateID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateOutputCreate struct {
|
|
||||||
TemplateID uint `json:"templateID" validate:"required"`
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
VariableValues map[string]string `json:"variableValues"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsitePreviewReq struct {
|
|
||||||
TemplateID uint `json:"templateID" validate:"required"`
|
|
||||||
VariableValues map[string]string `json:"variableValues"`
|
|
||||||
}
|
|
||||||
@@ -104,36 +104,34 @@ type AppDetail struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AppInstallDTO struct {
|
type AppInstallDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
AppID uint `json:"appID"`
|
AppID uint `json:"appID"`
|
||||||
AppDetailID uint `json:"appDetailID"`
|
AppDetailID uint `json:"appDetailID"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
HttpPort int `json:"httpPort"`
|
HttpPort int `json:"httpPort"`
|
||||||
HttpsPort int `json:"httpsPort"`
|
HttpsPort int `json:"httpsPort"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
CanUpdate bool `json:"canUpdate"`
|
CanUpdate bool `json:"canUpdate"`
|
||||||
Icon string `json:"icon"`
|
Icon string `json:"icon"`
|
||||||
AppName string `json:"appName"`
|
AppName string `json:"appName"`
|
||||||
Ready int `json:"ready"`
|
Ready int `json:"ready"`
|
||||||
Total int `json:"total"`
|
Total int `json:"total"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
AppType string `json:"appType"`
|
AppType string `json:"appType"`
|
||||||
AppStatus string `json:"appStatus"`
|
AppStatus string `json:"appStatus"`
|
||||||
DockerCompose string `json:"dockerCompose"`
|
DockerCompose string `json:"dockerCompose"`
|
||||||
WebUI string `json:"webUI"`
|
WebUI string `json:"webUI"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Favorite bool `json:"favorite"`
|
Favorite bool `json:"favorite"`
|
||||||
SortOrder int `json:"sortOrder"`
|
SortOrder int `json:"sortOrder"`
|
||||||
App AppDetail `json:"app"`
|
App AppDetail `json:"app"`
|
||||||
Container string `json:"container"`
|
Container string `json:"container"`
|
||||||
IsEdit bool `json:"isEdit"`
|
IsEdit bool `json:"isEdit"`
|
||||||
LinkDB bool `json:"linkDB"`
|
LinkDB bool `json:"linkDB"`
|
||||||
ResourceKeys []string `json:"resourceKeys"`
|
ServiceName string `json:"serviceName"`
|
||||||
ServiceName string `json:"serviceName"`
|
|
||||||
Env map[string]interface{} `json:"env"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstallInfo struct {
|
type AppInstallInfo struct {
|
||||||
|
|||||||
@@ -16,12 +16,6 @@ type McpServerDTO struct {
|
|||||||
Volumes []request.Volume `json:"volumes"`
|
Volumes []request.Volume `json:"volumes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type McpServerStatusDTO struct {
|
|
||||||
ID uint `json:"id"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type McpBindDomainRes struct {
|
type McpBindDomainRes struct {
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
SSLID uint `json:"sslID"`
|
SSLID uint `json:"sslID"`
|
||||||
@@ -30,11 +24,3 @@ type McpBindDomainRes struct {
|
|||||||
WebsiteID uint `json:"websiteID"`
|
WebsiteID uint `json:"websiteID"`
|
||||||
ConnUrl string `json:"connUrl"`
|
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"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,17 +17,6 @@ type NginxParam struct {
|
|||||||
Params []string `json:"params"`
|
Params []string `json:"params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NginxBrotliRes carries the brotli settings together with where they live.
|
|
||||||
// ManagedExternally is true when the user defined brotli by hand, in which
|
|
||||||
// case the panel only reports the values and must not write its own copy.
|
|
||||||
// ManagedUnavailable is true when the panel could not wire the managed
|
|
||||||
// configuration into nginx.conf at all, so the reported values are inert.
|
|
||||||
type NginxBrotliRes struct {
|
|
||||||
Params []NginxParam `json:"params"`
|
|
||||||
ManagedExternally bool `json:"managedExternally"`
|
|
||||||
ManagedUnavailable bool `json:"managedUnavailable"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type NginxAuthRes struct {
|
type NginxAuthRes struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Items []dto.NginxAuth `json:"items"`
|
Items []dto.NginxAuth `json:"items"`
|
||||||
@@ -80,25 +69,16 @@ type NginxProxyCache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NginxModule struct {
|
type NginxModule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Custom bool `json:"custom"`
|
Script string `json:"script"`
|
||||||
Script string `json:"script"`
|
Packages string `json:"packages"`
|
||||||
Packages string `json:"packages"`
|
Params string `json:"params"`
|
||||||
Params string `json:"params"`
|
Enable bool `json:"enable"`
|
||||||
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 {
|
type NginxBuildConfig struct {
|
||||||
Mirror string `json:"mirror"`
|
Mirror string `json:"mirror"`
|
||||||
DynamicSupported bool `json:"dynamicSupported"`
|
Modules []NginxModule `json:"modules"`
|
||||||
Modules []NginxModule `json:"modules"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NginxConfigRes struct {
|
type NginxConfigRes struct {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package response
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WebsiteTemplateDTO struct {
|
|
||||||
model.WebsiteTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateOutputDTO struct {
|
|
||||||
model.WebsiteTemplateOutput
|
|
||||||
TemplateName string `json:"templateName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsitePreviewDTO struct {
|
|
||||||
HTML string `json:"html"`
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,7 @@ type SettingUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentSettingUpdate struct {
|
type AgentSettingUpdate struct {
|
||||||
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin"`
|
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin FirewallPortWhiteList"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package dto
|
|
||||||
|
|
||||||
type TerminalSessionClose struct {
|
|
||||||
ID string `json:"id" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TerminalSessionRevoke struct {
|
|
||||||
Scope string `json:"scope" validate:"required,oneof=auth_session user all"`
|
|
||||||
UserID string `json:"userId"`
|
|
||||||
AuthSessionID string `json:"authSessionId"`
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@ type AgentAccount struct {
|
|||||||
APIKey string `json:"apiKey"`
|
APIKey string `json:"apiKey"`
|
||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
APIType string `json:"apiType"`
|
APIType string `json:"apiType"`
|
||||||
AuthMode string `json:"authMode"`
|
|
||||||
VerifyModel string `json:"verifyModel"`
|
|
||||||
RememberAPIKey bool `json:"rememberApiKey"`
|
RememberAPIKey bool `json:"rememberApiKey"`
|
||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ package model
|
|||||||
|
|
||||||
type AgentAccountModel struct {
|
type AgentAccountModel struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
AccountID uint `json:"accountId" gorm:"index"`
|
AccountID uint `json:"accountId" gorm:"index"`
|
||||||
Model string `json:"model" gorm:"index"`
|
Model string `json:"model" gorm:"index"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
SortOrder int `json:"sortOrder" gorm:"index"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (AgentAccountModel) TableName() string {
|
func (AgentAccountModel) TableName() string {
|
||||||
|
|||||||
+10
-27
@@ -1,12 +1,5 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
@@ -25,11 +18,10 @@ type Alert struct {
|
|||||||
|
|
||||||
type AlertTask struct {
|
type AlertTask struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||||
DeliveryLogID *uint `gorm:"uniqueIndex" json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertLog struct {
|
type AlertLog struct {
|
||||||
@@ -49,21 +41,12 @@ type AlertLog struct {
|
|||||||
|
|
||||||
type AlertConfig struct {
|
type AlertConfig struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
UID string `gorm:"type:varchar(64);not null;uniqueIndex" json:"uid"`
|
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||||
Config string `gorm:"type:text;not null" json:"config"`
|
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||||
SecretConfig string `gorm:"type:text;not null;default:''" json:"-"`
|
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
|
||||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlertConfig) BeforeCreate(_ *gorm.DB) error {
|
|
||||||
if strings.TrimSpace(a.UID) == "" {
|
|
||||||
a.UID = uuid.NewString()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginLog struct {
|
type LoginLog struct {
|
||||||
|
|||||||
@@ -31,5 +31,4 @@ type BackupRecord struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Args string `gorm:"not null;default:''" json:"args"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ type ComposeTemplate struct {
|
|||||||
type Compose struct {
|
type Compose struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
IsPinned bool `json:"isPinned"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
||||||
+13
-217
@@ -1,222 +1,18 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
type Firewall struct {
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
|
||||||
)
|
|
||||||
|
|
||||||
const FirewallRuleSequenceStep int64 = 1 << 32
|
|
||||||
|
|
||||||
type DockerPortGuardPolicy struct {
|
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
UUID string `gorm:"size:64;not null;uniqueIndex" json:"uuid"`
|
Type string `json:"type"`
|
||||||
ReadOnly bool `gorm:"not null;default:false;uniqueIndex:idx_docker_port_guard_endpoint" json:"-"`
|
Port string `json:"port"` // Deprecated
|
||||||
Family string `gorm:"size:16;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"family"`
|
Address string `json:"address"` // Deprecated
|
||||||
HostIP string `gorm:"size:64;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"hostIP"`
|
|
||||||
HostPort uint16 `gorm:"not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"hostPort"`
|
Chain string `json:"chain"`
|
||||||
Protocol string `gorm:"size:8;not null;uniqueIndex:idx_docker_port_guard_endpoint" json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
Mode string `gorm:"size:32;not null" json:"mode"`
|
SrcIP string `json:"srcIP"`
|
||||||
Sources string `gorm:"type:text" json:"-"`
|
SrcPort string `json:"srcPort"`
|
||||||
Description string `gorm:"type:text" json:"description"`
|
DstIP string `json:"dstIP"`
|
||||||
NativeAction string `gorm:"size:32;not null;default:''" json:"-"`
|
DstPort string `json:"dstPort"`
|
||||||
NativeRules string `gorm:"type:text" json:"-"`
|
Strategy string `gorm:"not null" json:"strategy"`
|
||||||
Sequence int64 `gorm:"not null;default:0" json:"-"`
|
Description string `json:"description"`
|
||||||
}
|
|
||||||
|
|
||||||
type ForwardingRule struct {
|
|
||||||
BaseModel
|
|
||||||
|
|
||||||
Family string `gorm:"size:16;not null;uniqueIndex:idx_forwarding_rule_identity" json:"family"`
|
|
||||||
Protocol string `gorm:"size:8;not null;uniqueIndex:idx_forwarding_rule_identity" json:"protocol"`
|
|
||||||
Port string `gorm:"size:32;not null;uniqueIndex:idx_forwarding_rule_identity" json:"port"`
|
|
||||||
TargetIP string `gorm:"size:64;not null;uniqueIndex:idx_forwarding_rule_identity" json:"targetIP"`
|
|
||||||
TargetPort string `gorm:"size:32;not null;uniqueIndex:idx_forwarding_rule_identity" json:"targetPort"`
|
|
||||||
Interface string `gorm:"size:32;not null;default:'';uniqueIndex:idx_forwarding_rule_identity" json:"interface"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRule struct {
|
|
||||||
UUID string `gorm:"size:64;primaryKey" json:"uuid"`
|
|
||||||
Family string `gorm:"size:16;not null" json:"family"`
|
|
||||||
|
|
||||||
Protocol string `gorm:"size:32;not null" json:"protocol"`
|
|
||||||
SourceAddress string `gorm:"size:255" json:"sourceAddress"`
|
|
||||||
SourcePort string `gorm:"size:64" json:"sourcePort"`
|
|
||||||
DestinationAddress string `gorm:"size:255" json:"destinationAddress"`
|
|
||||||
DestinationPort string `gorm:"size:64" json:"destinationPort"`
|
|
||||||
Interface string `gorm:"size:128" json:"interface"`
|
|
||||||
ConnectionStates string `gorm:"type:text" json:"connectionStates"`
|
|
||||||
Action string `gorm:"size:32;not null" json:"action"`
|
|
||||||
Description string `gorm:"type:text" json:"description"`
|
|
||||||
CompatibilityError string `gorm:"type:text" json:"compatibilityError,omitempty"`
|
|
||||||
Priority *int `json:"priority,omitempty"`
|
|
||||||
Sequence *int64 `gorm:"index" json:"sequence,omitempty"`
|
|
||||||
|
|
||||||
Origin string `gorm:"size:32;not null" json:"origin"`
|
|
||||||
Owner string `gorm:"size:320;not null" json:"owner"`
|
|
||||||
Revision uint `gorm:"not null;default:1" json:"revision"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func FirewallRuleOwner(sourceKind, sourceID string) string {
|
|
||||||
sourceKind = strings.TrimSpace(sourceKind)
|
|
||||||
sourceID = strings.TrimSpace(sourceID)
|
|
||||||
if sourceID == "" {
|
|
||||||
return sourceKind
|
|
||||||
}
|
|
||||||
return sourceKind + ":" + sourceID
|
|
||||||
}
|
|
||||||
|
|
||||||
func FirewallRuleFromDomain(rule filter.FirewallRule) (FirewallRule, error) {
|
|
||||||
normalized, err := filter.NormalizeRule(rule)
|
|
||||||
if err != nil {
|
|
||||||
return FirewallRule{}, err
|
|
||||||
}
|
|
||||||
switch normalized.NativeKind {
|
|
||||||
case "", filter.NativeKindRule, filter.NativeKindZonePort, filter.NativeKindRichRule, filter.NativeKindUFWRule:
|
|
||||||
default:
|
|
||||||
return FirewallRule{}, fmt.Errorf("%w: native rule %q cannot be stored as a provider-neutral policy", filter.ErrUnsupportedScope, normalized.NativeKind)
|
|
||||||
}
|
|
||||||
record := FirewallRule{
|
|
||||||
Family: string(normalized.Scope.Family),
|
|
||||||
Protocol: normalized.Protocol,
|
|
||||||
SourceAddress: normalized.SourceAddress,
|
|
||||||
SourcePort: normalized.SourcePort,
|
|
||||||
DestinationAddress: normalized.DestinationAddress,
|
|
||||||
DestinationPort: normalized.DestinationPort,
|
|
||||||
Interface: normalized.Interface,
|
|
||||||
ConnectionStates: strings.Join(normalized.ConnectionStates, ","),
|
|
||||||
Action: string(normalized.Action),
|
|
||||||
Description: normalized.Description,
|
|
||||||
}
|
|
||||||
if normalized.Scope.Provider == filter.ProviderFirewalld {
|
|
||||||
record.Priority = normalized.Priority
|
|
||||||
}
|
|
||||||
return record, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rule FirewallRule) PolicyKey() string {
|
|
||||||
payload, _ := json.Marshal(struct {
|
|
||||||
Family string `json:"family"`
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
SourceAddress string `json:"sourceAddress,omitempty"`
|
|
||||||
SourcePort string `json:"sourcePort,omitempty"`
|
|
||||||
DestinationAddress string `json:"destinationAddress,omitempty"`
|
|
||||||
DestinationPort string `json:"destinationPort,omitempty"`
|
|
||||||
Interface string `json:"interface,omitempty"`
|
|
||||||
ConnectionStates string `json:"connectionStates,omitempty"`
|
|
||||||
Action string `json:"action"`
|
|
||||||
}{
|
|
||||||
Family: rule.Family, Protocol: rule.Protocol,
|
|
||||||
SourceAddress: rule.SourceAddress, SourcePort: rule.SourcePort,
|
|
||||||
DestinationAddress: rule.DestinationAddress, DestinationPort: rule.DestinationPort,
|
|
||||||
Interface: rule.Interface, ConnectionStates: rule.ConnectionStates, Action: rule.Action,
|
|
||||||
})
|
|
||||||
sum := sha256.Sum256(payload)
|
|
||||||
return hex.EncodeToString(sum[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rule FirewallRule) RulesForProvider(provider filter.Provider) ([]filter.FirewallRule, error) {
|
|
||||||
if rule.CompatibilityError != "" {
|
|
||||||
return nil, fmt.Errorf("%w: %s", filter.ErrUnsupportedScope, rule.CompatibilityError)
|
|
||||||
}
|
|
||||||
connectionStates := make([]string, 0)
|
|
||||||
if rule.ConnectionStates != "" {
|
|
||||||
connectionStates = strings.Split(rule.ConnectionStates, ",")
|
|
||||||
}
|
|
||||||
base := filter.FirewallRule{
|
|
||||||
Protocol: rule.Protocol, SourceAddress: rule.SourceAddress, SourcePort: rule.SourcePort,
|
|
||||||
DestinationAddress: rule.DestinationAddress, DestinationPort: rule.DestinationPort,
|
|
||||||
Interface: rule.Interface, ConnectionStates: connectionStates,
|
|
||||||
Action: filter.Action(rule.Action), Description: rule.Description,
|
|
||||||
}
|
|
||||||
if provider != filter.ProviderUFW && strings.EqualFold(strings.TrimSpace(base.Protocol), "all") &&
|
|
||||||
strings.TrimSpace(base.SourcePort) == "" && strings.TrimSpace(base.DestinationPort) != "" {
|
|
||||||
base.Protocol = "tcp/udp"
|
|
||||||
}
|
|
||||||
if provider == filter.ProviderFirewalld {
|
|
||||||
base.Priority = rule.Priority
|
|
||||||
}
|
|
||||||
families := []filter.Family{filter.Family(rule.Family)}
|
|
||||||
if provider != filter.ProviderFirewalld && families[0] == filter.FamilyInet {
|
|
||||||
hasIPv4, hasIPv6 := ruleAddressFamilies(base)
|
|
||||||
switch {
|
|
||||||
case hasIPv4 && hasIPv6:
|
|
||||||
return nil, fmt.Errorf("%w: inet policy contains both IPv4 and IPv6 addresses", filter.ErrUnsupportedScope)
|
|
||||||
case hasIPv6 || strings.EqualFold(base.Protocol, "icmpv6"):
|
|
||||||
families = []filter.Family{filter.FamilyIPv6}
|
|
||||||
case hasIPv4:
|
|
||||||
families = []filter.Family{filter.FamilyIPv4}
|
|
||||||
default:
|
|
||||||
families = []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := make([]filter.FirewallRule, 0, len(families))
|
|
||||||
for _, family := range families {
|
|
||||||
compiled := base
|
|
||||||
compiled.Scope = filter.Scope{Provider: provider, Family: family, Direction: filter.DirectionInput}
|
|
||||||
switch provider {
|
|
||||||
case filter.ProviderIptables, filter.ProviderNftables:
|
|
||||||
compiled.Scope.Table, compiled.Scope.Chain = "filter", filter.IptablesInputChain
|
|
||||||
case filter.ProviderFirewalld:
|
|
||||||
compiled.Scope.Zone = filter.FirewalldInputZone
|
|
||||||
case filter.ProviderUFW:
|
|
||||||
compiled.Scope.Chain = filter.UFWInputChain
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("%w: unsupported firewall provider %q", filter.ErrProviderUnavailable, provider)
|
|
||||||
}
|
|
||||||
expanded, err := filter.ExpandAtomicRules(compiled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, expanded...)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SortFirewallRules(rules []FirewallRule, provider filter.Provider) {
|
|
||||||
sort.SliceStable(rules, func(i, j int) bool {
|
|
||||||
left, right := rules[i], rules[j]
|
|
||||||
if provider == filter.ProviderFirewalld {
|
|
||||||
switch {
|
|
||||||
case left.Priority == nil && right.Priority != nil:
|
|
||||||
return false
|
|
||||||
case left.Priority != nil && right.Priority == nil:
|
|
||||||
return true
|
|
||||||
case left.Priority != nil && right.Priority != nil && *left.Priority != *right.Priority:
|
|
||||||
return *left.Priority < *right.Priority
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch {
|
|
||||||
case left.Sequence == nil && right.Sequence != nil:
|
|
||||||
return false
|
|
||||||
case left.Sequence != nil && right.Sequence == nil:
|
|
||||||
return true
|
|
||||||
case left.Sequence != nil && right.Sequence != nil && *left.Sequence != *right.Sequence:
|
|
||||||
return *left.Sequence < *right.Sequence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return left.UUID < right.UUID
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleAddressFamilies(rule filter.FirewallRule) (bool, bool) {
|
|
||||||
hasIPv4, hasIPv6 := false, false
|
|
||||||
for _, address := range []string{rule.SourceAddress, rule.DestinationAddress} {
|
|
||||||
address = strings.TrimSpace(address)
|
|
||||||
if address == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(address, ":") {
|
|
||||||
hasIPv6 = true
|
|
||||||
} else {
|
|
||||||
hasIPv4 = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasIPv4, hasIPv6
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,4 @@ type Ftp struct {
|
|||||||
Status string `gorm:"not null" json:"status"`
|
Status string `gorm:"not null" json:"status"`
|
||||||
Path string `gorm:"not null" json:"path"`
|
Path string `gorm:"not null" json:"path"`
|
||||||
Description string `gorm:"not null" json:"description"`
|
Description string `gorm:"not null" json:"description"`
|
||||||
UID uint `gorm:"column:uid;not null;default:1000" json:"-"`
|
|
||||||
GID uint `gorm:"column:gid;not null;default:1000" json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,4 @@ type McpServer struct {
|
|||||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||||
OutputTransport string `json:"outputTransport"`
|
OutputTransport string `json:"outputTransport"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
GatewayImage string `json:"gatewayImage"`
|
|
||||||
ProtocolVersion string `json:"protocolVersion"`
|
|
||||||
GatewayArgs string `json:"gatewayArgs"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type WebsiteTemplate struct {
|
|
||||||
BaseModel
|
|
||||||
Name string `gorm:"not null" json:"name"`
|
|
||||||
Type string `gorm:"not null" json:"type"` // single | multi
|
|
||||||
Content string `gorm:"type:longtext" json:"content"`
|
|
||||||
FilePath string `json:"filePath"`
|
|
||||||
Variables string `gorm:"type:text" json:"variables"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w WebsiteTemplate) TableName() string {
|
|
||||||
return "website_templates"
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateOutput struct {
|
|
||||||
BaseModel
|
|
||||||
Name string `gorm:"not null" json:"name"`
|
|
||||||
TemplateID uint `gorm:"not null" json:"templateID"`
|
|
||||||
TemplateType string `json:"templateType"`
|
|
||||||
VariableValues string `gorm:"type:text" json:"variableValues"`
|
|
||||||
OutputPath string `json:"outputPath"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w WebsiteTemplateOutput) TableName() string {
|
|
||||||
return "website_template_outputs"
|
|
||||||
}
|
|
||||||
+327
-379
@@ -1,262 +1,319 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type APIConfig struct {
|
type Model struct {
|
||||||
APIType string
|
ID string
|
||||||
BaseURL string
|
Name string
|
||||||
EditableBaseURL bool
|
ContextWindow int
|
||||||
DiscoverModels bool
|
MaxTokens int
|
||||||
DefaultAuthMode string
|
Reasoning bool
|
||||||
AuthModes []string
|
Input []string
|
||||||
Models []Model
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
type RuntimeDefault struct {
|
||||||
AuthModeBearer = "bearer"
|
APIType string
|
||||||
AuthModeXAPIKey = "x-api-key"
|
ContextWindow int
|
||||||
)
|
MaxTokens int
|
||||||
|
Input []string
|
||||||
type Model struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
Key string
|
Key string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
DisplayNameKey string
|
|
||||||
Sort uint
|
Sort uint
|
||||||
DefaultAPIType string
|
DefaultBaseURL string
|
||||||
APIConfigs []APIConfig
|
|
||||||
EnvKey string
|
EnvKey string
|
||||||
|
Default RuntimeDefault
|
||||||
Models []Model
|
Models []Model
|
||||||
}
|
}
|
||||||
|
|
||||||
var catalog = map[string]Meta{
|
var catalog = map[string]Meta{
|
||||||
"custom": {
|
"custom": {
|
||||||
Key: "custom", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY",
|
Key: "custom",
|
||||||
APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "dashscope-images", "openai-embeddings"),
|
DisplayName: "Custom",
|
||||||
|
Sort: 10,
|
||||||
|
DefaultBaseURL: "",
|
||||||
|
EnvKey: "CUSTOM_API_KEY",
|
||||||
|
Default: RuntimeDefault{
|
||||||
|
APIType: "openai-completions",
|
||||||
|
ContextWindow: 128000,
|
||||||
|
MaxTokens: 8192,
|
||||||
|
Input: []string{"text"},
|
||||||
|
},
|
||||||
|
Models: []Model{},
|
||||||
},
|
},
|
||||||
"ollama": {
|
"ollama": {
|
||||||
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
Key: "ollama",
|
||||||
APIConfigs: editableAPIConfigs(false, "openai-responses", "openai-completions", "openai-embeddings"),
|
DisplayName: "Ollama",
|
||||||
},
|
Sort: 15,
|
||||||
// llmman (https://github.com/llmmanorg/llmman): local runner with Ollama/OpenAI-compatible routes on 127.0.0.1:17434.
|
Default: RuntimeDefault{
|
||||||
"llmman": {
|
APIType: "openai-responses",
|
||||||
Key: "llmman", DisplayName: "llmman", Sort: 16, DefaultAPIType: "openai-responses",
|
ContextWindow: 160000,
|
||||||
APIConfigs: []APIConfig{
|
MaxTokens: 8192,
|
||||||
{APIType: "openai-responses", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
Input: []string{"text"},
|
||||||
{APIType: "openai-completions", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
|
||||||
{APIType: "openai-embeddings", BaseURL: "http://127.0.0.1:17434/v1", EditableBaseURL: true},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"vllm": {
|
"vllm": {
|
||||||
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
Key: "vllm",
|
||||||
APIConfigs: editableAPIConfigs(false, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
DisplayName: "vLLM",
|
||||||
|
Sort: 20,
|
||||||
|
DefaultBaseURL: "",
|
||||||
|
EnvKey: "VLLM_API_KEY",
|
||||||
|
Default: RuntimeDefault{
|
||||||
|
APIType: "openai-completions",
|
||||||
|
ContextWindow: 128000,
|
||||||
|
MaxTokens: 8192,
|
||||||
|
Input: []string{"text"},
|
||||||
|
},
|
||||||
|
Models: []Model{},
|
||||||
},
|
},
|
||||||
"deepseek": {
|
"deepseek": {
|
||||||
Key: "deepseek", DisplayName: "DeepSeek", Sort: 25, DefaultAPIType: "openai-completions", EnvKey: "DEEPSEEK_API_KEY",
|
Key: "deepseek",
|
||||||
APIConfigs: []APIConfig{
|
DisplayName: "DeepSeek",
|
||||||
{APIType: "openai-completions", BaseURL: "https://api.deepseek.com"},
|
Sort: 25,
|
||||||
{APIType: "openai-responses", BaseURL: "https://api.deepseek.com"},
|
DefaultBaseURL: "https://api.deepseek.com/v1",
|
||||||
anthropicAPIConfig("https://api.deepseek.com/anthropic", AuthModeXAPIKey),
|
EnvKey: "DEEPSEEK_API_KEY",
|
||||||
},
|
Default: RuntimeDefault{
|
||||||
Models: []Model{{ID: "deepseek-v4-flash", Name: "deepseek-v4-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}},
|
APIType: "openai-completions",
|
||||||
},
|
ContextWindow: 131072,
|
||||||
"bailian-coding-plan": {
|
MaxTokens: 8192,
|
||||||
Key: "bailian-coding-plan", DisplayNameKey: "AIProviderBailianCodingPlan", Sort: 30, DefaultAPIType: "openai-completions", EnvKey: "QWEN_API_KEY",
|
Input: []string{"text"},
|
||||||
APIConfigs: []APIConfig{
|
|
||||||
{APIType: "openai-completions", BaseURL: "https://coding.dashscope.aliyuncs.com/v1"},
|
|
||||||
anthropicAPIConfig("https://coding.dashscope.aliyuncs.com/apps/anthropic", AuthModeBearer),
|
|
||||||
},
|
},
|
||||||
Models: []Model{
|
Models: []Model{
|
||||||
{ID: "qwen3-coder-plus", Name: "Qwen3-Coder-Plus"},
|
{ID: "deepseek/deepseek-v4-flash", Name: "deepseek-v4-flash"},
|
||||||
{ID: "qwen3-max-2026-01-23", Name: "Qwen3-Max-2026-01-23"},
|
{ID: "deepseek/deepseek-v4-pro", Name: "deepseek-v4-pro"},
|
||||||
{ID: "qwen3-coder-next", Name: "Qwen3-Coder-Next"},
|
},
|
||||||
{ID: "glm-4.7", Name: "GLM-4.7"},
|
},
|
||||||
{ID: "kimi-k2.5", Name: "Kimi K2.5"},
|
"bailian-coding-plan": {
|
||||||
{ID: "qwen3.5-plus", Name: "Qwen3.5-Plus"},
|
Key: "bailian-coding-plan",
|
||||||
{ID: "glm-5", Name: "GLM-5"},
|
DisplayName: "阿里云百炼 Coding Plan",
|
||||||
{ID: "MiniMax-M2.5", Name: "MiniMax M2.5"},
|
Sort: 30,
|
||||||
{ID: "qwen3.6-plus", Name: "Qwen3.6-Plus"},
|
DefaultBaseURL: "https://coding.dashscope.aliyuncs.com/v1",
|
||||||
{ID: "qwen3.7-plus", Name: "Qwen3.7-Plus"},
|
EnvKey: "QWEN_API_KEY",
|
||||||
|
Default: RuntimeDefault{
|
||||||
|
APIType: "openai-completions",
|
||||||
|
ContextWindow: 256000,
|
||||||
|
MaxTokens: 8192,
|
||||||
|
Input: []string{"text"},
|
||||||
|
},
|
||||||
|
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},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ark-coding-plan": {
|
"ark-coding-plan": {
|
||||||
Key: "ark-coding-plan", DisplayNameKey: "AIProviderArkCodingPlan", Sort: 35, DefaultAPIType: "openai-completions", EnvKey: "ARK_API_KEY",
|
Key: "ark-coding-plan",
|
||||||
APIConfigs: []APIConfig{
|
DisplayName: "方舟 Coding Plan",
|
||||||
{APIType: "openai-completions", BaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3"},
|
Sort: 35,
|
||||||
anthropicAPIConfig("https://ark.cn-beijing.volces.com/api/coding", AuthModeBearer),
|
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"},
|
||||||
},
|
},
|
||||||
Models: []Model{
|
Models: []Model{
|
||||||
{ID: "ark-code-latest", Name: "Ark Coding Plan"}, {ID: "doubao-seed-code", Name: "Doubao Seed Code"},
|
{ID: "ark-coding-plan/ark-code-latest", Name: "Ark Coding Plan", ContextWindow: 256000, MaxTokens: 4096},
|
||||||
{ID: "glm-4.7", Name: "GLM 4.7 Coding"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
{ID: "ark-coding-plan/doubao-seed-code", Name: "Doubao Seed Code", ContextWindow: 256000, MaxTokens: 4096},
|
||||||
{ID: "kimi-k2.5", Name: "Kimi K2.5 Coding"}, {ID: "doubao-seed-code-preview-251028", Name: "Doubao Seed Code Preview"},
|
{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},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"zai": {
|
"zai": {
|
||||||
Key: "zai", DisplayName: "Z.ai", Sort: 40, DefaultAPIType: "openai-completions", EnvKey: "ZAI_API_KEY",
|
Key: "zai",
|
||||||
APIConfigs: []APIConfig{
|
DisplayName: "Z.ai",
|
||||||
{APIType: "openai-completions", BaseURL: "https://open.bigmodel.cn/api/paas/v4", EditableBaseURL: true},
|
Sort: 40,
|
||||||
{APIType: "openai-images", BaseURL: "https://open.bigmodel.cn/api/paas/v4", EditableBaseURL: true},
|
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},
|
||||||
},
|
},
|
||||||
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": {
|
"minimax": {
|
||||||
Key: "minimax", DisplayName: "MiniMax (CN)", Sort: 45, DefaultAPIType: "anthropic-messages", EnvKey: "MINIMAX_API_KEY",
|
Key: "minimax",
|
||||||
APIConfigs: []APIConfig{
|
DisplayName: "MiniMax (CN)",
|
||||||
anthropicAPIConfig("https://api.minimaxi.com/anthropic", AuthModeXAPIKey, AuthModeBearer),
|
Sort: 45,
|
||||||
{APIType: "openai-completions", BaseURL: "https://api.minimaxi.com/v1"},
|
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
||||||
{APIType: "minimax-images", BaseURL: "https://api.minimaxi.com"},
|
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"},
|
||||||
},
|
},
|
||||||
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": {
|
"xiaomi": {
|
||||||
Key: "xiaomi", DisplayName: "Xiaomi", Sort: 46, DefaultAPIType: "openai-completions", EnvKey: "XIAOMI_API_KEY",
|
Key: "xiaomi",
|
||||||
APIConfigs: []APIConfig{
|
DisplayName: "Xiaomi",
|
||||||
{APIType: "openai-completions", BaseURL: "https://api.xiaomimimo.com/v1"},
|
Sort: 46,
|
||||||
{APIType: "openai-responses", BaseURL: "https://api.xiaomimimo.com/v1"},
|
DefaultBaseURL: "https://api.xiaomimimo.com/v1",
|
||||||
anthropicAPIConfig("https://api.xiaomimimo.com/anthropic", AuthModeBearer),
|
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"}},
|
||||||
},
|
},
|
||||||
Models: []Model{{ID: "mimo-v2.5", Name: "Xiaomi MiMo V2.5"}, {ID: "mimo-v2.5-pro", Name: "Xiaomi MiMo V2.5 Pro"}},
|
|
||||||
},
|
},
|
||||||
"kimi": {
|
"kimi": {
|
||||||
Key: "kimi", DisplayName: "Kimi (CN)", Sort: 50, DefaultAPIType: "openai-completions", EnvKey: "KIMI_API_KEY",
|
Key: "kimi",
|
||||||
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://api.moonshot.cn/v1"}},
|
DisplayName: "Kimi (CN)",
|
||||||
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"}},
|
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},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"kimi-coding": {
|
"kimi-coding": {
|
||||||
Key: "kimi-coding", DisplayName: "Kimi Coding", Sort: 51, DefaultAPIType: "anthropic-messages", EnvKey: "KIMI_API_KEY",
|
Key: "kimi-coding",
|
||||||
APIConfigs: []APIConfig{anthropicAPIConfig("https://api.kimi.com/coding/", AuthModeXAPIKey)},
|
DisplayName: "Kimi Coding",
|
||||||
Models: []Model{{ID: "kimi-code", Name: "Kimi Code"}, {ID: "k2p5", Name: "Kimi K2.5"}},
|
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"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"openai": {
|
"openai": {
|
||||||
Key: "openai", DisplayName: "OpenAI", Sort: 55, DefaultAPIType: "openai-responses", EnvKey: "OPENAI_API_KEY",
|
Key: "openai",
|
||||||
APIConfigs: []APIConfig{
|
DisplayName: "OpenAI",
|
||||||
{APIType: "openai-responses", BaseURL: "https://api.openai.com/v1"},
|
Sort: 55,
|
||||||
{APIType: "openai-completions", BaseURL: "https://api.openai.com/v1"},
|
DefaultBaseURL: "https://api.openai.com/v1",
|
||||||
{APIType: "openai-images", BaseURL: "https://api.openai.com/v1"},
|
EnvKey: "OPENAI_API_KEY",
|
||||||
{APIType: "openai-embeddings", BaseURL: "https://api.openai.com/v1", Models: []Model{
|
Default: RuntimeDefault{
|
||||||
{ID: "text-embedding-3-small", Name: "text-embedding-3-small"},
|
APIType: "openai-responses",
|
||||||
{ID: "text-embedding-3-large", Name: "text-embedding-3-large"},
|
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"}},
|
||||||
},
|
},
|
||||||
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": {
|
"openrouter": {
|
||||||
Key: "openrouter", DisplayName: "OpenRouter", Sort: 56, DefaultAPIType: "openai-completions", EnvKey: "OPENROUTER_API_KEY",
|
Key: "openrouter",
|
||||||
APIConfigs: []APIConfig{
|
DisplayName: "OpenRouter",
|
||||||
{APIType: "openai-completions", BaseURL: "https://openrouter.ai/api/v1"},
|
Sort: 56,
|
||||||
{APIType: "openrouter-images", BaseURL: "https://openrouter.ai"},
|
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"},
|
||||||
},
|
},
|
||||||
Models: []Model{{ID: "openrouter/free", Name: "openrouter/free"}, {ID: "openrouter/auto", Name: "openrouter/auto"}},
|
|
||||||
},
|
},
|
||||||
"anthropic": {
|
"anthropic": {
|
||||||
Key: "anthropic", DisplayName: "Anthropic", Sort: 60, DefaultAPIType: "anthropic-messages", EnvKey: "ANTHROPIC_API_KEY",
|
Key: "anthropic",
|
||||||
APIConfigs: []APIConfig{anthropicAPIConfig("https://api.anthropic.com", AuthModeXAPIKey)},
|
DisplayName: "Anthropic",
|
||||||
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"}},
|
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"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"gemini": {
|
"gemini": {
|
||||||
Key: "gemini", DisplayName: "Gemini", Sort: 65, DefaultAPIType: "gemini-generate-content", EnvKey: "GEMINI_API_KEY",
|
Key: "gemini",
|
||||||
APIConfigs: []APIConfig{{APIType: "gemini-generate-content", BaseURL: "https://generativelanguage.googleapis.com"}},
|
DisplayName: "Gemini",
|
||||||
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"}},
|
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},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"moonshot": {
|
"moonshot": {
|
||||||
Key: "moonshot", DisplayName: "Moonshot (Global)", Sort: 70, DefaultAPIType: "openai-completions", EnvKey: "MOONSHOT_API_KEY",
|
Key: "moonshot",
|
||||||
APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://api.moonshot.ai/v1"}},
|
DisplayName: "Moonshot (Global)",
|
||||||
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"}},
|
Sort: 70,
|
||||||
},
|
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
||||||
"bailian": {
|
EnvKey: "MOONSHOT_API_KEY",
|
||||||
Key: "bailian", DisplayNameKey: "AIProviderBailian", Sort: 31, DefaultAPIType: "openai-completions", EnvKey: "DASHSCOPE_API_KEY",
|
Default: RuntimeDefault{
|
||||||
APIConfigs: []APIConfig{
|
APIType: "openai-completions",
|
||||||
{
|
ContextWindow: 256000,
|
||||||
APIType: "openai-completions", BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
MaxTokens: 8192,
|
||||||
DiscoverModels: true,
|
Input: []string{"text"},
|
||||||
Models: []Model{{ID: "qwen3.7-plus", Name: "qwen3.7-plus"}, {ID: "qwen3.6-plus", Name: "qwen3.6-plus"}, {ID: "qwen3.6-flash", Name: "qwen3.6-flash"}},
|
},
|
||||||
},
|
Models: []Model{
|
||||||
{
|
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
||||||
APIType: "openai-responses", BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||||
DiscoverModels: true,
|
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
||||||
Models: []Model{{ID: "qwen3.7-plus", Name: "qwen3.7-plus"}, {ID: "qwen3.6-plus", Name: "qwen3.6-plus"}, {ID: "qwen3.6-flash", Name: "qwen3.6-flash"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIType: "anthropic-messages", BaseURL: "https://dashscope.aliyuncs.com/apps/anthropic",
|
|
||||||
DefaultAuthMode: AuthModeBearer,
|
|
||||||
AuthModes: []string{AuthModeBearer},
|
|
||||||
Models: []Model{{ID: "qwen3.7-plus", Name: "qwen3.7-plus"}, {ID: "qwen3.6-plus", Name: "qwen3.6-plus"}, {ID: "qwen3.6-flash", Name: "qwen3.6-flash"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIType: "dashscope-images", BaseURL: "https://dashscope.aliyuncs.com",
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "qwen-image-2.0-pro", Name: "qwen-image-2.0-pro"},
|
|
||||||
{ID: "qwen-image-2.0", Name: "qwen-image-2.0"},
|
|
||||||
{ID: "wan2.7-image-pro", Name: "wan2.7-image-pro"},
|
|
||||||
{ID: "wan2.7-image", Name: "wan2.7-image"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ark": {
|
|
||||||
Key: "ark", DisplayNameKey: "AIProviderArk", Sort: 36, DefaultAPIType: "openai-completions", EnvKey: "ARK_API_KEY",
|
|
||||||
APIConfigs: []APIConfig{
|
|
||||||
{
|
|
||||||
APIType: "openai-completions", BaseURL: "https://ark.cn-beijing.volces.com/api/v3",
|
|
||||||
DiscoverModels: true,
|
|
||||||
Models: []Model{{ID: "doubao-seed-2-0-pro-260215", Name: "doubao-seed-2-0-pro-260215"}, {ID: "doubao-seed-2-0-lite-260215", Name: "doubao-seed-2-0-lite-260215"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIType: "openai-responses", BaseURL: "https://ark.cn-beijing.volces.com/api/v3",
|
|
||||||
DiscoverModels: true,
|
|
||||||
Models: []Model{{ID: "doubao-seed-2-0-pro-260215", Name: "doubao-seed-2-0-pro-260215"}, {ID: "doubao-seed-2-0-lite-260215", Name: "doubao-seed-2-0-lite-260215"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIType: "openai-images", BaseURL: "https://ark.cn-beijing.volces.com/api/v3",
|
|
||||||
Models: []Model{
|
|
||||||
{ID: "doubao-seedream-5-0-260128", Name: "doubao-seedream-5-0-260128"},
|
|
||||||
{ID: "doubao-seedream-5-0-lite-260128", Name: "doubao-seedream-5-0-lite-260128"},
|
|
||||||
{ID: "doubao-seedream-4-5-251128", Name: "doubao-seedream-4-5-251128"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func editableAPIConfigs(discoverModels bool, 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,
|
|
||||||
DiscoverModels: discoverModels && (apiType == "openai-completions" || apiType == "openai-responses"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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) {
|
func Get(key string) (Meta, bool) {
|
||||||
@@ -275,148 +332,12 @@ func All() map[string]Meta {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindAPIConfig(key, apiType string) (APIConfig, bool) {
|
|
||||||
meta, ok := catalog[key]
|
|
||||||
if !ok {
|
|
||||||
return APIConfig{}, false
|
|
||||||
}
|
|
||||||
target := strings.TrimSpace(apiType)
|
|
||||||
if target == "" {
|
|
||||||
target = meta.DefaultAPIType
|
|
||||||
}
|
|
||||||
for _, config := range meta.APIConfigs {
|
|
||||||
if config.APIType == target {
|
|
||||||
config.AuthModes = append([]string(nil), config.AuthModes...)
|
|
||||||
config.Models = append([]Model(nil), config.Models...)
|
|
||||||
return config, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return APIConfig{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultModels(key, apiType string) []Model {
|
|
||||||
meta, ok := catalog[key]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
target := strings.TrimSpace(apiType)
|
|
||||||
if target == "" {
|
|
||||||
target = meta.DefaultAPIType
|
|
||||||
}
|
|
||||||
for _, config := range meta.APIConfigs {
|
|
||||||
if config.APIType != target {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(config.Models) > 0 {
|
|
||||||
return append([]Model(nil), config.Models...)
|
|
||||||
}
|
|
||||||
if IsImageAPIType(config.APIType) || IsEmbeddingAPIType(config.APIType) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return append([]Model(nil), meta.Models...)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
func DefaultBaseURL(key string) (string, bool) {
|
||||||
config, ok := FindAPIConfig(key, "")
|
meta, ok := catalog[key]
|
||||||
if !ok || strings.TrimSpace(config.BaseURL) == "" {
|
if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return config.BaseURL, true
|
return meta.DefaultBaseURL, 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")
|
|
||||||
}
|
|
||||||
if key == "custom" && (IsImageAPIType(config.APIType) || IsEmbeddingAPIType(config.APIType)) {
|
|
||||||
return baseURL, nil
|
|
||||||
}
|
|
||||||
parsed.Path = normalizeEndpointPath(config.APIType, parsed.Path)
|
|
||||||
parsed.RawQuery = ""
|
|
||||||
parsed.Fragment = ""
|
|
||||||
return strings.TrimRight(parsed.String(), "/"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeEndpointPath(apiType, value string) string {
|
|
||||||
if IsImageAPIType(apiType) {
|
|
||||||
return strings.TrimRight(value, "/")
|
|
||||||
}
|
|
||||||
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"}
|
|
||||||
case "openai-embeddings":
|
|
||||||
suffixes = []string{"/v1/embeddings", "/embeddings"}
|
|
||||||
}
|
|
||||||
for _, suffix := range suffixes {
|
|
||||||
if strings.HasSuffix(strings.ToLower(path), suffix) {
|
|
||||||
return path[:len(path)-len(suffix)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsEmbeddingAPIType(apiType string) bool {
|
|
||||||
return apiType == "openai-embeddings"
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsImageAPIType(apiType string) bool {
|
|
||||||
switch apiType {
|
|
||||||
case "openai-images", "dashscope-images", "minimax-images", "openrouter-images":
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnvKey(key string) string {
|
func EnvKey(key string) string {
|
||||||
@@ -429,62 +350,89 @@ func EnvKey(key string) string {
|
|||||||
|
|
||||||
func DisplayName(key string) string {
|
func DisplayName(key string) string {
|
||||||
meta, ok := catalog[key]
|
meta, ok := catalog[key]
|
||||||
if !ok || strings.TrimSpace(meta.DisplayName) == "" {
|
if !ok {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(meta.DisplayName) == "" {
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
return meta.DisplayName
|
return meta.DisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisplayNameKey(key string) string {
|
func FindModel(key, modelID string) (Model, bool) {
|
||||||
meta, ok := catalog[key]
|
meta, ok := Get(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return Model{}, false
|
||||||
}
|
}
|
||||||
return meta.DisplayNameKey
|
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 target
|
return Model{}, false
|
||||||
}
|
|
||||||
|
|
||||||
var legacyModelPrefixes = map[string][]string{
|
|
||||||
"custom": {"custom"},
|
|
||||||
"vllm": {"custom"},
|
|
||||||
"ollama": {"ollama"},
|
|
||||||
"llmman": {"llmman"},
|
|
||||||
"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 {
|
func cloneMeta(meta Meta) Meta {
|
||||||
clone := meta
|
clone := meta
|
||||||
clone.APIConfigs = make([]APIConfig, len(meta.APIConfigs))
|
if len(meta.Default.Input) > 0 {
|
||||||
for index, config := range meta.APIConfigs {
|
clone.Default.Input = make([]string, len(meta.Default.Input))
|
||||||
clone.APIConfigs[index] = config
|
copy(clone.Default.Input, meta.Default.Input)
|
||||||
clone.APIConfigs[index].AuthModes = append([]string(nil), config.AuthModes...)
|
}
|
||||||
clone.APIConfigs[index].Models = append([]Model(nil), config.Models...)
|
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.Models = append([]Model(nil), meta.Models...)
|
|
||||||
return clone
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"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, "/models"):
|
|
||||||
return base
|
|
||||||
case hasAPIVersionSuffix(base):
|
|
||||||
return base + "/models"
|
|
||||||
default:
|
|
||||||
return base + "/v1/models"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasAPIVersionSuffix(value string) bool {
|
|
||||||
segment := value[strings.LastIndex(value, "/")+1:]
|
|
||||||
if len(segment) < 2 || segment[0] != 'v' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err := strconv.Atoi(segment[1:])
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
@@ -15,52 +15,72 @@ type OpenClawProviderPatch struct {
|
|||||||
AuthHeader bool
|
AuthHeader bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL, apiKey string) (*OpenClawProviderPatch, error) {
|
func BuildOpenClawProviderPatch(provider, modelName, apiType, baseURL, apiKey string) (*OpenClawProviderPatch, error) {
|
||||||
if modelName == "" {
|
if modelName == "" {
|
||||||
return nil, fmt.Errorf("model is required")
|
return nil, fmt.Errorf("model is required")
|
||||||
}
|
}
|
||||||
resolvedAPIType := apiType
|
resolvedAPIType, _, _ := ResolveRuntimeParams(provider, apiType, 0, 0)
|
||||||
if _, ok := FindAPIConfig(provider, resolvedAPIType); !ok {
|
modelID := resolveOpenClawModelID(provider, modelName)
|
||||||
resolvedAPIType = DefaultAPIType(provider)
|
|
||||||
}
|
|
||||||
if IsImageAPIType(resolvedAPIType) || IsEmbeddingAPIType(resolvedAPIType) {
|
|
||||||
return nil, fmt.Errorf("api type %s does not support text generation", resolvedAPIType)
|
|
||||||
}
|
|
||||||
resolvedAuthMode, err := ResolveAuthMode(provider, resolvedAPIType, authMode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
usesBearer := resolvedAuthMode == AuthModeBearer
|
|
||||||
modelID := NormalizeModelID(provider, modelName)
|
|
||||||
providerKey := provider
|
|
||||||
preserveQualifiedModel := false
|
|
||||||
switch provider {
|
switch provider {
|
||||||
|
case "deepseek":
|
||||||
|
return newOpenClawProviderPatch(modelName, "deepseek", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||||
case "gemini":
|
case "gemini":
|
||||||
providerKey = "google"
|
return newOpenClawProviderPatch("google/"+modelID, "google", modelID, apiKey, baseURL, resolvedAPIType, false), nil
|
||||||
resolvedAPIType = "google-generative-ai"
|
|
||||||
usesBearer = false
|
|
||||||
case "moonshot", "kimi":
|
case "moonshot", "kimi":
|
||||||
providerKey = "moonshot"
|
return buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey), nil
|
||||||
resolvedAPIType = "openai-completions"
|
case "bailian-coding-plan":
|
||||||
usesBearer = false
|
return newOpenClawProviderPatch("bailian-coding-plan/"+modelID, "bailian-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||||
case "ollama", "llmman":
|
case "ark-coding-plan":
|
||||||
apiKey = provider
|
return newOpenClawProviderPatch("ark-coding-plan/"+modelID, "ark-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
|
||||||
usesBearer = false
|
case "minimax":
|
||||||
case "openai", "openrouter", "anthropic":
|
return newOpenClawProviderPatch("minimax/"+modelID, "minimax", modelID, apiKey, baseURL, "anthropic-messages", true), nil
|
||||||
preserveQualifiedModel = strings.Contains(modelName, "/")
|
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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
primaryModel := providerKey + "/" + modelID
|
func buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey string) *OpenClawProviderPatch {
|
||||||
if preserveQualifiedModel {
|
providerKey := provider
|
||||||
primaryModel = modelName
|
primaryModel := modelName
|
||||||
|
if provider == "kimi" {
|
||||||
|
providerKey = "moonshot"
|
||||||
|
primaryModel = "moonshot/" + modelID
|
||||||
}
|
}
|
||||||
|
return newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, "openai-completions", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, apiType string, authHeader bool) *OpenClawProviderPatch {
|
||||||
return &OpenClawProviderPatch{
|
return &OpenClawProviderPatch{
|
||||||
PrimaryModel: primaryModel,
|
PrimaryModel: primaryModel,
|
||||||
ProviderKey: providerKey,
|
ProviderKey: providerKey,
|
||||||
ModelID: modelID,
|
ModelID: modelID,
|
||||||
APIKey: apiKey,
|
APIKey: apiKey,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
APIType: resolvedAPIType,
|
APIType: apiType,
|
||||||
AuthHeader: usesBearer,
|
AuthHeader: authHeader,
|
||||||
}, nil
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-119
@@ -3,9 +3,7 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,158 +18,160 @@ type VerifyRequest struct {
|
|||||||
Body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type verifyErrorResponse struct {
|
|
||||||
Error struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
} `json:"error"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultVerifyTimeout = 30 * time.Second
|
defaultVerifyTimeout = 30 * time.Second
|
||||||
defaultVerifyMaxTokens = 16
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SkipVerification(provider string) bool {
|
func SkipVerification(key string) bool {
|
||||||
switch provider {
|
switch key {
|
||||||
case "vllm", "ollama", "llmman", "kimi-coding":
|
case "custom", "vllm", "ollama", "kimi-coding":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyAccount(provider, apiType, authMode, baseURL, apiKey, model string) error {
|
func VerifyAccount(provider, baseURL, apiKey string) error {
|
||||||
req := BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model)
|
req := BuildVerifyRequest(provider, baseURL, apiKey)
|
||||||
httpReq, err := http.NewRequest(req.Method, req.URL, bytes.NewReader(req.Body))
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for key, value := range req.Headers {
|
for key, value := range req.Headers {
|
||||||
httpReq.Header.Set(key, value)
|
httpReq.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("Accept", "application/json")
|
resp, err := (&http.Client{Timeout: verifyTimeout()}).Do(httpReq)
|
||||||
resp, err := (&http.Client{Timeout: defaultVerifyTimeout}).Do(httpReq)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return buserr.WithErr("ErrAgentAccountUnavailable", err)
|
return buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
if resp.StatusCode >= 400 {
|
||||||
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
|
return buserr.WithErr("ErrAgentAccountUnavailable", fmt.Errorf("verify failed: %s", resp.Status))
|
||||||
if readErr != nil {
|
|
||||||
return buserr.WithErr("ErrAgentAccountUnavailable", readErr)
|
|
||||||
}
|
|
||||||
return buserr.WithErr("ErrAgentAccountUnavailable", errors.New(verifyHTTPError(resp.StatusCode, body)))
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model string) VerifyRequest {
|
func verifyTimeout() time.Duration {
|
||||||
baseURL = strings.TrimSpace(baseURL)
|
return defaultVerifyTimeout
|
||||||
if provider != "custom" || !IsImageAPIType(apiType) {
|
}
|
||||||
baseURL = strings.TrimRight(baseURL, "/")
|
|
||||||
}
|
|
||||||
headers := map[string]string{"Content-Type": "application/json"}
|
|
||||||
request := VerifyRequest{Method: http.MethodPost, Headers: headers}
|
|
||||||
|
|
||||||
if provider == "gemini" {
|
func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
|
||||||
request.URL = baseURL + "/v1beta/models/" + strings.TrimSpace(model) + ":generateContent"
|
base := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||||
headers["x-goog-api-key"] = apiKey
|
headers := map[string]string{}
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
request := VerifyRequest{Method: http.MethodGet, Headers: headers}
|
||||||
"contents": []map[string]interface{}{{"parts": []map[string]string{{"text": "test"}}}},
|
|
||||||
})
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
switch apiType {
|
switch provider {
|
||||||
case "openai-embeddings":
|
case "anthropic", "kimi-coding":
|
||||||
request.URL = embeddingVerifyURL(baseURL)
|
headers["x-api-key"] = apiKey
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
|
||||||
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "ping"})
|
|
||||||
case "openai-images":
|
|
||||||
request.URL = imageVerifyURL(provider, baseURL, "/images/generations")
|
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
|
||||||
request.Body = mustJSON(map[string]interface{}{"model": model, "prompt": "test", "n": 1, "response_format": "url"})
|
|
||||||
case "dashscope-images":
|
|
||||||
request.URL = imageVerifyURL(provider, baseURL, "/api/v1/services/aigc/multimodal-generation/generation")
|
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
|
||||||
"model": model,
|
|
||||||
"input": map[string]interface{}{"messages": []map[string]interface{}{
|
|
||||||
{"role": "user", "content": []map[string]string{{"text": "test"}}},
|
|
||||||
}},
|
|
||||||
"parameters": map[string]interface{}{"n": 1},
|
|
||||||
})
|
|
||||||
case "minimax-images":
|
|
||||||
request.URL = imageVerifyURL(provider, baseURL, "/v1/image_generation")
|
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
|
||||||
request.Body = mustJSON(map[string]interface{}{"model": model, "prompt": "test", "n": 1, "response_format": "url"})
|
|
||||||
case "openrouter-images":
|
|
||||||
request.URL = imageVerifyURL(provider, baseURL, "/api/v1/images")
|
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
|
||||||
request.Body = mustJSON(map[string]interface{}{"model": model, "prompt": "test", "n": 1, "response_format": "url"})
|
|
||||||
case "anthropic-messages":
|
|
||||||
request.URL = baseURL + "/v1/messages"
|
|
||||||
if authMode == AuthModeBearer {
|
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
|
||||||
} else {
|
|
||||||
headers["x-api-key"] = apiKey
|
|
||||||
}
|
|
||||||
headers["anthropic-version"] = "2023-06-01"
|
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"
|
||||||
|
}
|
||||||
|
headers["x-goog-api-key"] = apiKey
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
request.Body = mustJSON(map[string]interface{}{
|
request.Body = mustJSON(map[string]interface{}{
|
||||||
"model": model, "max_tokens": defaultVerifyMaxTokens, "stream": false,
|
"contents": []map[string]interface{}{{
|
||||||
"messages": []map[string]interface{}{{"role": "user", "content": []map[string]string{{"type": "text", "text": "test"}}}},
|
"parts": []map[string]string{{
|
||||||
|
"text": "Explain how AI works in a few words",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
})
|
})
|
||||||
case "openai-responses":
|
case "zai":
|
||||||
request.URL = baseURL + "/responses"
|
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
request.URL = base + "/models"
|
||||||
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "test", "max_output_tokens": defaultVerifyMaxTokens, "stream": false})
|
case "bailian-coding-plan":
|
||||||
default:
|
request.Method = http.MethodPost
|
||||||
request.URL = baseURL + "/chat/completions"
|
if !strings.Contains(base, "/v1") {
|
||||||
if (provider != "ollama" && provider != "llmman") || strings.TrimSpace(apiKey) != "" {
|
base = base + "/v1"
|
||||||
headers["Authorization"] = "Bearer " + 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{}{
|
request.Body = mustJSON(map[string]interface{}{
|
||||||
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": defaultVerifyMaxTokens, "stream": false,
|
"model": "MiniMax-M2.5",
|
||||||
|
"max_tokens": 1,
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||||
|
if strings.Contains(base, "/v1") {
|
||||||
|
request.URL = base + "/models"
|
||||||
|
} else {
|
||||||
|
request.URL = base + "/v1/models"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
func embeddingVerifyURL(baseURL string) string {
|
|
||||||
lowerBaseURL := strings.ToLower(baseURL)
|
|
||||||
if strings.HasSuffix(lowerBaseURL, "/embeddings") {
|
|
||||||
return baseURL
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(lowerBaseURL, "/v1") {
|
|
||||||
return baseURL + "/embeddings"
|
|
||||||
}
|
|
||||||
return baseURL + "/v1/embeddings"
|
|
||||||
}
|
|
||||||
|
|
||||||
func imageVerifyURL(provider, baseURL, endpoint string) string {
|
|
||||||
if provider == "custom" || strings.HasSuffix(strings.ToLower(baseURL), endpoint) {
|
|
||||||
return baseURL
|
|
||||||
}
|
|
||||||
return baseURL + endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
func mustJSON(value interface{}) []byte {
|
||||||
payload, err := json.Marshal(value)
|
payload, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type IAgentAccountRepo interface {
|
|||||||
Save(account *model.AgentAccount) error
|
Save(account *model.AgentAccount) error
|
||||||
DeleteByID(id uint) error
|
DeleteByID(id uint) error
|
||||||
List(opts ...DBOption) ([]model.AgentAccount, error)
|
List(opts ...DBOption) ([]model.AgentAccount, error)
|
||||||
CountTextByProviders(providers []string) (map[string]int64, error)
|
CountByProviders(providers []string) (map[string]int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIAgentAccountRepo() IAgentAccountRepo {
|
func NewIAgentAccountRepo() IAgentAccountRepo {
|
||||||
@@ -67,7 +67,7 @@ func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) {
|
|||||||
return accounts, nil
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentAccountRepo) CountTextByProviders(providers []string) (map[string]int64, error) {
|
func (a AgentAccountRepo) CountByProviders(providers []string) (map[string]int64, error) {
|
||||||
normalizedProviders := normalizeProviders(providers)
|
normalizedProviders := normalizeProviders(providers)
|
||||||
counts := make(map[string]int64, len(normalizedProviders))
|
counts := make(map[string]int64, len(normalizedProviders))
|
||||||
for _, provider := range normalizedProviders {
|
for _, provider := range normalizedProviders {
|
||||||
@@ -86,7 +86,6 @@ func (a AgentAccountRepo) CountTextByProviders(providers []string) (map[string]i
|
|||||||
Model(&model.AgentAccount{}).
|
Model(&model.AgentAccount{}).
|
||||||
Select("provider, COUNT(*) as count").
|
Select("provider, COUNT(*) as count").
|
||||||
Where("provider IN ?", normalizedProviders).
|
Where("provider IN ?", normalizedProviders).
|
||||||
Scopes(WithTextAPIType()).
|
|
||||||
Group("provider").
|
Group("provider").
|
||||||
Scan(&rows).Error; err != nil {
|
Scan(&rows).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+9
-217
@@ -1,30 +1,20 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/google/uuid"
|
|
||||||
"google.golang.org/genproto/googleapis/type/date"
|
"google.golang.org/genproto/googleapis/type/date"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AlertRepo struct{}
|
type AlertRepo struct{}
|
||||||
|
|
||||||
var (
|
|
||||||
ErrAlertConfigRevisionConflict = errors.New("alert config revision conflict")
|
|
||||||
ErrAlertConfigRevisionRequired = errors.New("alert config revision is required")
|
|
||||||
)
|
|
||||||
|
|
||||||
type IAlertRepo interface {
|
type IAlertRepo interface {
|
||||||
WithByType(alertType string) DBOption
|
WithByType(alertType string) DBOption
|
||||||
WithByStatusIn(status []string) DBOption
|
WithByStatusIn(status []string) DBOption
|
||||||
@@ -34,7 +24,6 @@ type IAlertRepo interface {
|
|||||||
WithByCreateAt(date *date.Date) DBOption
|
WithByCreateAt(date *date.Date) DBOption
|
||||||
WithByLicenseId(licenseId string) DBOption
|
WithByLicenseId(licenseId string) DBOption
|
||||||
WithByRecordId(recordId uint) DBOption
|
WithByRecordId(recordId uint) DBOption
|
||||||
WithByDeliveryLogID(logID uint) DBOption
|
|
||||||
WithByAlertMethodContainsConfigID(id uint) DBOption
|
WithByAlertMethodContainsConfigID(id uint) DBOption
|
||||||
WithByMethodConfigIDs(ids []uint) DBOption
|
WithByMethodConfigIDs(ids []uint) DBOption
|
||||||
|
|
||||||
@@ -56,8 +45,6 @@ type IAlertRepo interface {
|
|||||||
CleanAlertLogs() error
|
CleanAlertLogs() error
|
||||||
|
|
||||||
CreateAlertTask(alertTaskBase *model.AlertTask) error
|
CreateAlertTask(alertTaskBase *model.AlertTask) error
|
||||||
CreatePendingAlertTask(logID, alertID uint, alertTask *model.AlertTask) (bool, error)
|
|
||||||
FinalizePendingAlertTask(logID uint, succeeded bool, message string, fallback *model.AlertTask) (bool, error)
|
|
||||||
DeleteAlertTask(opts ...DBOption) error
|
DeleteAlertTask(opts ...DBOption) error
|
||||||
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
|
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
|
||||||
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
|
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
|
||||||
@@ -68,7 +55,6 @@ type IAlertRepo interface {
|
|||||||
GetConfigById(id uint) (model.AlertConfig, error)
|
GetConfigById(id uint) (model.AlertConfig, error)
|
||||||
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
||||||
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
|
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
|
||||||
UpdateAlertConfigWithRevision(maps map[string]interface{}, revision *time.Time, opts ...DBOption) error
|
|
||||||
CreateAlertConfig(config *model.AlertConfig) error
|
CreateAlertConfig(config *model.AlertConfig) error
|
||||||
DeleteAlertConfig(opts ...DBOption) error
|
DeleteAlertConfig(opts ...DBOption) error
|
||||||
|
|
||||||
@@ -237,78 +223,13 @@ func (a *AlertRepo) DeleteLog(opts ...DBOption) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlertRepo) CleanAlertLogs() error {
|
func (a *AlertRepo) CleanAlertLogs() error {
|
||||||
return global.AlertDB.Where("status <> ?", constant.AlertPushing).Delete(&model.AlertLog{}).Error
|
return global.AlertDB.Where("1 = 1").Delete(&model.AlertLog{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
|
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
|
||||||
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error
|
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlertRepo) CreatePendingAlertTask(logID, alertID uint, alertTask *model.AlertTask) (bool, error) {
|
|
||||||
if alertTask == nil {
|
|
||||||
return false, fmt.Errorf("pending alert task is required")
|
|
||||||
}
|
|
||||||
created := false
|
|
||||||
err := global.AlertDB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
var log model.AlertLog
|
|
||||||
if err := tx.Where("id = ? AND status = ?", logID, constant.AlertPushing).First(&log).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if log.AlertId != alertID || log.Type != alertTask.Type || log.Method != alertTask.Method {
|
|
||||||
return fmt.Errorf("pending alert task does not match delivery log %d", logID)
|
|
||||||
}
|
|
||||||
alertTask.DeliveryLogID = &logID
|
|
||||||
result := tx.Clauses(clause.OnConflict{
|
|
||||||
Columns: []clause.Column{{Name: "delivery_log_id"}},
|
|
||||||
DoNothing: true,
|
|
||||||
}).Create(alertTask)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
created = result.RowsAffected > 0
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return created, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlertRepo) FinalizePendingAlertTask(logID uint, succeeded bool, message string, fallback *model.AlertTask) (bool, error) {
|
|
||||||
finalized := false
|
|
||||||
err := global.AlertDB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
status := constant.AlertError
|
|
||||||
if succeeded {
|
|
||||||
status = constant.AlertSuccess
|
|
||||||
message = ""
|
|
||||||
}
|
|
||||||
result := tx.Model(&model.AlertLog{}).
|
|
||||||
Where("id = ? AND status = ?", logID, constant.AlertPushing).
|
|
||||||
Updates(map[string]interface{}{"status": status, "message": message})
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
finalized = true
|
|
||||||
if !succeeded {
|
|
||||||
return tx.Where("delivery_log_id = ?", logID).Delete(&model.AlertTask{}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
var count int64
|
|
||||||
if err := tx.Model(&model.AlertTask{}).Where("delivery_log_id = ?", logID).Count(&count).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if fallback == nil {
|
|
||||||
return fmt.Errorf("pending alert task metadata is unavailable for delivery log %d", logID)
|
|
||||||
}
|
|
||||||
fallback.DeliveryLogID = &logID
|
|
||||||
return tx.Create(fallback).Error
|
|
||||||
})
|
|
||||||
return finalized, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
|
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
|
||||||
db, _ := getAlertDB(opts...)
|
db, _ := getAlertDB(opts...)
|
||||||
return db.Delete(&model.AlertTask{}).Error
|
return db.Delete(&model.AlertTask{}).Error
|
||||||
@@ -389,23 +310,7 @@ func (a *AlertRepo) UpdateAlertConfig(maps map[string]interface{}, opts ...DBOpt
|
|||||||
return db.Model(&model.AlertConfig{}).Updates(maps).Error
|
return db.Model(&model.AlertConfig{}).Updates(maps).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlertRepo) UpdateAlertConfigWithRevision(maps map[string]interface{}, revision *time.Time, opts ...DBOption) error {
|
|
||||||
if revision == nil {
|
|
||||||
return a.UpdateAlertConfig(maps, opts...)
|
|
||||||
}
|
|
||||||
db, _ := getAlertDB(opts...)
|
|
||||||
result := db.Model(&model.AlertConfig{}).Where("updated_at = ?", *revision).Updates(maps)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return ErrAlertConfigRevisionConflict
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
|
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
|
||||||
ensureAlertConfigUID(config)
|
|
||||||
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
|
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,12 +338,6 @@ func (a *AlertRepo) WithByTypeNotIn(types []string) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlertRepo) WithByDeliveryLogID(logID uint) DBOption {
|
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
|
||||||
return g.Where("delivery_log_id = ?", logID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
|
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
|
||||||
var configs []model.AlertConfig
|
var configs []model.AlertConfig
|
||||||
db := global.AlertDB.Model(&model.AlertConfig{})
|
db := global.AlertDB.Model(&model.AlertConfig{})
|
||||||
@@ -479,44 +378,26 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldConfigMap := make(map[string]model.AlertConfig)
|
oldConfigMap := make(map[string]uint)
|
||||||
oldConfigByUID := make(map[string]model.AlertConfig)
|
|
||||||
oldConfigByType := make(map[string][]model.AlertConfig)
|
oldConfigByType := make(map[string][]model.AlertConfig)
|
||||||
oldConfigByKey := make(map[string][]model.AlertConfig)
|
oldConfigByKey := make(map[string][]model.AlertConfig)
|
||||||
consumedConfigIDs := make(map[uint]struct{})
|
consumedConfigIDs := make(map[uint]struct{})
|
||||||
for _, item := range oldConfigs {
|
for _, item := range oldConfigs {
|
||||||
if strings.TrimSpace(item.UID) != "" {
|
|
||||||
oldConfigByUID[item.UID] = item
|
|
||||||
}
|
|
||||||
if singletonTypes[item.Type] {
|
if singletonTypes[item.Type] {
|
||||||
oldConfigMap[item.Type] = item
|
oldConfigMap[item.Type] = item.ID
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
|
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
|
||||||
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
|
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
|
||||||
}
|
}
|
||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
if uid := strings.TrimSpace(item.UID); uid != "" {
|
|
||||||
if matched, ok := oldConfigByUID[uid]; ok && matched.Type != item.Type {
|
|
||||||
tx.Rollback()
|
|
||||||
return fmt.Errorf("alert config UID %q belongs to type %q, not %q", uid, matched.Type, item.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if singletonTypes[item.Type] {
|
if singletonTypes[item.Type] {
|
||||||
if matched, ok := oldConfigMap[item.Type]; ok {
|
if val, ok := oldConfigMap[item.Type]; ok {
|
||||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
item.ID = val
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(oldConfigMap, item.Type)
|
delete(oldConfigMap, item.Type)
|
||||||
consumedConfigIDs[item.ID] = struct{}{}
|
consumedConfigIDs[item.ID] = struct{}{}
|
||||||
} else {
|
} else {
|
||||||
item.ID = 0
|
item.ID = 0
|
||||||
ensureAlertConfigUID(&item)
|
|
||||||
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if item.ID == 0 {
|
if item.ID == 0 {
|
||||||
if err := tx.Create(&item).Error; err != nil {
|
if err := tx.Create(&item).Error; err != nil {
|
||||||
@@ -530,31 +411,9 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(item.UID) != "" {
|
|
||||||
if matched, ok := oldConfigByUID[item.UID]; ok {
|
|
||||||
delete(oldConfigByUID, item.UID)
|
|
||||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
consumedConfigIDs[item.ID] = struct{}{}
|
|
||||||
if err := tx.Save(&item).Error; err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
deleteAlertConfigByID(oldConfigByType, matched.ID)
|
|
||||||
deleteAlertConfigByID(oldConfigByKey, matched.ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
key := alertConfigSyncKey(item)
|
key := alertConfigSyncKey(item)
|
||||||
if matched, ok := popAlertConfigByKey(oldConfigByKey, key); ok {
|
if matched, ok := popAlertConfigByKey(oldConfigByKey, key); ok {
|
||||||
delete(oldConfigByUID, matched.UID)
|
item.ID = matched.ID
|
||||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
consumedConfigIDs[item.ID] = struct{}{}
|
consumedConfigIDs[item.ID] = struct{}{}
|
||||||
if err := tx.Save(&item).Error; err != nil {
|
if err := tx.Save(&item).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
@@ -565,12 +424,7 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matched, ok := popUnusedAlertConfigByType(oldConfigByType, usedConfigIDs, item.Type); ok {
|
if matched, ok := popUnusedAlertConfigByType(oldConfigByType, usedConfigIDs, item.Type); ok {
|
||||||
delete(oldConfigByUID, matched.UID)
|
item.ID = matched.ID
|
||||||
deleteAlertConfigByID(oldConfigByKey, matched.ID)
|
|
||||||
if err := inheritAlertConfigSyncState(&item, matched); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
consumedConfigIDs[item.ID] = struct{}{}
|
consumedConfigIDs[item.ID] = struct{}{}
|
||||||
if err := tx.Save(&item).Error; err != nil {
|
if err := tx.Save(&item).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
@@ -580,11 +434,6 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.ID = 0
|
item.ID = 0
|
||||||
ensureAlertConfigUID(&item)
|
|
||||||
if err := validateAlertConfigSyncSecret(&item); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tx.Create(&item).Error; err != nil {
|
if err := tx.Create(&item).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return err
|
return err
|
||||||
@@ -609,63 +458,6 @@ func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureAlertConfigUID(config *model.AlertConfig) {
|
|
||||||
if config != nil && strings.TrimSpace(config.UID) == "" {
|
|
||||||
config.UID = uuid.NewString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func inheritAlertConfigSyncState(incoming *model.AlertConfig, existing model.AlertConfig) error {
|
|
||||||
if incoming.Type != existing.Type {
|
|
||||||
return fmt.Errorf("alert config UID %q belongs to type %q, not %q", incoming.UID, existing.Type, incoming.Type)
|
|
||||||
}
|
|
||||||
preserveExistingCustom := incoming.Type == constant.Custom &&
|
|
||||||
existing.Status == constant.AlertDisable &&
|
|
||||||
incoming.Title == existing.Title &&
|
|
||||||
incoming.Status == existing.Status &&
|
|
||||||
incoming.Config == existing.Config &&
|
|
||||||
(incoming.SecretConfig == "" || incoming.SecretConfig == existing.SecretConfig)
|
|
||||||
incoming.ID = existing.ID
|
|
||||||
if strings.TrimSpace(incoming.UID) == "" {
|
|
||||||
incoming.UID = existing.UID
|
|
||||||
}
|
|
||||||
if incoming.Type == constant.Custom && incoming.SecretConfig == "" {
|
|
||||||
incoming.SecretConfig = existing.SecretConfig
|
|
||||||
}
|
|
||||||
if preserveExistingCustom {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return validateAlertConfigSyncSecret(incoming)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateAlertConfigSyncSecret(incoming *model.AlertConfig) error {
|
|
||||||
if incoming.Type != constant.Custom {
|
|
||||||
incoming.SecretConfig = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(incoming.SecretConfig) == "" {
|
|
||||||
return fmt.Errorf("custom webhook sync secret is missing")
|
|
||||||
}
|
|
||||||
var version struct {
|
|
||||||
SchemaVersion int `json:"schemaVersion"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal([]byte(incoming.Config), &version); err != nil || version.SchemaVersion != 1 {
|
|
||||||
return fmt.Errorf("custom webhook sync config must use schemaVersion 1")
|
|
||||||
}
|
|
||||||
secret := incoming.SecretConfig
|
|
||||||
for _, prefix := range []string{"core:v1:", "agent:v1:"} {
|
|
||||||
if !strings.HasPrefix(secret, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ciphertext, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, prefix))
|
|
||||||
if err != nil || len(ciphertext) < 32 || len(ciphertext)%16 != 0 {
|
|
||||||
return fmt.Errorf("custom webhook sync secret envelope is invalid")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("custom webhook sync secret must use a versioned envelope")
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadUsedAlertConfigIDs(tx *gorm.DB) (map[uint]struct{}, error) {
|
func loadUsedAlertConfigIDs(tx *gorm.DB) (map[uint]struct{}, error) {
|
||||||
var alerts []model.Alert
|
var alerts []model.Alert
|
||||||
if err := tx.Select("method").Find(&alerts).Error; err != nil {
|
if err := tx.Select("method").Find(&alerts).Error; err != nil {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ type IAppInstallResourceRpo interface {
|
|||||||
WithAppInstallId(appInstallId uint) DBOption
|
WithAppInstallId(appInstallId uint) DBOption
|
||||||
WithLinkId(linkId uint) DBOption
|
WithLinkId(linkId uint) DBOption
|
||||||
WithResourceId(resourceId uint) DBOption
|
WithResourceId(resourceId uint) DBOption
|
||||||
WithResourceIds(resourceIds []uint) DBOption
|
|
||||||
GetBy(opts ...DBOption) ([]model.AppInstallResource, error)
|
GetBy(opts ...DBOption) ([]model.AppInstallResource, error)
|
||||||
GetFirst(opts ...DBOption) (model.AppInstallResource, error)
|
GetFirst(opts ...DBOption) (model.AppInstallResource, error)
|
||||||
Create(ctx context.Context, resource *model.AppInstallResource) error
|
Create(ctx context.Context, resource *model.AppInstallResource) error
|
||||||
@@ -45,12 +44,6 @@ 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) {
|
func (a AppInstallResourceRpo) GetBy(opts ...DBOption) ([]model.AppInstallResource, error) {
|
||||||
db := global.DB.Model(&model.AppInstallResource{})
|
db := global.DB.Model(&model.AppInstallResource{})
|
||||||
var resources []model.AppInstallResource
|
var resources []model.AppInstallResource
|
||||||
|
|||||||
@@ -49,12 +49,6 @@ func WithByName(name string) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithByPath(path string) DBOption {
|
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
|
||||||
return g.Where("path = ?", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithByAddr(addr string) DBOption {
|
func WithByAddr(addr string) DBOption {
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
return g.Where("addr = ?", addr)
|
return g.Where("addr = ?", addr)
|
||||||
@@ -100,18 +94,6 @@ func WithByProvider(provider string) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithByAPIType(apiType string) DBOption {
|
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
|
||||||
return g.Where("api_type = ?", apiType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithTextAPIType() DBOption {
|
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
|
||||||
return g.Where("api_type NOT LIKE ? AND api_type <> ?", "%-images", "openai-embeddings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithByModel(model string) DBOption {
|
func WithByModel(model string) DBOption {
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
if len(model) == 0 {
|
if len(model) == 0 {
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IDockerPortGuardRepo interface {
|
|
||||||
ListManaged(context.Context) ([]model.DockerPortGuardPolicy, error)
|
|
||||||
ListRuntimeReadOnly(context.Context) ([]model.DockerPortGuardPolicy, error)
|
|
||||||
DeleteBatch(context.Context, []string) error
|
|
||||||
UpsertBatch(context.Context, []model.DockerPortGuardPolicy) error
|
|
||||||
ReplaceRuntimeReadOnly(context.Context, []model.DockerPortGuardPolicy) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerPortGuardRepo struct{}
|
|
||||||
|
|
||||||
func NewIDockerPortGuardRepo() IDockerPortGuardRepo { return &DockerPortGuardRepo{} }
|
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) ListManaged(ctx context.Context) ([]model.DockerPortGuardPolicy, error) {
|
|
||||||
var policies []model.DockerPortGuardPolicy
|
|
||||||
err := global.DB.WithContext(ctx).
|
|
||||||
Where("read_only = ?", false).
|
|
||||||
Order("family, host_ip, host_port, protocol").
|
|
||||||
Find(&policies).Error
|
|
||||||
return policies, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) ListRuntimeReadOnly(ctx context.Context) ([]model.DockerPortGuardPolicy, error) {
|
|
||||||
var policies []model.DockerPortGuardPolicy
|
|
||||||
err := global.DB.WithContext(ctx).
|
|
||||||
Where("read_only = ?", true).
|
|
||||||
Order("family, sequence, host_ip, host_port, protocol").
|
|
||||||
Find(&policies).Error
|
|
||||||
return policies, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) DeleteBatch(ctx context.Context, uuids []string) error {
|
|
||||||
return global.DB.WithContext(ctx).
|
|
||||||
Where("read_only = ? AND uuid IN ?", false, uuids).
|
|
||||||
Delete(&model.DockerPortGuardPolicy{}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) UpsertBatch(ctx context.Context, policies []model.DockerPortGuardPolicy) error {
|
|
||||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
for i := range policies {
|
|
||||||
policies[i].ReadOnly = false
|
|
||||||
if err := tx.Clauses(clause.OnConflict{
|
|
||||||
Columns: []clause.Column{{Name: "read_only"}, {Name: "family"}, {Name: "host_ip"}, {Name: "host_port"}, {Name: "protocol"}},
|
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"mode", "sources", "description", "updated_at"}),
|
|
||||||
}).Create(&policies[i]).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DockerPortGuardRepo) ReplaceRuntimeReadOnly(ctx context.Context, policies []model.DockerPortGuardPolicy) error {
|
|
||||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := tx.Where("read_only = ?", true).
|
|
||||||
Delete(&model.DockerPortGuardPolicy{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(policies) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := range policies {
|
|
||||||
policies[i].ReadOnly = true
|
|
||||||
}
|
|
||||||
return tx.Create(&policies).Error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrFirewallRuleRevisionConflict = errors.New("firewall rule revision conflict")
|
|
||||||
ErrFirewallPersistenceInvalid = errors.New("invalid firewall persistence record")
|
|
||||||
)
|
|
||||||
|
|
||||||
type IFirewallRuleRepo interface {
|
|
||||||
Create(context.Context, *model.FirewallRule) error
|
|
||||||
GetByUUID(context.Context, string) (model.FirewallRule, error)
|
|
||||||
List(context.Context, ...DBOption) ([]model.FirewallRule, error)
|
|
||||||
UpdateWithRevision(context.Context, string, uint, map[string]interface{}) error
|
|
||||||
DeleteWithRevision(context.Context, string, uint) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type FirewallRuleRepo struct {
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithFirewallRuleSource(kind, id string) DBOption {
|
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Where("owner = ?", model.FirewallRuleOwner(kind, id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIFirewallRuleRepo() IFirewallRuleRepo {
|
|
||||||
return &FirewallRuleRepo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFirewallRuleRepo(db *gorm.DB) *FirewallRuleRepo {
|
|
||||||
return &FirewallRuleRepo{db: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FirewallRuleRepo) Create(ctx context.Context, rule *model.FirewallRule) error {
|
|
||||||
if err := prepareFirewallRule(rule); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.dbFor(ctx).Create(rule).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FirewallRuleRepo) GetByUUID(ctx context.Context, ruleUUID string) (model.FirewallRule, error) {
|
|
||||||
var rule model.FirewallRule
|
|
||||||
err := r.dbFor(ctx).Where("uuid = ?", ruleUUID).First(&rule).Error
|
|
||||||
return rule, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FirewallRuleRepo) List(ctx context.Context, opts ...DBOption) ([]model.FirewallRule, error) {
|
|
||||||
var rules []model.FirewallRule
|
|
||||||
db := r.dbFor(ctx).Model(&model.FirewallRule{})
|
|
||||||
for _, opt := range opts {
|
|
||||||
db = opt(db)
|
|
||||||
}
|
|
||||||
return rules, db.Find(&rules).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FirewallRuleRepo) UpdateWithRevision(ctx context.Context, ruleUUID string, expectedRevision uint, updates map[string]interface{}) error {
|
|
||||||
updates = sanitizeRuleUpdates(updates)
|
|
||||||
updates["revision"] = gorm.Expr("revision + 1")
|
|
||||||
result := r.dbFor(ctx).Model(&model.FirewallRule{}).
|
|
||||||
Where("uuid = ? AND revision = ?", ruleUUID, expectedRevision).
|
|
||||||
Updates(updates)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return ErrFirewallRuleRevisionConflict
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FirewallRuleRepo) DeleteWithRevision(ctx context.Context, ruleUUID string, expectedRevision uint) error {
|
|
||||||
result := r.dbFor(ctx).
|
|
||||||
Where("uuid = ? AND revision = ?", ruleUUID, expectedRevision).
|
|
||||||
Delete(&model.FirewallRule{})
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return ErrFirewallRuleRevisionConflict
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FirewallRuleRepo) dbFor(ctx context.Context) *gorm.DB {
|
|
||||||
return firewallDB(ctx, r.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func firewallDB(ctx context.Context, fallback *gorm.DB) *gorm.DB {
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
if tx, ok := ctx.Value(constant.DB).(*gorm.DB); ok && tx != nil {
|
|
||||||
return tx.WithContext(ctx)
|
|
||||||
}
|
|
||||||
if fallback == nil {
|
|
||||||
fallback = global.DB
|
|
||||||
}
|
|
||||||
return fallback.WithContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareFirewallRule(rule *model.FirewallRule) error {
|
|
||||||
if rule == nil {
|
|
||||||
return fmt.Errorf("%w: rule is nil", ErrFirewallPersistenceInvalid)
|
|
||||||
}
|
|
||||||
if rule.Family == "" || rule.Protocol == "" || rule.Action == "" {
|
|
||||||
return fmt.Errorf("%w: atomic rule identity fields are required", ErrFirewallPersistenceInvalid)
|
|
||||||
}
|
|
||||||
if rule.UUID == "" {
|
|
||||||
rule.UUID = uuid.NewString()
|
|
||||||
}
|
|
||||||
if rule.Revision == 0 {
|
|
||||||
rule.Revision = 1
|
|
||||||
}
|
|
||||||
if rule.Origin == "" {
|
|
||||||
rule.Origin = constant.FirewallRuleOriginCreated
|
|
||||||
}
|
|
||||||
if rule.Owner == "" {
|
|
||||||
rule.Owner = constant.FirewallRuleSourceUser
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeRuleUpdates(updates map[string]interface{}) map[string]interface{} {
|
|
||||||
result := cloneUpdates(updates)
|
|
||||||
delete(result, "id")
|
|
||||||
delete(result, "uuid")
|
|
||||||
delete(result, "revision")
|
|
||||||
delete(result, "created_at")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneUpdates(updates map[string]interface{}) map[string]interface{} {
|
|
||||||
result := make(map[string]interface{}, len(updates)+1)
|
|
||||||
for key, value := range updates {
|
|
||||||
result[key] = value
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IForwardingRuleRepo interface {
|
|
||||||
List(context.Context) ([]model.ForwardingRule, error)
|
|
||||||
ReplaceAll(context.Context, []model.ForwardingRule) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ForwardingRuleRepo struct{}
|
|
||||||
|
|
||||||
func NewIForwardingRuleRepo() IForwardingRuleRepo { return &ForwardingRuleRepo{} }
|
|
||||||
|
|
||||||
func (r *ForwardingRuleRepo) List(ctx context.Context) ([]model.ForwardingRule, error) {
|
|
||||||
var rules []model.ForwardingRule
|
|
||||||
err := global.DB.WithContext(ctx).Order("id ASC").Find(&rules).Error
|
|
||||||
return rules, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ForwardingRuleRepo) ReplaceAll(ctx context.Context, rules []model.ForwardingRule) error {
|
|
||||||
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&model.ForwardingRule{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(rules) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return tx.Create(&rules).Error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -22,6 +22,11 @@ type IHostRepo interface {
|
|||||||
WithByPort(port uint) DBOption
|
WithByPort(port uint) DBOption
|
||||||
WithByUser(user string) DBOption
|
WithByUser(user string) DBOption
|
||||||
|
|
||||||
|
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
|
||||||
|
ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error)
|
||||||
|
SaveFirewallRecord(firewall *model.Firewall) error
|
||||||
|
DeleteFirewallRecordByID(id uint) error
|
||||||
|
|
||||||
SyncCert(data []model.RootCert) error
|
SyncCert(data []model.RootCert) error
|
||||||
GetCert(opts ...DBOption) (model.RootCert, error)
|
GetCert(opts ...DBOption) (model.RootCert, error)
|
||||||
PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error)
|
PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error)
|
||||||
@@ -29,6 +34,8 @@ type IHostRepo interface {
|
|||||||
SaveCert(cert *model.RootCert) error
|
SaveCert(cert *model.RootCert) error
|
||||||
UpdateCert(id uint, vars map[string]interface{}) error
|
UpdateCert(id uint, vars map[string]interface{}) error
|
||||||
DeleteCert(opts ...DBOption) error
|
DeleteCert(opts ...DBOption) error
|
||||||
|
|
||||||
|
WithByChain(chain string) DBOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIHostRepo() IHostRepo {
|
func NewIHostRepo() IHostRepo {
|
||||||
@@ -109,6 +116,65 @@ func (h *HostRepo) Delete(opts ...DBOption) error {
|
|||||||
return db.Delete(&model.Host{}).Error
|
return db.Delete(&model.Host{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
|
||||||
|
var firewall model.Firewall
|
||||||
|
db := global.DB
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
err := db.First(&firewall).Error
|
||||||
|
return firewall, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HostRepo) ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error) {
|
||||||
|
var firewalls []model.Firewall
|
||||||
|
db := global.DB
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
if err := global.DB.Find(&firewalls).Error; err != nil {
|
||||||
|
return firewalls, nil
|
||||||
|
}
|
||||||
|
return firewalls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
|
||||||
|
if firewall.ID != 0 {
|
||||||
|
return global.DB.Save(firewall).Error
|
||||||
|
}
|
||||||
|
var data model.Firewall
|
||||||
|
switch firewall.Type {
|
||||||
|
case "port":
|
||||||
|
_ = global.DB.Where("type = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND strategy = ?", "port",
|
||||||
|
firewall.DstPort,
|
||||||
|
firewall.Protocol,
|
||||||
|
firewall.SrcIP,
|
||||||
|
firewall.Strategy,
|
||||||
|
).First(&data).Error
|
||||||
|
case "ip":
|
||||||
|
_ = global.DB.Where("type = ? AND src_ip = ? AND strategy = ?", "address", firewall.SrcIP, firewall.Strategy).First(&data)
|
||||||
|
default:
|
||||||
|
_ = global.DB.Where("type = ? AND chain = ? AND src_port = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND dst_ip = ? AND strategy = ?",
|
||||||
|
firewall.Type,
|
||||||
|
firewall.Chain,
|
||||||
|
firewall.SrcPort,
|
||||||
|
firewall.DstPort,
|
||||||
|
firewall.Protocol,
|
||||||
|
firewall.SrcIP,
|
||||||
|
firewall.DstIP,
|
||||||
|
firewall.Strategy,
|
||||||
|
).First(&data).Error
|
||||||
|
}
|
||||||
|
if data.ID != 0 {
|
||||||
|
firewall.ID = data.ID
|
||||||
|
}
|
||||||
|
return global.DB.Save(firewall).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
|
||||||
|
return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) {
|
func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) {
|
||||||
var cert model.RootCert
|
var cert model.RootCert
|
||||||
db := global.DB
|
db := global.DB
|
||||||
@@ -187,3 +253,9 @@ func (u *HostRepo) SyncCert(data []model.RootCert) error {
|
|||||||
tx.Commit()
|
tx.Commit()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *HostRepo) WithByChain(chain string) DBOption {
|
||||||
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
|
return g.Where("chain = ?", chain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,9 +73,6 @@ func (u *MonitorRepo) CreateMonitorBase(model model.MonitorBase) error {
|
|||||||
return global.MonitorDB.Create(&model).Error
|
return global.MonitorDB.Create(&model).Error
|
||||||
}
|
}
|
||||||
func (s *MonitorRepo) BatchCreateMonitorGPU(list []model.MonitorGPU) error {
|
func (s *MonitorRepo) BatchCreateMonitorGPU(list []model.MonitorGPU) error {
|
||||||
if len(list) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return global.GPUMonitorDB.CreateInBatches(&list, len(list)).Error
|
return global.GPUMonitorDB.CreateInBatches(&list, len(list)).Error
|
||||||
}
|
}
|
||||||
func (u *MonitorRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
|
func (u *MonitorRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ type IWebsiteRepo interface {
|
|||||||
WithGroupID(groupId uint) DBOption
|
WithGroupID(groupId uint) DBOption
|
||||||
WithDefaultServer() DBOption
|
WithDefaultServer() DBOption
|
||||||
WithDomainLike(domain string) DBOption
|
WithDomainLike(domain string) DBOption
|
||||||
WithSearchKeyword(keyword string, ids []uint) DBOption
|
|
||||||
WithRuntimeID(runtimeID uint) DBOption
|
WithRuntimeID(runtimeID uint) DBOption
|
||||||
WithParentID(websiteID uint) DBOption
|
WithParentID(websiteID uint) DBOption
|
||||||
WithType(websiteType string) DBOption
|
WithType(websiteType string) DBOption
|
||||||
WithDBType(dbType string) DBOption
|
WithDBType(dbType string) DBOption
|
||||||
WithDBTypes(dbTypes []string) DBOption
|
|
||||||
WithDBID(dbID uint) DBOption
|
WithDBID(dbID uint) DBOption
|
||||||
|
|
||||||
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
||||||
@@ -78,19 +76,6 @@ 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 {
|
func (w *WebsiteRepo) WithAlias(alias string) DBOption {
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Where("alias = ?", alias)
|
return db.Where("alias = ?", alias)
|
||||||
@@ -133,12 +118,6 @@ 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 {
|
func (w *WebsiteRepo) WithDBID(dbID uint) DBOption {
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Where("db_id = ?", dbID)
|
return db.Where("db_id = ?", dbID)
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IWebsiteTemplateRepo interface {
|
|
||||||
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteTemplate, error)
|
|
||||||
GetFirst(opts ...DBOption) (*model.WebsiteTemplate, error)
|
|
||||||
List(opts ...DBOption) ([]model.WebsiteTemplate, error)
|
|
||||||
Create(template *model.WebsiteTemplate) error
|
|
||||||
Save(template *model.WebsiteTemplate) error
|
|
||||||
DeleteBy(opts ...DBOption) error
|
|
||||||
WithName(name string) DBOption
|
|
||||||
WithType(templateType string) DBOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIWebsiteTemplateRepo() IWebsiteTemplateRepo {
|
|
||||||
return &WebsiteTemplateRepo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateRepo struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) WithName(name string) DBOption {
|
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Where("name like ?", "%"+name+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) WithType(templateType string) DBOption {
|
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Where("type = ?", templateType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteTemplate, error) {
|
|
||||||
var templates []model.WebsiteTemplate
|
|
||||||
db := getDb(opts...).Model(&model.WebsiteTemplate{})
|
|
||||||
count := int64(0)
|
|
||||||
db = db.Count(&count)
|
|
||||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&templates).Error
|
|
||||||
return count, templates, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) GetFirst(opts ...DBOption) (*model.WebsiteTemplate, error) {
|
|
||||||
var template model.WebsiteTemplate
|
|
||||||
db := getDb(opts...).Model(&model.WebsiteTemplate{})
|
|
||||||
if err := db.First(&template).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &template, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) List(opts ...DBOption) ([]model.WebsiteTemplate, error) {
|
|
||||||
var templates []model.WebsiteTemplate
|
|
||||||
err := getDb(opts...).Model(&model.WebsiteTemplate{}).Find(&templates).Error
|
|
||||||
return templates, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) Create(template *model.WebsiteTemplate) error {
|
|
||||||
return getDb().Create(template).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) Save(template *model.WebsiteTemplate) error {
|
|
||||||
return getDb().Save(template).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateRepo) DeleteBy(opts ...DBOption) error {
|
|
||||||
return getDb(opts...).Delete(&model.WebsiteTemplate{}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
type IWebsiteTemplateOutputRepo interface {
|
|
||||||
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteTemplateOutput, error)
|
|
||||||
GetFirst(opts ...DBOption) (*model.WebsiteTemplateOutput, error)
|
|
||||||
List(opts ...DBOption) ([]model.WebsiteTemplateOutput, error)
|
|
||||||
Create(output *model.WebsiteTemplateOutput) error
|
|
||||||
Save(output *model.WebsiteTemplateOutput) error
|
|
||||||
DeleteBy(opts ...DBOption) error
|
|
||||||
WithByTemplateID(templateID uint) DBOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIWebsiteTemplateOutputRepo() IWebsiteTemplateOutputRepo {
|
|
||||||
return &WebsiteTemplateOutputRepo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsiteTemplateOutputRepo struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateOutputRepo) WithByTemplateID(templateID uint) DBOption {
|
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Where("template_id = ?", templateID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateOutputRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteTemplateOutput, error) {
|
|
||||||
var outputs []model.WebsiteTemplateOutput
|
|
||||||
db := getDb(opts...).Model(&model.WebsiteTemplateOutput{})
|
|
||||||
count := int64(0)
|
|
||||||
db = db.Count(&count)
|
|
||||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&outputs).Error
|
|
||||||
return count, outputs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateOutputRepo) GetFirst(opts ...DBOption) (*model.WebsiteTemplateOutput, error) {
|
|
||||||
var output model.WebsiteTemplateOutput
|
|
||||||
db := getDb(opts...).Model(&model.WebsiteTemplateOutput{})
|
|
||||||
if err := db.First(&output).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &output, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateOutputRepo) List(opts ...DBOption) ([]model.WebsiteTemplateOutput, error) {
|
|
||||||
var outputs []model.WebsiteTemplateOutput
|
|
||||||
err := getDb(opts...).Model(&model.WebsiteTemplateOutput{}).Find(&outputs).Error
|
|
||||||
return outputs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateOutputRepo) Create(output *model.WebsiteTemplateOutput) error {
|
|
||||||
return getDb().Create(output).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateOutputRepo) Save(output *model.WebsiteTemplateOutput) error {
|
|
||||||
return getDb().Save(output).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebsiteTemplateOutputRepo) DeleteBy(opts ...DBOption) error {
|
|
||||||
return getDb(opts...).Delete(&model.WebsiteTemplateOutput{}).Error
|
|
||||||
}
|
|
||||||
+141
-280
@@ -8,8 +8,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
@@ -30,8 +30,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var agentAccountMutationMu sync.Mutex
|
|
||||||
|
|
||||||
type IAgentService interface {
|
type IAgentService interface {
|
||||||
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
|
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
|
||||||
BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error)
|
BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error)
|
||||||
@@ -79,7 +77,6 @@ type IAgentService interface {
|
|||||||
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
||||||
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
||||||
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
||||||
DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error)
|
|
||||||
CreateAccountModel(req dto.AgentAccountModelCreateReq) error
|
CreateAccountModel(req dto.AgentAccountModelCreateReq) error
|
||||||
UpdateAccountModel(req dto.AgentAccountModelUpdateReq) error
|
UpdateAccountModel(req dto.AgentAccountModelUpdateReq) error
|
||||||
DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error
|
DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error
|
||||||
@@ -105,10 +102,6 @@ type IAgentService interface {
|
|||||||
UpgradePlugin(req dto.AgentPluginUpgradeReq) error
|
UpgradePlugin(req dto.AgentPluginUpgradeReq) error
|
||||||
UninstallPlugin(req dto.AgentPluginUninstallReq) error
|
UninstallPlugin(req dto.AgentPluginUninstallReq) error
|
||||||
CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error)
|
CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error)
|
||||||
ListPlugins(req dto.AgentPluginsReq) ([]dto.AgentPluginItem, error)
|
|
||||||
SearchPlugins(req dto.AgentPluginSearchReq) ([]dto.AgentPluginSearchItem, error)
|
|
||||||
InstallMarketPlugin(req dto.AgentPluginMarketInstallReq) error
|
|
||||||
OperatePlugin(req dto.AgentPluginOperateReq) error
|
|
||||||
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
|
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +144,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
|
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
|
||||||
return nil, buserr.New("ErrNameIsExist")
|
return nil, buserr.New("ErrNameIsExist")
|
||||||
}
|
}
|
||||||
if !global.CONF.Base.IsEnterprise && !xpack.MultiNodeProvider.IsXpack() {
|
if !xpack.MultiNodeProvider.IsXpack() {
|
||||||
count, _, err := agentRepo.Page(1, 1)
|
count, _, err := agentRepo.Page(1, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -172,6 +165,8 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
provider := ""
|
provider := ""
|
||||||
baseURL := ""
|
baseURL := ""
|
||||||
apiType := ""
|
apiType := ""
|
||||||
|
maxTokens := 0
|
||||||
|
contextWindow := 0
|
||||||
apiKey := ""
|
apiKey := ""
|
||||||
runtimeModel := ""
|
runtimeModel := ""
|
||||||
accountID := uint(0)
|
accountID := uint(0)
|
||||||
@@ -181,7 +176,6 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
var allowedOrigins []string
|
var allowedOrigins []string
|
||||||
var account *model.AgentAccount
|
var account *model.AgentAccount
|
||||||
var installHooks *appInstallHooks
|
var installHooks *appInstallHooks
|
||||||
var dashboardAuth agentDashboardAuth
|
|
||||||
|
|
||||||
if agentType == constant.AppOpenclaw || agentType == constant.AppHermesAgent {
|
if agentType == constant.AppOpenclaw || agentType == constant.AppHermesAgent {
|
||||||
if req.AccountID == 0 {
|
if req.AccountID == 0 {
|
||||||
@@ -202,6 +196,8 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
}
|
}
|
||||||
storedModel = resolvedRuntime.StoredModel
|
storedModel = resolvedRuntime.StoredModel
|
||||||
apiType = resolvedRuntime.APIType
|
apiType = resolvedRuntime.APIType
|
||||||
|
maxTokens = resolvedRuntime.MaxTokens
|
||||||
|
contextWindow = resolvedRuntime.ContextWindow
|
||||||
runtimeModel = resolvedRuntime.PrimaryModel
|
runtimeModel = resolvedRuntime.PrimaryModel
|
||||||
apiKey = account.APIKey
|
apiKey = account.APIKey
|
||||||
accountID = account.ID
|
accountID = account.ID
|
||||||
@@ -226,17 +222,11 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if agentType == constant.AppHermesAgent {
|
} else if agentType == constant.AppHermesAgent {
|
||||||
dashboardAuth = normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
|
||||||
installHooks = &appInstallHooks{
|
installHooks = &appInstallHooks{
|
||||||
AfterCopyData: func(appInstall *model.AppInstall) error {
|
AfterCopyData: func(appInstall *model.AppInstall) error {
|
||||||
if err := prepareHermesInstallFiles(appInstall, account, storedModel); err != nil {
|
return prepareHermesInstallFiles(appInstall, account, storedModel)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeAgentDashboardAuthEnv(appInstall.GetEnvPath(), agentType, dashboardAuth, false)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if agentType == constant.AppCopaw {
|
|
||||||
dashboardAuth = normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
@@ -252,17 +242,12 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
params["PROVIDER"] = provider
|
params["PROVIDER"] = provider
|
||||||
params["MODEL"] = runtimeModel
|
params["MODEL"] = runtimeModel
|
||||||
params["API_TYPE"] = apiType
|
params["API_TYPE"] = apiType
|
||||||
|
params["MAX_TOKENS"] = maxTokens
|
||||||
|
params["CONTEXT_WINDOW"] = contextWindow
|
||||||
params["BASE_URL"] = baseURL
|
params["BASE_URL"] = baseURL
|
||||||
params["API_KEY"] = apiKey
|
params["API_KEY"] = apiKey
|
||||||
params["OPENCLAW_GATEWAY_TOKEN"] = token
|
params["OPENCLAW_GATEWAY_TOKEN"] = token
|
||||||
}
|
}
|
||||||
if usernameKey, passwordKey, ok := agentDashboardAuthEnvKeys(agentType); ok {
|
|
||||||
params[usernameKey] = dashboardAuth.Username
|
|
||||||
params[passwordKey] = dashboardAuth.Password
|
|
||||||
if agentType == constant.AppCopaw {
|
|
||||||
params[qwenPawAuthEnabledEnvKey] = "true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.EditCompose && strings.TrimSpace(req.DockerCompose) == "" {
|
if req.EditCompose && strings.TrimSpace(req.DockerCompose) == "" {
|
||||||
return nil, buserr.New("ErrAgentComposeRequired")
|
return nil, buserr.New("ErrAgentComposeRequired")
|
||||||
@@ -297,20 +282,22 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|||||||
configPath = path.Join(appInstall.GetPath(), "data", "config.yaml")
|
configPath = path.Join(appInstall.GetPath(), "data", "config.yaml")
|
||||||
}
|
}
|
||||||
agent := &model.Agent{
|
agent := &model.Agent{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Remark: req.Remark,
|
Remark: req.Remark,
|
||||||
AgentType: agentType,
|
AgentType: agentType,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
Model: storedModel,
|
Model: storedModel,
|
||||||
APIType: apiType,
|
APIType: apiType,
|
||||||
BaseURL: baseURL,
|
MaxTokens: maxTokens,
|
||||||
APIKey: apiKey,
|
ContextWindow: contextWindow,
|
||||||
Token: token,
|
BaseURL: baseURL,
|
||||||
Status: appInstall.Status,
|
APIKey: apiKey,
|
||||||
Message: appInstall.Message,
|
Token: token,
|
||||||
AppInstallID: appInstall.ID,
|
Status: appInstall.Status,
|
||||||
AccountID: accountID,
|
Message: appInstall.Message,
|
||||||
ConfigPath: configPath,
|
AppInstallID: appInstall.ID,
|
||||||
|
AccountID: accountID,
|
||||||
|
ConfigPath: configPath,
|
||||||
}
|
}
|
||||||
if err := agentRepo.Create(agent); err != nil {
|
if err := agentRepo.Create(agent); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -408,7 +395,7 @@ func (a AgentService) BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dt
|
|||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, buildBatchTaskID(req.TaskID, "batch-skill-install", agent.ID), agent.ID)
|
installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, buildBatchSkillInstallTaskID(req.TaskID, agent.ID), agent.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = err.Error()
|
result.Message = err.Error()
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
@@ -450,7 +437,7 @@ func (a AgentService) BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBat
|
|||||||
if operate == constant.Delete {
|
if operate == constant.Delete {
|
||||||
if err := a.Delete(dto.AgentDeleteReq{
|
if err := a.Delete(dto.AgentDeleteReq{
|
||||||
ID: agent.ID,
|
ID: agent.ID,
|
||||||
TaskID: buildBatchTaskID(req.TaskID, "batch-operate", agent.ID),
|
TaskID: buildBatchOperateTaskID(req.TaskID, agent.ID),
|
||||||
ForceDelete: req.ForceDelete,
|
ForceDelete: req.ForceDelete,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
result.Message = err.Error()
|
result.Message = err.Error()
|
||||||
@@ -478,7 +465,7 @@ func (a AgentService) BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBat
|
|||||||
if err := NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
if err := NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
||||||
InstallId: install.ID,
|
InstallId: install.ID,
|
||||||
Operate: operate,
|
Operate: operate,
|
||||||
TaskID: buildBatchTaskID(req.TaskID, "batch-operate", agent.ID),
|
TaskID: buildBatchOperateTaskID(req.TaskID, agent.ID),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
result.Message = err.Error()
|
result.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
@@ -562,19 +549,35 @@ func buildBatchUpgradePlans(req dto.AgentBatchUpgradeReq) ([]batchUpgradePlan, [
|
|||||||
DetailID: detail.ID,
|
DetailID: detail.ID,
|
||||||
Backup: req.Backup,
|
Backup: req.Backup,
|
||||||
PullImage: req.PullImage,
|
PullImage: req.PullImage,
|
||||||
TaskID: buildBatchTaskID(req.TaskID, "batch-upgrade", install.ID),
|
TaskID: buildBatchUpgradeTaskID(req.TaskID, install.ID),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return plans, results, nil
|
return plans, results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildBatchTaskID(taskID, prefix string, id uint) string {
|
func buildBatchUpgradeTaskID(taskID string, appInstallID uint) string {
|
||||||
taskID = strings.TrimSpace(taskID)
|
taskID = strings.TrimSpace(taskID)
|
||||||
if taskID == "" {
|
if taskID == "" {
|
||||||
taskID = fmt.Sprintf("%s-%d-%d", prefix, id, time.Now().UnixNano())
|
taskID = fmt.Sprintf("batch-upgrade-%d-%d", appInstallID, time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s-%d", taskID, id)
|
return fmt.Sprintf("%s-%d", taskID, appInstallID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBatchSkillInstallTaskID(taskID string, agentID uint) string {
|
||||||
|
taskID = strings.TrimSpace(taskID)
|
||||||
|
if taskID == "" {
|
||||||
|
taskID = fmt.Sprintf("batch-skill-install-%d-%d", agentID, time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s-%d", taskID, agentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBatchOperateTaskID(taskID string, agentID uint) string {
|
||||||
|
taskID = strings.TrimSpace(taskID)
|
||||||
|
if taskID == "" {
|
||||||
|
taskID = fmt.Sprintf("batch-operate-%d-%d", agentID, time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s-%d", taskID, agentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func batchOperateSkipMessage(operate constant.AppOperate, status string) string {
|
func batchOperateSkipMessage(operate constant.AppOperate, status string) string {
|
||||||
@@ -601,30 +604,28 @@ func batchOperateSkipMessage(operate constant.AppOperate, status string) string
|
|||||||
|
|
||||||
func buildCreateReqFromBatchInstallReq(req dto.AgentBatchInstallReq) dto.AgentCreateReq {
|
func buildCreateReqFromBatchInstallReq(req dto.AgentBatchInstallReq) dto.AgentCreateReq {
|
||||||
return dto.AgentCreateReq{
|
return dto.AgentCreateReq{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Remark: req.Remark,
|
Remark: req.Remark,
|
||||||
AppVersion: req.AppVersion,
|
AppVersion: req.AppVersion,
|
||||||
WebUIPort: req.WebUIPort,
|
WebUIPort: req.WebUIPort,
|
||||||
BridgePort: req.BridgePort,
|
BridgePort: req.BridgePort,
|
||||||
AllowedOrigins: req.AllowedOrigins,
|
AllowedOrigins: req.AllowedOrigins,
|
||||||
AgentType: req.AgentType,
|
AgentType: req.AgentType,
|
||||||
Model: req.Model,
|
Model: req.Model,
|
||||||
AccountID: req.AccountID,
|
AccountID: req.AccountID,
|
||||||
Token: req.Token,
|
Token: req.Token,
|
||||||
DashboardUsername: req.DashboardUsername,
|
TaskID: req.TaskID,
|
||||||
DashboardPassword: req.DashboardPassword,
|
Advanced: req.Advanced,
|
||||||
TaskID: req.TaskID,
|
ContainerName: req.ContainerName,
|
||||||
Advanced: req.Advanced,
|
AllowPort: req.AllowPort,
|
||||||
ContainerName: req.ContainerName,
|
SpecifyIP: req.SpecifyIP,
|
||||||
AllowPort: req.AllowPort,
|
RestartPolicy: req.RestartPolicy,
|
||||||
SpecifyIP: req.SpecifyIP,
|
CpuQuota: req.CpuQuota,
|
||||||
RestartPolicy: req.RestartPolicy,
|
MemoryLimit: req.MemoryLimit,
|
||||||
CpuQuota: req.CpuQuota,
|
MemoryUnit: req.MemoryUnit,
|
||||||
MemoryLimit: req.MemoryLimit,
|
PullImage: req.PullImage,
|
||||||
MemoryUnit: req.MemoryUnit,
|
EditCompose: req.EditCompose,
|
||||||
PullImage: req.PullImage,
|
DockerCompose: req.DockerCompose,
|
||||||
EditCompose: req.EditCompose,
|
|
||||||
DockerCompose: req.DockerCompose,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -703,7 +704,6 @@ func (a AgentService) ensureBatchInstallAccount(req dto.AgentBatchInstallReq) (u
|
|||||||
account.RememberAPIKey = snapshot.RememberAPIKey
|
account.RememberAPIKey = snapshot.RememberAPIKey
|
||||||
account.BaseURL = snapshot.BaseURL
|
account.BaseURL = snapshot.BaseURL
|
||||||
account.APIType = snapshot.APIType
|
account.APIType = snapshot.APIType
|
||||||
account.AuthMode = snapshot.AuthMode
|
|
||||||
account.Remark = snapshot.Remark
|
account.Remark = snapshot.Remark
|
||||||
account.Verified = true
|
account.Verified = true
|
||||||
|
|
||||||
@@ -711,11 +711,6 @@ func (a AgentService) ensureBatchInstallAccount(req dto.AgentBatchInstallReq) (u
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
verifyModel, err := resolveAgentAccountVerifyModel(account.Provider, snapshot.VerifyModel, initialModels)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
account.VerifyModel = verifyModel
|
|
||||||
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
if account.ID == 0 {
|
if account.ID == 0 {
|
||||||
if err := tx.Create(account).Error; err != nil {
|
if err := tx.Create(account).Error; err != nil {
|
||||||
@@ -953,7 +948,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
modelName := resolvedRuntime.StoredModel
|
modelName := resolvedRuntime.StoredModel
|
||||||
apiType := resolvedRuntime.APIType
|
apiType, maxTokens, contextWindow := resolvedRuntime.APIType, resolvedRuntime.MaxTokens, resolvedRuntime.ContextWindow
|
||||||
confDir := path.Dir(agent.ConfigPath)
|
confDir := path.Dir(agent.ConfigPath)
|
||||||
if agent.AgentType == constant.AppHermesAgent {
|
if agent.AgentType == constant.AppHermesAgent {
|
||||||
cfg, err := readHermesConfig(agent.ConfigPath)
|
cfg, err := readHermesConfig(agent.ConfigPath)
|
||||||
@@ -974,6 +969,8 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
|||||||
agent.Provider = account.Provider
|
agent.Provider = account.Provider
|
||||||
agent.Model = modelName
|
agent.Model = modelName
|
||||||
agent.APIType = apiType
|
agent.APIType = apiType
|
||||||
|
agent.MaxTokens = maxTokens
|
||||||
|
agent.ContextWindow = contextWindow
|
||||||
agent.BaseURL = account.BaseURL
|
agent.BaseURL = account.BaseURL
|
||||||
agent.APIKey = account.APIKey
|
agent.APIKey = account.APIKey
|
||||||
agent.AccountID = account.ID
|
agent.AccountID = account.ID
|
||||||
@@ -987,38 +984,20 @@ func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) {
|
|||||||
models := make([]dto.ProviderModelInfo, 0, len(def.Models))
|
models := make([]dto.ProviderModelInfo, 0, len(def.Models))
|
||||||
for _, item := range def.Models {
|
for _, item := range def.Models {
|
||||||
models = append(models, dto.ProviderModelInfo{
|
models = append(models, dto.ProviderModelInfo{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
|
ContextWindow: item.ContextWindow,
|
||||||
|
MaxTokens: item.MaxTokens,
|
||||||
|
Reasoning: item.Reasoning,
|
||||||
|
Input: append([]string(nil), item.Input...),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
apiTypes := make([]dto.ProviderAPIInfo, 0, len(def.APIConfigs))
|
|
||||||
for _, item := range def.APIConfigs {
|
|
||||||
apiModels := make([]dto.ProviderModelInfo, 0, len(item.Models))
|
|
||||||
for _, model := range item.Models {
|
|
||||||
apiModels = append(apiModels, dto.ProviderModelInfo{
|
|
||||||
ID: model.ID,
|
|
||||||
Name: model.Name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
apiTypes = append(apiTypes, dto.ProviderAPIInfo{
|
|
||||||
APIType: item.APIType,
|
|
||||||
BaseURL: item.BaseURL,
|
|
||||||
EditableBaseURL: item.EditableBaseURL,
|
|
||||||
SupportsModelDiscovery: item.DiscoverModels,
|
|
||||||
DefaultAuthMode: item.DefaultAuthMode,
|
|
||||||
AuthModes: item.AuthModes,
|
|
||||||
Models: apiModels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
baseURL, _ := providercatalog.DefaultBaseURL(key)
|
|
||||||
providers = append(providers, dto.ProviderInfo{
|
providers = append(providers, dto.ProviderInfo{
|
||||||
Sort: def.Sort,
|
Sort: def.Sort,
|
||||||
Provider: key,
|
Provider: key,
|
||||||
DisplayName: localizedAgentProviderName(key),
|
DisplayName: def.DisplayName,
|
||||||
BaseURL: baseURL,
|
BaseURL: def.DefaultBaseURL,
|
||||||
DefaultAPIType: def.DefaultAPIType,
|
Models: models,
|
||||||
APITypes: apiTypes,
|
|
||||||
Models: models,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.Slice(providers, func(i, j int) bool {
|
sort.Slice(providers, func(i, j int) bool {
|
||||||
@@ -1028,22 +1007,11 @@ func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
||||||
agentAccountMutationMu.Lock()
|
|
||||||
defer agentAccountMutationMu.Unlock()
|
|
||||||
provider := req.Provider
|
provider := req.Provider
|
||||||
if err := ensureAgentAccountNameAvailable(provider, req.Name, 0); err != nil {
|
if exist, _ := agentAccountRepo.GetFirst(repo.WithByProvider(provider), repo.WithByName(req.Name)); exist != nil && exist.ID > 0 {
|
||||||
return err
|
return buserr.New("ErrRecordExist")
|
||||||
}
|
}
|
||||||
initialModels, err := buildInitialAgentAccountModels(&model.AgentAccount{Provider: provider, APIType: req.APIType}, req.Models)
|
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
verifyModel, err := resolveAgentAccountVerifyModel(provider, req.VerifyModel, initialModels)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
validateAvailability := req.ValidateAvailability == nil || *req.ValidateAvailability
|
|
||||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel, validateAvailability)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1053,12 +1021,14 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
|||||||
APIKey: resolvedInput.APIKey,
|
APIKey: resolvedInput.APIKey,
|
||||||
RememberAPIKey: req.RememberAPIKey,
|
RememberAPIKey: req.RememberAPIKey,
|
||||||
BaseURL: resolvedInput.BaseURL,
|
BaseURL: resolvedInput.BaseURL,
|
||||||
APIType: resolvedInput.APIType,
|
APIType: req.APIType,
|
||||||
AuthMode: resolvedInput.AuthMode,
|
|
||||||
VerifyModel: verifyModel,
|
|
||||||
Verified: true,
|
Verified: true,
|
||||||
Remark: req.Remark,
|
Remark: req.Remark,
|
||||||
}
|
}
|
||||||
|
initialModels, err := buildInitialAgentAccountModels(account, req.Models)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.Create(account).Error; err != nil {
|
if err := tx.Create(account).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1075,33 +1045,12 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||||
agentAccountMutationMu.Lock()
|
|
||||||
defer agentAccountMutationMu.Unlock()
|
|
||||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.ID))
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if req.APIType != account.APIType {
|
|
||||||
return buserr.WithDetail("ErrInvalidParams", "API type cannot be changed", nil)
|
|
||||||
}
|
|
||||||
provider := account.Provider
|
provider := account.Provider
|
||||||
if err := ensureAgentAccountNameAvailable(provider, req.Name, account.ID); err != nil {
|
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL)
|
||||||
return err
|
|
||||||
}
|
|
||||||
models, err := loadAgentAccountModels(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
requestedVerifyModel := req.VerifyModel
|
|
||||||
if strings.TrimSpace(requestedVerifyModel) == "" {
|
|
||||||
requestedVerifyModel = account.VerifyModel
|
|
||||||
}
|
|
||||||
verifyModel, err := resolveAgentAccountVerifyModel(provider, requestedVerifyModel, models)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
validateAvailability := req.ValidateAvailability == nil || *req.ValidateAvailability
|
|
||||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel, validateAvailability)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1109,9 +1058,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
|||||||
account.APIKey = resolvedInput.APIKey
|
account.APIKey = resolvedInput.APIKey
|
||||||
account.RememberAPIKey = req.RememberAPIKey
|
account.RememberAPIKey = req.RememberAPIKey
|
||||||
account.BaseURL = resolvedInput.BaseURL
|
account.BaseURL = resolvedInput.BaseURL
|
||||||
account.APIType = resolvedInput.APIType
|
account.APIType = req.APIType
|
||||||
account.AuthMode = resolvedInput.AuthMode
|
|
||||||
account.VerifyModel = verifyModel
|
|
||||||
account.Remark = req.Remark
|
account.Remark = req.Remark
|
||||||
account.Verified = true
|
account.Verified = true
|
||||||
|
|
||||||
@@ -1133,12 +1080,6 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
|||||||
if strings.TrimSpace(req.Provider) != "" {
|
if strings.TrimSpace(req.Provider) != "" {
|
||||||
opts = append(opts, repo.WithByProvider(req.Provider))
|
opts = append(opts, repo.WithByProvider(req.Provider))
|
||||||
}
|
}
|
||||||
if apiType := strings.TrimSpace(req.APIType); apiType != "" {
|
|
||||||
opts = append(opts, repo.WithByAPIType(apiType))
|
|
||||||
}
|
|
||||||
if req.TextOnly {
|
|
||||||
opts = append(opts, repo.WithTextAPIType())
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.Name) != "" {
|
if strings.TrimSpace(req.Name) != "" {
|
||||||
opts = append(opts, repo.WithByLikeName(req.Name))
|
opts = append(opts, repo.WithByLikeName(req.Name))
|
||||||
}
|
}
|
||||||
@@ -1156,46 +1097,30 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
|||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
MasterAccountID: item.MasterAccountID,
|
MasterAccountID: item.MasterAccountID,
|
||||||
Provider: item.Provider,
|
Provider: item.Provider,
|
||||||
ProviderName: localizedAgentProviderName(item.Provider),
|
ProviderName: providercatalog.DisplayName(item.Provider),
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
APIKey: apiKey,
|
APIKey: apiKey,
|
||||||
RememberAPIKey: item.RememberAPIKey,
|
RememberAPIKey: item.RememberAPIKey,
|
||||||
BaseURL: item.BaseURL,
|
BaseURL: item.BaseURL,
|
||||||
Models: nil,
|
Models: nil,
|
||||||
APIType: item.APIType,
|
APIType: item.APIType,
|
||||||
AuthMode: item.AuthMode,
|
|
||||||
VerifyModel: item.VerifyModel,
|
|
||||||
Verified: item.Verified,
|
Verified: item.Verified,
|
||||||
Remark: item.Remark,
|
Remark: item.Remark,
|
||||||
CreatedAt: item.CreatedAt,
|
CreatedAt: item.CreatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if len(list) > 0 {
|
for i := range items {
|
||||||
accountIDs := make([]uint, 0, len(list))
|
models, err := loadAgentAccountModels(&list[i])
|
||||||
for _, account := range list {
|
if err != nil {
|
||||||
accountIDs = append(accountIDs, account.ID)
|
|
||||||
}
|
|
||||||
var rows []model.AgentAccountModel
|
|
||||||
if err := global.DB.Where("account_id IN ?", accountIDs).Order("account_id ASC, sort_order ASC, id ASC").Find(&rows).Error; err != nil {
|
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
modelsByAccount := make(map[uint][]dto.AgentAccountModel, len(list))
|
items[i].Models = models
|
||||||
for _, row := range rows {
|
|
||||||
modelsByAccount[row.AccountID] = append(modelsByAccount[row.AccountID], dto.AgentAccountModel{
|
|
||||||
RecordID: row.ID,
|
|
||||||
ID: strings.TrimSpace(row.Model),
|
|
||||||
Name: strings.TrimSpace(row.Name),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for index, account := range list {
|
|
||||||
items[index].Models = modelsByAccount[account.ID]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return count, items, nil
|
return count, items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error) {
|
func (a AgentService) CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error) {
|
||||||
return agentAccountRepo.CountTextByProviders(req.Providers)
|
return agentAccountRepo.CountByProviders(req.Providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error) {
|
func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error) {
|
||||||
@@ -1206,22 +1131,6 @@ func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.Agen
|
|||||||
return loadAgentAccountModels(account)
|
return loadAgentAccountModels(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error) {
|
|
||||||
config, ok := providercatalog.FindAPIConfig(req.Provider, req.APIType)
|
|
||||||
if !ok || !config.DiscoverModels {
|
|
||||||
return nil, buserr.New("ErrAgentAccountModelsRequired")
|
|
||||||
}
|
|
||||||
baseURL, err := providercatalog.ResolveBaseURL(req.Provider, req.APIType, req.BaseURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, buserr.WithErr("ErrAgentAccountUnavailable", err)
|
|
||||||
}
|
|
||||||
models, err := providercatalog.DiscoverModels(baseURL, req.APIKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, buserr.WithErr("ErrAgentAccountUnavailable", err)
|
|
||||||
}
|
|
||||||
return buildDiscoveredAgentAccountModels(models), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) error {
|
func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) error {
|
||||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1231,19 +1140,24 @@ func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nextModel, err := normalizeAgentAccountModel(account, req.Model)
|
nextModel := cloneAgentAccountModel(req.Model)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
|
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
|
||||||
return buserr.New("ErrRecordExist")
|
return buserr.New("ErrRecordExist")
|
||||||
}
|
}
|
||||||
|
inputPayload, err := json.Marshal(nextModel.Input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
sortOrder := len(models) + 1
|
sortOrder := len(models) + 1
|
||||||
record := &model.AgentAccountModel{
|
record := &model.AgentAccountModel{
|
||||||
AccountID: account.ID,
|
AccountID: account.ID,
|
||||||
Model: nextModel.ID,
|
Model: nextModel.ID,
|
||||||
Name: nextModel.Name,
|
Name: nextModel.Name,
|
||||||
SortOrder: sortOrder,
|
ContextWindow: nextModel.ContextWindow,
|
||||||
|
MaxTokens: nextModel.MaxTokens,
|
||||||
|
Reasoning: nextModel.Reasoning,
|
||||||
|
Input: string(inputPayload),
|
||||||
|
SortOrder: sortOrder,
|
||||||
}
|
}
|
||||||
if err := agentAccountModelRepo.Create(record); err != nil {
|
if err := agentAccountModelRepo.Create(record); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1264,11 +1178,7 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nextModel, err := normalizeAgentAccountModel(account, req.Model)
|
nextModel := cloneAgentAccountModel(req.Model)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nextModel.RecordID = req.Model.RecordID
|
|
||||||
for _, item := range models {
|
for _, item := range models {
|
||||||
if item.RecordID == req.Model.RecordID {
|
if item.RecordID == req.Model.RecordID {
|
||||||
continue
|
continue
|
||||||
@@ -1288,17 +1198,18 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
|||||||
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
previousModelID := record.Model
|
inputPayload, err := json.Marshal(nextModel.Input)
|
||||||
record.Model = nextModel.ID
|
if err != nil {
|
||||||
record.Name = nextModel.Name
|
|
||||||
if err := agentAccountModelRepo.Save(record); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if sameProviderModelID(account.Provider, account.VerifyModel, previousModelID) {
|
record.Model = nextModel.ID
|
||||||
account.VerifyModel = nextModel.ID
|
record.Name = nextModel.Name
|
||||||
if err := agentAccountRepo.Save(account); err != nil {
|
record.ContextWindow = nextModel.ContextWindow
|
||||||
return err
|
record.MaxTokens = nextModel.MaxTokens
|
||||||
}
|
record.Reasoning = nextModel.Reasoning
|
||||||
|
record.Input = string(inputPayload)
|
||||||
|
if err := agentAccountModelRepo.Save(record); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
terminalai.InvalidateTerminalRuntimeCache()
|
terminalai.InvalidateTerminalRuntimeCache()
|
||||||
terminalai.InvalidateFileAIRuntimeCache()
|
terminalai.InvalidateFileAIRuntimeCache()
|
||||||
@@ -1310,13 +1221,9 @@ func (a AgentService) DeleteAccountModel(req dto.AgentAccountModelDeleteReq) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
record, err := agentAccountModelRepo.GetFirst(repo.WithByID(req.RecordID), repo.WithByAccountID(req.AccountID))
|
if _, err := agentAccountModelRepo.GetFirst(repo.WithByID(req.RecordID), repo.WithByAccountID(req.AccountID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if sameProviderModelID(account.Provider, account.VerifyModel, record.Model) {
|
|
||||||
return buserr.New("ErrAgentVerifyModelInUse")
|
|
||||||
}
|
|
||||||
models, err := loadAgentAccountModels(account)
|
models, err := loadAgentAccountModels(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1350,45 +1257,25 @@ func (a AgentService) SyncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
|
func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
|
||||||
_, err := resolveAgentAccountInput(req.Provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, req.Model, true)
|
_, err := resolveAgentAccountInput(req.Provider, req.APIKey, req.BaseURL)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
|
func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
|
||||||
agentAccountMutationMu.Lock()
|
if exists, _ := agentRepo.GetFirst(repo.WithByAccountID(req.ID)); exists != nil && exists.ID > 0 {
|
||||||
defer agentAccountMutationMu.Unlock()
|
return buserr.New("ErrAgentAccountBound")
|
||||||
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
}
|
||||||
var agentCount int64
|
if aiStatus, _ := settingRepo.GetValueByKey("AIStatus"); strings.EqualFold(strings.TrimSpace(aiStatus), constant.StatusEnable) {
|
||||||
if err := tx.Model(&model.Agent{}).Where("account_id = ?", req.ID).Count(&agentCount).Error; err != nil {
|
if aiAccountID, _ := settingRepo.GetValueByKey("AIAccountID"); strings.TrimSpace(aiAccountID) == strconv.FormatUint(uint64(req.ID), 10) {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if agentCount > 0 {
|
|
||||||
return buserr.New("ErrAgentAccountBound")
|
|
||||||
}
|
|
||||||
used, err := agentAccountUsedBySetting(tx, req.ID, "AIStatus", "AIAccountID")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if used {
|
|
||||||
return buserr.New("ErrTerminalAIAccountInUse")
|
return buserr.New("ErrTerminalAIAccountInUse")
|
||||||
}
|
}
|
||||||
used, err = agentAccountUsedBySetting(tx, req.ID, "FileAIStatus", "FileAIAccountID")
|
}
|
||||||
if err != nil {
|
if err := agentAccountModelRepo.Delete(repo.WithByAccountID(req.ID)); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if used {
|
|
||||||
return buserr.New("ErrFileAIAccountInUse")
|
|
||||||
}
|
|
||||||
if err := tx.Where("account_id = ?", req.ID).Delete(&model.AgentAccountModel{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Delete(&model.AgentAccount{}, req.ID).Error
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
terminalai.InvalidateTerminalRuntimeCache()
|
terminalai.InvalidateTerminalRuntimeCache()
|
||||||
terminalai.InvalidateFileAIRuntimeCache()
|
terminalai.InvalidateFileAIRuntimeCache()
|
||||||
return nil
|
return agentAccountRepo.DeleteByID(req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error) {
|
func (a AgentService) GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error) {
|
||||||
@@ -1440,20 +1327,10 @@ func (a AgentService) GetOtherConfig(req dto.AgentIDReq) (*dto.AgentOtherConfig,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
auth := readAgentDashboardAuthFromInstall(install, agent.AgentType)
|
|
||||||
return &dto.AgentOtherConfig{
|
return &dto.AgentOtherConfig{
|
||||||
UserTimezone: cfg.Timezone,
|
UserTimezone: cfg.Timezone,
|
||||||
BrowserEnabled: true,
|
BrowserEnabled: true,
|
||||||
NPMRegistry: "https://registry.npmjs.org/",
|
NPMRegistry: "https://registry.npmjs.org/",
|
||||||
DashboardUsername: auth.Username,
|
|
||||||
DashboardPassword: auth.Password,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
if agent.AgentType == constant.AppCopaw {
|
|
||||||
auth := readAgentDashboardAuthFromInstall(install, agent.AgentType)
|
|
||||||
return &dto.AgentOtherConfig{
|
|
||||||
DashboardUsername: auth.Username,
|
|
||||||
DashboardPassword: auth.Password,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||||
@@ -1474,36 +1351,18 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if agent.AgentType == constant.AppHermesAgent {
|
if agent.AgentType == constant.AppHermesAgent {
|
||||||
if strings.TrimSpace(req.UserTimezone) == "" {
|
|
||||||
return buserr.New("ErrInvalidParams")
|
|
||||||
}
|
|
||||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(agent.AccountID))
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(agent.AccountID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
previousAuth := readAgentDashboardAuthFromInstall(install, agent.AgentType)
|
|
||||||
nextAuth := normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
|
||||||
if err := writeHermesConfig(path.Dir(agent.ConfigPath), account, agent.Model, strings.TrimSpace(req.UserTimezone)); err != nil {
|
if err := writeHermesConfig(path.Dir(agent.ConfigPath), account, agent.Model, strings.TrimSpace(req.UserTimezone)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := writeAgentDashboardAuthEnv(install.GetEnvPath(), agent.AgentType, nextAuth, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
operate := constant.Restart
|
|
||||||
if previousAuth.Username != nextAuth.Username || previousAuth.Password != nextAuth.Password {
|
|
||||||
operate = constant.Rebuild
|
|
||||||
}
|
|
||||||
return NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
return NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
||||||
InstallId: install.ID,
|
InstallId: install.ID,
|
||||||
Operate: operate,
|
Operate: constant.Restart,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if agent.AgentType == constant.AppCopaw {
|
|
||||||
return updateQwenPawDashboardAuth(install, normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword))
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.UserTimezone) == "" || strings.TrimSpace(req.NPMRegistry) == "" {
|
|
||||||
return buserr.New("ErrInvalidParams")
|
|
||||||
}
|
|
||||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1675,7 +1534,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
modelName := resolvedRuntime.StoredModel
|
modelName := resolvedRuntime.StoredModel
|
||||||
apiType := resolvedRuntime.APIType
|
apiType, maxTokens, contextWindow := resolvedRuntime.APIType, resolvedRuntime.MaxTokens, resolvedRuntime.ContextWindow
|
||||||
confDir := path.Dir(agent.ConfigPath)
|
confDir := path.Dir(agent.ConfigPath)
|
||||||
switch agent.AgentType {
|
switch agent.AgentType {
|
||||||
case constant.AppOpenclaw:
|
case constant.AppOpenclaw:
|
||||||
@@ -1703,6 +1562,8 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
|||||||
agent.Provider = account.Provider
|
agent.Provider = account.Provider
|
||||||
agent.Model = modelName
|
agent.Model = modelName
|
||||||
agent.APIType = apiType
|
agent.APIType = apiType
|
||||||
|
agent.MaxTokens = maxTokens
|
||||||
|
agent.ContextWindow = contextWindow
|
||||||
_ = agentRepo.Save(&agent)
|
_ = agentRepo.Save(&agent)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -812,7 +811,9 @@ func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig
|
|||||||
result.RequireMention = result.GroupPolicy == "allowlist"
|
result.RequireMention = result.GroupPolicy == "allowlist"
|
||||||
result.GroupAllowFrom = extractStringList(telegram["groupAllowFrom"])
|
result.GroupAllowFrom = extractStringList(telegram["groupAllowFrom"])
|
||||||
result.Proxy = extractStringValue(telegram["proxy"])
|
result.Proxy = extractStringValue(telegram["proxy"])
|
||||||
result.Streaming = normalizeTelegramStreamingMode(telegram["streaming"], result.Streaming)
|
if streaming := extractStringValue(telegram["streaming"]); streaming != "" {
|
||||||
|
result.Streaming = streaming
|
||||||
|
}
|
||||||
accounts := childMap(telegram, "accounts")
|
accounts := childMap(telegram, "accounts")
|
||||||
if len(accounts) == 0 {
|
if len(accounts) == 0 {
|
||||||
botToken := extractStringValue(telegram["botToken"])
|
botToken := extractStringValue(telegram["botToken"])
|
||||||
@@ -838,7 +839,7 @@ func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig
|
|||||||
BotToken: extractStringValue(account["botToken"]),
|
BotToken: extractStringValue(account["botToken"]),
|
||||||
DmPolicy: extractStringValue(account["dmPolicy"]),
|
DmPolicy: extractStringValue(account["dmPolicy"]),
|
||||||
GroupPolicy: extractStringValue(account["groupPolicy"]),
|
GroupPolicy: extractStringValue(account["groupPolicy"]),
|
||||||
Streaming: normalizeTelegramStreamingMode(account["streaming"], result.Streaming),
|
Streaming: extractStringValue(account["streaming"]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
result.DefaultAccount = normalizeDefaultAccount(extractStringValue(telegram["defaultAccount"]), getTelegramBotAccountIDs(bots))
|
result.DefaultAccount = normalizeDefaultAccount(extractStringValue(telegram["defaultAccount"]), getTelegramBotAccountIDs(bots))
|
||||||
@@ -878,7 +879,7 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
|
|||||||
} else {
|
} else {
|
||||||
delete(telegram, "proxy")
|
delete(telegram, "proxy")
|
||||||
}
|
}
|
||||||
telegram["streaming"] = buildTelegramStreamingConfig(config.Streaming)
|
telegram["streaming"] = config.Streaming
|
||||||
accounts := make(map[string]interface{}, len(config.Bots))
|
accounts := make(map[string]interface{}, len(config.Bots))
|
||||||
for _, bot := range config.Bots {
|
for _, bot := range config.Bots {
|
||||||
account := map[string]interface{}{
|
account := map[string]interface{}{
|
||||||
@@ -887,7 +888,7 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
|
|||||||
"botToken": bot.BotToken,
|
"botToken": bot.BotToken,
|
||||||
"dmPolicy": bot.DmPolicy,
|
"dmPolicy": bot.DmPolicy,
|
||||||
"groupPolicy": bot.GroupPolicy,
|
"groupPolicy": bot.GroupPolicy,
|
||||||
"streaming": buildTelegramStreamingConfig(bot.Streaming),
|
"streaming": bot.Streaming,
|
||||||
}
|
}
|
||||||
if bot.DmPolicy == "open" {
|
if bot.DmPolicy == "open" {
|
||||||
account["allowFrom"] = []string{"*"}
|
account["allowFrom"] = []string{"*"}
|
||||||
@@ -898,29 +899,6 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf
|
|||||||
delete(telegram, "botToken")
|
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 {
|
func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig {
|
||||||
result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", AllowFrom: []string{}, RequireMention: false, GroupPolicy: "open"}
|
result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", AllowFrom: []string{}, RequireMention: false, GroupPolicy: "open"}
|
||||||
discord := getChannelConfig(conf, "discord")
|
discord := getChannelConfig(conf, "discord")
|
||||||
@@ -1321,10 +1299,6 @@ func appendPluginAllow(conf map[string]interface{}, pluginID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error {
|
func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error {
|
||||||
help, err := cmd.RunDockerExecWithStdout(time.Minute, containerName, "openclaw", "plugins", "install", "--help")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
workdir := path.Join(openclawPluginPackageTmpDir, pluginID)
|
workdir := path.Join(openclawPluginPackageTmpDir, pluginID)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir)
|
_ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir)
|
||||||
@@ -1346,15 +1320,7 @@ func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID
|
|||||||
if pkgPath == "" {
|
if pkgPath == "" {
|
||||||
return fmt.Errorf("openclaw plugin package not found")
|
return fmt.Errorf("openclaw plugin package not found")
|
||||||
}
|
}
|
||||||
args := []string{"exec", containerName, "openclaw", "plugins", "install", pkgPath}
|
return mgr.Run("docker", "exec", containerName, "openclaw", "plugins", "install", pkgPath, "--dangerously-force-unsafe-install")
|
||||||
// Newer CLIs require source confirmation; older releases do not support --force.
|
|
||||||
options := strings.Fields(help)
|
|
||||||
if slices.Contains(options, "--force") {
|
|
||||||
args = append(args, "--force")
|
|
||||||
} else if slices.Contains(options, "--dangerously-force-unsafe-install") {
|
|
||||||
args = append(args, "--dangerously-force-unsafe-install")
|
|
||||||
}
|
|
||||||
return mgr.Run("docker", args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error {
|
func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error {
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type qwenPawAuthStatus struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
HasUsers bool `json:"has_users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type qwenPawLoginResponse struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateQwenPawDashboardAuth(install *model.AppInstall, next agentDashboardAuth) error {
|
|
||||||
if install == nil || install.ID == 0 {
|
|
||||||
return buserr.New("ErrRecordNotFound")
|
|
||||||
}
|
|
||||||
current, err := readAgentDashboardAuthEnv(install.GetEnvPath(), constant.AppCopaw)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if current == next {
|
|
||||||
return writeAgentDashboardAuthEnv(install.GetEnvPath(), constant.AppCopaw, next, true)
|
|
||||||
}
|
|
||||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://127.0.0.1:%d/api/auth", install.HttpPort)
|
|
||||||
var status qwenPawAuthStatus
|
|
||||||
if _, err := requestQwenPawAuth(http.MethodGet, baseURL+"/status", nil, "", &status); err != nil {
|
|
||||||
return buserr.WithMap("ErrQwenPawAuthRequest", map[string]interface{}{"err": err.Error()}, err)
|
|
||||||
}
|
|
||||||
if !status.Enabled {
|
|
||||||
return buserr.New("ErrQwenPawAuthDisabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !status.HasUsers {
|
|
||||||
payload := map[string]string{"username": next.Username, "password": next.Password}
|
|
||||||
if _, err := requestQwenPawAuth(http.MethodPost, baseURL+"/register", payload, "", nil); err != nil {
|
|
||||||
return buserr.WithMap("ErrQwenPawAuthRequest", map[string]interface{}{"err": err.Error()}, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var login qwenPawLoginResponse
|
|
||||||
payload := map[string]string{"username": current.Username, "password": current.Password}
|
|
||||||
statusCode, err := requestQwenPawAuth(http.MethodPost, baseURL+"/login", payload, "", &login)
|
|
||||||
if statusCode == http.StatusUnauthorized {
|
|
||||||
return buserr.New("ErrQwenPawAuthOutOfSync")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return buserr.WithMap("ErrQwenPawAuthRequest", map[string]interface{}{"err": err.Error()}, err)
|
|
||||||
}
|
|
||||||
payload = map[string]string{"current_password": current.Password}
|
|
||||||
if current.Username != next.Username {
|
|
||||||
payload["new_username"] = next.Username
|
|
||||||
}
|
|
||||||
if current.Password != next.Password {
|
|
||||||
payload["new_password"] = next.Password
|
|
||||||
}
|
|
||||||
if _, err := requestQwenPawAuth(http.MethodPost, baseURL+"/update-profile", payload, login.Token, nil); err != nil {
|
|
||||||
return buserr.WithMap("ErrQwenPawAuthRequest", map[string]interface{}{"err": err.Error()}, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return writeAgentDashboardAuthEnv(install.GetEnvPath(), constant.AppCopaw, next, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestQwenPawAuth(method, reqURL string, payload interface{}, token string, result interface{}) (int, error) {
|
|
||||||
var body io.Reader
|
|
||||||
if payload != nil {
|
|
||||||
data, err := json.Marshal(payload)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
body = bytes.NewReader(data)
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, reqURL, body)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
if token != "" {
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
}
|
|
||||||
resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
data, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
|
||||||
if err != nil {
|
|
||||||
return resp.StatusCode, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
|
||||||
detail := strings.TrimSpace(string(data))
|
|
||||||
var errorResponse struct {
|
|
||||||
Detail string `json:"detail"`
|
|
||||||
}
|
|
||||||
if json.Unmarshal(data, &errorResponse) == nil && strings.TrimSpace(errorResponse.Detail) != "" {
|
|
||||||
detail = strings.TrimSpace(errorResponse.Detail)
|
|
||||||
}
|
|
||||||
if detail == "" {
|
|
||||||
detail = resp.Status
|
|
||||||
}
|
|
||||||
return resp.StatusCode, errors.New(detail)
|
|
||||||
}
|
|
||||||
if result != nil && len(data) > 0 {
|
|
||||||
if err := json.Unmarshal(data, result); err != nil {
|
|
||||||
return resp.StatusCode, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp.StatusCode, nil
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,9 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"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/files"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ func writeHermesConfig(confDir string, account *model.AgentAccount, modelName st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := resolveHermesProvider(account.Provider, account.APIType)
|
provider := resolveHermesProvider(account.Provider)
|
||||||
configPath := path.Join(confDir, "config.yaml")
|
configPath := path.Join(confDir, "config.yaml")
|
||||||
cfg, err := readHermesConfigMap(configPath)
|
cfg, err := readHermesConfigMap(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -150,7 +152,7 @@ func writeHermesConfigMap(configPath string, cfg map[string]interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig, error) {
|
func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig, error) {
|
||||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -207,7 +209,7 @@ func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig,
|
|||||||
|
|
||||||
func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramConfig) error {
|
func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramConfig) error {
|
||||||
envPath := path.Join(confDir, ".env")
|
envPath := path.Join(confDir, ".env")
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -227,7 +229,7 @@ func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramCo
|
|||||||
envMap["TELEGRAM_ALLOWED_USERS"] = allow
|
envMap["TELEGRAM_ALLOWED_USERS"] = allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||||
"TELEGRAM_BOT_TOKEN",
|
"TELEGRAM_BOT_TOKEN",
|
||||||
"TELEGRAM_ALLOWED_USERS",
|
"TELEGRAM_ALLOWED_USERS",
|
||||||
"TELEGRAM_ALLOW_ALL_USERS",
|
"TELEGRAM_ALLOW_ALL_USERS",
|
||||||
@@ -246,7 +248,7 @@ func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, error) {
|
func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, error) {
|
||||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -298,7 +300,7 @@ func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, er
|
|||||||
|
|
||||||
func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConfig) error {
|
func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConfig) error {
|
||||||
envPath := path.Join(confDir, ".env")
|
envPath := path.Join(confDir, ".env")
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -318,7 +320,7 @@ func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConf
|
|||||||
envMap["DISCORD_ALLOWED_USERS"] = allow
|
envMap["DISCORD_ALLOWED_USERS"] = allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||||
"DISCORD_BOT_TOKEN",
|
"DISCORD_BOT_TOKEN",
|
||||||
"DISCORD_ALLOWED_USERS",
|
"DISCORD_ALLOWED_USERS",
|
||||||
"DISCORD_ALLOW_ALL_USERS",
|
"DISCORD_ALLOW_ALL_USERS",
|
||||||
@@ -338,14 +340,14 @@ func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConf
|
|||||||
|
|
||||||
func deleteHermesEnvKeys(confDir string, keys ...string) error {
|
func deleteHermesEnvKeys(confDir string, keys ...string) error {
|
||||||
envPath := path.Join(confDir, ".env")
|
envPath := path.Join(confDir, ".env")
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
delete(envMap, key)
|
delete(envMap, key)
|
||||||
}
|
}
|
||||||
return writeAgentEnvMap(envPath, envMap, keys)
|
return writeHermesEnvMap(envPath, envMap, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteHermesConfigSections(confDir string, topLevelKeys []string, platformKeys []string) error {
|
func deleteHermesConfigSections(confDir string, topLevelKeys []string, platformKeys []string) error {
|
||||||
@@ -405,18 +407,11 @@ func normalizeHermesTimezone(timezone string) string {
|
|||||||
return timezone
|
return timezone
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveHermesProvider(provider, apiType string) string {
|
func resolveHermesProvider(provider string) string {
|
||||||
if apiType == "anthropic-messages" && (provider == "deepseek" || provider == "bailian-coding-plan" || provider == "ark-coding-plan" || provider == "xiaomi") {
|
|
||||||
return "anthropic"
|
|
||||||
}
|
|
||||||
switch provider {
|
switch provider {
|
||||||
case "":
|
case "":
|
||||||
return "custom"
|
return "custom"
|
||||||
case "moonshot":
|
case "openrouter", "anthropic", "gemini", "zai", "kimi-coding", "xiaomi":
|
||||||
return "kimi-coding"
|
|
||||||
case "kimi":
|
|
||||||
return "kimi-coding-cn"
|
|
||||||
case "openrouter", "anthropic", "deepseek", "gemini", "zai", "kimi-coding", "xiaomi":
|
|
||||||
return provider
|
return provider
|
||||||
case "minimax":
|
case "minimax":
|
||||||
return "minimax-cn"
|
return "minimax-cn"
|
||||||
@@ -433,7 +428,17 @@ func resolveHermesModel(sourceProvider, targetProvider, modelName string) string
|
|||||||
if targetProvider != "custom" {
|
if targetProvider != "custom" {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
return providercatalog.NormalizeModelID(sourceProvider, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) {
|
func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) {
|
||||||
@@ -444,7 +449,7 @@ func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels [
|
|||||||
if configuredModel == "" {
|
if configuredModel == "" {
|
||||||
return "", buserr.New("ErrAgentModelNotInAccount")
|
return "", buserr.New("ErrAgentModelNotInAccount")
|
||||||
}
|
}
|
||||||
provider := resolveHermesProvider(account.Provider, account.APIType)
|
provider := resolveHermesProvider(account.Provider)
|
||||||
for _, item := range accountModels {
|
for _, item := range accountModels {
|
||||||
if resolveHermesModel(account.Provider, provider, item.ID) == configuredModel {
|
if resolveHermesModel(account.Provider, provider, item.ID) == configuredModel {
|
||||||
return item.ID, nil
|
return item.ID, nil
|
||||||
@@ -457,8 +462,7 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|||||||
if account == nil {
|
if account == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resolvedProvider := resolveHermesProvider(account.Provider, account.APIType)
|
if resolveHermesProvider(account.Provider) == "custom" {
|
||||||
if resolvedProvider == "custom" {
|
|
||||||
if account.APIKey == "" {
|
if account.APIKey == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -476,11 +480,6 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|||||||
}
|
}
|
||||||
entries = append(entries, hermesEnvEntry{Key: key, Value: value})
|
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 {
|
switch account.Provider {
|
||||||
case "openrouter":
|
case "openrouter":
|
||||||
@@ -488,7 +487,6 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|||||||
appendEntry("OPENROUTER_BASE_URL", baseURL)
|
appendEntry("OPENROUTER_BASE_URL", baseURL)
|
||||||
case "anthropic":
|
case "anthropic":
|
||||||
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
||||||
appendEntry("ANTHROPIC_BASE_URL", baseURL)
|
|
||||||
case "gemini":
|
case "gemini":
|
||||||
appendEntry("GOOGLE_API_KEY", apiKey)
|
appendEntry("GOOGLE_API_KEY", apiKey)
|
||||||
appendEntry("GEMINI_API_KEY", apiKey)
|
appendEntry("GEMINI_API_KEY", apiKey)
|
||||||
@@ -534,7 +532,7 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
|
func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -550,7 +548,7 @@ func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
|
|||||||
envMap[entry.Key] = entry.Value
|
envMap[entry.Key] = entry.Value
|
||||||
order = append(order, entry.Key)
|
order = append(order, entry.Key)
|
||||||
}
|
}
|
||||||
return writeAgentEnvMap(envPath, envMap, order)
|
return writeHermesEnvMap(envPath, envMap, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hermesManagedModelEnvKeys() []string {
|
func hermesManagedModelEnvKeys() []string {
|
||||||
@@ -558,7 +556,6 @@ func hermesManagedModelEnvKeys() []string {
|
|||||||
"OPENROUTER_API_KEY",
|
"OPENROUTER_API_KEY",
|
||||||
"OPENROUTER_BASE_URL",
|
"OPENROUTER_BASE_URL",
|
||||||
"ANTHROPIC_API_KEY",
|
"ANTHROPIC_API_KEY",
|
||||||
"ANTHROPIC_BASE_URL",
|
|
||||||
"GOOGLE_API_KEY",
|
"GOOGLE_API_KEY",
|
||||||
"GEMINI_API_KEY",
|
"GEMINI_API_KEY",
|
||||||
"GEMINI_BASE_URL",
|
"GEMINI_BASE_URL",
|
||||||
@@ -601,6 +598,46 @@ func hermesManagedModelEnvKeys() []string {
|
|||||||
return result
|
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 {
|
func splitHermesEnvList(value string) []string {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return []string{}
|
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) {
|
func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error) {
|
||||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error)
|
|||||||
|
|
||||||
func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig) error {
|
func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig) error {
|
||||||
envPath := path.Join(confDir, ".env")
|
envPath := path.Join(confDir, ".env")
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig)
|
|||||||
envMap["QQ_GROUP_ALLOWED_USERS"] = allow
|
envMap["QQ_GROUP_ALLOWED_USERS"] = allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||||
"QQ_APP_ID",
|
"QQ_APP_ID",
|
||||||
"QQ_CLIENT_SECRET",
|
"QQ_CLIENT_SECRET",
|
||||||
"QQ_ALLOW_ALL_USERS",
|
"QQ_ALLOW_ALL_USERS",
|
||||||
@@ -163,7 +163,7 @@ func deleteHermesQQBotChannelConfig(confDir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readHermesWecomChannelConfig(confDir string) (*dto.AgentWecomConfig, error) {
|
func readHermesWecomChannelConfig(confDir string) (*dto.AgentWecomConfig, error) {
|
||||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ func readHermesWecomChannelConfig(confDir string) (*dto.AgentWecomConfig, error)
|
|||||||
|
|
||||||
func writeHermesWecomChannelConfig(confDir string, config dto.AgentWecomConfig) error {
|
func writeHermesWecomChannelConfig(confDir string, config dto.AgentWecomConfig) error {
|
||||||
envPath := path.Join(confDir, ".env")
|
envPath := path.Join(confDir, ".env")
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ func writeHermesWecomChannelConfig(confDir string, config dto.AgentWecomConfig)
|
|||||||
envMap["WECOM_GROUP_ALLOWED_USERS"] = allow
|
envMap["WECOM_GROUP_ALLOWED_USERS"] = allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||||
"WECOM_BOT_ID",
|
"WECOM_BOT_ID",
|
||||||
"WECOM_SECRET",
|
"WECOM_SECRET",
|
||||||
"WECOM_DM_POLICY",
|
"WECOM_DM_POLICY",
|
||||||
@@ -282,7 +282,7 @@ func deleteHermesWecomChannelConfig(confDir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readHermesDingTalkChannelConfig(confDir string) (*dto.AgentDingTalkConfig, error) {
|
func readHermesDingTalkChannelConfig(confDir string) (*dto.AgentDingTalkConfig, error) {
|
||||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ func readHermesDingTalkChannelConfig(confDir string) (*dto.AgentDingTalkConfig,
|
|||||||
|
|
||||||
func writeHermesDingTalkChannelConfig(confDir string, config dto.AgentDingTalkConfig) error {
|
func writeHermesDingTalkChannelConfig(confDir string, config dto.AgentDingTalkConfig) error {
|
||||||
envPath := path.Join(confDir, ".env")
|
envPath := path.Join(confDir, ".env")
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ func writeHermesDingTalkChannelConfig(confDir string, config dto.AgentDingTalkCo
|
|||||||
} else if config.DmPolicy == "open" {
|
} else if config.DmPolicy == "open" {
|
||||||
envMap["DINGTALK_ALLOW_ALL_USERS"] = "true"
|
envMap["DINGTALK_ALLOW_ALL_USERS"] = "true"
|
||||||
}
|
}
|
||||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||||
"DINGTALK_CLIENT_ID",
|
"DINGTALK_CLIENT_ID",
|
||||||
"DINGTALK_CLIENT_SECRET",
|
"DINGTALK_CLIENT_SECRET",
|
||||||
"DINGTALK_ALLOW_ALL_USERS",
|
"DINGTALK_ALLOW_ALL_USERS",
|
||||||
@@ -398,7 +398,7 @@ func deleteHermesDingTalkChannelConfig(confDir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readHermesFeishuChannelConfig(confDir string) (*dto.AgentFeishuConfig, error) {
|
func readHermesFeishuChannelConfig(confDir string) (*dto.AgentFeishuConfig, error) {
|
||||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -452,7 +452,7 @@ func readHermesFeishuChannelConfig(confDir string) (*dto.AgentFeishuConfig, erro
|
|||||||
|
|
||||||
func writeHermesFeishuChannelConfig(confDir string, config dto.AgentFeishuConfig) error {
|
func writeHermesFeishuChannelConfig(confDir string, config dto.AgentFeishuConfig) error {
|
||||||
envPath := path.Join(confDir, ".env")
|
envPath := path.Join(confDir, ".env")
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
envMap, err := readHermesEnvMap(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -488,7 +488,7 @@ func writeHermesFeishuChannelConfig(confDir string, config dto.AgentFeishuConfig
|
|||||||
envMap["FEISHU_ALLOWED_USERS"] = allow
|
envMap["FEISHU_ALLOWED_USERS"] = allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := writeAgentEnvMap(envPath, envMap, []string{
|
if err := writeHermesEnvMap(envPath, envMap, []string{
|
||||||
"FEISHU_APP_ID",
|
"FEISHU_APP_ID",
|
||||||
"FEISHU_APP_SECRET",
|
"FEISHU_APP_SECRET",
|
||||||
"FEISHU_DOMAIN",
|
"FEISHU_DOMAIN",
|
||||||
@@ -529,7 +529,7 @@ func deleteHermesFeishuChannelConfig(confDir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readHermesWeixinChannelConfig(confDir string) (*dto.AgentWeixinConfig, error) {
|
func readHermesWeixinChannelConfig(confDir string) (*dto.AgentWeixinConfig, error) {
|
||||||
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
envMap, err := readHermesEnvMap(path.Join(confDir, ".env"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,315 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
openclawPluginPackagePattern = regexp.MustCompile(`^(@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$`)
|
|
||||||
openclawPluginVersionPattern = regexp.MustCompile(`^[0-9A-Za-z][0-9A-Za-z._-]*$`)
|
|
||||||
openclawPluginIDPattern = regexp.MustCompile(`^(@[A-Za-z0-9][A-Za-z0-9._-]*/)?[A-Za-z0-9][A-Za-z0-9._-]*$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
type openclawPluginListOutput struct {
|
|
||||||
Plugins []struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Origin string `json:"origin"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
} `json:"plugins"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type openclawPluginIndexItem struct {
|
|
||||||
PluginID string `json:"pluginId"`
|
|
||||||
PackageName string `json:"packageName"`
|
|
||||||
PackageVersion string `json:"packageVersion"`
|
|
||||||
Origin string `json:"origin"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type openclawPluginSearchOutput struct {
|
|
||||||
Results []struct {
|
|
||||||
Score float64 `json:"score"`
|
|
||||||
Package struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
RuntimeID string `json:"runtimeId"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
Summary string `json:"summary"`
|
|
||||||
LatestVersion string `json:"latestVersion"`
|
|
||||||
Categories []string `json:"categories"`
|
|
||||||
Channel string `json:"channel"`
|
|
||||||
IsOfficial bool `json:"isOfficial"`
|
|
||||||
VerificationTier string `json:"verificationTier"`
|
|
||||||
Stats struct {
|
|
||||||
Downloads int64 `json:"downloads"`
|
|
||||||
} `json:"stats"`
|
|
||||||
} `json:"package"`
|
|
||||||
} `json:"results"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AgentService) ListPlugins(req dto.AgentPluginsReq) ([]dto.AgentPluginItem, error) {
|
|
||||||
agent, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if plugins, err := readOpenclawPluginIndex(filepath.Join(filepath.Dir(agent.ConfigPath), "state", "openclaw.sqlite")); err == nil {
|
|
||||||
return plugins, nil
|
|
||||||
}
|
|
||||||
output, err := cmd.RunDockerExecWithStdout(2*time.Minute, install.ContainerName, "openclaw", "plugins", "list", "--json")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return parseOpenclawPluginList([]byte(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AgentService) SearchPlugins(req dto.AgentPluginSearchReq) ([]dto.AgentPluginSearchItem, error) {
|
|
||||||
_, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
limit := req.Limit
|
|
||||||
if limit == 0 {
|
|
||||||
limit = 20
|
|
||||||
}
|
|
||||||
output, err := cmd.RunDockerExecWithStdout(
|
|
||||||
2*time.Minute,
|
|
||||||
install.ContainerName,
|
|
||||||
"openclaw", "plugins", "search", strings.TrimSpace(req.Keyword), "--limit", fmt.Sprint(limit), "--json",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return parseOpenclawPluginSearch([]byte(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AgentService) InstallMarketPlugin(req dto.AgentPluginMarketInstallReq) error {
|
|
||||||
spec, err := buildOpenclawPluginInstallSpec(req.Package, req.Version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeAI, req.AgentID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
taskName := fmt.Sprintf("%s [%s]", i18n.GetMsgByKey("AgentPluginInstall"), req.Package)
|
|
||||||
installTask, err := task.NewTask(taskName, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
installTask.AddSubTask(taskName, func(t *task.Task) error {
|
|
||||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
|
||||||
return mgr.Run("docker", "exec", install.ContainerName, "openclaw", "plugins", "install", spec)
|
|
||||||
}, nil)
|
|
||||||
addOpenclawPluginRestartTask(installTask, install)
|
|
||||||
go executeAgentPluginTask(installTask)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AgentService) OperatePlugin(req dto.AgentPluginOperateReq) error {
|
|
||||||
if !openclawPluginIDPattern.MatchString(req.PluginID) {
|
|
||||||
return buserr.New("ErrInvalidChar")
|
|
||||||
}
|
|
||||||
agent, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeAI, req.AgentID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if req.Operate == "update" || req.Operate == "uninstall" {
|
|
||||||
plugins, err := a.ListPlugins(dto.AgentPluginsReq{AgentID: req.AgentID})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, plugin := range plugins {
|
|
||||||
if plugin.ID == req.PluginID && plugin.Origin == "bundled" {
|
|
||||||
return buserr.WithName("ErrNotSupportType", req.Operate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
taskType := map[string]string{
|
|
||||||
"enable": task.TaskUpdate,
|
|
||||||
"disable": task.TaskUpdate,
|
|
||||||
"update": task.TaskUpgrade,
|
|
||||||
"uninstall": task.TaskUninstall,
|
|
||||||
}[req.Operate]
|
|
||||||
taskName := fmt.Sprintf("%s [%s]", i18n.GetMsgByKey(map[string]string{
|
|
||||||
"enable": "AgentPluginEnable",
|
|
||||||
"disable": "AgentPluginDisable",
|
|
||||||
"update": "AgentPluginUpdate",
|
|
||||||
"uninstall": "AgentPluginUninstall",
|
|
||||||
}[req.Operate]), req.PluginID)
|
|
||||||
operateTask, err := task.NewTask(taskName, taskType, task.TaskScopeAI, req.TaskID, req.AgentID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
operateTask.AddSubTask(taskName, func(t *task.Task) error {
|
|
||||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
|
||||||
if req.Operate == "uninstall" {
|
|
||||||
if err := uninstallOpenclawPlugin(mgr, install.ContainerName, req.PluginID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return cleanupManagedOpenclawPlugin(agent, req.PluginID)
|
|
||||||
}
|
|
||||||
return mgr.Run("docker", "exec", install.ContainerName, "openclaw", "plugins", req.Operate, req.PluginID)
|
|
||||||
}, nil)
|
|
||||||
addOpenclawPluginRestartTask(operateTask, install)
|
|
||||||
go executeAgentPluginTask(operateTask)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOpenclawPluginList(raw []byte) ([]dto.AgentPluginItem, error) {
|
|
||||||
payload, err := extractEmbeddedJSON(string(raw))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(payload) == 0 {
|
|
||||||
return []dto.AgentPluginItem{}, nil
|
|
||||||
}
|
|
||||||
var output openclawPluginListOutput
|
|
||||||
if err := json.Unmarshal(payload, &output); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items := make([]dto.AgentPluginItem, 0, len(output.Plugins))
|
|
||||||
for _, plugin := range output.Plugins {
|
|
||||||
items = append(items, dto.AgentPluginItem{
|
|
||||||
ID: plugin.ID,
|
|
||||||
Name: plugin.Name,
|
|
||||||
Version: plugin.Version,
|
|
||||||
Origin: plugin.Origin,
|
|
||||||
Enabled: plugin.Enabled,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readOpenclawPluginIndex(dbPath string) ([]dto.AgentPluginItem, error) {
|
|
||||||
db, err := sql.Open("sqlite", "file:"+filepath.ToSlash(dbPath)+"?mode=ro")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
var raw []byte
|
|
||||||
if err := db.QueryRow(
|
|
||||||
"SELECT plugins_json FROM installed_plugin_index WHERE index_key = ?",
|
|
||||||
"installed-plugin-index",
|
|
||||||
).Scan(&raw); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var plugins []openclawPluginIndexItem
|
|
||||||
if err := json.Unmarshal(raw, &plugins); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items := make([]dto.AgentPluginItem, 0, len(plugins))
|
|
||||||
for _, plugin := range plugins {
|
|
||||||
name := plugin.PackageName
|
|
||||||
if name == "" {
|
|
||||||
name = plugin.PluginID
|
|
||||||
}
|
|
||||||
items = append(items, dto.AgentPluginItem{
|
|
||||||
ID: plugin.PluginID,
|
|
||||||
Name: name,
|
|
||||||
Version: plugin.PackageVersion,
|
|
||||||
Origin: plugin.Origin,
|
|
||||||
Enabled: plugin.Enabled,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOpenclawPluginSearch(raw []byte) ([]dto.AgentPluginSearchItem, error) {
|
|
||||||
payload, err := extractEmbeddedJSON(string(raw))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(payload) == 0 {
|
|
||||||
return []dto.AgentPluginSearchItem{}, nil
|
|
||||||
}
|
|
||||||
var output openclawPluginSearchOutput
|
|
||||||
if err := json.Unmarshal(payload, &output); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items := make([]dto.AgentPluginSearchItem, 0, len(output.Results))
|
|
||||||
for _, result := range output.Results {
|
|
||||||
items = append(items, dto.AgentPluginSearchItem{
|
|
||||||
Package: result.Package.Name,
|
|
||||||
PluginID: result.Package.RuntimeID,
|
|
||||||
Name: result.Package.DisplayName,
|
|
||||||
Description: result.Package.Summary,
|
|
||||||
Version: result.Package.LatestVersion,
|
|
||||||
Channel: result.Package.Channel,
|
|
||||||
VerificationTier: result.Package.VerificationTier,
|
|
||||||
Categories: append([]string{}, result.Package.Categories...),
|
|
||||||
Official: result.Package.IsOfficial,
|
|
||||||
Downloads: result.Package.Stats.Downloads,
|
|
||||||
Score: result.Score,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpenclawPluginInstallSpec(packageName, version string) (string, error) {
|
|
||||||
packageName = strings.TrimSpace(packageName)
|
|
||||||
version = strings.TrimSpace(version)
|
|
||||||
if !openclawPluginPackagePattern.MatchString(packageName) || !openclawPluginVersionPattern.MatchString(version) {
|
|
||||||
return "", buserr.New("ErrInvalidChar")
|
|
||||||
}
|
|
||||||
return "clawhub:" + packageName + "@" + version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupManagedOpenclawPlugin(agent *model.Agent, pluginID string) error {
|
|
||||||
pluginType := map[string]string{
|
|
||||||
"openclaw-lark": "feishu",
|
|
||||||
"openclaw-qqbot": "qqbot",
|
|
||||||
"wecom-openclaw-plugin": "wecom",
|
|
||||||
"dingtalk-connector": "dingtalk",
|
|
||||||
"openclaw-weixin": "weixin",
|
|
||||||
}[pluginID]
|
|
||||||
if pluginType == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cleanupOpenclawPluginConfig(conf, pluginType)
|
|
||||||
return writeOpenclawConfigRaw(agent.ConfigPath, conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addOpenclawPluginRestartTask(t *task.Task, install *model.AppInstall) {
|
|
||||||
t.AddSubTask(task.GetTaskName("OpenClaw", task.TaskRestart, task.TaskScopeAI), func(t *task.Task) error {
|
|
||||||
output, err := compose.Restart(install.GetComposePath())
|
|
||||||
if output != "" {
|
|
||||||
t.Log(output)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeAgentPluginTask(t *task.Task) {
|
|
||||||
if err := t.Execute(); err != nil {
|
|
||||||
global.LOG.Errorf("operate openclaw plugin failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+273
-277
@@ -4,12 +4,10 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -21,12 +19,9 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"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/files"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
|
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,39 +35,6 @@ type resolvedAgentAccountInput struct {
|
|||||||
Provider string
|
Provider string
|
||||||
APIKey string
|
APIKey string
|
||||||
BaseURL 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) {
|
func loadOpenclawAgentByID(agentID uint) (*model.Agent, error) {
|
||||||
@@ -97,27 +59,19 @@ func ensureContainerRunning(containerName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, modelID string, validateAvailability bool) (resolvedAgentAccountInput, error) {
|
func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAccountInput, error) {
|
||||||
resolvedAPIKey := strings.TrimSpace(apiKey)
|
resolvedAPIKey := strings.TrimSpace(apiKey)
|
||||||
resolvedAPIType := strings.TrimSpace(apiType)
|
resolvedBaseURL := strings.TrimSpace(baseURL)
|
||||||
resolvedAuthMode, err := providercatalog.ResolveAuthMode(provider, resolvedAPIType, authMode)
|
if resolvedBaseURL == "" {
|
||||||
if err != nil {
|
if requiresInitialAgentAccountModels(provider) {
|
||||||
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")
|
return resolvedAgentAccountInput{}, buserr.New("ErrAgentBaseURLRequired")
|
||||||
}
|
}
|
||||||
return resolvedAgentAccountInput{}, err
|
if defaultURL, ok := providercatalog.DefaultBaseURL(provider); ok {
|
||||||
|
resolvedBaseURL = defaultURL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
modelID = strings.TrimSpace(modelID)
|
if !providercatalog.SkipVerification(provider) {
|
||||||
if modelID == "" {
|
if err := providercatalog.VerifyAccount(provider, resolvedBaseURL, resolvedAPIKey); err != nil {
|
||||||
return resolvedAgentAccountInput{}, buserr.New("ErrAgentAccountModelsRequired")
|
|
||||||
}
|
|
||||||
imageAPI := providercatalog.IsImageAPIType(resolvedAPIType)
|
|
||||||
if validateAvailability && (imageAPI || providercatalog.IsEmbeddingAPIType(resolvedAPIType) || !providercatalog.SkipVerification(provider)) {
|
|
||||||
if err := providercatalog.VerifyAccount(provider, resolvedAPIType, resolvedAuthMode, resolvedBaseURL, resolvedAPIKey, modelID); err != nil {
|
|
||||||
return resolvedAgentAccountInput{}, err
|
return resolvedAgentAccountInput{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,8 +79,6 @@ func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, mode
|
|||||||
Provider: provider,
|
Provider: provider,
|
||||||
APIKey: resolvedAPIKey,
|
APIKey: resolvedAPIKey,
|
||||||
BaseURL: resolvedBaseURL,
|
BaseURL: resolvedBaseURL,
|
||||||
APIType: resolvedAPIType,
|
|
||||||
AuthMode: resolvedAuthMode,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,24 +335,26 @@ func setOtherConfig(conf map[string]interface{}, config dto.AgentOtherConfig) {
|
|||||||
func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map[string]interface{}) dto.AgentItem {
|
func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map[string]interface{}) dto.AgentItem {
|
||||||
agentType := agent.AgentType
|
agentType := agent.AgentType
|
||||||
item := dto.AgentItem{
|
item := dto.AgentItem{
|
||||||
ID: agent.ID,
|
ID: agent.ID,
|
||||||
Name: agent.Name,
|
Name: agent.Name,
|
||||||
Remark: agent.Remark,
|
Remark: agent.Remark,
|
||||||
AgentType: agentType,
|
AgentType: agentType,
|
||||||
Provider: agent.Provider,
|
Provider: agent.Provider,
|
||||||
ProviderName: localizedAgentProviderName(agent.Provider),
|
ProviderName: providercatalog.DisplayName(agent.Provider),
|
||||||
Model: agent.Model,
|
Model: agent.Model,
|
||||||
APIType: agent.APIType,
|
APIType: agent.APIType,
|
||||||
BaseURL: agent.BaseURL,
|
MaxTokens: agent.MaxTokens,
|
||||||
APIKey: maskKey(agent.APIKey),
|
ContextWindow: agent.ContextWindow,
|
||||||
Token: agent.Token,
|
BaseURL: agent.BaseURL,
|
||||||
Status: agent.Status,
|
APIKey: maskKey(agent.APIKey),
|
||||||
Message: agent.Message,
|
Token: agent.Token,
|
||||||
AppInstallID: agent.AppInstallID,
|
Status: agent.Status,
|
||||||
WebsiteID: agent.WebsiteID,
|
Message: agent.Message,
|
||||||
AccountID: agent.AccountID,
|
AppInstallID: agent.AppInstallID,
|
||||||
ConfigPath: agent.ConfigPath,
|
WebsiteID: agent.WebsiteID,
|
||||||
CreatedAt: agent.CreatedAt,
|
AccountID: agent.AccountID,
|
||||||
|
ConfigPath: agent.ConfigPath,
|
||||||
|
CreatedAt: agent.CreatedAt,
|
||||||
}
|
}
|
||||||
if appInstall != nil && appInstall.ID > 0 {
|
if appInstall != nil && appInstall.ID > 0 {
|
||||||
item.Container = appInstall.ContainerName
|
item.Container = appInstall.ContainerName
|
||||||
@@ -422,24 +376,10 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
|
|||||||
item.BridgePort = toInt(bridge)
|
item.BridgePort = toInt(bridge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, _, ok := agentDashboardAuthEnvKeys(agentType); ok {
|
|
||||||
auth := readAgentDashboardAuthFromInstall(appInstall, agentType)
|
|
||||||
item.DashboardUsername = auth.Username
|
|
||||||
item.DashboardPassword = auth.Password
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func localizedAgentProviderName(provider string) string {
|
|
||||||
if key := providercatalog.DisplayNameKey(provider); key != "" {
|
|
||||||
if name := strings.TrimSpace(i18n.GetMsgByKey(key)); name != "" {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return providercatalog.DisplayName(provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAgentAppKey(appKey string) bool {
|
func isAgentAppKey(appKey string) bool {
|
||||||
return appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppHermesAgent
|
return appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppHermesAgent
|
||||||
}
|
}
|
||||||
@@ -737,9 +677,13 @@ type modelProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type modelEntry struct {
|
type modelEntry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Input []string `json:"input,omitempty"`
|
Reasoning bool `json:"reasoning"`
|
||||||
|
Input []string `json:"input"`
|
||||||
|
ContextWindow int `json:"contextWindow"`
|
||||||
|
MaxTokens int `json:"maxTokens"`
|
||||||
|
Cost modelCost `json:"cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func requiresOpenclawProviderModels(provider string) bool {
|
func requiresOpenclawProviderModels(provider string) bool {
|
||||||
@@ -759,6 +703,13 @@ func applyOpenclawModelsConfig(conf map[string]interface{}, models *modelsConfig
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type modelCost struct {
|
||||||
|
Input float64 `json:"input"`
|
||||||
|
Output float64 `json:"output"`
|
||||||
|
CacheRead float64 `json:"cacheRead"`
|
||||||
|
CacheWrite float64 `json:"cacheWrite"`
|
||||||
|
}
|
||||||
|
|
||||||
type browserConfig struct {
|
type browserConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
ExecutablePath string `json:"executablePath"`
|
ExecutablePath string `json:"executablePath"`
|
||||||
@@ -909,15 +860,13 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
|||||||
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
envMap := map[string]string{
|
envPath := path.Join(confDir, ".env")
|
||||||
"OPENCLAW_GATEWAY_TOKEN": token,
|
lines := []string{fmt.Sprintf("OPENCLAW_GATEWAY_TOKEN=%s", token)}
|
||||||
}
|
|
||||||
order := []string{"OPENCLAW_GATEWAY_TOKEN"}
|
|
||||||
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && account.APIKey != "" {
|
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && account.APIKey != "" {
|
||||||
envMap[envKey] = account.APIKey
|
lines = append(lines, fmt.Sprintf("%s=%s", envKey, account.APIKey))
|
||||||
order = append(order, envKey)
|
|
||||||
}
|
}
|
||||||
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
content := strings.Join(lines, "\n") + "\n"
|
||||||
|
return fileOp.SaveFile(envPath, content, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||||
@@ -1104,7 +1053,7 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) {
|
func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) {
|
||||||
providerPatch, err := providercatalog.BuildOpenClawProviderPatch(account.Provider, model.ID, account.APIType, account.AuthMode, account.BaseURL, account.APIKey)
|
providerPatch, err := providercatalog.BuildOpenClawProviderPatch(account.Provider, model.ID, account.APIType, account.BaseURL, account.APIKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", modelEntry{}, "", modelProvider{}, err
|
return "", modelEntry{}, "", modelProvider{}, err
|
||||||
}
|
}
|
||||||
@@ -1116,24 +1065,28 @@ func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.Agen
|
|||||||
}, nil
|
}, 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 {
|
func buildOpenclawModelEntry(modelID string, model dto.AgentAccountModel) modelEntry {
|
||||||
name := strings.TrimSpace(model.Name)
|
name := strings.TrimSpace(model.Name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = strings.TrimSpace(modelID)
|
name = strings.TrimSpace(modelID)
|
||||||
}
|
}
|
||||||
entry := modelEntry{ID: strings.TrimSpace(modelID), Name: name}
|
return modelEntry{
|
||||||
if openclawVisionModelPattern.MatchString(modelID) {
|
ID: strings.TrimSpace(modelID),
|
||||||
entry.Input = []string{"text", "image"}
|
Name: name,
|
||||||
|
Reasoning: model.Reasoning,
|
||||||
|
Input: sanitizeAgentAccountModelInputs(model.Input),
|
||||||
|
ContextWindow: model.ContextWindow,
|
||||||
|
MaxTokens: model.MaxTokens,
|
||||||
|
Cost: modelCost{},
|
||||||
}
|
}
|
||||||
return entry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type openclawAccountModelRuntime struct {
|
type openclawAccountModelRuntime struct {
|
||||||
StoredModel string
|
StoredModel string
|
||||||
PrimaryModel string
|
PrimaryModel string
|
||||||
APIType string
|
APIType string
|
||||||
|
MaxTokens int
|
||||||
|
ContextWindow int
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.AgentAccountModel) (openclawAccountModelRuntime, error) {
|
func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.AgentAccountModel) (openclawAccountModelRuntime, error) {
|
||||||
@@ -1142,9 +1095,11 @@ func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.Age
|
|||||||
return openclawAccountModelRuntime{}, err
|
return openclawAccountModelRuntime{}, err
|
||||||
}
|
}
|
||||||
return openclawAccountModelRuntime{
|
return openclawAccountModelRuntime{
|
||||||
StoredModel: model.ID,
|
StoredModel: model.ID,
|
||||||
PrimaryModel: primaryModel,
|
PrimaryModel: primaryModel,
|
||||||
APIType: account.APIType,
|
APIType: account.APIType,
|
||||||
|
MaxTokens: model.MaxTokens,
|
||||||
|
ContextWindow: model.ContextWindow,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1164,38 +1119,35 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto
|
|||||||
if account == nil {
|
if account == nil {
|
||||||
return nil, fmt.Errorf("account is required")
|
return nil, fmt.Errorf("account is required")
|
||||||
}
|
}
|
||||||
if account.Provider != "custom" && requiresInitialAgentAccountModels(account.Provider) && len(requested) > 1 {
|
if requiresInitialAgentAccountModels(account.Provider) && len(requested) > 1 {
|
||||||
return nil, buserr.New("ErrAgentAccountSingleInitialModel")
|
return nil, buserr.New("ErrAgentAccountSingleInitialModel")
|
||||||
}
|
}
|
||||||
if len(requested) > 0 {
|
if len(requested) > 0 {
|
||||||
return normalizeAgentAccountModels(account, requested)
|
models := make([]dto.AgentAccountModel, 0, len(requested))
|
||||||
|
for _, item := range requested {
|
||||||
|
models = append(models, cloneAgentAccountModel(item))
|
||||||
|
}
|
||||||
|
return models, nil
|
||||||
}
|
}
|
||||||
defaultModels := providercatalog.DefaultModels(account.Provider, account.APIType)
|
meta, ok := providercatalog.Get(account.Provider)
|
||||||
if len(defaultModels) == 0 {
|
if !ok || len(meta.Models) == 0 {
|
||||||
if requiresInitialAgentAccountModels(account.Provider) {
|
if requiresInitialAgentAccountModels(account.Provider) {
|
||||||
return nil, buserr.New("ErrAgentAccountModelsRequired")
|
return nil, buserr.New("ErrAgentAccountModelsRequired")
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
requested = make([]dto.AgentAccountModel, 0, len(defaultModels))
|
requested = make([]dto.AgentAccountModel, 0, len(meta.Models))
|
||||||
for _, item := range defaultModels {
|
for _, item := range meta.Models {
|
||||||
requested = append(requested, dto.AgentAccountModel{
|
requested = append(requested, dto.AgentAccountModel{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
|
ContextWindow: item.ContextWindow,
|
||||||
|
MaxTokens: item.MaxTokens,
|
||||||
|
Reasoning: item.Reasoning,
|
||||||
|
Input: append([]string(nil), item.Input...),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return normalizeAgentAccountModels(account, requested)
|
return requested, nil
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
func compactPersistedAgentAccountModelSortOrder(accountID uint) error {
|
||||||
@@ -1223,6 +1175,18 @@ func loadAgentAccountModels(account *model.AgentAccount) ([]dto.AgentAccountMode
|
|||||||
return listPersistedAgentAccountModels(account.ID)
|
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) {
|
func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, existing []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) {
|
||||||
if account == nil {
|
if account == nil {
|
||||||
return nil, fmt.Errorf("account is required")
|
return nil, fmt.Errorf("account is required")
|
||||||
@@ -1246,8 +1210,12 @@ func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, exi
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
requested = append(requested, dto.AgentAccountModel{
|
requested = append(requested, dto.AgentAccountModel{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
|
ContextWindow: item.ContextWindow,
|
||||||
|
MaxTokens: item.MaxTokens,
|
||||||
|
Reasoning: item.Reasoning,
|
||||||
|
Input: append([]string(nil), item.Input...),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if len(requested) == len(existing) {
|
if len(requested) == len(existing) {
|
||||||
@@ -1266,10 +1234,18 @@ func listPersistedAgentAccountModels(accountID uint) ([]dto.AgentAccountModel, e
|
|||||||
}
|
}
|
||||||
result := make([]dto.AgentAccountModel, 0, len(rows))
|
result := make([]dto.AgentAccountModel, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
inputs := []string{}
|
||||||
|
if strings.TrimSpace(row.Input) != "" {
|
||||||
|
_ = json.Unmarshal([]byte(row.Input), &inputs)
|
||||||
|
}
|
||||||
result = append(result, dto.AgentAccountModel{
|
result = append(result, dto.AgentAccountModel{
|
||||||
RecordID: row.ID,
|
RecordID: row.ID,
|
||||||
ID: strings.TrimSpace(row.Model),
|
ID: strings.TrimSpace(row.Model),
|
||||||
Name: strings.TrimSpace(row.Name),
|
Name: strings.TrimSpace(row.Name),
|
||||||
|
ContextWindow: row.ContextWindow,
|
||||||
|
MaxTokens: row.MaxTokens,
|
||||||
|
Reasoning: row.Reasoning,
|
||||||
|
Input: sanitizeAgentAccountModelInputs(inputs),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -1280,11 +1256,19 @@ func replacePersistedAgentAccountModelsWithTx(tx *gorm.DB, accountID uint, model
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for index, item := range models {
|
for index, item := range models {
|
||||||
|
inputPayload, err := json.Marshal(sanitizeAgentAccountModelInputs(item.Input))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
record := &model.AgentAccountModel{
|
record := &model.AgentAccountModel{
|
||||||
AccountID: accountID,
|
AccountID: accountID,
|
||||||
Model: strings.TrimSpace(item.ID),
|
Model: strings.TrimSpace(item.ID),
|
||||||
Name: strings.TrimSpace(item.Name),
|
Name: strings.TrimSpace(item.Name),
|
||||||
SortOrder: index + 1,
|
ContextWindow: item.ContextWindow,
|
||||||
|
MaxTokens: item.MaxTokens,
|
||||||
|
Reasoning: item.Reasoning,
|
||||||
|
Input: string(inputPayload),
|
||||||
|
SortOrder: index + 1,
|
||||||
}
|
}
|
||||||
if err := tx.Create(record).Error; err != nil {
|
if err := tx.Create(record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1325,34 +1309,125 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco
|
|||||||
if modelID == "" {
|
if modelID == "" {
|
||||||
return dto.AgentAccountModel{}, fmt.Errorf("model is required")
|
return dto.AgentAccountModel{}, fmt.Errorf("model is required")
|
||||||
}
|
}
|
||||||
modelID = providercatalog.NormalizeModelID(account.Provider, modelID)
|
|
||||||
name := strings.TrimSpace(model.Name)
|
name := strings.TrimSpace(model.Name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = modelID
|
name = modelID
|
||||||
}
|
}
|
||||||
|
inputs := sanitizeAgentAccountModelInputs(model.Input)
|
||||||
return dto.AgentAccountModel{
|
return dto.AgentAccountModel{
|
||||||
ID: modelID,
|
ID: normalizeAgentAccountModelID(account.Provider, modelID),
|
||||||
Name: name,
|
Name: name,
|
||||||
|
ContextWindow: model.ContextWindow,
|
||||||
|
MaxTokens: model.MaxTokens,
|
||||||
|
Reasoning: model.Reasoning,
|
||||||
|
Input: inputs,
|
||||||
}, nil
|
}, 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 {
|
func requiresInitialAgentAccountModels(provider string) bool {
|
||||||
switch provider {
|
switch provider {
|
||||||
case "custom", "vllm", "ollama", "llmman":
|
case "custom", "vllm", "ollama":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func sameProviderModelID(provider, left, right string) bool {
|
||||||
leftTrimmed := strings.TrimSpace(left)
|
leftTrimmed := strings.TrimSpace(left)
|
||||||
rightTrimmed := strings.TrimSpace(right)
|
rightTrimmed := strings.TrimSpace(right)
|
||||||
if leftTrimmed == rightTrimmed {
|
if leftTrimmed == rightTrimmed {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
leftComparable := providercatalog.NormalizeModelID(provider, leftTrimmed)
|
leftComparable := normalizeComparableProviderModelID(provider, leftTrimmed)
|
||||||
rightComparable := providercatalog.NormalizeModelID(provider, rightTrimmed)
|
rightComparable := normalizeComparableProviderModelID(provider, rightTrimmed)
|
||||||
return leftComparable != "" && leftComparable == rightComparable
|
return leftComparable != "" && leftComparable == rightComparable
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1373,20 +1448,6 @@ func requireAgentAccountModelForProvider(provider string, models []dto.AgentAcco
|
|||||||
return selectedAccountModel, nil
|
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 {
|
func ensureAccountModelsNotBound(account *model.AgentAccount, models []dto.AgentAccountModel) error {
|
||||||
if account == nil || account.ID == 0 {
|
if account == nil || account.ID == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -1463,122 +1524,6 @@ func readInstallEnv(envStr string) map[string]interface{} {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
hermesDashboardUsernameEnvKey = "HERMES_DASHBOARD_USERNAME"
|
|
||||||
hermesDashboardPasswordEnvKey = "HERMES_DASHBOARD_PASSWORD"
|
|
||||||
qwenPawAuthEnabledEnvKey = "QWENPAW_AUTH_ENABLED"
|
|
||||||
qwenPawAuthUsernameEnvKey = "QWENPAW_AUTH_USERNAME"
|
|
||||||
qwenPawAuthPasswordEnvKey = "QWENPAW_AUTH_PASSWORD"
|
|
||||||
)
|
|
||||||
|
|
||||||
type agentDashboardAuth struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeAgentDashboardAuth(username, password string) agentDashboardAuth {
|
|
||||||
auth := agentDashboardAuth{
|
|
||||||
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 agentDashboardAuthEnvKeys(agentType string) (string, string, bool) {
|
|
||||||
switch agentType {
|
|
||||||
case constant.AppHermesAgent:
|
|
||||||
return hermesDashboardUsernameEnvKey, hermesDashboardPasswordEnvKey, true
|
|
||||||
case constant.AppCopaw:
|
|
||||||
return qwenPawAuthUsernameEnvKey, qwenPawAuthPasswordEnvKey, true
|
|
||||||
default:
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeAgentDashboardAuthEnv(envPath, agentType string, auth agentDashboardAuth, overwrite bool) error {
|
|
||||||
usernameKey, passwordKey, ok := agentDashboardAuthEnvKeys(agentType)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("dashboard auth is not supported for %s", agentType)
|
|
||||||
}
|
|
||||||
values := map[string]string{
|
|
||||||
usernameKey: auth.Username,
|
|
||||||
passwordKey: auth.Password,
|
|
||||||
}
|
|
||||||
order := []string{usernameKey, passwordKey}
|
|
||||||
if agentType == constant.AppCopaw {
|
|
||||||
values[qwenPawAuthEnabledEnvKey] = "true"
|
|
||||||
order = append([]string{qwenPawAuthEnabledEnvKey}, order...)
|
|
||||||
}
|
|
||||||
return upsertAgentEnv(envPath, values, order, overwrite)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAgentDashboardAuthEnv(envPath, agentType string) (agentDashboardAuth, error) {
|
|
||||||
usernameKey, passwordKey, ok := agentDashboardAuthEnvKeys(agentType)
|
|
||||||
if !ok {
|
|
||||||
return agentDashboardAuth{}, fmt.Errorf("dashboard auth is not supported for %s", agentType)
|
|
||||||
}
|
|
||||||
envMap, err := readAgentEnvMap(envPath)
|
|
||||||
if err != nil {
|
|
||||||
return agentDashboardAuth{}, err
|
|
||||||
}
|
|
||||||
return agentDashboardAuth{
|
|
||||||
Username: strings.TrimSpace(envMap[usernameKey]),
|
|
||||||
Password: strings.TrimSpace(envMap[passwordKey]),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAgentDashboardAuthFromInstall(appInstall *model.AppInstall, agentType string) agentDashboardAuth {
|
|
||||||
if appInstall == nil || appInstall.ID == 0 {
|
|
||||||
return agentDashboardAuth{}
|
|
||||||
}
|
|
||||||
auth, err := readAgentDashboardAuthEnv(appInstall.GetEnvPath(), agentType)
|
|
||||||
if err != nil {
|
|
||||||
return agentDashboardAuth{}
|
|
||||||
}
|
|
||||||
return auth
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
func maskKey(value string) string {
|
||||||
trim := strings.TrimSpace(value)
|
trim := strings.TrimSpace(value)
|
||||||
if len(trim) <= 6 {
|
if len(trim) <= 6 {
|
||||||
@@ -1606,6 +1551,57 @@ 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 {
|
func generateToken() string {
|
||||||
bytes := make([]byte, 24)
|
bytes := make([]byte, 24)
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
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)
|
websiteMap := make(map[uint]model.Website)
|
||||||
duplicateAppInstallIDs := make(map[uint]struct{})
|
duplicateAppInstallIDs := make(map[uint]struct{})
|
||||||
for _, website := range websites {
|
for _, website := range websites {
|
||||||
@@ -182,6 +182,10 @@ func UniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]m
|
|||||||
return websiteMap
|
return websiteMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UniqueDeploymentWebsiteMapForMigration(websites []model.Website) map[uint]model.Website {
|
||||||
|
return uniqueDeploymentWebsiteMapByAppInstall(websites)
|
||||||
|
}
|
||||||
|
|
||||||
func uniqueUintList(items []uint) []uint {
|
func uniqueUintList(items []uint) []uint {
|
||||||
itemMap := make(map[uint]struct{}, len(items))
|
itemMap := make(map[uint]struct{}, len(items))
|
||||||
uniq := make([]uint, 0, len(items))
|
uniq := make([]uint, 0, len(items))
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
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"))
|
|
||||||
}
|
|
||||||
+27
-330
@@ -17,13 +17,10 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
|
|
||||||
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
|
|
||||||
"github.com/shirou/gopsutil/v4/disk"
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,28 +34,6 @@ var communityAlertMethodTypeNames = map[string]string{
|
|||||||
constant.SMS: "SMS",
|
constant.SMS: "SMS",
|
||||||
}
|
}
|
||||||
|
|
||||||
var legacyAlertMethodTypeMap = map[string]string{
|
|
||||||
"mail": constant.Email,
|
|
||||||
constant.Email: constant.Email,
|
|
||||||
constant.SMS: constant.SMS,
|
|
||||||
constant.Bark: constant.Bark,
|
|
||||||
constant.WeChat: constant.WeCom,
|
|
||||||
constant.WeCom: constant.WeCom,
|
|
||||||
constant.DingTalk: constant.DingTalk,
|
|
||||||
constant.FeiShu: constant.FeiShu,
|
|
||||||
constant.Custom: constant.Custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedAlertMethodTypes = map[string]struct{}{
|
|
||||||
constant.Email: {},
|
|
||||||
constant.SMS: {},
|
|
||||||
constant.Bark: {},
|
|
||||||
constant.WeCom: {},
|
|
||||||
constant.DingTalk: {},
|
|
||||||
constant.FeiShu: {},
|
|
||||||
constant.Custom: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
type IAlertService interface {
|
type IAlertService interface {
|
||||||
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
||||||
GetAlerts() ([]dto.AlertDTO, error)
|
GetAlerts() ([]dto.AlertDTO, error)
|
||||||
@@ -78,10 +53,8 @@ type IAlertService interface {
|
|||||||
GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, error)
|
GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, error)
|
||||||
PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error)
|
PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error)
|
||||||
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
||||||
UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error
|
|
||||||
DeleteAlertConfig(id uint) error
|
DeleteAlertConfig(id uint) error
|
||||||
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
||||||
TestCustomAlertConfig(req dto.AlertConfigTest) (dto.AlertConfigTestResult, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIAlertService() IAlertService {
|
func NewIAlertService() IAlertService {
|
||||||
@@ -207,15 +180,9 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||||
methodTypes, err := a.validateAlertMethodReferences(req.Method)
|
if err := a.validateCommunityAlertMethod(req.Method); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if req.Status != constant.AlertDisable {
|
|
||||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
upMap := make(map[string]interface{})
|
upMap := make(map[string]interface{})
|
||||||
upMap["id"] = req.ID
|
upMap["id"] = req.ID
|
||||||
@@ -273,16 +240,7 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
|||||||
if alertInfo.ID == 0 {
|
if alertInfo.ID == 0 {
|
||||||
return buserr.New("ErrRecordNotFound")
|
return buserr.New("ErrRecordNotFound")
|
||||||
}
|
}
|
||||||
methodTypes, err := a.validateAlertMethodReferences(alertInfo.Method)
|
err := alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if status == constant.AlertEnable {
|
|
||||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -426,9 +384,6 @@ func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.Ale
|
|||||||
if search.Count != 0 {
|
if search.Count != 0 {
|
||||||
opts = append(opts, alertRepo.WithByCount(search.Count))
|
opts = append(opts, alertRepo.WithByCount(search.Count))
|
||||||
}
|
}
|
||||||
if !search.StartTime.IsZero() && !search.EndTime.IsZero() {
|
|
||||||
opts = append(opts, repo.WithByCreatedAt(search.StartTime, search.EndTime))
|
|
||||||
}
|
|
||||||
opts = append(opts, repo.WithOrderDesc("created_at"))
|
opts = append(opts, repo.WithOrderDesc("created_at"))
|
||||||
|
|
||||||
total, alerts, err := alertRepo.PageLog(search.Page, search.PageSize, opts...)
|
total, alerts, err := alertRepo.PageLog(search.Page, search.PageSize, opts...)
|
||||||
@@ -454,7 +409,6 @@ func (a AlertService) parseAlertLog(item model.AlertLog) (dto.AlertLogDTO, error
|
|||||||
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
|
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
|
||||||
return dto.AlertLogDTO{}, err
|
return dto.AlertLogDTO{}, err
|
||||||
}
|
}
|
||||||
alertDetail.Task = nil
|
|
||||||
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
|
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
|
||||||
return dto.AlertLogDTO{}, err
|
return dto.AlertLogDTO{}, err
|
||||||
}
|
}
|
||||||
@@ -537,13 +491,7 @@ func (a AlertService) GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertCon
|
|||||||
}
|
}
|
||||||
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
||||||
configs, err := alertRepo.AlertConfigList(opts...)
|
configs, err := alertRepo.AlertConfigList(opts...)
|
||||||
if err != nil {
|
return configs, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := exposeCustomAlertConfigSecrets(configs); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return configs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error) {
|
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error) {
|
||||||
@@ -554,49 +502,13 @@ func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []mode
|
|||||||
if len(req.ExcludeTypes) > 0 {
|
if len(req.ExcludeTypes) > 0 {
|
||||||
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
opts = append(opts, alertRepo.WithByTypeNotIn(req.ExcludeTypes))
|
||||||
}
|
}
|
||||||
total, configs, err := alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
return alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
if err := exposeCustomAlertConfigSecrets(configs); err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
return total, configs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||||
if req.Type == constant.Custom {
|
|
||||||
if req.ID != 0 && req.Revision == nil {
|
|
||||||
return repo.ErrAlertConfigRevisionRequired
|
|
||||||
}
|
|
||||||
return a.updateCustomAlertConfig(req, operator)
|
|
||||||
}
|
|
||||||
usesMutation, err := alertconfig.UsesMutation(req.Type, req.Config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if req.ID != 0 && usesMutation && req.Revision == nil {
|
|
||||||
return repo.ErrAlertConfigRevisionRequired
|
|
||||||
}
|
|
||||||
var existing *model.AlertConfig
|
|
||||||
if req.ID != 0 {
|
|
||||||
stored, err := alertRepo.GetConfigById(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if stored.Type != req.Type {
|
|
||||||
return fmt.Errorf("alert config %d has type %s, not %s", req.ID, stored.Type, req.Type)
|
|
||||||
}
|
|
||||||
existing = &stored
|
|
||||||
}
|
|
||||||
if err := a.validateCommunityAlertConfigType(req.Type); err != nil {
|
if err := a.validateCommunityAlertConfigType(req.Type); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
prepared, err := alertconfig.Prepare(req.Type, req.Config, req.Status, existing)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Config = prepared
|
|
||||||
if err := a.checkAlertConfigDisplayNameUnique(req); err != nil {
|
if err := a.checkAlertConfigDisplayNameUnique(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -611,7 +523,7 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
|||||||
upMap["status"] = req.Status
|
upMap["status"] = req.Status
|
||||||
upMap["config"] = req.Config
|
upMap["config"] = req.Config
|
||||||
upMap["update_user"] = operator
|
upMap["update_user"] = operator
|
||||||
if err := alertRepo.UpdateAlertConfigWithRevision(upMap, req.Revision, repo.WithByID(req.ID)); err != nil {
|
if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -629,99 +541,6 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator stri
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) updateCustomAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
|
||||||
if err := validateAlertConfigStatus(req.Status); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var existing *model.AlertConfig
|
|
||||||
if req.ID != 0 {
|
|
||||||
config, err := alertRepo.GetConfigById(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if config.Type != constant.Custom {
|
|
||||||
return fmt.Errorf("alert config %d is not a custom webhook", req.ID)
|
|
||||||
}
|
|
||||||
existing = &config
|
|
||||||
}
|
|
||||||
prepared, err := alertwebhook.Prepare(req.Config, req.Status, existing)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
validatedReq := req
|
|
||||||
validatedReq.Config = prepared.Config
|
|
||||||
if err := a.checkAlertConfigDisplayNameUnique(validatedReq); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if existing != nil {
|
|
||||||
return alertRepo.UpdateAlertConfigWithRevision(map[string]interface{}{
|
|
||||||
"type": constant.Custom,
|
|
||||||
"title": req.Title,
|
|
||||||
"status": req.Status,
|
|
||||||
"config": prepared.Config,
|
|
||||||
"secret_config": prepared.SecretConfig,
|
|
||||||
"update_user": operator,
|
|
||||||
}, req.Revision, repo.WithByID(req.ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
return alertRepo.CreateAlertConfig(&model.AlertConfig{
|
|
||||||
Type: constant.Custom,
|
|
||||||
Title: req.Title,
|
|
||||||
Status: req.Status,
|
|
||||||
Config: prepared.Config,
|
|
||||||
SecretConfig: prepared.SecretConfig,
|
|
||||||
CreateUser: operator,
|
|
||||||
UpdateUser: operator,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AlertService) UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error {
|
|
||||||
if err := validateAlertConfigStatus(req.Status); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config, err := alertRepo.GetConfigById(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if req.Status == constant.AlertEnable {
|
|
||||||
if err := a.validateCommunityAlertConfigType(config.Type); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if config.Type == constant.Custom {
|
|
||||||
if _, err := alertwebhook.Resolve(config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return alertRepo.UpdateAlertConfig(map[string]interface{}{
|
|
||||||
"status": req.Status,
|
|
||||||
"update_user": operator,
|
|
||||||
}, repo.WithByID(req.ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateAlertConfigStatus(status string) error {
|
|
||||||
if status != constant.AlertEnable && status != constant.AlertDisable {
|
|
||||||
return fmt.Errorf("alert config status must be Enable or Disable")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func exposeCustomAlertConfigSecrets(configs []model.AlertConfig) error {
|
|
||||||
for index := range configs {
|
|
||||||
if configs[index].Type != constant.Custom {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
view, err := alertwebhook.PlainView(configs[index])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("build editable custom alert config %d: %w", configs[index].ID, err)
|
|
||||||
}
|
|
||||||
configs[index].Config = view
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate) error {
|
func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate) error {
|
||||||
if req.Type != constant.SMSConfig {
|
if req.Type != constant.SMSConfig {
|
||||||
return nil
|
return nil
|
||||||
@@ -746,9 +565,6 @@ func (a AlertService) checkAlertConfigSMSPhoneUnique(req dto.AlertConfigUpdate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdate) error {
|
func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdate) error {
|
||||||
if req.Type != constant.Custom && (global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
displayName := alertConfigDisplayName(req.Type, req.Config)
|
displayName := alertConfigDisplayName(req.Type, req.Config)
|
||||||
if displayName == "" {
|
if displayName == "" {
|
||||||
return nil
|
return nil
|
||||||
@@ -772,67 +588,37 @@ func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) validateCommunityAlertMethod(method string) error {
|
func (a AlertService) validateCommunityAlertMethod(method string) error {
|
||||||
methodTypes, err := a.validateAlertMethodReferences(method)
|
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||||
if err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return a.validateAlertMethodEntitlement(methodTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AlertService) validateAlertMethodReferences(method string) ([]string, error) {
|
|
||||||
if strings.TrimSpace(method) == "" {
|
if strings.TrimSpace(method) == "" {
|
||||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
return nil
|
||||||
}
|
}
|
||||||
methodTypes := make([]string, 0)
|
|
||||||
for _, item := range strings.Split(method, ",") {
|
for _, item := range strings.Split(method, ",") {
|
||||||
item = strings.TrimSpace(item)
|
item = strings.TrimSpace(item)
|
||||||
if item == "" {
|
if item == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
configType := ""
|
|
||||||
if configID, err := strconv.ParseUint(item, 10, 64); err == nil {
|
if configID, err := strconv.ParseUint(item, 10, 64); err == nil {
|
||||||
config, err := alertRepo.GetConfigById(uint(configID))
|
config, err := alertRepo.GetConfigById(uint(configID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
configType = config.Type
|
if _, ok := communityAlertMethodTypeNames[config.Type]; ok {
|
||||||
} else {
|
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||||
var ok bool
|
|
||||||
configType, ok = legacyAlertMethodTypeMap[item]
|
|
||||||
if !ok {
|
|
||||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if _, ok := supportedAlertMethodTypes[configType]; !ok {
|
|
||||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
|
||||||
}
|
|
||||||
methodTypes = append(methodTypes, configType)
|
|
||||||
}
|
|
||||||
if len(methodTypes) == 0 {
|
|
||||||
return nil, buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
|
||||||
}
|
|
||||||
return methodTypes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AlertService) validateAlertMethodEntitlement(methodTypes []string) error {
|
|
||||||
for _, configType := range methodTypes {
|
|
||||||
if configType == constant.Custom {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
if _, ok := communityAlertMethodTypeNames[item]; ok {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := communityAlertMethodTypeNames[configType]; ok {
|
|
||||||
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
return buserr.WithErr("ErrAlertMethodNotSupported", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) validateCommunityAlertConfigType(configType string) error {
|
func (a AlertService) validateCommunityAlertConfigType(configType string) error {
|
||||||
if configType == constant.Custom {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "cn" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -844,7 +630,7 @@ func (a AlertService) validateCommunityAlertConfigType(configType string) error
|
|||||||
|
|
||||||
func alertConfigDisplayName(configType, configData string) string {
|
func alertConfigDisplayName(configType, configData string) string {
|
||||||
switch configType {
|
switch configType {
|
||||||
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark, constant.SMS, constant.Custom:
|
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark, constant.SMS:
|
||||||
var cfg struct {
|
var cfg struct {
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
}
|
}
|
||||||
@@ -883,24 +669,20 @@ func (a AlertService) DeleteAlertConfig(id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||||
emailConfig, err := resolveEmailTestConfig(req)
|
username := req.UserName
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
username := emailConfig.UserName
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = emailConfig.Sender
|
username = req.Sender
|
||||||
}
|
}
|
||||||
encodedDisplayName := mime.BEncoding.Encode("UTF-8", emailConfig.DisplayName)
|
encodedDisplayName := mime.BEncoding.Encode("UTF-8", req.DisplayName)
|
||||||
cfg := email.SMTPConfig{
|
cfg := email.SMTPConfig{
|
||||||
Host: emailConfig.Host,
|
Host: req.Host,
|
||||||
Port: emailConfig.Port,
|
Port: req.Port,
|
||||||
Sender: emailConfig.Sender,
|
Sender: req.Sender,
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: emailConfig.Password,
|
Password: req.Password,
|
||||||
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, emailConfig.Sender),
|
From: fmt.Sprintf(`"%s" <%s>`, encodedDisplayName, req.Sender),
|
||||||
Encryption: emailConfig.Encryption,
|
Encryption: req.Encryption,
|
||||||
Recipient: emailConfig.Recipient,
|
Recipient: req.Recipient,
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := email.EmailMessage{
|
msg := email.EmailMessage{
|
||||||
@@ -915,94 +697,9 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveEmailTestConfig(req dto.AlertConfigTest) (dto.AlertEmailConfig, error) {
|
|
||||||
emailConfig := dto.AlertEmailConfig{
|
|
||||||
Host: req.Host,
|
|
||||||
Port: req.Port,
|
|
||||||
Sender: req.Sender,
|
|
||||||
UserName: req.UserName,
|
|
||||||
Password: req.Password,
|
|
||||||
DisplayName: req.DisplayName,
|
|
||||||
Encryption: req.Encryption,
|
|
||||||
Recipient: req.Recipient,
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.Config) != "" {
|
|
||||||
configType := req.Type
|
|
||||||
if configType == "" {
|
|
||||||
configType = constant.EmailConfig
|
|
||||||
}
|
|
||||||
if configType != constant.EmailConfig {
|
|
||||||
return dto.AlertEmailConfig{}, fmt.Errorf("alert config test type must be email")
|
|
||||||
}
|
|
||||||
var existing *model.AlertConfig
|
|
||||||
if req.ID != 0 {
|
|
||||||
stored, err := alertRepo.GetConfigById(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return dto.AlertEmailConfig{}, err
|
|
||||||
}
|
|
||||||
existing = &stored
|
|
||||||
}
|
|
||||||
prepared, err := alertconfig.Prepare(configType, req.Config, constant.AlertEnable, existing)
|
|
||||||
if err != nil {
|
|
||||||
return dto.AlertEmailConfig{}, err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal([]byte(prepared), &emailConfig); err != nil {
|
|
||||||
return dto.AlertEmailConfig{}, fmt.Errorf("decode email alert config: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return emailConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AlertService) TestCustomAlertConfig(req dto.AlertConfigTest) (dto.AlertConfigTestResult, error) {
|
|
||||||
if req.Type != constant.Custom {
|
|
||||||
return dto.AlertConfigTestResult{}, fmt.Errorf("alert config test type must be custom")
|
|
||||||
}
|
|
||||||
var existing *model.AlertConfig
|
|
||||||
if req.ID != 0 {
|
|
||||||
config, err := alertRepo.GetConfigById(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return dto.AlertConfigTestResult{}, err
|
|
||||||
}
|
|
||||||
if config.Type != constant.Custom {
|
|
||||||
return dto.AlertConfigTestResult{}, fmt.Errorf("alert config %d is not a custom webhook", req.ID)
|
|
||||||
}
|
|
||||||
existing = &config
|
|
||||||
}
|
|
||||||
prepared, err := alertwebhook.Prepare(req.Config, constant.AlertEnable, existing)
|
|
||||||
if err != nil {
|
|
||||||
return dto.AlertConfigTestResult{}, err
|
|
||||||
}
|
|
||||||
resolved, err := alertwebhook.Resolve(model.AlertConfig{
|
|
||||||
Type: constant.Custom,
|
|
||||||
Config: prepared.Config,
|
|
||||||
SecretConfig: prepared.SecretConfig,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return dto.AlertConfigTestResult{}, err
|
|
||||||
}
|
|
||||||
tester, ok := xpack.AlertProvider.(providers.CustomWebhookTester)
|
|
||||||
if !ok {
|
|
||||||
return dto.AlertConfigTestResult{
|
|
||||||
Success: false,
|
|
||||||
Message: providers.ErrCustomWebhookUnsupported.Error(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return tester.TestCustomWebhook(resolved)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error {
|
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error {
|
||||||
var methodTypes []string
|
if err := a.validateCommunityAlertMethod(updateAlert.Method); err != nil {
|
||||||
if updateAlert.SendCount != 0 || strings.TrimSpace(updateAlert.Method) != "" {
|
return err
|
||||||
var err error
|
|
||||||
methodTypes, err = a.validateAlertMethodReferences(updateAlert.Method)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if updateAlert.SendCount != 0 {
|
|
||||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
upMap := make(map[string]interface{})
|
upMap := make(map[string]interface{})
|
||||||
var newStatus string
|
var newStatus string
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
@@ -33,7 +32,6 @@ const (
|
|||||||
ResourceAlertInterval = 30
|
ResourceAlertInterval = 30
|
||||||
CheckIntervalSec = 3
|
CheckIntervalSec = 3
|
||||||
LoadCheckIntervalMin = 5
|
LoadCheckIntervalMin = 5
|
||||||
sshIPLoginWindow = 30 * time.Minute
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AlertTaskHelper struct {
|
type AlertTaskHelper struct {
|
||||||
@@ -514,28 +512,10 @@ func loadPanelLogin(alert dto.AlertDTO) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadSSHLogin(alert dto.AlertDTO) {
|
func loadSSHLogin(alert dto.AlertDTO) {
|
||||||
now := time.Now()
|
count, isAlert, err := alertUtil.CountRecentFailedSSHLog(alert.Cycle, alert.Count)
|
||||||
failedWindow := time.Duration(alert.Cycle) * time.Minute
|
|
||||||
loadWindow := failedWindow
|
|
||||||
if loadWindow < sshIPLoginWindow {
|
|
||||||
loadWindow = sshIPLoginWindow
|
|
||||||
}
|
|
||||||
location, err := time.LoadLocation(common.LoadTimeZoneByCmd())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("Failed to load timezone for ssh login logs: %v", err)
|
global.LOG.Errorf("Failed to count recent failed ssh login logs: %v", err)
|
||||||
location = time.Local
|
|
||||||
}
|
}
|
||||||
histories, err := loadSSHAlertHistories(defaultSSHLogDir, now.Add(-loadWindow), now, location)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
|
||||||
}
|
|
||||||
count, records := summarizeSSHLoginHistories(
|
|
||||||
histories,
|
|
||||||
now,
|
|
||||||
failedWindow,
|
|
||||||
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"),
|
|
||||||
)
|
|
||||||
isAlert := count >= int(alert.Count)
|
|
||||||
if isAlert {
|
if isAlert {
|
||||||
params := []dto.Param{
|
params := []dto.Param{
|
||||||
{
|
{
|
||||||
@@ -551,6 +531,12 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
|||||||
}
|
}
|
||||||
sendAlerts(alert, "sshLogin", strconv.Itoa(count), "sshLogin", params)
|
sendAlerts(alert, "sshLogin", strconv.Itoa(count), "sshLogin", params)
|
||||||
}
|
}
|
||||||
|
whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n")
|
||||||
|
records, err := alertUtil.FindRecentSuccessLoginNotInWhitelist(30, whitelist)
|
||||||
|
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 {
|
if len(records) > 0 {
|
||||||
quota := strings.Join(records, "\n")
|
quota := strings.Join(records, "\n")
|
||||||
params := []dto.Param{
|
params := []dto.Param{
|
||||||
@@ -579,6 +565,20 @@ func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string)
|
|||||||
return filtered
|
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 {
|
func isIPInWhitelist(ip string, whitelist []string) bool {
|
||||||
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
||||||
if targetIP == nil {
|
if targetIP == nil {
|
||||||
@@ -695,10 +695,9 @@ func sendAlertsByConfigId(alert dto.AlertDTO, alertType, quota, quotaType string
|
|||||||
|
|
||||||
func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, method string) {
|
func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, method string) {
|
||||||
typeMap := map[string]string{
|
typeMap := map[string]string{
|
||||||
"mail": constant.Email,
|
"mail": constant.Email,
|
||||||
constant.Bark: constant.Bark,
|
constant.Bark: constant.Bark,
|
||||||
constant.SMS: constant.SMS,
|
constant.SMS: constant.SMS,
|
||||||
constant.Custom: constant.Custom,
|
|
||||||
}
|
}
|
||||||
configType, ok := typeMap[method]
|
configType, ok := typeMap[method]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -706,6 +705,7 @@ func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType st
|
|||||||
}
|
}
|
||||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
doSendAlert(alert, alertType, quota, quotaType, params, config)
|
doSendAlert(alert, alertType, quota, quotaType, params, config)
|
||||||
@@ -786,7 +786,7 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
|||||||
}
|
}
|
||||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||||
|
|
||||||
case constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Custom:
|
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||||
if !isValid {
|
if !isValid {
|
||||||
return
|
return
|
||||||
@@ -799,31 +799,12 @@ func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params
|
|||||||
}
|
}
|
||||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||||
queued := false
|
alertErr := xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo)
|
||||||
var alertErr error
|
|
||||||
if config.Type == constant.Custom {
|
|
||||||
task := dto.AlertTaskMetadata{
|
|
||||||
AlertID: alert.ID,
|
|
||||||
Type: alertType,
|
|
||||||
Quota: quota,
|
|
||||||
QuotaType: quotaType,
|
|
||||||
Method: methodStr,
|
|
||||||
}
|
|
||||||
result, deliveryErr := xpack.DeliverCustomWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo, task)
|
|
||||||
queued, alertErr = result.Queued, deliveryErr
|
|
||||||
if alertErr == nil && result.Queued {
|
|
||||||
_, alertErr = alertUtil.RecordQueuedAlertTask(result.LogID, task)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alertErr = xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo)
|
|
||||||
}
|
|
||||||
if alertErr != nil {
|
if alertErr != nil {
|
||||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !queued {
|
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1117,55 +1098,3 @@ func calculateMinutesDifference(newDate time.Time) int {
|
|||||||
minutesDifference := int(now.Sub(newDate).Minutes())
|
minutesDifference := int(now.Sub(newDate).Minutes())
|
||||||
return minutesDifference
|
return minutesDifference
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSSHAlertHistories(
|
|
||||||
baseDir string,
|
|
||||||
startTime, endTime time.Time,
|
|
||||||
location *time.Location,
|
|
||||||
) ([]dto.SSHHistory, error) {
|
|
||||||
fileList, err := listSSHLogFiles(baseDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
histories []dto.SSHHistory
|
|
||||||
loadErr error
|
|
||||||
)
|
|
||||||
for _, file := range fileList {
|
|
||||||
items, err := loadSSHHistoriesFromFile(file.Name, "", "", startTime, endTime, file.Year, location)
|
|
||||||
if err != nil {
|
|
||||||
loadErr = errors.Join(loadErr, fmt.Errorf("load SSH log file %s: %w", file.Name, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
histories = append(histories, items...)
|
|
||||||
}
|
|
||||||
return histories, loadErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func summarizeSSHLoginHistories(
|
|
||||||
histories []dto.SSHHistory,
|
|
||||||
now time.Time,
|
|
||||||
failedWindow time.Duration,
|
|
||||||
whitelist []string,
|
|
||||||
) (int, []string) {
|
|
||||||
failedStartTime := now.Add(-failedWindow)
|
|
||||||
successStartTime := now.Add(-sshIPLoginWindow)
|
|
||||||
failedCount := 0
|
|
||||||
var abnormalLogins []string
|
|
||||||
|
|
||||||
for _, item := range histories {
|
|
||||||
switch item.Status {
|
|
||||||
case constant.StatusFailed:
|
|
||||||
if isSSHLogWithinTimeRange(item.Date, failedStartTime, now) {
|
|
||||||
failedCount++
|
|
||||||
}
|
|
||||||
case constant.StatusSuccess:
|
|
||||||
if !isSSHLogWithinTimeRange(item.Date, successStartTime, now) || isIPInWhitelist(item.Address, whitelist) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
abnormalLogins = append(abnormalLogins, fmt.Sprintf("%s-%s", item.Address, item.Date.Format(constant.DateTimeLayout)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return failedCount, abnormalLogins
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, param
|
|||||||
} else {
|
} else {
|
||||||
s.sendBarkWithConfig(config, quota, params)
|
s.sendBarkWithConfig(config, quota, params)
|
||||||
}
|
}
|
||||||
case constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Custom:
|
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||||
if isResource {
|
if isResource {
|
||||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||||
} else {
|
} else {
|
||||||
@@ -86,13 +86,14 @@ func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, param
|
|||||||
|
|
||||||
func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
|
func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
|
||||||
alertRepo := repo.NewIAlertRepo()
|
alertRepo := repo.NewIAlertRepo()
|
||||||
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS, constant.Custom: constant.Custom}
|
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS}
|
||||||
configType := method
|
configType := method
|
||||||
if mapped, ok := typeMap[method]; ok {
|
if mapped, ok := typeMap[method]; ok {
|
||||||
configType = mapped
|
configType = mapped
|
||||||
}
|
}
|
||||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||||
@@ -308,31 +309,12 @@ func (s *AlertSender) sendWebhookWithConfig(config model.AlertConfig, quota stri
|
|||||||
}
|
}
|
||||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||||
queued := false
|
err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
||||||
var err error
|
|
||||||
if config.Type == constant.Custom {
|
|
||||||
task := dto.AlertTaskMetadata{
|
|
||||||
AlertID: s.alert.ID,
|
|
||||||
Type: s.alert.Type,
|
|
||||||
Quota: quota,
|
|
||||||
QuotaType: s.quotaType,
|
|
||||||
Method: strconv.Itoa(int(config.ID)),
|
|
||||||
}
|
|
||||||
result, deliveryErr := xpack.DeliverCustomWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo, task)
|
|
||||||
queued, err = result.Queued, deliveryErr
|
|
||||||
if err == nil && result.Queued {
|
|
||||||
_, err = alertUtil.RecordQueuedAlertTask(result.LogID, task)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !queued {
|
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||||
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) {
|
func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||||
@@ -353,31 +335,11 @@ func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, qu
|
|||||||
}
|
}
|
||||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||||
queued := false
|
if err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo); err != nil {
|
||||||
var err error
|
|
||||||
if config.Type == constant.Custom {
|
|
||||||
task := dto.AlertTaskMetadata{
|
|
||||||
AlertID: s.alert.ID,
|
|
||||||
Type: s.alert.Type,
|
|
||||||
Quota: quota,
|
|
||||||
QuotaType: s.quotaType,
|
|
||||||
Method: strconv.Itoa(int(config.ID)),
|
|
||||||
}
|
|
||||||
result, deliveryErr := xpack.DeliverCustomWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo, task)
|
|
||||||
queued, err = result.Queued, deliveryErr
|
|
||||||
if err == nil && result.Queued {
|
|
||||||
_, err = alertUtil.RecordQueuedAlertTask(result.LogID, task)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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)
|
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !queued {
|
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||||
|
|||||||
+14
-29
@@ -223,9 +223,6 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, err
|
return appDetailDTO, err
|
||||||
}
|
}
|
||||||
if err = checkVllmVersionAccess(app.Key, version); err != nil {
|
|
||||||
return appDetailDTO, err
|
|
||||||
}
|
|
||||||
appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version))
|
appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, err
|
return appDetailDTO, err
|
||||||
@@ -244,17 +241,14 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, err
|
return appDetailDTO, err
|
||||||
}
|
}
|
||||||
app, err := appRepo.GetFirst(repo.WithByID(detail.AppId))
|
|
||||||
if err != nil {
|
|
||||||
return appDetailDTO, err
|
|
||||||
}
|
|
||||||
if err = checkVllmVersionAccess(app.Key, detail.Version); err != nil {
|
|
||||||
return appDetailDTO, err
|
|
||||||
}
|
|
||||||
appDetailDTO.AppDetail = detail
|
appDetailDTO.AppDetail = detail
|
||||||
appDetailDTO.Enable = true
|
appDetailDTO.Enable = true
|
||||||
|
|
||||||
if appType == "runtime" {
|
if appType == "runtime" {
|
||||||
|
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
||||||
|
if err != nil {
|
||||||
|
return appDetailDTO, err
|
||||||
|
}
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
|
|
||||||
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
||||||
@@ -309,9 +303,6 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
filename := filepath.Base(appDetailDTO.DownloadUrl)
|
||||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
|
||||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||||
if statusCode == http.StatusNotFound {
|
|
||||||
return appDetailDTO, buserr.New("ErrAppVersionUnavailable")
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err)
|
return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err)
|
||||||
}
|
}
|
||||||
@@ -325,6 +316,10 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
|
|
||||||
appDetailDTO.HostMode = isHostModel(appDetailDTO.DockerCompose)
|
appDetailDTO.HostMode = isHostModel(appDetailDTO.DockerCompose)
|
||||||
|
|
||||||
|
app, err := appRepo.GetFirst(repo.WithByID(detail.AppId))
|
||||||
|
if err != nil {
|
||||||
|
return appDetailDTO, err
|
||||||
|
}
|
||||||
if err := checkLimit(app); err != nil {
|
if err := checkLimit(app); err != nil {
|
||||||
appDetailDTO.Enable = false
|
appDetailDTO.Enable = false
|
||||||
}
|
}
|
||||||
@@ -376,9 +371,6 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = checkVllmVersionAccess(app.Key, appDetail.Version); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if DatabaseKeys[app.Key] > 0 {
|
if DatabaseKeys[app.Key] > 0 {
|
||||||
if existDatabases, _ := databaseRepo.GetList(repo.WithByName(req.Name)); len(existDatabases) > 0 {
|
if existDatabases, _ := databaseRepo.GetList(repo.WithByName(req.Name)); len(existDatabases) > 0 {
|
||||||
err = buserr.New("ErrRemoteExist")
|
err = buserr.New("ErrRemoteExist")
|
||||||
@@ -434,12 +426,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
} else {
|
} else {
|
||||||
if appDetail.DockerCompose == "" {
|
if appDetail.DockerCompose == "" {
|
||||||
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
|
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
|
||||||
var statusCode int
|
_, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||||
statusCode, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
|
||||||
if statusCode == http.StatusNotFound {
|
|
||||||
err = buserr.New("ErrAppVersionUnavailable")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -488,17 +475,15 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
newServiceName := strings.ToLower(appInstall.Name)
|
newServiceName := strings.ToLower(appInstall.Name)
|
||||||
if app.Limit == 0 && newServiceName != serviceName && len(servicesMap) == 1 && !req.KeepServiceName {
|
if app.Limit == 0 && newServiceName != serviceName && len(servicesMap) == 1 {
|
||||||
servicesMap[newServiceName] = servicesMap[serviceName]
|
servicesMap[newServiceName] = servicesMap[serviceName]
|
||||||
delete(servicesMap, serviceName)
|
delete(servicesMap, serviceName)
|
||||||
serviceName = newServiceName
|
serviceName = newServiceName
|
||||||
}
|
}
|
||||||
appInstall.ServiceName = serviceName
|
appInstall.ServiceName = serviceName
|
||||||
|
|
||||||
if !req.SkipComposeCommonConfig {
|
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||||
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
composeByte []byte
|
composeByte []byte
|
||||||
@@ -566,7 +551,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if executeScript || req.UseLifecycleScripts {
|
if executeScript {
|
||||||
if err = runScript(t, appInstall, "init"); err != nil {
|
if err = runScript(t, appInstall, "init"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -579,7 +564,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = upApp(t, appInstall, req.PullImage, req.UseLifecycleScripts); err != nil {
|
if err = upApp(t, appInstall, req.PullImage); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updateToolApp(appInstall)
|
updateToolApp(appInstall)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -14,14 +13,12 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"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/request"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -255,9 +252,6 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
|||||||
return buserr.New("ErrInstallDirNotFound")
|
return buserr.New("ErrInstallDirNotFound")
|
||||||
}
|
}
|
||||||
dockerComposePath := install.GetComposePath()
|
dockerComposePath := install.GetComposePath()
|
||||||
if req.UseLifecycleScripts && (req.Operate == constant.Start || req.Operate == constant.Stop || req.Operate == constant.Restart) {
|
|
||||||
return operateAppWithLifecycleScripts(install, req, nil)
|
|
||||||
}
|
|
||||||
switch req.Operate {
|
switch req.Operate {
|
||||||
case constant.Rebuild:
|
case constant.Rebuild:
|
||||||
return rebuildApp(install)
|
return rebuildApp(install)
|
||||||
@@ -281,13 +275,12 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
|||||||
return syncAppInstallStatus(&install, false)
|
return syncAppInstallStatus(&install, false)
|
||||||
case constant.Delete:
|
case constant.Delete:
|
||||||
deleteReq := request.AppInstallDelete{
|
deleteReq := request.AppInstallDelete{
|
||||||
Install: install,
|
Install: install,
|
||||||
DeleteBackup: req.DeleteBackup,
|
DeleteBackup: req.DeleteBackup,
|
||||||
ForceDelete: req.ForceDelete,
|
ForceDelete: req.ForceDelete,
|
||||||
DeleteDB: req.DeleteDB,
|
DeleteDB: req.DeleteDB,
|
||||||
DeleteImage: req.DeleteImage,
|
DeleteImage: req.DeleteImage,
|
||||||
TaskID: req.TaskID,
|
TaskID: req.TaskID,
|
||||||
UseLifecycleScripts: req.UseLifecycleScripts,
|
|
||||||
}
|
}
|
||||||
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
|
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
|
||||||
return err
|
return err
|
||||||
@@ -301,7 +294,6 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
|||||||
DetailID: req.DetailId,
|
DetailID: req.DetailId,
|
||||||
Backup: req.Backup,
|
Backup: req.Backup,
|
||||||
PullImage: req.PullImage,
|
PullImage: req.PullImage,
|
||||||
DeleteImage: req.DeleteImage,
|
|
||||||
DockerCompose: req.DockerCompose,
|
DockerCompose: req.DockerCompose,
|
||||||
TaskID: req.TaskID,
|
TaskID: req.TaskID,
|
||||||
}
|
}
|
||||||
@@ -319,70 +311,6 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func operateAppWithLifecycleScripts(install model.AppInstall, req request.AppInstalledOperate, onFailure func(error)) error {
|
|
||||||
taskType := task.TaskUpdate
|
|
||||||
switch req.Operate {
|
|
||||||
case constant.Start:
|
|
||||||
install.Status = constant.StatusStarting
|
|
||||||
case constant.Restart:
|
|
||||||
taskType = task.TaskRestart
|
|
||||||
install.Status = constant.StatusRestarting
|
|
||||||
case constant.Stop:
|
|
||||||
install.Status = constant.StatusWaiting
|
|
||||||
default:
|
|
||||||
return errors.New("lifecycle script operation not supported")
|
|
||||||
}
|
|
||||||
install.Message = ""
|
|
||||||
if err := appInstallRepo.Save(context.Background(), &install); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
operationTask, err := task.NewTaskWithOps(install.Name, taskType, task.TaskScopeApp, req.TaskID, install.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
operation := string(req.Operate)
|
|
||||||
operationTask.AddSubTaskWithOps(
|
|
||||||
task.GetTaskName(install.Name, taskType, task.TaskScopeApp),
|
|
||||||
func(t *task.Task) error {
|
|
||||||
if err := runScript(t, &install, operation); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if req.Operate == constant.Stop {
|
|
||||||
install.Status = constant.StatusStopped
|
|
||||||
install.Message = ""
|
|
||||||
return appInstallRepo.Save(context.Background(), &install)
|
|
||||||
}
|
|
||||||
containerNames, err := getContainerNames(install)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(containerNames) == 0 {
|
|
||||||
return buserr.WithName("ErrContainerNotFound", install.Name)
|
|
||||||
}
|
|
||||||
install.ContainerName = strings.Join(containerNames, ",")
|
|
||||||
install.Status = constant.StatusRunning
|
|
||||||
install.Message = ""
|
|
||||||
return appInstallRepo.Save(context.Background(), &install)
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
0,
|
|
||||||
time.Hour,
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
if taskErr := operationTask.Execute(); taskErr != nil {
|
|
||||||
if onFailure != nil {
|
|
||||||
onFailure(taskErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
install.Status = constant.StatusUpErr
|
|
||||||
install.Message = taskErr.Error()
|
|
||||||
_ = appInstallRepo.Save(context.Background(), &install)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error {
|
func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error {
|
||||||
installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -445,10 +373,8 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !req.SkipComposeCommonConfig {
|
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||||
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
composeByte, err := yaml.Marshal(composeMap)
|
composeByte, err := yaml.Marshal(composeMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -481,7 +407,7 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backupEnvMaps := maps.Clone(oldEnvMaps)
|
backupEnvMaps := oldEnvMaps
|
||||||
handleMap(req.Params, oldEnvMaps)
|
handleMap(req.Params, oldEnvMaps)
|
||||||
paramByte, err := json.Marshal(oldEnvMaps)
|
paramByte, err := json.Marshal(oldEnvMaps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -493,32 +419,13 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
|||||||
}
|
}
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), constant.DirPerm)
|
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), constant.DirPerm)
|
||||||
restoreConfig := func(operationErr error) {
|
if err := rebuildApp(installed); err != nil {
|
||||||
_ = env.Write(backupEnvMaps, envPath)
|
_ = env.Write(backupEnvMaps, envPath)
|
||||||
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), constant.DirPerm)
|
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), constant.DirPerm)
|
||||||
failed := oldInstalled
|
|
||||||
failed.Status = constant.StatusUpErr
|
|
||||||
failed.Message = operationErr.Error()
|
|
||||||
_ = appInstallRepo.Save(context.Background(), &failed)
|
|
||||||
}
|
|
||||||
if req.UseLifecycleScripts {
|
|
||||||
err = operateAppWithLifecycleScripts(installed, request.AppInstalledOperate{
|
|
||||||
InstallId: installed.ID,
|
|
||||||
Operate: constant.Restart,
|
|
||||||
TaskID: req.TaskID,
|
|
||||||
UseLifecycleScripts: true,
|
|
||||||
}, restoreConfig)
|
|
||||||
} else {
|
|
||||||
err = rebuildApp(installed)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
restoreConfig(err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !req.UseLifecycleScripts {
|
installed.Status = constant.StatusRunning
|
||||||
installed.Status = constant.StatusRunning
|
_ = appInstallRepo.Save(context.Background(), &installed)
|
||||||
_ = appInstallRepo.Save(context.Background(), &installed)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyChanged := hasAppInstallProxyPassChanged(&oldInstalled, &installed)
|
proxyChanged := hasAppInstallProxyPassChanged(&oldInstalled, &installed)
|
||||||
currentProxy, currentProxyErr := getAppInstallProxyPass(&installed)
|
currentProxy, currentProxyErr := getAppInstallProxyPass(&installed)
|
||||||
@@ -675,9 +582,6 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
|||||||
return versions, err
|
return versions, err
|
||||||
}
|
}
|
||||||
for _, detail := range details {
|
for _, detail := range details {
|
||||||
if !canAccessVllmVersion(app.Key, detail.Version) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
||||||
if len(ignores) > 0 {
|
if len(ignores) > 0 {
|
||||||
continue
|
continue
|
||||||
@@ -696,9 +600,6 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
|||||||
filename := filepath.Base(detail.DownloadUrl)
|
filename := filepath.Base(detail.DownloadUrl)
|
||||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
|
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
|
||||||
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||||
if statusCode == http.StatusNotFound {
|
|
||||||
return versions, buserr.New("ErrAppVersionUnavailable")
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return versions, err
|
return versions, err
|
||||||
}
|
}
|
||||||
@@ -931,9 +832,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
||||||
switch appInstall.Status {
|
if appInstall.Status == constant.StatusInstalling || appInstall.Status == constant.StatusRebuilding || appInstall.Status == constant.StatusUpgrading || appInstall.Status == constant.StatusUninstalling {
|
||||||
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
|
||||||
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cli, err := docker.NewClient()
|
cli, err := docker.NewClient()
|
||||||
|
|||||||
@@ -1,951 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"maps"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type appUpgradePhase int
|
|
||||||
|
|
||||||
const (
|
|
||||||
appUpgradePreparing appUpgradePhase = iota
|
|
||||||
appUpgradePrepared
|
|
||||||
appUpgradeStopped
|
|
||||||
appUpgradeBackedUp
|
|
||||||
appUpgradeDown
|
|
||||||
appUpgradeMutated
|
|
||||||
appUpgradeStarted
|
|
||||||
appUpgradeReady
|
|
||||||
appUpgradeCommitted
|
|
||||||
)
|
|
||||||
|
|
||||||
const composeServiceLabel = "com.docker.compose.service"
|
|
||||||
|
|
||||||
var appUpgradeLocks sync.Map
|
|
||||||
|
|
||||||
type appUpgradeSnapshot interface {
|
|
||||||
Restore() error
|
|
||||||
Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
type upgradeFileSnapshot struct {
|
|
||||||
installPath string
|
|
||||||
backupPath string
|
|
||||||
paths []string
|
|
||||||
existing map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type appUpgradeContext struct {
|
|
||||||
req request.AppInstallUpgrade
|
|
||||||
original model.AppInstall
|
|
||||||
candidate model.AppInstall
|
|
||||||
detail model.AppDetail
|
|
||||||
|
|
||||||
phase appUpgradePhase
|
|
||||||
stopAttempted bool
|
|
||||||
downAttempted bool
|
|
||||||
rollbackErr error
|
|
||||||
|
|
||||||
detailDir string
|
|
||||||
stageDir string
|
|
||||||
envContent []byte
|
|
||||||
oldEnvContent []byte
|
|
||||||
oldDockerCompose string
|
|
||||||
oldImageIDs []appImageID
|
|
||||||
backupFile string
|
|
||||||
snapshot appUpgradeSnapshot
|
|
||||||
createdPaths []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func upgradeInstall(req request.AppInstallUpgrade) error {
|
|
||||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if install.Status == constant.StatusUpgrading {
|
|
||||||
return buserr.New("TaskIsExecuting")
|
|
||||||
}
|
|
||||||
if err = task.CheckScopeTaskIsExecuting(task.TaskScopeApp, install.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, loaded := appUpgradeLocks.LoadOrStore(install.ID, struct{}{}); loaded {
|
|
||||||
return buserr.New("TaskIsExecuting")
|
|
||||||
}
|
|
||||||
releaseLock := true
|
|
||||||
defer func() {
|
|
||||||
if releaseLock {
|
|
||||||
appUpgradeLocks.Delete(install.ID)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = checkVllmVersionAccess(install.App.Key, detail.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) {
|
|
||||||
return errors.New("vLLM can only upgrade within the same image type")
|
|
||||||
}
|
|
||||||
if install.Version == detail.Version {
|
|
||||||
return errors.New("two version is same")
|
|
||||||
}
|
|
||||||
|
|
||||||
upgradeTask, err := task.NewTaskWithOps(install.Name, task.TaskUpgrade, task.TaskScopeApp, req.TaskID, install.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx := &appUpgradeContext{
|
|
||||||
req: req,
|
|
||||||
original: install,
|
|
||||||
candidate: install,
|
|
||||||
detail: detail,
|
|
||||||
phase: appUpgradePreparing,
|
|
||||||
oldDockerCompose: install.DockerCompose,
|
|
||||||
}
|
|
||||||
upgradeTask.AddSubTaskWithOps(i18n.GetMsgByKey("UpgradePrepare"), ctx.prepare, nil, 0, 0)
|
|
||||||
upgradeTask.AddSubTaskWithOps(
|
|
||||||
task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp),
|
|
||||||
ctx.cutover,
|
|
||||||
func(t *task.Task) {
|
|
||||||
ctx.rollbackErr = ctx.rollback(t)
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
upgradingInstall := install
|
|
||||||
upgradingInstall.Status = constant.StatusUpgrading
|
|
||||||
upgradingInstall.Message = ""
|
|
||||||
if err = appInstallRepo.Save(context.Background(), &upgradingInstall); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseLock = false
|
|
||||||
go func() {
|
|
||||||
defer appUpgradeLocks.Delete(install.ID)
|
|
||||||
defer ctx.cleanup()
|
|
||||||
taskErr := upgradeTask.Execute()
|
|
||||||
if taskErr == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ctx.rollbackErr != nil {
|
|
||||||
taskErr = fmt.Errorf("%w; %s: %v", taskErr, i18n.GetMsgByKey("UpgradeRollbackFailed"), ctx.rollbackErr)
|
|
||||||
upgradeTask.Task.ErrorMsg = taskErr.Error()
|
|
||||||
_ = repo.NewITaskRepo().Update(context.Background(), upgradeTask.Task)
|
|
||||||
}
|
|
||||||
if !ctx.stopAttempted || ctx.rollbackErr == nil {
|
|
||||||
restored := ctx.original
|
|
||||||
_ = appInstallRepo.Save(context.Background(), &restored)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
failed := ctx.original
|
|
||||||
failed.Status = constant.StatusUpgradeErr
|
|
||||||
failed.Message = taskErr.Error()
|
|
||||||
_ = appInstallRepo.Save(context.Background(), &failed)
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) prepare(t *task.Task) error {
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
u.detailDir = path.Join(u.original.App.GetAppResourcePath(), u.detail.Version)
|
|
||||||
if u.original.App.Resource == constant.AppResourceRemote {
|
|
||||||
if err := downloadApp(u.original.App, u.detail, nil, t.Logger); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !fileOp.Stat(u.detailDir) {
|
|
||||||
return buserr.WithName("ErrFileNotFound", u.detailDir)
|
|
||||||
}
|
|
||||||
if u.detail.DockerCompose == "" {
|
|
||||||
composeContent, err := fileOp.GetContent(path.Join(u.detailDir, "docker-compose.yml"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.detail.DockerCompose = string(composeContent)
|
|
||||||
_ = appDetailRepo.Update(context.Background(), u.detail)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(u.detail.DockerCompose) == "" && strings.TrimSpace(u.req.DockerCompose) == "" {
|
|
||||||
return buserr.WithName("ErrFileNotFound", "docker-compose.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
u.oldEnvContent, err = fileOp.GetContent(u.original.GetEnvPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.stageDir, err = os.MkdirTemp(u.original.GetAppPath(), "."+u.original.Name+"-upgrade-")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = fileOp.CopyDirWithNewName(u.detailDir, u.stageDir, "."); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = copyUpgradeStageFile(u.original.GetPath(), u.stageDir, ".env"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if u.original.App.Key == constant.AppOpenclaw {
|
|
||||||
if err = copyUpgradeStageFile(u.original.GetPath(), u.stageDir, path.Join("data", "conf", "openclaw.json")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if u.original.App.Key == constant.AppOpenresty {
|
|
||||||
for _, relativePath := range []string{
|
|
||||||
nginxModuleBuildDir,
|
|
||||||
nginxModuleModulesDir,
|
|
||||||
path.Join(nginxModuleConfDir, nginxModuleEnabledConfDir),
|
|
||||||
} {
|
|
||||||
if err = copyUpgradeStageFile(u.original.GetPath(), u.stageDir, relativePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stagedInstall := u.original
|
|
||||||
stagedInstall.Name = path.Base(u.stageDir)
|
|
||||||
stagedInstall.Version = u.detail.Version
|
|
||||||
stagedInstall.AppDetailId = u.req.DetailID
|
|
||||||
if stagedInstall.App.Key == vllmAppKeyForUpgrade {
|
|
||||||
envs := make(map[string]interface{})
|
|
||||||
if err = json.Unmarshal([]byte(stagedInstall.Env), &envs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
image := buildVllmUpgradeImage(loadVllmImageFromEnv(stagedInstall.Env), u.original.Version, u.detail.Version)
|
|
||||||
envs[vllmImageEnvKey] = image
|
|
||||||
paramBytes, marshalErr := json.Marshal(envs)
|
|
||||||
if marshalErr != nil {
|
|
||||||
return marshalErr
|
|
||||||
}
|
|
||||||
stagedInstall.Env = string(paramBytes)
|
|
||||||
}
|
|
||||||
if err = migrateOpenclawProtocolUpgrade(&stagedInstall, u.original.Version, u.detail.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
u.candidate = stagedInstall
|
|
||||||
u.candidate.Name = u.original.Name
|
|
||||||
u.candidate.DockerCompose, err = renderUpgradeCompose(u.candidate, u.detail, u.req.DockerCompose)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(u.candidate.DockerCompose) == "" {
|
|
||||||
return buserr.WithName("ErrFileNotFound", "docker-compose.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
u.envContent, err = renderUpgradeEnv(&u.candidate, u.oldEnvContent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = writeUpgradeFile(path.Join(u.stageDir, ".env"), u.envContent, constant.FilePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = writeUpgradeFile(path.Join(u.stageDir, "docker-compose.yml"), []byte(u.candidate.DockerCompose), constant.FilePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
project, err := docker.GetComposeProject(u.original.Name, u.stageDir, []byte(u.candidate.DockerCompose), u.envContent, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hasBuild := false
|
|
||||||
for _, service := range project.Services {
|
|
||||||
if service.Image == "" && service.Build == nil {
|
|
||||||
return fmt.Errorf("compose service %s has neither image nor build configuration", service.Name)
|
|
||||||
}
|
|
||||||
hasBuild = hasBuild || service.Build != nil
|
|
||||||
}
|
|
||||||
if u.req.DeleteImage {
|
|
||||||
dockerClient, clientErr := docker.NewClient()
|
|
||||||
if clientErr != nil {
|
|
||||||
return clientErr
|
|
||||||
}
|
|
||||||
u.oldImageIDs, err = getAppImageIDsByCompose(dockerClient, u.oldEnvContent, []byte(u.oldDockerCompose))
|
|
||||||
dockerClient.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
images := make([]string, 0, len(project.Services))
|
|
||||||
for _, service := range project.Services {
|
|
||||||
if service.Image != "" {
|
|
||||||
images = append(images, service.Image)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = prepareUpgradeImages(t, images, u.req.PullImage); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if u.candidate.App.Key == constant.AppOpenresty {
|
|
||||||
if err = u.prepareOpenresty(t, stagedInstall); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = verifyUpgradeImages(images); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if hasBuild {
|
|
||||||
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image"))
|
|
||||||
t.LogStart(logStr)
|
|
||||||
if err = compose.BuildWithTask(path.Join(u.stageDir, "docker-compose.yml"), project.Name, t); err != nil {
|
|
||||||
t.LogFailedWithErr(logStr, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.LogSuccess(logStr)
|
|
||||||
if err = verifyUpgradeImages(images); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if u.original.App.Resource == constant.AppResourceRemote {
|
|
||||||
go RequestDownloadCallBack(u.detail.DownloadCallBackUrl)
|
|
||||||
}
|
|
||||||
u.phase = appUpgradePrepared
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) prepareOpenresty(t *task.Task, stagedInstall model.AppInstall) error {
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
detailBuildDir := path.Join(u.detailDir, nginxModuleBuildDir)
|
|
||||||
installBuildDir := path.Join(u.stageDir, nginxModuleBuildDir)
|
|
||||||
if !fileOp.Stat(installBuildDir) {
|
|
||||||
if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := copyAppDetailMissing(fileOp, detailBuildDir, installBuildDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fileOp.DeleteDir(path.Join(installBuildDir, nginxModuleTmpDir)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fileOp.CopyDir(path.Join(detailBuildDir, nginxModuleTmpDir), installBuildDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, fileName := range []string{"Dockerfile", "nginx.conf", "nginx.vh.default.conf"} {
|
|
||||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, fileName), installBuildDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := syncNginxModuleBuilder(detailBuildDir, installBuildDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
targetCatalogSource := path.Join(detailBuildDir, nginxModuleCatalogFile)
|
|
||||||
if !fileOp.Stat(targetCatalogSource) {
|
|
||||||
return fmt.Errorf("target OpenResty module catalog not found: %s", targetCatalogSource)
|
|
||||||
}
|
|
||||||
targetCatalogPath := path.Join(installBuildDir, nginxModuleCatalogPendingFile)
|
|
||||||
if err := stageNginxModuleCatalog(targetCatalogSource, targetCatalogPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stagedInstall.Name = path.Base(u.stageDir)
|
|
||||||
stagedInstall.Version = u.candidate.Version
|
|
||||||
stagedInstall.Env = u.candidate.Env
|
|
||||||
stagedInstall.DockerCompose = u.candidate.DockerCompose
|
|
||||||
return buildNginx(t, stagedInstall, targetCatalogPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) cutover(t *task.Task) error {
|
|
||||||
u.stopAttempted = true
|
|
||||||
t.LogStart(i18n.GetMsgByKey("UpgradeStop"))
|
|
||||||
if out, err := compose.Stop(u.original.GetComposePath()); err != nil {
|
|
||||||
if out != "" {
|
|
||||||
err = fmt.Errorf("%s: %w", out, err)
|
|
||||||
}
|
|
||||||
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeStop"), err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.LogSuccess(i18n.GetMsgByKey("UpgradeStop"))
|
|
||||||
u.phase = appUpgradeStopped
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if u.original.App.Key == constant.AppOpenresty {
|
|
||||||
u.snapshot, err = createOpenrestyUpgradeSnapshot(u.original.GetPath())
|
|
||||||
} else {
|
|
||||||
snapshotPaths := []string{".env", "docker-compose.yml", "scripts"}
|
|
||||||
if u.original.App.Key == constant.AppOpenclaw {
|
|
||||||
snapshotPaths = append(snapshotPaths, path.Join("data", "conf", "openclaw.json"))
|
|
||||||
}
|
|
||||||
u.snapshot, err = createUpgradeFileSnapshot(u.original.GetPath(), snapshotPaths)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.req.Backup {
|
|
||||||
if err = u.backup(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.phase = appUpgradeBackedUp
|
|
||||||
} else {
|
|
||||||
t.Log(i18n.GetMsgByKey("UpgradeBackupDisabled"))
|
|
||||||
}
|
|
||||||
|
|
||||||
u.downAttempted = true
|
|
||||||
if out, downErr := compose.Down(u.original.GetComposePath()); downErr != nil {
|
|
||||||
if out != "" {
|
|
||||||
downErr = fmt.Errorf("%s: %w", out, downErr)
|
|
||||||
}
|
|
||||||
return downErr
|
|
||||||
}
|
|
||||||
u.phase = appUpgradeDown
|
|
||||||
|
|
||||||
u.phase = appUpgradeMutated
|
|
||||||
if err = u.applyStagedFiles(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = writeUpgradeFile(u.original.GetEnvPath(), u.envContent, constant.FilePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = runScript(t, &u.candidate, "upgrade"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = writeUpgradeFile(u.original.GetComposePath(), []byte(u.candidate.DockerCompose), constant.FilePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
|
|
||||||
t.LogStart(logStr)
|
|
||||||
if out, upErr := compose.UpWithoutBuild(u.original.GetComposePath()); upErr != nil {
|
|
||||||
if out != "" {
|
|
||||||
upErr = fmt.Errorf("%s: %w", out, upErr)
|
|
||||||
}
|
|
||||||
t.LogFailedWithErr(logStr, upErr)
|
|
||||||
return upErr
|
|
||||||
}
|
|
||||||
t.LogSuccess(logStr)
|
|
||||||
u.phase = appUpgradeStarted
|
|
||||||
|
|
||||||
t.LogStart(i18n.GetMsgByKey("UpgradeWaitReady"))
|
|
||||||
containerNames, err := waitAppContainersReady(context.Background(), u.candidate)
|
|
||||||
if err != nil {
|
|
||||||
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeWaitReady"), err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.LogSuccess(i18n.GetMsgByKey("UpgradeWaitReady"))
|
|
||||||
u.phase = appUpgradeReady
|
|
||||||
u.candidate.ContainerName = strings.Join(containerNames, ",")
|
|
||||||
u.candidate.Status = constant.StatusRunning
|
|
||||||
u.candidate.Message = ""
|
|
||||||
|
|
||||||
if u.candidate.App.Key == constant.AppOpenresty {
|
|
||||||
liveCatalogPath := path.Join(u.candidate.GetPath(), nginxModuleBuildDir, nginxModuleCatalogPendingFile)
|
|
||||||
if err = commitStaticNginxModuleBuilds(u.candidate, liveCatalogPath, t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
activeCatalogPath := path.Join(u.candidate.GetPath(), nginxModuleBuildDir, nginxModuleCatalogFile)
|
|
||||||
if err = activateNginxModuleCatalogAndCommit(liveCatalogPath, activeCatalogPath, func() error {
|
|
||||||
return appInstallRepo.Save(context.Background(), &u.candidate)
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Upgrades deliberately keep the user's nginx.conf, so corrected gzip
|
|
||||||
// defaults shipped with a new version would never reach existing
|
|
||||||
// installations. Rewrite only an untouched factory configuration, and
|
|
||||||
// never fail the upgrade over it.
|
|
||||||
if gzipErr := upgradeStockNginxGzipConfig(u.candidate); gzipErr != nil {
|
|
||||||
t.Logf("WARNING: update stock gzip configuration failed, keeping the current one: %v", gzipErr)
|
|
||||||
}
|
|
||||||
} else if err = appInstallRepo.Save(context.Background(), &u.candidate); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.phase = appUpgradeCommitted
|
|
||||||
u.deleteOldImages(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) backup(t *task.Task) error {
|
|
||||||
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", u.original.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
|
||||||
record, err := backupAppWithParentTask(&u.original, t, fileName)
|
|
||||||
if err != nil {
|
|
||||||
return buserr.WithNameAndErr("ErrAppBackup", u.original.Name, err)
|
|
||||||
}
|
|
||||||
u.backupFile = path.Join(global.Dir.LocalBackupDir, record.FileDir, record.FileName)
|
|
||||||
info, err := os.Stat(u.backupFile)
|
|
||||||
if err != nil || info.Size() == 0 || record.Status != constant.StatusSuccess {
|
|
||||||
if err == nil {
|
|
||||||
err = errors.New("backup archive is empty or incomplete")
|
|
||||||
}
|
|
||||||
markBackupFailed(record.ID, err)
|
|
||||||
return buserr.WithNameAndErr("ErrAppBackup", u.original.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
backupRecordService := NewIBackupRecordService()
|
|
||||||
backups, _ := backupRecordService.ListAppRecords(u.original.App.Key, u.original.Name, "upgrade_backup")
|
|
||||||
if len(backups) > 3 {
|
|
||||||
deleteIDs := make([]uint, 0, len(backups)-3)
|
|
||||||
for _, backup := range backups[:len(backups)-3] {
|
|
||||||
deleteIDs = append(deleteIDs, backup.ID)
|
|
||||||
}
|
|
||||||
_ = backupRecordService.BatchDeleteRecord(deleteIDs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) applyStagedFiles() error {
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
if err := copyAppDetailMissingTracked(fileOp, u.detailDir, u.original.GetPath(), &u.createdPaths); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := replaceUpgradePath(u.stageDir, u.original.GetPath(), "scripts"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if u.original.App.Key == constant.AppOpenclaw {
|
|
||||||
if err := replaceUpgradePath(u.stageDir, u.original.GetPath(), path.Join("data", "conf", "openclaw.json")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if u.original.App.Key == constant.AppOpenresty {
|
|
||||||
for _, relativePath := range []string{
|
|
||||||
nginxModuleBuildDir,
|
|
||||||
nginxModuleModulesDir,
|
|
||||||
path.Join(nginxModuleConfDir, nginxModuleEnabledConfDir),
|
|
||||||
} {
|
|
||||||
if err := replaceUpgradePath(u.stageDir, u.original.GetPath(), relativePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
|
||||||
if !u.stopAttempted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logStr := i18n.GetWithName("AppRecover", u.original.Name)
|
|
||||||
t.LogStart(logStr)
|
|
||||||
defer func() {
|
|
||||||
if rollbackErr != nil {
|
|
||||||
t.LogFailedWithErr(logStr, rollbackErr)
|
|
||||||
} else {
|
|
||||||
t.LogSuccess(logStr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if !u.downAttempted {
|
|
||||||
if out, err := compose.Operate(u.original.GetComposePath(), "start"); err != nil {
|
|
||||||
if out != "" {
|
|
||||||
err = fmt.Errorf("%s: %w", out, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return u.finishRollback()
|
|
||||||
}
|
|
||||||
if u.phase < appUpgradeMutated {
|
|
||||||
if out, err := compose.UpWithoutBuild(u.original.GetComposePath()); err != nil {
|
|
||||||
if out != "" {
|
|
||||||
err = fmt.Errorf("%s: %w", out, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return u.finishRollback()
|
|
||||||
}
|
|
||||||
|
|
||||||
if out, err := compose.Down(u.original.GetComposePath()); err != nil {
|
|
||||||
if out != "" {
|
|
||||||
err = fmt.Errorf("%s: %w", out, err)
|
|
||||||
}
|
|
||||||
rollbackErr = err
|
|
||||||
}
|
|
||||||
if u.backupFile != "" {
|
|
||||||
_ = u.restoreManagedFiles()
|
|
||||||
if err := handleAppRecover(&u.original, t, u.backupFile, true, "", ""); err != nil {
|
|
||||||
_, _ = compose.UpWithoutBuild(u.original.GetComposePath())
|
|
||||||
return errors.Join(rollbackErr, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := u.restoreManagedFiles(); err != nil {
|
|
||||||
return errors.Join(rollbackErr, err)
|
|
||||||
}
|
|
||||||
if out, err := compose.UpWithoutBuild(u.original.GetComposePath()); err != nil {
|
|
||||||
if out != "" {
|
|
||||||
err = fmt.Errorf("%s: %w", out, err)
|
|
||||||
}
|
|
||||||
return errors.Join(rollbackErr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(rollbackErr, u.finishRollback())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) finishRollback() error {
|
|
||||||
if _, err := waitAppContainersReady(context.Background(), u.original); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
restored := u.original
|
|
||||||
if err := appInstallRepo.Save(context.Background(), &restored); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) restoreManagedFiles() error {
|
|
||||||
var restoreErr error
|
|
||||||
if u.snapshot != nil {
|
|
||||||
restoreErr = u.snapshot.Restore()
|
|
||||||
}
|
|
||||||
for index := len(u.createdPaths) - 1; index >= 0; index-- {
|
|
||||||
if err := os.RemoveAll(u.createdPaths[index]); err != nil {
|
|
||||||
restoreErr = errors.Join(restoreErr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restoreErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) deleteOldImages(t *task.Task) {
|
|
||||||
if !u.req.DeleteImage {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
excludeImages, err := docker.GetImagesFromDockerCompose(u.envContent, []byte(u.candidate.DockerCompose))
|
|
||||||
if err != nil {
|
|
||||||
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dockerClient, err := docker.NewClient()
|
|
||||||
if err != nil {
|
|
||||||
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer dockerClient.Close()
|
|
||||||
if err = deleteAppImagesByIDs(t, dockerClient, u.oldImageIDs, excludeImages); err != nil {
|
|
||||||
t.LogFailedWithErr(i18n.GetMsgByKey("TaskDelete")+i18n.GetMsgByKey("Image"), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *appUpgradeContext) cleanup() {
|
|
||||||
if u.snapshot != nil {
|
|
||||||
u.snapshot.Cleanup()
|
|
||||||
}
|
|
||||||
if u.stageDir != "" {
|
|
||||||
_ = os.RemoveAll(u.stageDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type upgradeImageClient interface {
|
|
||||||
PullImageWithProcess(*task.Task, string) error
|
|
||||||
ImageExists(string) (bool, error)
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareUpgradeImages(t *task.Task, images []string, pull bool) error {
|
|
||||||
dockerClient, err := docker.NewClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return prepareUpgradeImagesWithClient(t, dockerClient, images, pull)
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareUpgradeImagesWithClient(t *task.Task, dockerClient upgradeImageClient, images []string, pull bool) error {
|
|
||||||
defer dockerClient.Close()
|
|
||||||
seen := make(map[string]struct{}, len(images))
|
|
||||||
for _, image := range images {
|
|
||||||
image = strings.TrimSpace(image)
|
|
||||||
if image == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[image]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[image] = struct{}{}
|
|
||||||
if pull {
|
|
||||||
if t != nil {
|
|
||||||
t.Log(i18n.GetWithName("PullImageStart", image))
|
|
||||||
}
|
|
||||||
if pullErr := dockerClient.PullImageWithProcess(t, image); pullErr != nil {
|
|
||||||
if exists, _ := dockerClient.ImageExists(image); exists {
|
|
||||||
if t != nil {
|
|
||||||
t.Log(i18n.GetMsgByKey("UseExistImage"))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return buserr.WithNameAndErr("ErrDockerPullImage", "", pullErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exists, inspectErr := dockerClient.ImageExists(image)
|
|
||||||
if inspectErr != nil || !exists {
|
|
||||||
return buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s is not available locally: %v", image, inspectErr))
|
|
||||||
}
|
|
||||||
if pull && t != nil {
|
|
||||||
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyUpgradeImages(images []string) error {
|
|
||||||
dockerClient, err := docker.NewClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dockerClient.Close()
|
|
||||||
for _, image := range images {
|
|
||||||
exists, inspectErr := dockerClient.ImageExists(image)
|
|
||||||
if inspectErr != nil || !exists {
|
|
||||||
return buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s is not available locally: %v", image, inspectErr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderUpgradeEnv(install *model.AppInstall, original []byte) ([]byte, error) {
|
|
||||||
originalEnv := make(map[string]string)
|
|
||||||
if len(original) > 0 {
|
|
||||||
var err error
|
|
||||||
originalEnv, err = godotenv.UnmarshalBytes(original)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params := make(map[string]string, len(originalEnv))
|
|
||||||
maps.Copy(params, originalEnv)
|
|
||||||
envs := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
handleMap(envs, params)
|
|
||||||
if install.App.Key == "openlist" {
|
|
||||||
// The upgrade script updates this too late for the pre-pull phase.
|
|
||||||
image := "openlistteam/openlist:v" + strings.TrimPrefix(install.Version, "v")
|
|
||||||
if preInstalled := params["PRE_INSTALLED"]; preInstalled != "" {
|
|
||||||
image += "-" + preInstalled
|
|
||||||
}
|
|
||||||
params["OPENLIST_IMAGE"] = image
|
|
||||||
envs["OPENLIST_IMAGE"] = image
|
|
||||||
content, err := json.Marshal(envs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
install.Env = string(content)
|
|
||||||
}
|
|
||||||
if install.App.Key == constant.AppOpenresty {
|
|
||||||
for _, key := range []string{"CONTAINER_PACKAGE_URL", "RESTY_ADD_PACKAGE_BUILDDEPS", "RESTY_CONFIG_OPTIONS_MORE"} {
|
|
||||||
if value, ok := originalEnv[key]; ok {
|
|
||||||
params[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if websiteDir := strings.TrimSpace(originalEnv["WEBSITE_DIR"]); websiteDir != "" {
|
|
||||||
params["WEBSITE_DIR"] = websiteDir
|
|
||||||
}
|
|
||||||
websiteDir := strings.TrimSpace(params["WEBSITE_DIR"])
|
|
||||||
if websiteDir == "" {
|
|
||||||
websiteDir = NewISettingService().GetWebsiteDir()
|
|
||||||
}
|
|
||||||
if !path.IsAbs(websiteDir) {
|
|
||||||
websiteDir = path.Join(global.Dir.DataDir, websiteDir)
|
|
||||||
}
|
|
||||||
params["WEBSITE_DIR"] = websiteDir
|
|
||||||
envs["WEBSITE_DIR"] = websiteDir
|
|
||||||
content, marshalErr := json.Marshal(envs)
|
|
||||||
if marshalErr != nil {
|
|
||||||
return nil, marshalErr
|
|
||||||
}
|
|
||||||
install.Env = string(content)
|
|
||||||
}
|
|
||||||
content, err := godotenv.Marshal(params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return []byte(content), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderUpgradeCompose(install model.AppInstall, detail model.AppDetail, customCompose string) (string, error) {
|
|
||||||
if customCompose != "" {
|
|
||||||
return customCompose, nil
|
|
||||||
}
|
|
||||||
if install.App.Key == vllmAppKeyForUpgrade {
|
|
||||||
return install.DockerCompose, nil
|
|
||||||
}
|
|
||||||
return getUpgradeCompose(install, detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeUpgradeFile(filePath string, content []byte, mode os.FileMode) error {
|
|
||||||
tmp, err := os.CreateTemp(path.Dir(filePath), "."+path.Base(filePath)+".*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tmpPath := tmp.Name()
|
|
||||||
defer os.Remove(tmpPath)
|
|
||||||
if err = tmp.Chmod(mode); err == nil {
|
|
||||||
_, err = tmp.Write(content)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = tmp.Sync()
|
|
||||||
}
|
|
||||||
if closeErr := tmp.Close(); err == nil {
|
|
||||||
err = closeErr
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Rename(tmpPath, filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyUpgradeStageFile(sourceRoot, targetRoot, relativePath string) error {
|
|
||||||
source := path.Join(sourceRoot, relativePath)
|
|
||||||
if _, err := os.Stat(source); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target := path.Join(targetRoot, relativePath)
|
|
||||||
_ = os.RemoveAll(target)
|
|
||||||
return copyOpenrestyUpgradeSnapshotEntry(source, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceUpgradePath(sourceRoot, targetRoot, relativePath string) error {
|
|
||||||
source := path.Join(sourceRoot, relativePath)
|
|
||||||
if _, err := os.Stat(source); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target := path.Join(targetRoot, relativePath)
|
|
||||||
if err := os.RemoveAll(target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return copyOpenrestyUpgradeSnapshotEntry(source, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createUpgradeFileSnapshot(installPath string, paths []string) (*upgradeFileSnapshot, error) {
|
|
||||||
backupPath, err := os.MkdirTemp("", "1panel-app-upgrade-*")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
snapshot := &upgradeFileSnapshot{
|
|
||||||
installPath: installPath,
|
|
||||||
backupPath: backupPath,
|
|
||||||
paths: paths,
|
|
||||||
existing: make(map[string]bool, len(paths)),
|
|
||||||
}
|
|
||||||
for _, relativePath := range paths {
|
|
||||||
source := path.Join(installPath, relativePath)
|
|
||||||
if _, err = os.Stat(source); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
snapshot.Cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
snapshot.existing[relativePath] = true
|
|
||||||
if err = copyOpenrestyUpgradeSnapshotEntry(source, path.Join(backupPath, relativePath)); err != nil {
|
|
||||||
snapshot.Cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return snapshot, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *upgradeFileSnapshot) Restore() error {
|
|
||||||
for _, relativePath := range s.paths {
|
|
||||||
target := path.Join(s.installPath, relativePath)
|
|
||||||
if err := os.RemoveAll(target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !s.existing[relativePath] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := copyOpenrestyUpgradeSnapshotEntry(path.Join(s.backupPath, relativePath), target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *upgradeFileSnapshot) Cleanup() {
|
|
||||||
if s != nil && s.backupPath != "" {
|
|
||||||
_ = os.RemoveAll(s.backupPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type appContainerReadinessClient interface {
|
|
||||||
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
|
|
||||||
ContainerInspect(context.Context, string) (container.InspectResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitAppContainersReady(ctx context.Context, install model.AppInstall) ([]string, error) {
|
|
||||||
client, err := docker.NewDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
return waitAppContainersReadyWithClient(ctx, client, install)
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitAppContainersReadyWithClient(ctx context.Context, client appContainerReadinessClient, install model.AppInstall) ([]string, error) {
|
|
||||||
envContent, err := os.ReadFile(install.GetEnvPath())
|
|
||||||
if err != nil {
|
|
||||||
envContent, err = renderUpgradeEnv(&install, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
project, err := docker.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), envContent, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
expectedServices := make(map[string]struct{})
|
|
||||||
for _, service := range project.Services {
|
|
||||||
if !skipCheckStatus(service) {
|
|
||||||
expectedServices[service.Name] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(expectedServices) == 0 {
|
|
||||||
return strings.Split(install.ContainerName, ","), nil
|
|
||||||
}
|
|
||||||
options := container.ListOptions{
|
|
||||||
All: true,
|
|
||||||
Filters: filters.NewArgs(
|
|
||||||
filters.Arg("label", composeWorkdirLabel+"="+install.GetPath()),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
containers, err := client.ContainerList(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
foundServices := make(map[string]bool, len(expectedServices))
|
|
||||||
containerNames := make([]string, 0, len(containers))
|
|
||||||
for _, item := range containers {
|
|
||||||
serviceName := item.Labels[composeServiceLabel]
|
|
||||||
if _, ok := expectedServices[serviceName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err = waitContainerReady(ctx, client, item.ID); err != nil {
|
|
||||||
return nil, fmt.Errorf("container %s is not ready: %w", serviceName, err)
|
|
||||||
}
|
|
||||||
foundServices[serviceName] = true
|
|
||||||
if len(item.Names) > 0 {
|
|
||||||
containerNames = append(containerNames, strings.TrimPrefix(item.Names[0], "/"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for serviceName := range expectedServices {
|
|
||||||
if !foundServices[serviceName] {
|
|
||||||
return nil, fmt.Errorf("container for service %s was not created", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(containerNames)
|
|
||||||
return containerNames, nil
|
|
||||||
}
|
|
||||||
+378
-269
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -87,15 +89,7 @@ func checkPort(key string, params map[string]interface{}) (int, error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPortInUse(port int, protocol string) bool {
|
|
||||||
return common.ScanPortWithProto(port, normalizeComposeProtocol(protocol))
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPortExist(port int) error {
|
func checkPortExist(port int) error {
|
||||||
return checkPortExistWithProtocol(port, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPortExistWithProtocol(port int, protocol string) error {
|
|
||||||
errMap := make(map[string]interface{})
|
errMap := make(map[string]interface{})
|
||||||
errMap["port"] = port
|
errMap["port"] = port
|
||||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
||||||
@@ -116,7 +110,7 @@ func checkPortExistWithProtocol(port int, protocol string) error {
|
|||||||
errMap["name"] = domain.Domain
|
errMap["name"] = domain.Domain
|
||||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||||
}
|
}
|
||||||
if isPortInUse(port, protocol) {
|
if common.ScanPort(port) {
|
||||||
return buserr.WithDetail("ErrPortInUsed", port, nil)
|
return buserr.WithDetail("ErrPortInUsed", port, nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -297,8 +291,8 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
|
|||||||
}
|
}
|
||||||
resourceId = oldMysqlDb.ID
|
resourceId = oldMysqlDb.ID
|
||||||
if oldMysqlDb.ID > 0 {
|
if oldMysqlDb.ID > 0 {
|
||||||
if err := ensureAppMysqlDBUser(database, dbConfig); err != nil {
|
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
|
||||||
return err
|
return buserr.New("ErrDbUserNotValid")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var createMysql dto.MysqlDBCreate
|
var createMysql dto.MysqlDBCreate
|
||||||
@@ -353,29 +347,43 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
|||||||
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App")
|
||||||
t.Log(logStr)
|
t.Log(logStr)
|
||||||
|
|
||||||
if deleteReq.UseLifecycleScripts {
|
out, err := compose.Down(install.GetComposePath())
|
||||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
if err != nil && !deleteReq.ForceDelete {
|
||||||
return err
|
return handleErr(install, err, out)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out, err := compose.Down(install.GetComposePath())
|
|
||||||
if err != nil && !deleteReq.ForceDelete {
|
|
||||||
return handleErr(install, err, out)
|
|
||||||
}
|
|
||||||
if err = runScript(t, &install, "uninstall"); err != nil {
|
|
||||||
_, _ = compose.Up(install.GetComposePath())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
t.LogSuccess(logStr)
|
t.LogSuccess(logStr)
|
||||||
|
if err = runScript(t, &install, "uninstall"); err != nil {
|
||||||
|
_, _ = compose.Up(install.GetComposePath())
|
||||||
|
return err
|
||||||
|
}
|
||||||
if deleteReq.DeleteImage {
|
if deleteReq.DeleteImage {
|
||||||
|
delImageStr := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image")
|
||||||
content, err := op.GetContent(install.GetEnvPath())
|
content, err := op.GetContent(install.GetEnvPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = deleteAppImagesByCompose(t, content, []byte(install.DockerCompose), nil); err != nil {
|
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
client, err := docker.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
for _, image := range images {
|
||||||
|
imageID, err := client.GetImageIDByName(image)
|
||||||
|
if err == nil {
|
||||||
|
imgStr := delImageStr + image
|
||||||
|
t.Log(imgStr)
|
||||||
|
|
||||||
|
if err = client.DeleteImage(imageID); err != nil {
|
||||||
|
t.LogFailedWithErr(imgStr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.LogSuccess(delImageStr + image)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx, ctx := helper.GetTxAndContext()
|
tx, ctx := helper.GetTxAndContext()
|
||||||
@@ -416,20 +424,6 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
|||||||
|
|
||||||
switch install.App.Key {
|
switch install.App.Key {
|
||||||
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
|
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
|
||||||
if err = databaseUserGrantRepo.DeleteBy(
|
|
||||||
ctx,
|
|
||||||
repo.WithByType(install.App.Key),
|
|
||||||
databaseUserGrantRepo.WithByDatabase(install.Name),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = databaseUserRepo.DeleteBy(
|
|
||||||
ctx,
|
|
||||||
repo.WithByType(install.App.Key),
|
|
||||||
databaseUserRepo.WithByDatabase(install.Name),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
|
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
|
||||||
case constant.AppMongodb:
|
case constant.AppMongodb:
|
||||||
_ = mongodbRepo.Delete(ctx, mongodbRepo.WithByMongodbName(install.Name))
|
_ = mongodbRepo.Delete(ctx, mongodbRepo.WithByMongodbName(install.Name))
|
||||||
@@ -474,107 +468,6 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type appImageID struct {
|
|
||||||
name string
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAppImageIDsByCompose(client docker.Client, envContent, composeContent []byte) ([]appImageID, error) {
|
|
||||||
images, err := docker.GetImagesFromDockerCompose(envContent, composeContent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
imageIDs := make([]appImageID, 0, len(images))
|
|
||||||
for _, image := range images {
|
|
||||||
imageID, err := client.GetImageIDByName(image)
|
|
||||||
if err == nil && imageID != "" {
|
|
||||||
imageIDs = append(imageIDs, appImageID{name: image, id: imageID})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imageIDs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAppImagesByCompose(t *task.Task, envContent, composeContent []byte, excludeImages []string) error {
|
|
||||||
client, err := docker.NewClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
imageIDs, err := getAppImageIDsByCompose(client, envContent, composeContent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return deleteAppImagesByIDs(t, client, imageIDs, excludeImages)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAppImagesByIDs(t *task.Task, client docker.Client, imageIDs []appImageID, excludeImages []string) error {
|
|
||||||
delImageStr := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image")
|
|
||||||
excludeImageIDs := make(map[string]struct{}, len(excludeImages))
|
|
||||||
for _, image := range excludeImages {
|
|
||||||
imageID, err := client.GetImageIDByName(image)
|
|
||||||
if err == nil && imageID != "" {
|
|
||||||
excludeImageIDs[imageID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deletedImageIDs := make(map[string]struct{}, len(imageIDs))
|
|
||||||
for _, image := range imageIDs {
|
|
||||||
if _, ok := excludeImageIDs[image.id]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := deletedImageIDs[image.id]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
deletedImageIDs[image.id] = struct{}{}
|
|
||||||
imgStr := delImageStr + image.name
|
|
||||||
t.Log(imgStr)
|
|
||||||
if err := client.DeleteImage(image.id); err != nil {
|
|
||||||
t.LogFailedWithErr(imgStr, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.LogSuccess(imgStr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureAppMysqlDBUser(database model.Database, dbConfig dto.AppDatabase) error {
|
|
||||||
const host = "%"
|
|
||||||
mysqlService := NewIMysqlService()
|
|
||||||
users, err := mysqlService.ListUsers(dto.MysqlUserSearch{Database: database.Name})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userExists := false
|
|
||||||
passwordValid := false
|
|
||||||
for _, user := range users {
|
|
||||||
if user.Username != dbConfig.DbUser || user.Host != host || user.IsDelete {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
userExists = true
|
|
||||||
passwordValid = user.Password == dbConfig.Password
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !userExists || !passwordValid {
|
|
||||||
return buserr.New("ErrDbUserNotValid")
|
|
||||||
}
|
|
||||||
|
|
||||||
grants, err := mysqlService.ListGrants(dto.MysqlUserSearch{Database: database.Name})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, grant := range grants {
|
|
||||||
if grant.Database == dbConfig.DbName && grant.Username == dbConfig.DbUser && grant.Host == host {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mysqlService.GrantUser(dto.MysqlGrantCreate{
|
|
||||||
Database: database.Name,
|
|
||||||
DB: dbConfig.DbName,
|
|
||||||
Username: dbConfig.DbUser,
|
|
||||||
Host: host,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteLink(del dto.DelAppLink) error {
|
func deleteLink(del dto.DelAppLink) error {
|
||||||
install := del.Install
|
install := del.Install
|
||||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||||
@@ -584,20 +477,19 @@ func deleteLink(del dto.DelAppLink) error {
|
|||||||
for _, re := range resources {
|
for _, re := range resources {
|
||||||
switch re.Key {
|
switch re.Key {
|
||||||
case constant.AppMysql, constant.AppMariaDB:
|
case constant.AppMysql, constant.AppMariaDB:
|
||||||
|
mysqlService := NewIMysqlService()
|
||||||
database, _ := mysqlRepo.Get(repo.WithByID(re.ResourceId))
|
database, _ := mysqlRepo.Get(repo.WithByID(re.ResourceId))
|
||||||
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
|
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := deleteMysqlDatabaseForResourceOwner(del.Ctx, dto.MysqlDBDelete{
|
if err := mysqlService.Delete(del.Ctx, dto.MysqlDBDelete{
|
||||||
ID: database.ID,
|
ID: database.ID,
|
||||||
ForceDelete: del.ForceDelete,
|
ForceDelete: del.ForceDelete,
|
||||||
DeleteBackup: true,
|
DeleteBackup: true,
|
||||||
Type: re.Key,
|
Type: re.Key,
|
||||||
Database: database.MysqlName,
|
Database: database.MysqlName,
|
||||||
}, dto.DBResource{Type: constant.TypeApp, Name: install.Name}); err != nil {
|
}); err != nil && !del.ForceDelete {
|
||||||
if isMysqlDatabaseResourceInUseError(err) || !del.ForceDelete {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case constant.AppPostgresql:
|
case constant.AppPostgresql:
|
||||||
pgsqlService := NewIPostgresqlService()
|
pgsqlService := NewIPostgresqlService()
|
||||||
@@ -672,58 +564,11 @@ func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map
|
|||||||
if oldServiceValue["restart"] != nil {
|
if oldServiceValue["restart"] != nil {
|
||||||
serviceValue["restart"] = oldServiceValue["restart"]
|
serviceValue["restart"] = oldServiceValue["restart"]
|
||||||
}
|
}
|
||||||
if install.App.Key == constant.AppOpenresty {
|
|
||||||
mergeOpenrestyModuleVolumes(serviceValue, oldServiceValue)
|
|
||||||
}
|
|
||||||
servicesMap[install.ServiceName] = serviceValue
|
servicesMap[install.ServiceName] = serviceValue
|
||||||
composeMap["services"] = servicesMap
|
composeMap["services"] = servicesMap
|
||||||
return composeMap, nil
|
return composeMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeOpenrestyModuleVolumes carries the dynamic module mounts of the old
|
|
||||||
// compose over to the upgraded one when it does not declare them, so built
|
|
||||||
// module artifacts and their load configuration stay mounted across upgrades.
|
|
||||||
func mergeOpenrestyModuleVolumes(serviceValue, oldServiceValue map[string]interface{}) {
|
|
||||||
oldVolumes, ok := oldServiceValue["volumes"].([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newVolumes, _ := serviceValue["volumes"].([]interface{})
|
|
||||||
existing := make(map[string]struct{}, len(newVolumes))
|
|
||||||
for _, volume := range newVolumes {
|
|
||||||
if containerPath, ok := composeVolumeContainerPath(volume); ok {
|
|
||||||
existing[containerPath] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, volume := range oldVolumes {
|
|
||||||
containerPath, ok := composeVolumeContainerPath(volume)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !strings.Contains(containerPath, nginxModuleEnabledConfDir) && !strings.Contains(containerPath, "nginx/modules/1panel") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok = existing[containerPath]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newVolumes = append(newVolumes, volume)
|
|
||||||
existing[containerPath] = struct{}{}
|
|
||||||
}
|
|
||||||
serviceValue["volumes"] = newVolumes
|
|
||||||
}
|
|
||||||
|
|
||||||
func composeVolumeContainerPath(volume interface{}) (string, bool) {
|
|
||||||
volumeStr, ok := volume.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
parts := strings.Split(volumeStr, ":")
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return parts[1], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
|
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
|
||||||
if detail.DockerCompose == "" {
|
if detail.DockerCompose == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -757,35 +602,340 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
|
|||||||
return string(composeByte), nil
|
return string(composeByte), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildNginx(parentTask *task.Task, nginxInstall model.AppInstall, catalogPath string) error {
|
func buildNginx(parentTask *task.Task) error {
|
||||||
|
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
buildPath := path.Join(nginxInstall.GetPath(), nginxModuleBuildDir)
|
buildPath := path.Join(nginxInstall.GetPath(), "build")
|
||||||
if !fileOp.Stat(buildPath) {
|
if !fileOp.Stat(buildPath) {
|
||||||
return buserr.New("ErrBuildDirNotFound")
|
return buserr.New("ErrBuildDirNotFound")
|
||||||
}
|
}
|
||||||
modules, err := loadNginxModulesWithCatalog(nginxInstall, catalogPath)
|
moduleConfigPath := path.Join(buildPath, "module.json")
|
||||||
|
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
previousModules := cloneNginxModules(modules)
|
var (
|
||||||
staticBuild := hasEnabledStaticNginxModules(modules)
|
modules []dto.NginxModule
|
||||||
if err = configureStaticNginxModules(nginxInstall, modules, ""); err != nil {
|
addModuleParams []string
|
||||||
return err
|
addPackages []string
|
||||||
}
|
)
|
||||||
if staticBuild {
|
if len(moduleContent) > 0 {
|
||||||
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image"))
|
_ = json.Unmarshal(moduleContent, &modules)
|
||||||
parentTask.LogStart(logStr)
|
bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm)
|
||||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(120*time.Minute))
|
if err != nil {
|
||||||
if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
defer bashFile.Close()
|
||||||
|
bashFileWriter := bufio.NewWriter(bashFile)
|
||||||
|
for _, module := range modules {
|
||||||
|
if !module.Enable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = bashFileWriter.WriteString(module.Script + "\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
addModuleParams = append(addModuleParams, module.Params)
|
||||||
|
addPackages = append(addPackages, module.Packages...)
|
||||||
|
}
|
||||||
|
err = bashFileWriter.Flush()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
parentTask.LogSuccess(logStr)
|
|
||||||
}
|
}
|
||||||
modules, err = buildDynamicNginxModules(nginxInstall, modules, nil, false, "", catalogPath, parentTask)
|
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return commitNginxModuleBuilds(nginxInstall, previousModules, modules, false, catalogPath)
|
envs["RESTY_CONFIG_OPTIONS_MORE"] = ""
|
||||||
|
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = ""
|
||||||
|
if len(addModuleParams) > 0 {
|
||||||
|
envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ")
|
||||||
|
}
|
||||||
|
if len(addPackages) > 0 {
|
||||||
|
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ")
|
||||||
|
}
|
||||||
|
_ = gotenv.Write(envs, nginxInstall.GetEnvPath())
|
||||||
|
if len(addModuleParams) == 0 && len(addPackages) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image"))
|
||||||
|
parentTask.LogStart(logStr)
|
||||||
|
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(60*time.Minute))
|
||||||
|
if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parentTask.LogSuccess(logStr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||||
|
install, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oldVersion := install.Version
|
||||||
|
detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) {
|
||||||
|
return errors.New("vLLM can only upgrade within the same image type")
|
||||||
|
}
|
||||||
|
if install.Version == detail.Version {
|
||||||
|
return errors.New("two version is same")
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeTask, err := task.NewTaskWithOps(install.Name, task.TaskUpgrade, task.TaskScopeApp, req.TaskID, install.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
install.Status = constant.StatusUpgrading
|
||||||
|
|
||||||
|
var (
|
||||||
|
upErr error
|
||||||
|
backupFile string
|
||||||
|
)
|
||||||
|
backUpApp := func(t *task.Task) error {
|
||||||
|
backupService := NewIBackupService()
|
||||||
|
backupRecordService := NewIBackupRecordService()
|
||||||
|
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||||
|
backupRecord, err := backupService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
|
||||||
|
if err == nil {
|
||||||
|
backups, _ := backupRecordService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
|
||||||
|
if len(backups) > 3 {
|
||||||
|
backupsToDelete := backups[:len(backups)-3]
|
||||||
|
var deleteIDs []uint
|
||||||
|
for _, backup := range backupsToDelete {
|
||||||
|
deleteIDs = append(deleteIDs, backup.ID)
|
||||||
|
}
|
||||||
|
_ = backupRecordService.BatchDeleteRecord(deleteIDs)
|
||||||
|
}
|
||||||
|
backupFile = path.Join(global.Dir.LocalBackupDir, backupRecord.FileDir, backupRecord.FileName)
|
||||||
|
} else {
|
||||||
|
return buserr.WithNameAndErr("ErrAppBackup", install.Name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if req.Backup {
|
||||||
|
upgradeTask.AddSubTask(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeApp), backUpApp, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeApp := func(t *task.Task) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
detailDir := path.Join(global.Dir.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version)
|
||||||
|
if install.App.Resource == constant.AppResourceRemote {
|
||||||
|
if err = downloadApp(install.App, detail, &install, t.Logger); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if detail.DockerCompose == "" {
|
||||||
|
composeDetail, err := fileOp.GetContent(path.Join(detailDir, "docker-compose.yml"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
detail.DockerCompose = string(composeDetail)
|
||||||
|
_ = appDetailRepo.Update(context.Background(), detail)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
RequestDownloadCallBack(detail.DownloadCallBackUrl)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if install.App.Resource == constant.AppResourceLocal {
|
||||||
|
detailDir = path.Join(global.Dir.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := fileOp.GetContent(install.GetEnvPath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if install.App.Key == vllmAppKeyForUpgrade {
|
||||||
|
envs := make(map[string]interface{})
|
||||||
|
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
image := buildVllmUpgradeImage(loadVllmImageFromEnv(install.Env), oldVersion, detail.Version)
|
||||||
|
envs[vllmImageEnvKey] = image
|
||||||
|
paramByte, err := json.Marshal(envs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
install.Env = string(paramByte)
|
||||||
|
content = setVllmImageInEnvContent(content, image)
|
||||||
|
}
|
||||||
|
if req.PullImage {
|
||||||
|
composeContent := []byte(detail.DockerCompose)
|
||||||
|
if install.App.Key == vllmAppKeyForUpgrade {
|
||||||
|
composeContent = []byte(install.DockerCompose)
|
||||||
|
}
|
||||||
|
if req.DockerCompose != "" {
|
||||||
|
composeContent = []byte(req.DockerCompose)
|
||||||
|
}
|
||||||
|
images, err := docker.GetImagesFromDockerCompose(content, composeContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dockerCLi, err := docker.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dockerCLi.Close()
|
||||||
|
for _, image := range images {
|
||||||
|
t.Log(i18n.GetWithName("PullImageStart", image))
|
||||||
|
if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
|
||||||
|
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exist, err := dockerCLi.ImageExists(image)
|
||||||
|
if err != nil {
|
||||||
|
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
err = buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s not found", image))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
installBuildDir := path.Join(install.GetPath(), "build")
|
||||||
|
detailBuildDir := path.Join(detailDir, "build")
|
||||||
|
if !fileOp.Stat(installBuildDir) {
|
||||||
|
if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := fileOp.DeleteDir(path.Join(installBuildDir, "tmp")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fileOp.CopyDir(path.Join(detailBuildDir, "tmp"), installBuildDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.vh.default.conf"), installBuildDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceScripts := path.Join(detailDir, "scripts")
|
||||||
|
if fileOp.Stat(sourceScripts) {
|
||||||
|
dstScripts := path.Join(install.GetPath(), "scripts")
|
||||||
|
_ = fileOp.DeleteDir(dstScripts)
|
||||||
|
_ = fileOp.CreateDir(dstScripts, constant.DirPerm)
|
||||||
|
scriptCmd := exec.Command("cp", "-rf", sourceScripts+"/.", dstScripts+"/")
|
||||||
|
_, _ = scriptCmd.CombinedOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
var newCompose string
|
||||||
|
if err = migrateOpenclawProtocolUpgrade(&install, oldVersion, detail.Version); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if req.DockerCompose == "" {
|
||||||
|
if install.App.Key == vllmAppKeyForUpgrade {
|
||||||
|
newCompose = install.DockerCompose
|
||||||
|
} else {
|
||||||
|
newCompose, err = getUpgradeCompose(install, detail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newCompose = req.DockerCompose
|
||||||
|
}
|
||||||
|
|
||||||
|
install.DockerCompose = newCompose
|
||||||
|
install.Version = detail.Version
|
||||||
|
install.AppDetailId = req.DetailID
|
||||||
|
|
||||||
|
if out, err := compose.Down(install.GetComposePath()); err != nil {
|
||||||
|
if out != "" {
|
||||||
|
upErr = errors.New(out)
|
||||||
|
return upErr
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
envs := make(map[string]interface{})
|
||||||
|
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
envParams := make(map[string]string, len(envs))
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
packageUrl, _ := env.GetEnvValueByKey(install.GetEnvPath(), "CONTAINER_PACKAGE_URL")
|
||||||
|
addPackage, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_ADD_PACKAGE_BUILDDEPS")
|
||||||
|
options, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_CONFIG_OPTIONS_MORE")
|
||||||
|
envParams["CONTAINER_PACKAGE_URL"] = packageUrl
|
||||||
|
envParams["RESTY_ADD_PACKAGE_BUILDDEPS"] = addPackage
|
||||||
|
envParams["RESTY_CONFIG_OPTIONS_MORE"] = options
|
||||||
|
}
|
||||||
|
handleMap(envs, envParams)
|
||||||
|
if err = env.Write(envParams, install.GetEnvPath()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = runScript(t, &install, "upgrade"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), constant.FilePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
if err = buildNginx(t); err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
|
||||||
|
t.Log(logStr)
|
||||||
|
if out, err := compose.Up(install.GetComposePath()); err != nil {
|
||||||
|
if out != "" {
|
||||||
|
return errors.New(out)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.LogSuccess(logStr)
|
||||||
|
install.Status = constant.StatusRunning
|
||||||
|
return appInstallRepo.Save(context.Background(), &install)
|
||||||
|
}
|
||||||
|
|
||||||
|
rollBackApp := func(t *task.Task) {
|
||||||
|
if req.Backup {
|
||||||
|
t.Log(i18n.GetWithName("AppRecover", install.Name))
|
||||||
|
if err := NewIBackupService().AppRecover(dto.CommonRecover{Name: install.App.Key, DetailName: install.Name, Type: "app", DownloadAccountID: 1, File: backupFile}); err != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp), upgradeApp, rollBackApp, 0, 1*time.Hour)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err = upgradeTask.Execute()
|
||||||
|
if err != nil {
|
||||||
|
existInstall, _ := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||||
|
if existInstall.ID > 0 && existInstall.Status != constant.StatusRunning {
|
||||||
|
existInstall.Status = constant.StatusUpgradeErr
|
||||||
|
existInstall.Message = err.Error()
|
||||||
|
_ = appInstallRepo.Save(context.Background(), &existInstall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return appInstallRepo.Save(context.Background(), &install)
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipCheckStatus(service types.ServiceConfig) bool {
|
func skipCheckStatus(service types.ServiceConfig) bool {
|
||||||
@@ -1005,12 +1155,6 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
|||||||
scriptPath = path.Join(workDir, "scripts", "upgrade.sh")
|
scriptPath = path.Join(workDir, "scripts", "upgrade.sh")
|
||||||
case "uninstall":
|
case "uninstall":
|
||||||
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
|
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
|
||||||
case "start":
|
|
||||||
scriptPath = path.Join(workDir, "scripts", "start.sh")
|
|
||||||
case "stop":
|
|
||||||
scriptPath = path.Join(workDir, "scripts", "stop.sh")
|
|
||||||
case "restart":
|
|
||||||
scriptPath = path.Join(workDir, "scripts", "restart.sh")
|
|
||||||
}
|
}
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if !fileOp.Stat(scriptPath) {
|
if !fileOp.Stat(scriptPath) {
|
||||||
@@ -1020,11 +1164,7 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
|
|||||||
logStr := i18n.GetWithName("ExecShell", operate)
|
logStr := i18n.GetWithName("ExecShell", operate)
|
||||||
task.LogStart(logStr)
|
task.LogStart(logStr)
|
||||||
|
|
||||||
timeout := 10 * time.Minute
|
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir))
|
||||||
if operate == "start" || operate == "restart" {
|
|
||||||
timeout = time.Hour
|
|
||||||
}
|
|
||||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout), cmd.WithWorkDir(workDir), cmd.WithTask(*task))
|
|
||||||
if err := cmdMgr.Run("bash", scriptPath); err != nil {
|
if err := cmdMgr.Run("bash", scriptPath); err != nil {
|
||||||
task.LogFailedWithErr(logStr, err)
|
task.LogFailedWithErr(logStr, err)
|
||||||
return err
|
return err
|
||||||
@@ -1059,15 +1199,12 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func upApp(task *task.Task, appInstall *model.AppInstall, pullImages, useLifecycleScripts bool) error {
|
func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error {
|
||||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||||
var (
|
var (
|
||||||
out string
|
out string
|
||||||
errMsg string
|
errMsg string
|
||||||
)
|
)
|
||||||
if useLifecycleScripts {
|
|
||||||
return runScript(task, appInstall, "start")
|
|
||||||
}
|
|
||||||
if pullImages && appInstall.App.Type != "php" {
|
if pullImages && appInstall.App.Type != "php" {
|
||||||
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1394,8 +1531,7 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
|||||||
|
|
||||||
func doNotNeedSync(installed model.AppInstall) bool {
|
func doNotNeedSync(installed model.AppInstall) bool {
|
||||||
return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading ||
|
return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading ||
|
||||||
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr ||
|
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr
|
||||||
installed.Status == constant.StatusStarting || installed.Status == constant.StatusRestarting || installed.Status == constant.StatusWaiting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
||||||
@@ -1497,9 +1633,6 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
synAppInstall(containersMap, &installed, false)
|
synAppInstall(containersMap, &installed, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
|
||||||
envMap := make(map[string]interface{})
|
|
||||||
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
|
||||||
installDTO := response.AppInstallDTO{
|
installDTO := response.AppInstallDTO{
|
||||||
ID: installed.ID,
|
ID: installed.ID,
|
||||||
Name: installed.Name,
|
Name: installed.Name,
|
||||||
@@ -1521,16 +1654,14 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
Website: installed.App.Website,
|
Website: installed.App.Website,
|
||||||
Document: installed.App.Document,
|
Document: installed.App.Document,
|
||||||
},
|
},
|
||||||
Favorite: installed.Favorite,
|
Favorite: installed.Favorite,
|
||||||
SortOrder: installed.SortOrder,
|
SortOrder: installed.SortOrder,
|
||||||
Container: installed.ContainerName,
|
Container: installed.ContainerName,
|
||||||
ServiceName: strings.ToLower(installed.ServiceName),
|
ServiceName: strings.ToLower(installed.ServiceName),
|
||||||
ResourceKeys: resourceKeys,
|
|
||||||
Env: envMap,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !updated && !checkUpdate {
|
if !updated && !checkUpdate {
|
||||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||||
res = append(res, installDTO)
|
res = append(res, installDTO)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1538,7 +1669,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
if installed.Version == "latest" {
|
if installed.Version == "latest" {
|
||||||
if checkUpdate {
|
if checkUpdate {
|
||||||
installDTO.CanUpdate = false
|
installDTO.CanUpdate = false
|
||||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||||
res = append(res, installDTO)
|
res = append(res, installDTO)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -1567,7 +1698,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
if len(versions) == 0 {
|
if len(versions) == 0 {
|
||||||
if checkUpdate {
|
if checkUpdate {
|
||||||
installDTO.CanUpdate = false
|
installDTO.CanUpdate = false
|
||||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||||
res = append(res, installDTO)
|
res = append(res, installDTO)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -1599,7 +1730,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
res = append(res, installDTO)
|
res = append(res, installDTO)
|
||||||
}
|
}
|
||||||
} else if checkUpdate {
|
} else if checkUpdate {
|
||||||
installDTO.LinkDB = hasLinkDBFromKeys(resourceKeys)
|
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||||
res = append(res, installDTO)
|
res = append(res, installDTO)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1842,10 +1973,6 @@ func isHostModel(dockerCompose string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func copyAppDetailMissing(fileOp files.FileOp, srcDir, dstDir string) error {
|
func copyAppDetailMissing(fileOp files.FileOp, srcDir, dstDir string) error {
|
||||||
return copyAppDetailMissingTracked(fileOp, srcDir, dstDir, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyAppDetailMissingTracked(fileOp files.FileOp, srcDir, dstDir string, createdPaths *[]string) error {
|
|
||||||
entries, err := os.ReadDir(srcDir)
|
entries, err := os.ReadDir(srcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1857,9 +1984,6 @@ func copyAppDetailMissingTracked(fileOp files.FileOp, srcDir, dstDir string, cre
|
|||||||
srcPath := path.Join(srcDir, entry.Name())
|
srcPath := path.Join(srcDir, entry.Name())
|
||||||
dstPath := path.Join(dstDir, entry.Name())
|
dstPath := path.Join(dstDir, entry.Name())
|
||||||
if !fileOp.Stat(dstPath) {
|
if !fileOp.Stat(dstPath) {
|
||||||
if createdPaths != nil {
|
|
||||||
*createdPaths = append(*createdPaths, dstPath)
|
|
||||||
}
|
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
if err := fileOp.CopyDir(srcPath, dstDir); err != nil {
|
if err := fileOp.CopyDir(srcPath, dstDir); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1874,7 +1998,7 @@ func copyAppDetailMissingTracked(fileOp files.FileOp, srcDir, dstDir string, cre
|
|||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := copyAppDetailMissingTracked(fileOp, srcPath, dstPath, createdPaths); err != nil {
|
if err := copyAppDetailMissing(fileOp, srcPath, dstPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2037,7 +2161,7 @@ func handleOpenrestyFile(appInstall *model.AppInstall) error {
|
|||||||
|
|
||||||
func handleDefaultServer(appInstall *model.AppInstall) error {
|
func handleDefaultServer(appInstall *model.AppInstall) error {
|
||||||
installDir := appInstall.GetPath()
|
installDir := appInstall.GetPath()
|
||||||
defaultConfigPath := path.Join(installDir, nginxModuleConfDir, "default", "00.default.conf")
|
defaultConfigPath := path.Join(installDir, "conf", "default", "00.default.conf")
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
content, err := fileOp.GetContent(defaultConfigPath)
|
content, err := fileOp.GetContent(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2051,7 +2175,7 @@ func handleDefaultServer(appInstall *model.AppInstall) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRejectHandshake bool) error {
|
func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRejectHandshake bool) error {
|
||||||
sslDir := path.Join(appInstall.GetPath(), nginxModuleConfDir, "ssl")
|
sslDir := path.Join(appInstall.GetPath(), "conf", "ssl")
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if !fileOp.Stat(sslDir) {
|
if !fileOp.Stat(sslDir) {
|
||||||
return errors.New("ssl dir not found")
|
return errors.New("ssl dir not found")
|
||||||
@@ -2081,7 +2205,7 @@ func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRe
|
|||||||
_ = NewIWebsiteSSLService().Delete([]uint{websiteSSL.ID})
|
_ = NewIWebsiteSSLService().Delete([]uint{websiteSSL.ID})
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
defaultConfigPath := path.Join(appInstall.GetPath(), nginxModuleConfDir, "default", "00.default.conf")
|
defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf")
|
||||||
content, err := os.ReadFile(defaultConfigPath)
|
content, err := os.ReadFile(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2173,25 +2297,13 @@ func needsUpdate(localTag *model.Tag, remoteTag dto.Tag, translations string) bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hasLinkDB(installID uint) bool {
|
func hasLinkDB(installID uint) bool {
|
||||||
return hasLinkDBFromKeys(getAppInstallResourceKeys(installID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAppInstallResourceKeys(installID uint) []string {
|
|
||||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(installID))
|
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(installID))
|
||||||
keys := make([]string, 0, len(resources))
|
|
||||||
for _, resource := range resources {
|
|
||||||
keys = append(keys, resource.Key)
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasLinkDBFromKeys(resourceKeys []string) bool {
|
|
||||||
hasDB := false
|
hasDB := false
|
||||||
if len(resourceKeys) > 0 {
|
if len(resources) > 0 {
|
||||||
for _, resourceKey := range resourceKeys {
|
for _, resource := range resources {
|
||||||
if resourceKey == constant.AppPostgres || resourceKey == constant.AppMysql ||
|
if resource.Key == constant.AppPostgres || resource.Key == constant.AppMysql ||
|
||||||
resourceKey == constant.AppMariaDB || resourceKey == constant.AppMysqlCluster ||
|
resource.Key == constant.AppMariaDB || resource.Key == constant.AppMysqlCluster ||
|
||||||
resourceKey == constant.AppPostgresql || resourceKey == constant.AppPostgresqlCluster {
|
resource.Key == constant.AppPostgresql || resource.Key == constant.AppPostgresqlCluster {
|
||||||
hasDB = true
|
hasDB = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -2249,9 +2361,6 @@ func getAppVersions(key string, details []model.AppDetail) []string {
|
|||||||
hasLatest := false
|
hasLatest := false
|
||||||
latestVersion := ""
|
latestVersion := ""
|
||||||
for _, detail := range details {
|
for _, detail := range details {
|
||||||
if !canAccessVllmVersion(key, detail.Version) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if key != "mssql" && strings.Contains(detail.Version, "latest") {
|
if key != "mssql" && strings.Contains(detail.Version, "latest") {
|
||||||
hasLatest = true
|
hasLatest = true
|
||||||
latestVersion = detail.Version
|
latestVersion = detail.Version
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -373,7 +372,7 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
|
|||||||
targetPath = strings.TrimPrefix(targetPath, "/")
|
targetPath = strings.TrimPrefix(targetPath, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := client.Upload(context.Background(), fileItem, targetPath); err != nil {
|
if _, err := client.Upload(fileItem, targetPath); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
_, _ = client.Delete(path.Join(backup.BackupPath, "test/1panel"))
|
_, _ = client.Delete(path.Join(backup.BackupPath, "test/1panel"))
|
||||||
@@ -438,10 +437,6 @@ type backupClientHelper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewBackupClientMap(ids []string) map[string]backupClientHelper {
|
func NewBackupClientMap(ids []string) map[string]backupClientHelper {
|
||||||
return NewBackupClientMapWithContext(context.Background(), ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBackupClientMapWithContext(ctx context.Context, ids []string) map[string]backupClientHelper {
|
|
||||||
var accounts []model.BackupAccount
|
var accounts []model.BackupAccount
|
||||||
var idItems []uint
|
var idItems []uint
|
||||||
for i := 0; i < len(ids); i++ {
|
for i := 0; i < len(ids); i++ {
|
||||||
@@ -451,7 +446,7 @@ func NewBackupClientMapWithContext(ctx context.Context, ids []string) map[string
|
|||||||
accounts, _ = backupRepo.List(repo.WithByIDs(idItems))
|
accounts, _ = backupRepo.List(repo.WithByIDs(idItems))
|
||||||
clientMap := make(map[string]backupClientHelper)
|
clientMap := make(map[string]backupClientHelper)
|
||||||
for _, item := range accounts {
|
for _, item := range accounts {
|
||||||
backClient, err := newClientWithContext(ctx, &item, true)
|
backClient, err := newClient(&item, true)
|
||||||
itemHelper := backupClientHelper{
|
itemHelper := backupClientHelper{
|
||||||
client: backClient,
|
client: backClient,
|
||||||
name: item.Name,
|
name: item.Name,
|
||||||
@@ -468,11 +463,7 @@ func NewBackupClientMapWithContext(ctx context.Context, ids []string) map[string
|
|||||||
return clientMap
|
return clientMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint, cleanOnFailure bool) error {
|
func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint) error {
|
||||||
return uploadWithMapWithContext(context.Background(), taskItem, accountMap, src, dst, accountIDs, downloadAccountID, retry, cleanOnFailure, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uploadWithMapWithContext(ctx context.Context, taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint, cleanOnFailure, removeSrc bool) error {
|
|
||||||
accounts := strings.Split(accountIDs, ",")
|
accounts := strings.Split(accountIDs, ",")
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
if len(account) == 0 {
|
if len(account) == 0 {
|
||||||
@@ -498,13 +489,10 @@ func uploadWithMapWithContext(ctx context.Context, taskItem task.Task, accountMa
|
|||||||
"backup": name,
|
"backup": name,
|
||||||
}))
|
}))
|
||||||
for i := 0; i < int(retry)+1; i++ {
|
for i := 0; i < int(retry)+1; i++ {
|
||||||
_, err := itemBackup.client.Upload(ctx, src, path.Join(itemBackup.backupPath, dst))
|
_, err := itemBackup.client.Upload(src, path.Join(itemBackup.backupPath, dst))
|
||||||
taskItem.LogWithStatus(i18n.GetMsgByKey("Upload"), err)
|
taskItem.LogWithStatus(i18n.GetMsgByKey("Upload"), err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if account == fmt.Sprintf("%d", downloadAccountID) {
|
if account == fmt.Sprintf("%d", downloadAccountID) {
|
||||||
if cleanOnFailure {
|
|
||||||
cleanupCronjobBackupArtifacts(accountMap, src, dst)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -514,71 +502,11 @@ func uploadWithMapWithContext(ctx context.Context, taskItem task.Task, accountMa
|
|||||||
itemBackup.hasBackup = true
|
itemBackup.hasBackup = true
|
||||||
accountMap[account] = itemBackup
|
accountMap[account] = itemBackup
|
||||||
}
|
}
|
||||||
if removeSrc {
|
os.RemoveAll(src)
|
||||||
os.RemoveAll(src)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupCronjobBackupArtifacts(accountMap map[string]backupClientHelper, src, dst string) {
|
|
||||||
if err := os.RemoveAll(src); err != nil {
|
|
||||||
global.LOG.Errorf("remove failed local cronjob backup file %s failed, err: %v", src, err)
|
|
||||||
}
|
|
||||||
for _, account := range accountMap {
|
|
||||||
if !account.isOk {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := account.client.Delete(path.Join(account.backupPath, dst)); err != nil {
|
|
||||||
global.LOG.Errorf("remove failed cronjob backup file %s failed, err: %v", dst, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func markBackupFailed(recordID uint, backupErr error) {
|
|
||||||
_ = backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": backupErr.Error()})
|
|
||||||
|
|
||||||
record, err := backupRepo.GetRecord(repo.WithByID(recordID))
|
|
||||||
if err != nil || record.ID == 0 {
|
|
||||||
global.LOG.Errorf("load failed backup record %d for cleanup failed, err: %v", recordID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := path.Join(record.FileDir, record.FileName)
|
|
||||||
if err := os.Remove(path.Join(global.Dir.LocalBackupDir, filePath)); err != nil && !os.IsNotExist(err) {
|
|
||||||
global.LOG.Errorf("remove failed local backup file %s failed, err: %v", filePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cleaned := make(map[string]struct{})
|
|
||||||
for _, accountID := range strings.Split(record.SourceAccountIDs, ",") {
|
|
||||||
if accountID == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := cleaned[accountID]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cleaned[accountID] = struct{}{}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(accountID)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("parse backup account %s for failed backup cleanup failed, err: %v", accountID, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
account, storageClient, err := NewBackupClientWithID(uint(id))
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("new backup client for failed backup cleanup failed, err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := storageClient.Delete(path.Join(account.BackupPath, filePath)); err != nil {
|
|
||||||
global.LOG.Errorf("remove failed backup file %s failed, err: %v", filePath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
|
func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
|
||||||
return newClientWithContext(context.Background(), account, isEncrypt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClientWithContext(ctx context.Context, account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
|
|
||||||
varMap := make(map[string]interface{})
|
varMap := make(map[string]interface{})
|
||||||
if len(account.Vars) != 0 {
|
if len(account.Vars) != 0 {
|
||||||
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
||||||
@@ -603,7 +531,7 @@ func newClientWithContext(ctx context.Context, account *model.BackupAccount, isE
|
|||||||
varMap["password"] = account.Credential
|
varMap["password"] = account.Credential
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := cloud_storage.NewCloudStorageClientWithContext(ctx, account.Type, varMap)
|
client, err := cloud_storage.NewCloudStorageClient(account.Type, varMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-107
@@ -63,58 +63,11 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !req.IsImmediate {
|
if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||||
if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
|
||||||
markBackupFailed(record.ID, err)
|
return nil, err
|
||||||
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return record, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backupTask, err := task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeBackup, req.TaskID, install.ID)
|
|
||||||
if err != nil {
|
|
||||||
markBackupFailed(record.ID, err)
|
|
||||||
record.Status = constant.StatusFailed
|
|
||||||
record.Message = err.Error()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = doAppBackup(&install, backupTask, backupDir, fileName, "", req.Secret); err != nil {
|
|
||||||
markBackupFailed(record.ID, err)
|
|
||||||
record.Status = constant.StatusFailed
|
|
||||||
record.Message = err.Error()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusSuccess})
|
|
||||||
record.Status = constant.StatusSuccess
|
|
||||||
return record, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func backupAppWithParentTask(install *model.AppInstall, parentTask *task.Task, fileName string) (*model.BackupRecord, error) {
|
|
||||||
itemDir := fmt.Sprintf("app/%s/%s", install.App.Key, install.Name)
|
|
||||||
backupDir := path.Join(global.Dir.LocalBackupDir, itemDir)
|
|
||||||
record := &model.BackupRecord{
|
|
||||||
Type: "app",
|
|
||||||
Name: install.App.Key,
|
|
||||||
DetailName: install.Name,
|
|
||||||
SourceAccountIDs: "1",
|
|
||||||
DownloadAccountID: 1,
|
|
||||||
FileDir: itemDir,
|
|
||||||
FileName: fileName,
|
|
||||||
TaskID: parentTask.TaskID,
|
|
||||||
Status: constant.StatusWaiting,
|
|
||||||
}
|
|
||||||
if err := backupRepo.CreateRecord(record); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := handleAppBackup(install, parentTask, record.ID, backupDir, fileName, "", "", parentTask.TaskID); err != nil {
|
|
||||||
markBackupFailed(record.ID, err)
|
|
||||||
record.Status = constant.StatusFailed
|
|
||||||
record.Message = err.Error()
|
|
||||||
return record, err
|
|
||||||
}
|
|
||||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusSuccess})
|
|
||||||
record.Status = constant.StatusSuccess
|
|
||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +143,7 @@ func handleAppBackup(install *model.AppInstall, parentTask *task.Task, recordID
|
|||||||
backupTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
|
backupTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
|
||||||
go func() {
|
go func() {
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
markBackupFailed(recordID, err)
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
@@ -231,11 +184,7 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if isRollback {
|
_, _ = compose.Up(install.GetComposePath())
|
||||||
_, _ = compose.UpWithoutBuild(install.GetComposePath())
|
|
||||||
} else {
|
|
||||||
_, _ = compose.Up(install.GetComposePath())
|
|
||||||
}
|
|
||||||
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
|
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -312,7 +261,7 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newDB, err := reCreateDB(db.ID, database, backupEnvMap, install.Name)
|
newDB, err := reCreateDB(db.ID, database, backupEnvMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -440,16 +389,10 @@ func doAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reCreateDB(dbID uint, database model.Database, envMap map[string]interface{}, appInstallName string) (*model.DatabaseMysql, error) {
|
func reCreateDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabaseMysql, error) {
|
||||||
mysqlService := NewIMysqlService()
|
mysqlService := NewIMysqlService()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := deleteMysqlDatabaseForResourceOwner(
|
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true})
|
||||||
ctx,
|
|
||||||
dto.MysqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true},
|
|
||||||
dto.DBResource{Type: constant.TypeApp, Name: appInstallName},
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dbInfo := getDBCreateInfoFromEnv(envMap, "utf8mb4")
|
dbInfo := getDBCreateInfoFromEnv(envMap, "utf8mb4")
|
||||||
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
|
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
|
||||||
@@ -457,57 +400,17 @@ func reCreateDB(dbID uint, database model.Database, envMap map[string]interface{
|
|||||||
From: database.From,
|
From: database.From,
|
||||||
Database: database.Name,
|
Database: database.Name,
|
||||||
Format: dbInfo.Format,
|
Format: dbInfo.Format,
|
||||||
|
Username: dbInfo.User,
|
||||||
|
Password: dbInfo.Password,
|
||||||
Permission: "%",
|
Permission: "%",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(dbInfo.User) != 0 {
|
|
||||||
if err := ensureMysqlDBUser(mysqlService, database, dbInfo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateCronjobsDBRef(dbID, createDB.ID)
|
updateCronjobsDBRef(dbID, createDB.ID)
|
||||||
return createDB, nil
|
return createDB, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureMysqlDBUser(mysqlService IMysqlService, database model.Database, dbInfo dbRecreateInfo) error {
|
|
||||||
const host = "%"
|
|
||||||
users, err := mysqlService.ListUsers(dto.MysqlUserSearch{Database: database.Name})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var oldUser dto.MysqlUser
|
|
||||||
exists := false
|
|
||||||
for _, user := range users {
|
|
||||||
if user.Username == dbInfo.User && user.Host == host && !user.IsDelete {
|
|
||||||
oldUser = user
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
if len(oldUser.Password) != 0 && oldUser.Password != dbInfo.Password {
|
|
||||||
return buserr.New("ErrDbUserNotValid")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := mysqlService.CreateUser(dto.MysqlUserCreate{
|
|
||||||
Database: database.Name,
|
|
||||||
Username: dbInfo.User,
|
|
||||||
Host: host,
|
|
||||||
Password: dbInfo.Password,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mysqlService.GrantUser(dto.MysqlGrantCreate{
|
|
||||||
Database: database.Name,
|
|
||||||
DB: dbInfo.Name,
|
|
||||||
Username: dbInfo.User,
|
|
||||||
Host: host,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func reCreatePostgresqlDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabasePostgresql, error) {
|
func reCreatePostgresqlDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabasePostgresql, error) {
|
||||||
postgresqlService := NewIPostgresqlService()
|
postgresqlService := NewIPostgresqlService()
|
||||||
_ = postgresqlService.Delete(context.Background(), dto.PostgresqlDBDelete{
|
_ = postgresqlService.Delete(context.Background(), dto.PostgresqlDBDelete{
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (u *BackupService) ComposeBackup(req dto.CommonBackup) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handleComposeBackup(req, nil, record.ID, backupDir, fileName); err != nil {
|
if err := handleComposeBackup(req, nil, record.ID, backupDir, fileName); err != nil {
|
||||||
markBackupFailed(record.ID, err)
|
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -163,7 +163,7 @@ func handleComposeBackup(req dto.CommonBackup, parentTask *task.Task, recordID u
|
|||||||
go func() {
|
go func() {
|
||||||
defer composeCtx.close()
|
defer composeCtx.close()
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
markBackupFailed(recordID, err)
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (u *BackupService) ContainerBackup(req dto.CommonBackup) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handleContainerBackup(req.Name, nil, record.ID, backupDir, fileName, req.TaskID, req.Secret, req.StopBefore); err != nil {
|
if err := handleContainerBackup(req.Name, nil, record.ID, backupDir, fileName, req.TaskID, req.Secret, req.StopBefore); err != nil {
|
||||||
markBackupFailed(record.ID, err)
|
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -158,7 +158,7 @@ func handleContainerBackup(containerName string, parentTask *task.Task, recordID
|
|||||||
go func() {
|
go func() {
|
||||||
defer backupCtx.close()
|
defer backupCtx.close()
|
||||||
if err := backupTask.Execute(); err != nil {
|
if err := backupTask.Execute(); err != nil {
|
||||||
markBackupFailed(recordID, err)
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||||
@@ -582,10 +582,6 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
|||||||
if config.Image == "" {
|
if config.Image == "" {
|
||||||
return fmt.Errorf("container image not found in backup file")
|
return fmt.Errorf("container image not found in backup file")
|
||||||
}
|
}
|
||||||
networkConf, extraNetworks := buildContainerRecoverNetworkConfig(recoverCtx.inspectInfo.NetworkSettings, hostConfig)
|
|
||||||
if err := normalizeContainerEndpointSettings(ctx, recoverCtx.client, networkConf, extraNetworks); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !checkImageExist(recoverCtx.client, config.Image) {
|
if !checkImageExist(recoverCtx.client, config.Image) {
|
||||||
if err := pullImages(taskItem, recoverCtx.client, config.Image); err != nil {
|
if err := pullImages(taskItem, recoverCtx.client, config.Image); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -600,7 +596,7 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
createRes, err := createContainerWithNetworks(ctx, recoverCtx.client, config, hostConfig, networkConf, extraNetworks, recoverCtx.targetName)
|
createRes, err := createContainerWithOldNetworks(ctx, recoverCtx.client, config, hostConfig, recoverCtx.inspectInfo.NetworkSettings, recoverCtx.targetName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -608,68 +604,44 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeContainerEndpointSettings(ctx context.Context, cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) error {
|
func removeUnsupportedEndpointStaticIPAM(cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
||||||
if cli.NewVersionError(ctx, "1.44", "specify mac-address per network") != nil {
|
|
||||||
removeEndpointMacAddresses(primary, extras)
|
|
||||||
}
|
|
||||||
endpointGroups := []map[string]*network.EndpointSettings{extras}
|
|
||||||
if primary != nil {
|
if primary != nil {
|
||||||
endpointGroups = append(endpointGroups, primary.EndpointsConfig)
|
removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, primary.EndpointsConfig)
|
||||||
}
|
}
|
||||||
for _, endpoints := range endpointGroups {
|
removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, extras)
|
||||||
for netName, endpoint := range endpoints {
|
|
||||||
if endpoint == nil || endpoint.IPAMConfig == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
info, err := cli.NetworkInspect(ctx, netName, network.InspectOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("inspect network %s failed: %w", netName, err)
|
|
||||||
}
|
|
||||||
if err := validateContainerEndpointStaticIP(netName, info, endpoint); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeEndpointMacAddresses(primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) {
|
func removeUnsupportedEndpointStaticIPAMFromEndpoints(cli *client.Client, endpoints map[string]*network.EndpointSettings) {
|
||||||
if primary != nil {
|
for netName, endpoint := range endpoints {
|
||||||
for _, endpoint := range primary.EndpointsConfig {
|
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||||
if endpoint != nil {
|
continue
|
||||||
endpoint.MacAddress = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
info, err := cli.NetworkInspect(context.Background(), netName, network.InspectOptions{})
|
||||||
for _, endpoint := range extras {
|
if err != nil {
|
||||||
if endpoint != nil {
|
continue
|
||||||
endpoint.MacAddress = ""
|
|
||||||
}
|
}
|
||||||
|
removeUnsupportedEndpointStaticIP(netName, info, endpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateContainerEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) error {
|
func removeUnsupportedEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) {
|
||||||
if endpoint == nil || endpoint.IPAMConfig == nil {
|
if endpoint == nil || endpoint.IPAMConfig == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
ipam := endpoint.IPAMConfig
|
if isDefaultBridgeNetwork(netName, info) {
|
||||||
if err := ipam.Validate(); err != nil {
|
endpoint.IPAMConfig = nil
|
||||||
return fmt.Errorf("invalid IP configuration for network %s: %w", netName, err)
|
return
|
||||||
}
|
|
||||||
if ipam.IPv4Address == "" && ipam.IPv6Address == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if netName == "host" || netName == "none" || isDefaultBridgeNetwork(netName, info) {
|
|
||||||
return fmt.Errorf("network %s does not support static IP configuration", netName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipam.IPv4Address != "" && !networkSupportsStaticIP(info, ipam.IPv4Address, false) {
|
if endpoint.IPAMConfig.IPv4Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv4Address, false) {
|
||||||
return fmt.Errorf("static IPv4 address %s is not in a configured subnet of network %s", ipam.IPv4Address, netName)
|
endpoint.IPAMConfig.IPv4Address = ""
|
||||||
}
|
}
|
||||||
if ipam.IPv6Address != "" && !networkSupportsStaticIP(info, ipam.IPv6Address, true) {
|
if endpoint.IPAMConfig.IPv6Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv6Address, true) {
|
||||||
return fmt.Errorf("static IPv6 address %s is not in a configured subnet of network %s", ipam.IPv6Address, netName)
|
endpoint.IPAMConfig.IPv6Address = ""
|
||||||
|
}
|
||||||
|
if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" {
|
||||||
|
endpoint.IPAMConfig = nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDefaultBridgeNetwork(netName string, info network.Inspect) bool {
|
func isDefaultBridgeNetwork(netName string, info network.Inspect) bool {
|
||||||
@@ -684,7 +656,6 @@ func networkSupportsStaticIP(info network.Inspect, ip string, isIPv6 bool) bool
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
addr = addr.Unmap()
|
|
||||||
if addr.Is6() != isIPv6 {
|
if addr.Is6() != isIPv6 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -805,25 +776,16 @@ func buildContainerRecoverNetworkConfig(networkSettings *container.NetworkSettin
|
|||||||
if name == "host" || name == "none" {
|
if name == "host" || name == "none" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if endpoint == nil {
|
endpointSetting := &network.EndpointSettings{Aliases: append([]string(nil), endpoint.Aliases...), MacAddress: endpoint.MacAddress}
|
||||||
if name == primaryName {
|
|
||||||
config.EndpointsConfig[name] = &network.EndpointSettings{}
|
|
||||||
} else {
|
|
||||||
extraNetworks[name] = &network.EndpointSettings{}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
endpointSetting := &network.EndpointSettings{
|
|
||||||
Links: append([]string(nil), endpoint.Links...),
|
|
||||||
Aliases: append([]string(nil), endpoint.Aliases...),
|
|
||||||
DriverOpts: cloneStringMap(endpoint.DriverOpts),
|
|
||||||
GwPriority: endpoint.GwPriority,
|
|
||||||
}
|
|
||||||
if endpoint.IPAMConfig != nil {
|
if endpoint.IPAMConfig != nil {
|
||||||
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
||||||
IPv4Address: endpoint.IPAMConfig.IPv4Address,
|
IPv4Address: endpoint.IPAMConfig.IPv4Address,
|
||||||
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
IPv6Address: endpoint.IPAMConfig.IPv6Address,
|
||||||
LinkLocalIPs: append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...),
|
}
|
||||||
|
} else if endpoint.IPAddress != "" || endpoint.GlobalIPv6Address != "" {
|
||||||
|
endpointSetting.IPAMConfig = &network.EndpointIPAMConfig{
|
||||||
|
IPv4Address: endpoint.IPAddress,
|
||||||
|
IPv6Address: endpoint.GlobalIPv6Address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if name == primaryName {
|
if name == primaryName {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user