mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-10 16:00:31 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3c7fe320d | ||
|
|
14728f889e | ||
|
|
ff199245b0 | ||
|
|
667807e249 | ||
|
|
63e09c8c47 | ||
|
|
17a8835d59 | ||
|
|
b306bfa77a | ||
|
|
b1eff2a893 | ||
|
|
5ac7c80881 | ||
|
|
d402f67fc3 | ||
|
|
c13793c445 | ||
|
|
daa3f6b206 | ||
|
|
a2d85c911d | ||
|
|
1b76c91e1b | ||
|
|
d663a4397a | ||
|
|
d1558c5eae | ||
|
|
0da4f77a2e |
@@ -1,9 +1,6 @@
|
|||||||
<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">
|
||||||
Trusted by <strong>2,000,000+</strong> self-hosters worldwide
|
Loved by a global community of <strong>2.5M+</strong> self-hosters.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -12,7 +9,6 @@
|
|||||||
|
|
||||||
<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>
|
||||||
@@ -41,34 +37,28 @@
|
|||||||
|
|
||||||
## What is 1Panel?
|
## What is 1Panel?
|
||||||
|
|
||||||
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.
|
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:
|
||||||
|
- **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.
|
||||||
👉 Watch the [2-minute introduction](https://www.youtube.com/watch?v=Jl_wqp-XA08)
|
- **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.
|
||||||
|
- **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 | ✅ |
|
||||||
| Native AI agent runtime | ✅ | ❌ | ❌ | ❌ |
|
| AI management | ✅ | ❌ | ❌ | ❌ |
|
||||||
| 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
|
||||||
|
|
||||||
> **Requirements:** Linux VPS (Debian / Ubuntu / CentOS / Rocky), 1 GB RAM, internet access.
|
Prepare your Linux server and run the following script:
|
||||||
> 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)"
|
||||||
@@ -83,24 +73,20 @@ Run `1pctl user-info` via SSH if you need to retrieve your access credentials.
|
|||||||
|
|
||||||
## Pro Edition
|
## Pro Edition
|
||||||
|
|
||||||
1Panel OSS is free forever. Pro adds features built for teams and production workloads:
|
1Panel OSS is free forever. 1Panel Pro and Ent adds features built for teams and production workloads:
|
||||||
|
|
||||||
| Feature | OSS | Pro |
|
| Feature | OSS | Pro | Ent |
|
||||||
|---------|:---:|:---:|
|
|---------|:---:|:---:|:---:|
|
||||||
| One-click app installs | ✅ | ✅ |
|
| One-click app installs | ✅ | ✅ | ✅ |
|
||||||
| AI agents (OpenClaw) | 1 agent | Unlimited |
|
| AI agents (OpenClaw) | 5 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 | ❌ | ✅ | ✅ |
|
||||||
| Priority support | ❌ | ✅ |
|
| KVM Web UI | ❌ | ❌ | ✅ |
|
||||||
|
| AI Gateway | ❌ | ❌ | ✅ |
|
||||||
**From $80/year.** [Compare plans & start 30-day free trial →](https://1panel.pro/pricing)
|
| Priority support | ❌ | ❌ | ✅ |
|
||||||
|
|
||||||
## Star History
|
|
||||||
|
|
||||||
[](https://star-history.com/#1Panel-dev/1Panel&Date)
|
|
||||||
|
|
||||||
## Community & Support
|
## Community & Support
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ func (b *BaseApi) PageAgents(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, list, err := agentService.Page(req)
|
total, list, err := agentService.Page(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
@@ -447,7 +447,7 @@ func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, list, err := agentService.PageAccounts(req)
|
total, list, err := agentService.PageAccounts(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ func (b *BaseApi) PageAlertConfig(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, configs, err := alertService.PageAlertConfig(req)
|
total, configs, err := alertService.PageAlertConfig(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
version := c.Param("version")
|
version := c.Param("version")
|
||||||
appType := c.Param("type")
|
appType := c.Param("type")
|
||||||
appDetailDTO, err := appService.GetAppDetail(appID, version, appType)
|
appDetailDTO, err := appService.GetAppDetail(appID, version, appType, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /apps/ignored/detail [get]
|
// @Router /apps/ignored/detail [get]
|
||||||
func (b *BaseApi) ListAppIgnored(c *gin.Context) {
|
func (b *BaseApi) ListAppIgnored(c *gin.Context) {
|
||||||
res, err := appIgnoreUpgradeService.List()
|
res, err := appIgnoreUpgradeService.List(helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.ReadOnly = helper.IsDemoRequest(c)
|
||||||
if req.All {
|
if req.All {
|
||||||
list, err := appInstallService.SearchForWebsite(req)
|
list, err := appInstallService.SearchForWebsite(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,6 +74,7 @@ func (b *BaseApi) CheckAppInstalled(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.ReadOnly = helper.IsDemoRequest(c)
|
||||||
checkData, err := appInstallService.CheckExist(req)
|
checkData, err := appInstallService.CheckExist(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
@@ -115,7 +117,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn, err := appInstallService.LoadConnInfo(req)
|
conn, err := appInstallService.LoadConnInfo(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -137,7 +139,7 @@ func (b *BaseApi) DeleteCheck(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkData, err := appInstallService.DeleteCheck(appInstallId)
|
checkData, err := appInstallService.DeleteCheck(appInstallId, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -277,7 +279,7 @@ func (b *BaseApi) GetParams(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content, err := appInstallService.GetParams(appInstallId)
|
content, err := appInstallService.GetParams(appInstallId, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -341,7 +343,7 @@ func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := appInstallService.GetAppInstallInfo(appInstallId)
|
info, err := appInstallService.GetAppInstallInfo(appInstallId, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ func (b *BaseApi) SearchBackup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := backupService.SearchWithPage(req)
|
total, list, err := backupService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (b *BaseApi) SearchClam(c *gin.Context) {
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /toolbox/clam/base [post]
|
// @Router /toolbox/clam/base [post]
|
||||||
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
||||||
info, err := clamService.LoadBaseInfo()
|
info, err := clamService.LoadBaseInfo(helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := composeTemplateService.SearchWithPage(req)
|
total, list, err := composeTemplateService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -85,7 +85,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /containers/template [get]
|
// @Router /containers/template [get]
|
||||||
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
|
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
|
||||||
list, err := composeTemplateService.List()
|
list, err := composeTemplateService.List(helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ func (b *BaseApi) SearchCompose(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := containerService.PageCompose(req)
|
total, list, err := containerService.PageCompose(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -387,7 +387,7 @@ func (b *BaseApi) ContainerInfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := containerService.ContainerInfo(req)
|
data, err := containerService.ContainerInfo(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func (b *BaseApi) SearchCronjob(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := cronjobService.SearchWithPage(req)
|
total, list, err := cronjobService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func (b *BaseApi) SearchDatabase(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := databaseService.SearchWithPage(req)
|
total, list, err := databaseService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -147,7 +147,7 @@ func (b *BaseApi) GetDatabase(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := databaseService.Get(name)
|
data, err := databaseService.Get(name, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (b *BaseApi) SearchMongodb(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := mongodbService.SearchWithPage(req)
|
total, list, err := mongodbService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (b *BaseApi) ListMysqlUsers(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := mysqlService.ListUsers(req)
|
data, err := mysqlService.ListUsers(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := postgresqlService.SearchWithPage(req)
|
total, list, err := postgresqlService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
+451
-29
@@ -35,6 +35,81 @@ var cancelledChunkUploads = struct {
|
|||||||
ids map[string]struct{}
|
ids map[string]struct{}
|
||||||
}{ids: make(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
|
||||||
@@ -73,7 +148,7 @@ func (b *BaseApi) FileAISearch(c *gin.Context) {
|
|||||||
if strings.TrimSpace(req.ResponseLanguage) == "" {
|
if strings.TrimSpace(req.ResponseLanguage) == "" {
|
||||||
req.ResponseLanguage = strings.TrimSpace(c.GetHeader("Accept-Language"))
|
req.ResponseLanguage = strings.TrimSpace(c.GetHeader("Accept-Language"))
|
||||||
}
|
}
|
||||||
res, err := fileService.AISearch(req)
|
res, err := fileService.AISearch(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -474,11 +549,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dstInfo, statErr := os.Stat(dstFilename)
|
dstInfo, statErr := os.Stat(dstFilename)
|
||||||
if overwrite {
|
err = finalizeUploadedFile(tmpFilename, dstFilename, 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)
|
||||||
@@ -638,6 +709,26 @@ 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
|
||||||
@@ -792,6 +883,289 @@ 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)
|
||||||
@@ -871,6 +1245,10 @@ 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) {
|
||||||
@@ -885,20 +1263,25 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
uploadID := strings.TrimSpace(c.PostForm("uploadID"))
|
uploadID := strings.TrimSpace(c.PostForm("uploadID"))
|
||||||
|
resumable := c.PostForm("fileSize") != "" || c.PostForm("offset") != ""
|
||||||
cancellable := uploadID != ""
|
cancellable := uploadID != ""
|
||||||
if cancellable && (filepath.Base(uploadID) != uploadID || strings.ContainsAny(uploadID, `/\\`)) {
|
if cancellable && (filepath.Base(uploadID) != uploadID || strings.ContainsAny(uploadID, `/\\`)) {
|
||||||
helper.BadRequest(c, errors.New("invalid upload ID"))
|
helper.BadRequest(c, errors.New("invalid upload ID"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if resumable && !cancellable {
|
||||||
|
helper.BadRequest(c, errors.New("upload ID is required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
if !cancellable {
|
if !cancellable {
|
||||||
uploadID = filename
|
uploadID = filename
|
||||||
}
|
}
|
||||||
fileDir := filepath.Join(tmpDir, uploadID)
|
fileDir := filepath.Join(tmpDir, uploadID)
|
||||||
if cancellable && chunkUploadCancelled(uploadID) {
|
if cancellable && chunkUploadCancelled(uploadID) {
|
||||||
helper.BadRequest(c, errors.New("upload cancelled"))
|
helper.BadRequest(c, errChunkUploadCancelled)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if chunkIndex == 0 {
|
if !resumable && chunkIndex == 0 {
|
||||||
if fileOp.Stat(fileDir) {
|
if fileOp.Stat(fileDir) {
|
||||||
_ = fileOp.DeleteDir(fileDir)
|
_ = fileOp.DeleteDir(fileDir)
|
||||||
}
|
}
|
||||||
@@ -907,32 +1290,67 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
|||||||
filePath := filepath.Join(fileDir, filename)
|
filePath := filepath.Join(fileDir, filename)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if !resumable && err != nil {
|
||||||
_ = os.RemoveAll(fileDir)
|
_ = os.RemoveAll(fileDir)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var (
|
chunkData, err := io.ReadAll(uploadFile)
|
||||||
emptyFile *os.File
|
|
||||||
chunkData []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
emptyFile, err = os.Create(filePath)
|
|
||||||
if err != nil {
|
|
||||||
helper.BadRequest(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer emptyFile.Close()
|
|
||||||
|
|
||||||
chunkData, err = io.ReadAll(uploadFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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 && chunkUploadCancelled(uploadID) {
|
if cancellable && chunkUploadCancelled(uploadID) {
|
||||||
err = errors.New("upload cancelled")
|
err = errChunkUploadCancelled
|
||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
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)
|
||||||
|
if err != nil {
|
||||||
|
helper.BadRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer emptyFile.Close()
|
||||||
|
|
||||||
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)
|
||||||
@@ -985,6 +1403,10 @@ func (b *BaseApi) StopChunkUpload(c *gin.Context) {
|
|||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := removeActiveResumableUploadPart(uploadID); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
helper.Success(c)
|
helper.Success(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1211,7 +1633,7 @@ func (b *BaseApi) SearchFileShare(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, list, err := fileShareService.Page(req)
|
total, list, err := fileShareService.Page(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -1235,7 +1657,7 @@ func (b *BaseApi) GetFileShareDetail(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := fileShareService.GetByPath(req.Path)
|
info, err := fileShareService.GetByPath(req.Path, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -1254,7 +1676,7 @@ func (b *BaseApi) GetPublicFileShareInfo(c *gin.Context) {
|
|||||||
helper.BadRequest(c, errors.New("code is required"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := fileShareService.GetPublicByCode(code)
|
info, err := fileShareService.GetPublicByCode(code, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if be, ok := err.(buserr.BusinessError); ok {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||||
@@ -1307,7 +1729,7 @@ func (b *BaseApi) GetFileShareQRCode(c *gin.Context) {
|
|||||||
helper.BadRequest(c, errors.New("code is required"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := fileShareService.GetByCode(code); err != nil {
|
if _, err := fileShareService.GetByCode(code, helper.IsDemoRequest(c)); err != nil {
|
||||||
if be, ok := err.(buserr.BusinessError); ok {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||||
return
|
return
|
||||||
@@ -1389,7 +1811,7 @@ func (b *BaseApi) CheckFileShare(c *gin.Context) {
|
|||||||
helper.BadRequest(c, errors.New("code is required"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := fileShareService.Check(code, password); err != nil {
|
if err := fileShareService.Check(code, password, helper.IsDemoRequest(c)); err != nil {
|
||||||
if be, ok := err.(buserr.BusinessError); ok {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||||
return
|
return
|
||||||
@@ -1414,7 +1836,7 @@ func (b *BaseApi) DownloadFileShare(c *gin.Context) {
|
|||||||
helper.BadRequest(c, errors.New("code is required"))
|
helper.BadRequest(c, errors.New("code is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
filePath, displayName, err := fileShareService.PrepareDownload(code, password)
|
filePath, displayName, err := fileShareService.PrepareDownload(code, password, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if be, ok := err.(buserr.BusinessError); ok {
|
if be, ok := err.(buserr.BusinessError); ok {
|
||||||
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
helper.ErrorWithDetail(c, http.StatusBadRequest, be.Msg, be)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func (b *BaseApi) SearchFtp(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := ftpService.SearchWithPage(req)
|
total, list, err := ftpService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
|||||||
ctx.Abort()
|
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)
|
||||||
}
|
}
|
||||||
@@ -38,6 +48,10 @@ func BadRequest(ctx *gin.Context, err error) {
|
|||||||
ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err)
|
ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsDemoRequest(ctx *gin.Context) bool {
|
||||||
|
return global.CONF.Base.IsDemo || ctx.GetHeader(constant.DemoModeHeader) == strconv.FormatBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
func SuccessWithData(ctx *gin.Context, data interface{}) {
|
func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
data = gin.H{}
|
data = gin.H{}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := hostService.SearchWithPage(req)
|
total, list, err := hostService.SearchWithPage(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := imageRepoService.Page(req)
|
total, list, err := imageRepoService.Page(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
list := mcpServerService.Page(req)
|
list := mcpServerService.Page(req, helper.IsDemoRequest(c))
|
||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ func (b *BaseApi) SearchRuntimes(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.ReadOnly = helper.IsDemoRequest(c)
|
||||||
total, items, err := runtimeService.Page(req)
|
total, items, err := runtimeService.Page(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
@@ -132,7 +133,7 @@ func (b *BaseApi) GetRuntime(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := runtimeService.Get(id)
|
res, err := runtimeService.Get(id, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -529,7 +530,7 @@ func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := runtimeService.GetPHPContainerConfig(id)
|
data, err := runtimeService.GetPHPContainerConfig(id, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
|||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /settings/ssh/conn [get]
|
// @Router /settings/ssh/conn [get]
|
||||||
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
||||||
helper.SuccessWithData(c, settingService.GetLocalConn())
|
helper.SuccessWithData(c, settingService.GetLocalConn(helper.IsDemoRequest(c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags System Setting
|
// @Tags System Setting
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func (b *BaseApi) SearchRootCert(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, data, err := sshService.SearchRootCerts(req)
|
total, data, err := sshService.SearchRootCerts(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (b *BaseApi) PageTensorRTLLMs(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
list := tensorrtLLMService.Page(req)
|
list := tensorrtLLMService.Page(req, helper.IsDemoRequest(c))
|
||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
|
|||||||
return nil, 0, 0, false
|
return nil, 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if global.CONF.Base.IsDemo {
|
if helper.IsDemoRequest(c) {
|
||||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||||
return nil, 0, 0, false
|
return nil, 0, 0, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := websiteService.GetWebsiteHTTPS(id)
|
res, err := websiteService.GetWebsiteHTTPS(id, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -1080,7 +1080,7 @@ func (b *BaseApi) GetWebsiteResource(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := websiteService.GetWebsiteResource(id)
|
res, err := websiteService.GetWebsiteResource(id, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteAcmeAccount(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, accounts, err := websiteAcmeAccountService.Page(req)
|
total, accounts, err := websiteAcmeAccountService.Page(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (b *BaseApi) PageWebsiteCA(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, cas, err := websiteCAService.Page(req)
|
total, cas, err := websiteCAService.Page(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -70,7 +70,7 @@ func (b *BaseApi) GetWebsiteCA(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res, err := websiteCAService.GetCA(id)
|
res, err := websiteCAService.GetCA(id, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (b *BaseApi) PageWebsiteDnsAccount(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, accounts, err := websiteDnsAccountService.Page(req)
|
total, accounts, err := websiteDnsAccountService.Page(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, accounts, err := websiteSSLService.Page(req)
|
total, accounts, err := websiteSSLService.Page(req, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -159,7 +159,7 @@ func (b *BaseApi) GetWebsiteSSLByWebsiteId(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId)
|
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -181,7 +181,7 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) {
|
|||||||
helper.BadRequest(c, err)
|
helper.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
websiteSSL, err := websiteSSLService.GetSSL(id)
|
websiteSSL, err := websiteSSLService.GetSSL(id, helper.IsDemoRequest(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ type DiskInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GPUInfo struct {
|
type GPUInfo struct {
|
||||||
|
Type string `json:"type"`
|
||||||
Index uint `json:"index"`
|
Index uint `json:"index"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
GPUUtil string `json:"gpuUtil"`
|
GPUUtil string `json:"gpuUtil"`
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type GPUChartHide struct {
|
|||||||
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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,11 +62,13 @@ type AppInstalledSearch struct {
|
|||||||
All bool `json:"all"`
|
All bool `json:"all"`
|
||||||
Sync bool `json:"sync"`
|
Sync bool `json:"sync"`
|
||||||
CheckUpdate bool `json:"checkUpdate"`
|
CheckUpdate bool `json:"checkUpdate"`
|
||||||
|
ReadOnly bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstalledInfo struct {
|
type AppInstalledInfo struct {
|
||||||
Key string `json:"key" validate:"required"`
|
Key string `json:"key" validate:"required"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
ReadOnly bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppBackupSearch struct {
|
type AppBackupSearch struct {
|
||||||
|
|||||||
@@ -131,6 +131,11 @@ 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 {
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import (
|
|||||||
|
|
||||||
type RuntimeSearch struct {
|
type RuntimeSearch struct {
|
||||||
dto.PageInfo
|
dto.PageInfo
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
ReadOnly bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuntimeCreate struct {
|
type RuntimeCreate struct {
|
||||||
|
|||||||
@@ -40,20 +40,21 @@ type Meta struct {
|
|||||||
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", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY",
|
||||||
APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images"),
|
APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
||||||
},
|
},
|
||||||
"ollama": {
|
"ollama": {
|
||||||
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses",
|
||||||
APIConfigs: editableAPIConfigs(false, "openai-responses", "openai-completions"),
|
APIConfigs: editableAPIConfigs(false, "openai-responses", "openai-completions", "openai-embeddings"),
|
||||||
},
|
},
|
||||||
"vllm": {
|
"vllm": {
|
||||||
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY",
|
||||||
APIConfigs: editableAPIConfigs(false, "openai-completions", "openai-responses", "anthropic-messages", "openai-images"),
|
APIConfigs: editableAPIConfigs(false, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "openai-embeddings"),
|
||||||
},
|
},
|
||||||
"deepseek": {
|
"deepseek": {
|
||||||
Key: "deepseek", DisplayName: "DeepSeek", Sort: 25, DefaultAPIType: "openai-completions", EnvKey: "DEEPSEEK_API_KEY",
|
Key: "deepseek", DisplayName: "DeepSeek", Sort: 25, DefaultAPIType: "openai-completions", EnvKey: "DEEPSEEK_API_KEY",
|
||||||
APIConfigs: []APIConfig{
|
APIConfigs: []APIConfig{
|
||||||
{APIType: "openai-completions", BaseURL: "https://api.deepseek.com"},
|
{APIType: "openai-completions", BaseURL: "https://api.deepseek.com"},
|
||||||
|
{APIType: "openai-responses", BaseURL: "https://api.deepseek.com"},
|
||||||
anthropicAPIConfig("https://api.deepseek.com/anthropic", AuthModeXAPIKey),
|
anthropicAPIConfig("https://api.deepseek.com/anthropic", AuthModeXAPIKey),
|
||||||
},
|
},
|
||||||
Models: []Model{{ID: "deepseek-v4-flash", Name: "deepseek-v4-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}},
|
Models: []Model{{ID: "deepseek-v4-flash", Name: "deepseek-v4-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}},
|
||||||
@@ -131,6 +132,10 @@ var catalog = map[string]Meta{
|
|||||||
{APIType: "openai-responses", BaseURL: "https://api.openai.com/v1"},
|
{APIType: "openai-responses", BaseURL: "https://api.openai.com/v1"},
|
||||||
{APIType: "openai-completions", BaseURL: "https://api.openai.com/v1"},
|
{APIType: "openai-completions", BaseURL: "https://api.openai.com/v1"},
|
||||||
{APIType: "openai-images", BaseURL: "https://api.openai.com/v1"},
|
{APIType: "openai-images", BaseURL: "https://api.openai.com/v1"},
|
||||||
|
{APIType: "openai-embeddings", BaseURL: "https://api.openai.com/v1", Models: []Model{
|
||||||
|
{ID: "text-embedding-3-small", Name: "text-embedding-3-small"},
|
||||||
|
{ID: "text-embedding-3-large", Name: "text-embedding-3-large"},
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
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"}},
|
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"}},
|
||||||
},
|
},
|
||||||
@@ -296,7 +301,7 @@ func DefaultModels(key, apiType string) []Model {
|
|||||||
if len(config.Models) > 0 {
|
if len(config.Models) > 0 {
|
||||||
return append([]Model(nil), config.Models...)
|
return append([]Model(nil), config.Models...)
|
||||||
}
|
}
|
||||||
if IsImageAPIType(config.APIType) {
|
if IsImageAPIType(config.APIType) || IsEmbeddingAPIType(config.APIType) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -359,7 +364,7 @@ func ResolveBaseURL(key, apiType, requested string) (string, error) {
|
|||||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||||
return "", fmt.Errorf("invalid base url")
|
return "", fmt.Errorf("invalid base url")
|
||||||
}
|
}
|
||||||
if key == "custom" && IsImageAPIType(config.APIType) {
|
if key == "custom" && (IsImageAPIType(config.APIType) || IsEmbeddingAPIType(config.APIType)) {
|
||||||
return baseURL, nil
|
return baseURL, nil
|
||||||
}
|
}
|
||||||
parsed.Path = normalizeEndpointPath(config.APIType, parsed.Path)
|
parsed.Path = normalizeEndpointPath(config.APIType, parsed.Path)
|
||||||
@@ -381,6 +386,8 @@ func normalizeEndpointPath(apiType, value string) string {
|
|||||||
suffixes = []string{"/responses"}
|
suffixes = []string{"/responses"}
|
||||||
case "anthropic-messages":
|
case "anthropic-messages":
|
||||||
suffixes = []string{"/v1/messages", "/messages"}
|
suffixes = []string{"/v1/messages", "/messages"}
|
||||||
|
case "openai-embeddings":
|
||||||
|
suffixes = []string{"/v1/embeddings", "/embeddings"}
|
||||||
}
|
}
|
||||||
for _, suffix := range suffixes {
|
for _, suffix := range suffixes {
|
||||||
if strings.HasSuffix(strings.ToLower(path), suffix) {
|
if strings.HasSuffix(strings.ToLower(path), suffix) {
|
||||||
@@ -390,6 +397,10 @@ func normalizeEndpointPath(apiType, value string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsEmbeddingAPIType(apiType string) bool {
|
||||||
|
return apiType == "openai-embeddings"
|
||||||
|
}
|
||||||
|
|
||||||
func IsImageAPIType(apiType string) bool {
|
func IsImageAPIType(apiType string) bool {
|
||||||
switch apiType {
|
switch apiType {
|
||||||
case "openai-images", "dashscope-images", "minimax-images", "openrouter-images":
|
case "openai-images", "dashscope-images", "minimax-images", "openrouter-images":
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL,
|
|||||||
if _, ok := FindAPIConfig(provider, resolvedAPIType); !ok {
|
if _, ok := FindAPIConfig(provider, resolvedAPIType); !ok {
|
||||||
resolvedAPIType = DefaultAPIType(provider)
|
resolvedAPIType = DefaultAPIType(provider)
|
||||||
}
|
}
|
||||||
if IsImageAPIType(resolvedAPIType) {
|
if IsImageAPIType(resolvedAPIType) || IsEmbeddingAPIType(resolvedAPIType) {
|
||||||
return nil, fmt.Errorf("api type %s does not support text generation", resolvedAPIType)
|
return nil, fmt.Errorf("api type %s does not support text generation", resolvedAPIType)
|
||||||
}
|
}
|
||||||
resolvedAuthMode, err := ResolveAuthMode(provider, resolvedAPIType, authMode)
|
resolvedAuthMode, err := ResolveAuthMode(provider, resolvedAPIType, authMode)
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch apiType {
|
switch apiType {
|
||||||
|
case "openai-embeddings":
|
||||||
|
request.URL = embeddingVerifyURL(baseURL)
|
||||||
|
headers["Authorization"] = "Bearer " + apiKey
|
||||||
|
request.Body = mustJSON(map[string]interface{}{"model": model, "input": "ping"})
|
||||||
case "openai-images":
|
case "openai-images":
|
||||||
request.URL = imageVerifyURL(provider, baseURL, "/images/generations")
|
request.URL = imageVerifyURL(provider, baseURL, "/images/generations")
|
||||||
headers["Authorization"] = "Bearer " + apiKey
|
headers["Authorization"] = "Bearer " + apiKey
|
||||||
@@ -131,6 +135,17 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri
|
|||||||
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 {
|
func imageVerifyURL(provider, baseURL, endpoint string) string {
|
||||||
if provider == "custom" || strings.HasSuffix(strings.ToLower(baseURL), endpoint) {
|
if provider == "custom" || strings.HasSuffix(strings.ToLower(baseURL), endpoint) {
|
||||||
return baseURL
|
return baseURL
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ 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).
|
||||||
Where("api_type NOT LIKE ?", "%-images").
|
Scopes(WithTextAPIType()).
|
||||||
Group("provider").
|
Group("provider").
|
||||||
Scan(&rows).Error; err != nil {
|
Scan(&rows).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func WithByAPIType(apiType string) DBOption {
|
|||||||
|
|
||||||
func WithTextAPIType() DBOption {
|
func WithTextAPIType() DBOption {
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
return g.Where("api_type NOT LIKE ?", "%-images")
|
return g.Where("api_type NOT LIKE ? AND api_type <> ?", "%-images", "openai-embeddings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ 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 {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ type IAgentService interface {
|
|||||||
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
||||||
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
||||||
BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error)
|
BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error)
|
||||||
Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error)
|
Page(req dto.SearchWithPage, readOnly bool) (int64, []dto.AgentItem, error)
|
||||||
DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error)
|
DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error)
|
||||||
Delete(req dto.AgentDeleteReq) error
|
Delete(req dto.AgentDeleteReq) error
|
||||||
ResetToken(req dto.AgentTokenResetReq) error
|
ResetToken(req dto.AgentTokenResetReq) error
|
||||||
@@ -76,7 +76,7 @@ type IAgentService interface {
|
|||||||
CreateAccount(req dto.AgentAccountCreateReq) error
|
CreateAccount(req dto.AgentAccountCreateReq) error
|
||||||
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
||||||
SyncAgentsByAccount(account *model.AgentAccount) error
|
SyncAgentsByAccount(account *model.AgentAccount) error
|
||||||
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
PageAccounts(req dto.AgentAccountSearch, readOnly ...bool) (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)
|
DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error)
|
||||||
@@ -745,7 +745,7 @@ func setAgentWebUIParams(params map[string]interface{}, agentType, appVersion st
|
|||||||
params["PANEL_APP_PORT_HTTP"] = webUIPort
|
params["PANEL_APP_PORT_HTTP"] = webUIPort
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error) {
|
func (a AgentService) Page(req dto.SearchWithPage, readOnly bool) (int64, []dto.AgentItem, error) {
|
||||||
var opts []repo.DBOption
|
var opts []repo.DBOption
|
||||||
if strings.TrimSpace(req.Info) != "" {
|
if strings.TrimSpace(req.Info) != "" {
|
||||||
opts = append(opts, repo.WithByLikeName(req.Info))
|
opts = append(opts, repo.WithByLikeName(req.Info))
|
||||||
@@ -760,11 +760,19 @@ func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, erro
|
|||||||
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
|
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
|
||||||
appInstalls = append(appInstalls, appInstall)
|
appInstalls = append(appInstalls, appInstall)
|
||||||
}
|
}
|
||||||
syncAgentAppInstalls(appInstalls)
|
readOnlyMode := isDemoReadOnly(readOnly)
|
||||||
|
if !readOnlyMode {
|
||||||
|
syncAgentAppInstalls(appInstalls)
|
||||||
|
}
|
||||||
for index, item := range list {
|
for index, item := range list {
|
||||||
appInstall := appInstalls[index]
|
appInstall := appInstalls[index]
|
||||||
envMap := readInstallEnv(appInstall.Env)
|
envMap := readInstallEnv(appInstall.Env)
|
||||||
agentItem := buildAgentItem(&item, &appInstall, envMap)
|
agentItem := buildAgentItem(&item, &appInstall, envMap)
|
||||||
|
if readOnlyMode {
|
||||||
|
agentItem.Token = ""
|
||||||
|
agentItem.APIKey = ""
|
||||||
|
agentItem.DashboardPassword = ""
|
||||||
|
}
|
||||||
agentItem.Upgradable = checkAgentUpgradable(appInstall)
|
agentItem.Upgradable = checkAgentUpgradable(appInstall)
|
||||||
items = append(items, agentItem)
|
items = append(items, agentItem)
|
||||||
}
|
}
|
||||||
@@ -1128,7 +1136,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error) {
|
func (a AgentService) PageAccounts(req dto.AgentAccountSearch, readOnly ...bool) (int64, []dto.AgentAccountInfo, error) {
|
||||||
var opts []repo.DBOption
|
var opts []repo.DBOption
|
||||||
if strings.TrimSpace(req.Provider) != "" {
|
if strings.TrimSpace(req.Provider) != "" {
|
||||||
opts = append(opts, repo.WithByProvider(req.Provider))
|
opts = append(opts, repo.WithByProvider(req.Provider))
|
||||||
@@ -1149,7 +1157,7 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
|||||||
items := make([]dto.AgentAccountInfo, 0, len(list))
|
items := make([]dto.AgentAccountInfo, 0, len(list))
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
apiKey := ""
|
apiKey := ""
|
||||||
if item.RememberAPIKey {
|
if item.RememberAPIKey && !isDemoReadOnly(readOnly...) {
|
||||||
apiKey = item.APIKey
|
apiKey = item.APIKey
|
||||||
}
|
}
|
||||||
items = append(items, dto.AgentAccountInfo{
|
items = append(items, dto.AgentAccountInfo{
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, mode
|
|||||||
return resolvedAgentAccountInput{}, buserr.New("ErrAgentAccountModelsRequired")
|
return resolvedAgentAccountInput{}, buserr.New("ErrAgentAccountModelsRequired")
|
||||||
}
|
}
|
||||||
imageAPI := providercatalog.IsImageAPIType(resolvedAPIType)
|
imageAPI := providercatalog.IsImageAPIType(resolvedAPIType)
|
||||||
if validateAvailability && (imageAPI || !providercatalog.SkipVerification(provider)) {
|
if validateAvailability && (imageAPI || providercatalog.IsEmbeddingAPIType(resolvedAPIType) || !providercatalog.SkipVerification(provider)) {
|
||||||
if err := providercatalog.VerifyAccount(provider, resolvedAPIType, resolvedAuthMode, resolvedBaseURL, resolvedAPIKey, modelID); err != nil {
|
if err := providercatalog.VerifyAccount(provider, resolvedAPIType, resolvedAuthMode, resolvedBaseURL, resolvedAPIKey, modelID); err != nil {
|
||||||
return resolvedAgentAccountInput{}, err
|
return resolvedAgentAccountInput{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ type IAlertService interface {
|
|||||||
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
||||||
|
|
||||||
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, readOnly ...bool) (int64, []model.AlertConfig, error)
|
||||||
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
||||||
DeleteAlertConfig(id uint) error
|
DeleteAlertConfig(id uint) error
|
||||||
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
||||||
@@ -497,7 +497,7 @@ func (a AlertService) GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertCon
|
|||||||
return configs, err
|
return configs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error) {
|
func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq, readOnly ...bool) (int64, []model.AlertConfig, error) {
|
||||||
opts := []repo.DBOption{
|
opts := []repo.DBOption{
|
||||||
alertRepo.WithByTypeNotIn([]string{"common"}),
|
alertRepo.WithByTypeNotIn([]string{"common"}),
|
||||||
repo.WithOrderDesc("created_at"),
|
repo.WithOrderDesc("created_at"),
|
||||||
@@ -505,7 +505,25 @@ 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))
|
||||||
}
|
}
|
||||||
return alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
total, configs, err := alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
||||||
|
if err != nil || !isDemoReadOnly(readOnly...) {
|
||||||
|
return total, configs, err
|
||||||
|
}
|
||||||
|
for i := range configs {
|
||||||
|
var value interface{}
|
||||||
|
if err := json.Unmarshal([]byte(configs[i].Config), &value); err != nil {
|
||||||
|
configs[i].Config = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
redactSensitiveData(value)
|
||||||
|
data, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
configs[i].Config = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configs[i].Config = string(data)
|
||||||
|
}
|
||||||
|
return total, configs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||||
|
|||||||
+40
-35
@@ -48,7 +48,7 @@ type IAppService interface {
|
|||||||
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
|
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
|
||||||
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
||||||
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
|
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
|
||||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
GetAppDetail(appId uint, version, appType string, readOnly ...bool) (response.AppDetailDTO, error)
|
||||||
Install(req request.AppInstallCreate, executeScript bool) (*model.AppInstall, error)
|
Install(req request.AppInstallCreate, executeScript bool) (*model.AppInstall, error)
|
||||||
SyncAppListFromRemote(taskID string) error
|
SyncAppListFromRemote(taskID string) error
|
||||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||||
@@ -231,7 +231,7 @@ func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetai
|
|||||||
return appDetailDTO, nil
|
return appDetailDTO, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AppService) GetAppDetail(appID uint, version, appType string) (response.AppDetailDTO, error) {
|
func (a AppService) GetAppDetail(appID uint, version, appType string, readOnly ...bool) (response.AppDetailDTO, error) {
|
||||||
var (
|
var (
|
||||||
appDetailDTO response.AppDetailDTO
|
appDetailDTO response.AppDetailDTO
|
||||||
opts []repo.DBOption
|
opts []repo.DBOption
|
||||||
@@ -243,6 +243,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
}
|
}
|
||||||
appDetailDTO.AppDetail = detail
|
appDetailDTO.AppDetail = detail
|
||||||
appDetailDTO.Enable = true
|
appDetailDTO.Enable = true
|
||||||
|
readOnlyMode := isDemoReadOnly(readOnly...)
|
||||||
|
|
||||||
if appType == "runtime" {
|
if appType == "runtime" {
|
||||||
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
||||||
@@ -252,42 +253,46 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
|
|
||||||
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
||||||
if !fileOp.Stat(versionPath) || detail.Update {
|
versionExists := fileOp.Stat(versionPath)
|
||||||
|
if (!versionExists || detail.Update) && !readOnlyMode {
|
||||||
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
|
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
|
||||||
return appDetailDTO, err
|
return appDetailDTO, err
|
||||||
}
|
}
|
||||||
|
versionExists = fileOp.Stat(versionPath)
|
||||||
}
|
}
|
||||||
switch app.Type {
|
if versionExists {
|
||||||
case constant.RuntimePHP:
|
switch app.Type {
|
||||||
paramsPath := filepath.Join(versionPath, "data.yml")
|
case constant.RuntimePHP:
|
||||||
if !fileOp.Stat(paramsPath) {
|
paramsPath := filepath.Join(versionPath, "data.yml")
|
||||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
if !fileOp.Stat(paramsPath) {
|
||||||
}
|
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
|
||||||
param, err := fileOp.GetContent(paramsPath)
|
}
|
||||||
if err != nil {
|
param, err := fileOp.GetContent(paramsPath)
|
||||||
return appDetailDTO, err
|
if err != nil {
|
||||||
}
|
return appDetailDTO, err
|
||||||
paramMap := make(map[string]interface{})
|
}
|
||||||
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
paramMap := make(map[string]interface{})
|
||||||
return appDetailDTO, err
|
if err = yaml.Unmarshal(param, ¶mMap); err != nil {
|
||||||
}
|
return appDetailDTO, err
|
||||||
appDetailDTO.Params = paramMap["additionalProperties"]
|
}
|
||||||
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
appDetailDTO.Params = paramMap["additionalProperties"]
|
||||||
if !fileOp.Stat(composePath) {
|
composePath := filepath.Join(versionPath, "docker-compose.yml")
|
||||||
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
if !fileOp.Stat(composePath) {
|
||||||
}
|
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
|
||||||
compose, err := fileOp.GetContent(composePath)
|
}
|
||||||
if err != nil {
|
compose, err := fileOp.GetContent(composePath)
|
||||||
return appDetailDTO, err
|
if err != nil {
|
||||||
}
|
return appDetailDTO, err
|
||||||
composeMap := make(map[string]interface{})
|
}
|
||||||
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
composeMap := make(map[string]interface{})
|
||||||
return appDetailDTO, err
|
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
|
||||||
}
|
return appDetailDTO, err
|
||||||
if service, ok := composeMap["services"]; ok {
|
}
|
||||||
servicesMap := service.(map[string]interface{})
|
if service, ok := composeMap["services"]; ok {
|
||||||
for k := range servicesMap {
|
servicesMap := service.(map[string]interface{})
|
||||||
appDetailDTO.Image = k
|
for k := range servicesMap {
|
||||||
|
appDetailDTO.Image = k
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +304,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
appDetailDTO.Params = paramMap
|
appDetailDTO.Params = paramMap
|
||||||
}
|
}
|
||||||
|
|
||||||
if appDetailDTO.DockerCompose == "" {
|
if appDetailDTO.DockerCompose == "" && !readOnlyMode {
|
||||||
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)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type AppIgnoreUpgradeService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IAppIgnoreUpgradeService interface {
|
type IAppIgnoreUpgradeService interface {
|
||||||
List() ([]response.AppIgnoreUpgradeDTO, error)
|
List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error)
|
||||||
CreateAppIgnore(req request.AppIgnoreUpgradeReq) error
|
CreateAppIgnore(req request.AppIgnoreUpgradeReq) error
|
||||||
Delete(req request.ReqWithID) error
|
Delete(req request.ReqWithID) error
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ func NewIAppIgnoreUpgradeService() IAppIgnoreUpgradeService {
|
|||||||
return AppIgnoreUpgradeService{}
|
return AppIgnoreUpgradeService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error) {
|
func (a AppIgnoreUpgradeService) List(readOnly ...bool) ([]response.AppIgnoreUpgradeDTO, error) {
|
||||||
var res []response.AppIgnoreUpgradeDTO
|
var res []response.AppIgnoreUpgradeDTO
|
||||||
ignores, err := appIgnoreUpgradeRepo.List()
|
ignores, err := appIgnoreUpgradeRepo.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -37,14 +37,18 @@ func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error)
|
|||||||
}
|
}
|
||||||
app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID))
|
app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID))
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dto.Name = app.Name
|
dto.Name = app.Name
|
||||||
if ignore.Scope == "version" {
|
if ignore.Scope == "version" {
|
||||||
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
|
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dto.Version = appDetail.Version
|
dto.Version = appDetail.Version
|
||||||
|
|||||||
@@ -42,21 +42,21 @@ type IAppInstallService interface {
|
|||||||
Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error)
|
Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error)
|
||||||
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
|
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
|
||||||
LoadPort(req dto.OperationWithNameAndType) (int64, error)
|
LoadPort(req dto.OperationWithNameAndType) (int64, error)
|
||||||
LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error)
|
LoadConnInfo(req dto.OperationWithNameAndType, readOnly ...bool) (response.DatabaseConn, error)
|
||||||
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error)
|
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error)
|
||||||
Operate(req request.AppInstalledOperate) error
|
Operate(req request.AppInstalledOperate) error
|
||||||
Update(req request.AppInstalledUpdate) error
|
Update(req request.AppInstalledUpdate) error
|
||||||
SyncAll(systemInit bool) error
|
SyncAll(systemInit bool) error
|
||||||
GetServices(key string) ([]response.AppService, error)
|
GetServices(key string) ([]response.AppService, error)
|
||||||
GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error)
|
GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error)
|
||||||
GetParams(id uint) (*response.AppConfig, error)
|
GetParams(id uint, readOnly ...bool) (*response.AppConfig, error)
|
||||||
ChangeAppPort(req request.PortUpdate) error
|
ChangeAppPort(req request.PortUpdate) error
|
||||||
GetDefaultConfigByKey(key, name string) (string, error)
|
GetDefaultConfigByKey(key, name string) (string, error)
|
||||||
DeleteCheck(installId uint) ([]dto.AppResource, error)
|
DeleteCheck(installId uint, readOnly ...bool) ([]dto.AppResource, error)
|
||||||
|
|
||||||
UpdateAppConfig(req request.AppConfigUpdate) error
|
UpdateAppConfig(req request.AppConfigUpdate) error
|
||||||
GetInstallList() ([]dto.AppInstallInfo, error)
|
GetInstallList() ([]dto.AppInstallInfo, error)
|
||||||
GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error)
|
GetAppInstallInfo(appInstallID uint, readOnly ...bool) (*response.AppInstallInfo, error)
|
||||||
UpdateSort(req request.AppInstallSort) error
|
UpdateSort(req request.AppInstallSort) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync, req.CheckUpdate)
|
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync && !req.ReadOnly, req.CheckUpdate, req.ReadOnly)
|
||||||
if req.Update {
|
if req.Update {
|
||||||
total = int64(len(installDTOs))
|
total = int64(len(installDTOs))
|
||||||
}
|
}
|
||||||
@@ -151,8 +151,10 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
|
|||||||
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
if !req.ReadOnly {
|
||||||
return nil, err
|
if err = syncAppInstallStatus(&appInstall, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.ContainerName = appInstall.ContainerName
|
res.ContainerName = appInstall.ContainerName
|
||||||
@@ -182,7 +184,7 @@ func (a *AppInstallService) LoadPort(req dto.OperationWithNameAndType) (int64, e
|
|||||||
return app.Port, nil
|
return app.Port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error) {
|
func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType, readOnly ...bool) (response.DatabaseConn, error) {
|
||||||
var data response.DatabaseConn
|
var data response.DatabaseConn
|
||||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
|
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -191,6 +193,9 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp
|
|||||||
data.Status = app.Status
|
data.Status = app.Status
|
||||||
data.Username = app.UserName
|
data.Username = app.UserName
|
||||||
data.Password = app.Password
|
data.Password = app.Password
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
data.Password = ""
|
||||||
|
}
|
||||||
data.ServiceName = app.ServiceName
|
data.ServiceName = app.ServiceName
|
||||||
data.Port = app.Port
|
data.Port = app.Port
|
||||||
data.ContainerName = app.ContainerName
|
data.ContainerName = app.ContainerName
|
||||||
@@ -240,7 +245,7 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleInstalled(installs, false, true, false)
|
return handleInstalled(installs, false, !req.ReadOnly, false, req.ReadOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||||
@@ -664,7 +669,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppInstallService) DeleteCheck(installID uint) ([]dto.AppResource, error) {
|
func (a *AppInstallService) DeleteCheck(installID uint, readOnly ...bool) ([]dto.AppResource, error) {
|
||||||
var res []dto.AppResource
|
var res []dto.AppResource
|
||||||
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(installID))
|
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -685,7 +690,7 @@ func (a *AppInstallService) DeleteCheck(installID uint) ([]dto.AppResource, erro
|
|||||||
Type: "app",
|
Type: "app",
|
||||||
Name: linkInstall.Name,
|
Name: linkInstall.Name,
|
||||||
})
|
})
|
||||||
} else {
|
} else if !isDemoReadOnly(readOnly...) {
|
||||||
_ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId))
|
_ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -723,7 +728,7 @@ func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, err
|
|||||||
return string(contentByte), nil
|
return string(contentByte), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
func (a *AppInstallService) GetParams(id uint, readOnly ...bool) (*response.AppConfig, error) {
|
||||||
var (
|
var (
|
||||||
params []response.AppParam
|
params []response.AppParam
|
||||||
appForm dto.AppForm
|
appForm dto.AppForm
|
||||||
@@ -818,8 +823,14 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readOnlyMode := isDemoReadOnly(readOnly...)
|
||||||
|
if readOnlyMode {
|
||||||
|
redactSensitiveAppParams(params)
|
||||||
|
}
|
||||||
config := getAppCommonConfig(envs)
|
config := getAppCommonConfig(envs)
|
||||||
config.DockerCompose = install.DockerCompose
|
if !readOnlyMode {
|
||||||
|
config.DockerCompose = install.DockerCompose
|
||||||
|
}
|
||||||
res.Params = params
|
res.Params = params
|
||||||
if config.ContainerName == "" {
|
if config.ContainerName == "" {
|
||||||
config.ContainerName = install.ContainerName
|
config.ContainerName = install.ContainerName
|
||||||
@@ -829,8 +840,10 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
|||||||
res.RestartPolicy = getRestartPolicy(install.DockerCompose)
|
res.RestartPolicy = getRestartPolicy(install.DockerCompose)
|
||||||
res.WebUI = install.WebUI
|
res.WebUI = install.WebUI
|
||||||
res.Type = install.App.Type
|
res.Type = install.App.Type
|
||||||
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
if !readOnlyMode {
|
||||||
res.RawCompose = rawCompose
|
if rawCompose, err := getUpgradeCompose(install, detail); err == nil {
|
||||||
|
res.RawCompose = rawCompose
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
@@ -958,20 +971,25 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppInstallService) GetAppInstallInfo(installID uint) (*response.AppInstallInfo, error) {
|
func (a *AppInstallService) GetAppInstallInfo(installID uint, readOnly ...bool) (*response.AppInstallInfo, error) {
|
||||||
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID))
|
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||||
if appInstall.ID == 0 {
|
if appInstall.ID == 0 {
|
||||||
return &response.AppInstallInfo{
|
return &response.AppInstallInfo{
|
||||||
Status: constant.StatusDeleted,
|
Status: constant.StatusDeleted,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
_ = syncAppInstallStatus(&appInstall, false)
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
_ = syncAppInstallStatus(&appInstall, false)
|
||||||
|
}
|
||||||
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
|
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
|
||||||
var envMap map[string]interface{}
|
var envMap map[string]interface{}
|
||||||
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
|
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
redactSensitiveValues(envMap)
|
||||||
|
}
|
||||||
res := &response.AppInstallInfo{
|
res := &response.AppInstallInfo{
|
||||||
ID: appInstall.ID,
|
ID: appInstall.ID,
|
||||||
Name: appInstall.Name,
|
Name: appInstall.Name,
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
|
|||||||
|
|
||||||
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
|
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
|
||||||
t.LogStart(logStr)
|
t.LogStart(logStr)
|
||||||
if out, upErr := compose.UpWithoutPull(u.original.GetComposePath()); upErr != nil {
|
if out, upErr := compose.UpWithoutBuild(u.original.GetComposePath()); upErr != nil {
|
||||||
if out != "" {
|
if out != "" {
|
||||||
upErr = fmt.Errorf("%s: %w", out, upErr)
|
upErr = fmt.Errorf("%s: %w", out, upErr)
|
||||||
}
|
}
|
||||||
@@ -513,7 +513,6 @@ func (u *appUpgradeContext) applyStagedFiles() error {
|
|||||||
nginxModuleBuildDir,
|
nginxModuleBuildDir,
|
||||||
nginxModuleModulesDir,
|
nginxModuleModulesDir,
|
||||||
path.Join(nginxModuleConfDir, nginxModuleEnabledConfDir),
|
path.Join(nginxModuleConfDir, nginxModuleEnabledConfDir),
|
||||||
path.Join(nginxModuleConfDir, "nginx.conf"),
|
|
||||||
} {
|
} {
|
||||||
if err := replaceUpgradePath(u.stageDir, u.original.GetPath(), relativePath); err != nil {
|
if err := replaceUpgradePath(u.stageDir, u.original.GetPath(), relativePath); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -546,7 +545,7 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
|||||||
return u.finishRollback()
|
return u.finishRollback()
|
||||||
}
|
}
|
||||||
if u.phase < appUpgradeMutated {
|
if u.phase < appUpgradeMutated {
|
||||||
if out, err := compose.UpWithoutPull(u.original.GetComposePath()); err != nil {
|
if out, err := compose.UpWithoutBuild(u.original.GetComposePath()); err != nil {
|
||||||
if out != "" {
|
if out != "" {
|
||||||
err = fmt.Errorf("%s: %w", out, err)
|
err = fmt.Errorf("%s: %w", out, err)
|
||||||
}
|
}
|
||||||
@@ -564,14 +563,14 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
|
|||||||
if u.backupFile != "" {
|
if u.backupFile != "" {
|
||||||
_ = u.restoreManagedFiles()
|
_ = u.restoreManagedFiles()
|
||||||
if err := handleAppRecover(&u.original, t, u.backupFile, true, "", ""); err != nil {
|
if err := handleAppRecover(&u.original, t, u.backupFile, true, "", ""); err != nil {
|
||||||
_, _ = compose.UpWithoutPull(u.original.GetComposePath())
|
_, _ = compose.UpWithoutBuild(u.original.GetComposePath())
|
||||||
return errors.Join(rollbackErr, err)
|
return errors.Join(rollbackErr, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := u.restoreManagedFiles(); err != nil {
|
if err := u.restoreManagedFiles(); err != nil {
|
||||||
return errors.Join(rollbackErr, err)
|
return errors.Join(rollbackErr, err)
|
||||||
}
|
}
|
||||||
if out, err := compose.UpWithoutPull(u.original.GetComposePath()); err != nil {
|
if out, err := compose.UpWithoutBuild(u.original.GetComposePath()); err != nil {
|
||||||
if out != "" {
|
if out != "" {
|
||||||
err = fmt.Errorf("%s: %w", out, err)
|
err = fmt.Errorf("%s: %w", out, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,62 @@ var (
|
|||||||
Delete DatabaseOp = "delete"
|
Delete DatabaseOp = "delete"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isDemoReadOnly(readOnly ...bool) bool {
|
||||||
|
return global.CONF.Base.IsDemo || len(readOnly) > 0 && readOnly[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeConfigKey(key string) string {
|
||||||
|
return strings.ToLower(strings.NewReplacer("_", "", "-", "", ".", "", " ", "").Replace(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSensitiveConfigKey(key string) bool {
|
||||||
|
normalized := normalizeConfigKey(key)
|
||||||
|
for _, marker := range []string{"password", "passwd", "passphrase", "secret", "token", "credential", "privatekey", "authorization"} {
|
||||||
|
if strings.Contains(normalized, marker) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized == "key" || strings.HasSuffix(normalized, "key")
|
||||||
|
}
|
||||||
|
|
||||||
|
func redactSensitiveValues(values map[string]interface{}) {
|
||||||
|
redactSensitiveData(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redactSensitiveData(value interface{}) {
|
||||||
|
switch data := value.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
for key, item := range data {
|
||||||
|
if isSensitiveConfigKey(key) {
|
||||||
|
data[key] = ""
|
||||||
|
} else {
|
||||||
|
redactSensitiveData(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for _, item := range data {
|
||||||
|
redactSensitiveData(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func redactSensitiveAppParams(params []response.AppParam) {
|
||||||
|
for i := range params {
|
||||||
|
if strings.EqualFold(params[i].Type, "password") || isSensitiveConfigKey(params[i].Key) {
|
||||||
|
params[i].Value = ""
|
||||||
|
params[i].ShowValue = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func redactSensitiveEnvironments(environments []request.Environment) {
|
||||||
|
for i := range environments {
|
||||||
|
if isSensitiveConfigKey(environments[i].Key) {
|
||||||
|
environments[i].Value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkPort(key string, params map[string]interface{}) (int, error) {
|
func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||||
port, ok := params[key]
|
port, ok := params[key]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -1387,7 +1443,9 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
|||||||
}
|
}
|
||||||
appInstall.Status = constant.StatusError
|
appInstall.Status = constant.StatusError
|
||||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
if !global.CONF.Base.IsDemo {
|
||||||
|
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
notFoundNames := make([]string, 0)
|
notFoundNames := make([]string, 0)
|
||||||
@@ -1446,10 +1504,12 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
|||||||
appInstall.Message = msg
|
appInstall.Message = msg
|
||||||
appInstall.Status = constant.StatusUnHealthy
|
appInstall.Status = constant.StatusUnHealthy
|
||||||
}
|
}
|
||||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
if !global.CONF.Base.IsDemo {
|
||||||
|
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate bool) ([]response.AppInstallDTO, error) {
|
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate, readOnly bool) ([]response.AppInstallDTO, error) {
|
||||||
var (
|
var (
|
||||||
res []response.AppInstallDTO
|
res []response.AppInstallDTO
|
||||||
containersMap map[string]container.Summary
|
containersMap map[string]container.Summary
|
||||||
@@ -1480,6 +1540,9 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
resourceKeys := getAppInstallResourceKeys(installed.ID)
|
||||||
envMap := make(map[string]interface{})
|
envMap := make(map[string]interface{})
|
||||||
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
_ = json.Unmarshal([]byte(installed.Env), &envMap)
|
||||||
|
if isDemoReadOnly(readOnly) {
|
||||||
|
redactSensitiveValues(envMap)
|
||||||
|
}
|
||||||
installDTO := response.AppInstallDTO{
|
installDTO := response.AppInstallDTO{
|
||||||
ID: installed.ID,
|
ID: installed.ID,
|
||||||
Name: installed.Name,
|
Name: installed.Name,
|
||||||
@@ -1524,7 +1587,9 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
installDTO.DockerCompose = installed.DockerCompose
|
if !isDemoReadOnly(readOnly) {
|
||||||
|
installDTO.DockerCompose = installed.DockerCompose
|
||||||
|
}
|
||||||
installDTO.IsEdit = isEditCompose(installed)
|
installDTO.IsEdit = isEditCompose(installed)
|
||||||
|
|
||||||
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type IBackupService interface {
|
|||||||
CheckUsed(name string, isPublic bool) error
|
CheckUsed(name string, isPublic bool) error
|
||||||
|
|
||||||
LoadBackupOptions() ([]dto.BackupOption, error)
|
LoadBackupOptions() ([]dto.BackupOption, error)
|
||||||
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchPageWithType, readOnly ...bool) (int64, interface{}, error)
|
||||||
Create(backupDto dto.BackupOperate) error
|
Create(backupDto dto.BackupOperate) error
|
||||||
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
|
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
|
||||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||||
@@ -80,7 +80,7 @@ func (u *BackupService) GetLocalDir() (string, error) {
|
|||||||
return account.BackupPath, nil
|
return account.BackupPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
|
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType, readOnly ...bool) (int64, interface{}, error) {
|
||||||
options := []repo.DBOption{repo.WithOrderDesc("created_at")}
|
options := []repo.DBOption{repo.WithOrderDesc("created_at")}
|
||||||
if len(req.Type) != 0 {
|
if len(req.Type) != 0 {
|
||||||
options = append(options, repo.WithByType(req.Type))
|
options = append(options, repo.WithByType(req.Type))
|
||||||
@@ -128,11 +128,36 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, inter
|
|||||||
itemVars, _ := json.Marshal(varMap)
|
itemVars, _ := json.Marshal(varMap)
|
||||||
item.Vars = string(itemVars)
|
item.Vars = string(itemVars)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.AccessKey = ""
|
||||||
|
item.Credential = ""
|
||||||
|
item.Vars = sanitizeBackupVars(item.Vars)
|
||||||
|
}
|
||||||
data = append(data, item)
|
data = append(data, item)
|
||||||
}
|
}
|
||||||
return count, data, nil
|
return count, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sanitizeBackupVars(vars string) string {
|
||||||
|
if vars == "" {
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
var values map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(vars), &values); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for key := range values {
|
||||||
|
if isSensitiveConfigKey(key) || normalizeConfigKey(key) == "code" {
|
||||||
|
delete(values, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(values)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes {
|
func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes {
|
||||||
var res dto.BackupCheckRes
|
var res dto.BackupCheckRes
|
||||||
var backup model.BackupAccount
|
var backup model.BackupAccount
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if isRollback {
|
if isRollback {
|
||||||
_, _ = compose.UpWithoutPull(install.GetComposePath())
|
_, _ = compose.UpWithoutBuild(install.GetComposePath())
|
||||||
} else {
|
} else {
|
||||||
_, _ = compose.Up(install.GetComposePath())
|
_, _ = compose.Up(install.GetComposePath())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type ClamService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IClamService interface {
|
type IClamService interface {
|
||||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error)
|
||||||
Operate(operate string) error
|
Operate(operate string) error
|
||||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||||
Create(req dto.ClamCreate, operator string) error
|
Create(req dto.ClamCreate, operator string) error
|
||||||
@@ -53,7 +53,7 @@ func NewIClamService() IClamService {
|
|||||||
return &ClamService{}
|
return &ClamService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
func (c *ClamService) LoadBaseInfo(readOnly bool) (dto.ClamBaseInfo, error) {
|
||||||
var baseInfo dto.ClamBaseInfo
|
var baseInfo dto.ClamBaseInfo
|
||||||
baseInfo.Version = "-"
|
baseInfo.Version = "-"
|
||||||
baseInfo.FreshVersion = "-"
|
baseInfo.FreshVersion = "-"
|
||||||
@@ -96,7 +96,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
|||||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if !readOnly && !global.CONF.Base.IsDemo {
|
||||||
_ = clam.CheckWithStopAll(false, clamRepo)
|
_ = clam.CheckWithStopAll(false, clamRepo)
|
||||||
}
|
}
|
||||||
if baseInfo.FreshIsActive {
|
if baseInfo.FreshIsActive {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
type ComposeTemplateService struct{}
|
type ComposeTemplateService struct{}
|
||||||
|
|
||||||
type IComposeTemplateService interface {
|
type IComposeTemplateService interface {
|
||||||
List() ([]dto.ComposeTemplateInfo, error)
|
List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error)
|
||||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||||
Create(req dto.ComposeTemplateCreate) error
|
Create(req dto.ComposeTemplateCreate) error
|
||||||
Update(id uint, upMap map[string]interface{}) error
|
Update(id uint, upMap map[string]interface{}) error
|
||||||
Batch(req dto.ComposeTemplateBatch) error
|
Batch(req dto.ComposeTemplateBatch) error
|
||||||
@@ -23,7 +23,7 @@ func NewIComposeTemplateService() IComposeTemplateService {
|
|||||||
return &ComposeTemplateService{}
|
return &ComposeTemplateService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ComposeTemplateService) List() ([]dto.ComposeTemplateInfo, error) {
|
func (u *ComposeTemplateService) List(readOnly ...bool) ([]dto.ComposeTemplateInfo, error) {
|
||||||
composes, err := composeRepo.List()
|
composes, err := composeRepo.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, buserr.New("ErrRecordNotFound")
|
return nil, buserr.New("ErrRecordNotFound")
|
||||||
@@ -34,12 +34,15 @@ func (u *ComposeTemplateService) List() ([]dto.ComposeTemplateInfo, error) {
|
|||||||
if err := copier.Copy(&item, &compose); err != nil {
|
if err := copier.Copy(&item, &compose); err != nil {
|
||||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Content = ""
|
||||||
|
}
|
||||||
dtoLists = append(dtoLists, item)
|
dtoLists = append(dtoLists, item)
|
||||||
}
|
}
|
||||||
return dtoLists, err
|
return dtoLists, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, composes, err := composeRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
total, composes, err := composeRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
||||||
var dtoComposeTemplates []dto.ComposeTemplateInfo
|
var dtoComposeTemplates []dto.ComposeTemplateInfo
|
||||||
for _, compose := range composes {
|
for _, compose := range composes {
|
||||||
@@ -47,6 +50,9 @@ func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage) (int64,
|
|||||||
if err := copier.Copy(&item, &compose); err != nil {
|
if err := copier.Copy(&item, &compose); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Content = ""
|
||||||
|
}
|
||||||
dtoComposeTemplates = append(dtoComposeTemplates, item)
|
dtoComposeTemplates = append(dtoComposeTemplates, item)
|
||||||
}
|
}
|
||||||
return total, dtoComposeTemplates, err
|
return total, dtoComposeTemplates, err
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ type IContainerService interface {
|
|||||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||||
ListVolume() ([]dto.Options, error)
|
ListVolume() ([]dto.Options, error)
|
||||||
|
|
||||||
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
|
PageCompose(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||||
LoadComposeEnv(name string) (string, error)
|
LoadComposeEnv(name string) (string, error)
|
||||||
CreateCompose(req dto.ComposeCreate) error
|
CreateCompose(req dto.ComposeCreate) error
|
||||||
ComposeOperation(req dto.ComposeOperation) error
|
ComposeOperation(req dto.ComposeOperation) error
|
||||||
@@ -77,7 +77,7 @@ type IContainerService interface {
|
|||||||
ContainerCreate(req dto.ContainerOperate, inThread bool) error
|
ContainerCreate(req dto.ContainerOperate, inThread bool) error
|
||||||
ContainerUpdate(req dto.ContainerOperate) error
|
ContainerUpdate(req dto.ContainerOperate) error
|
||||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
ContainerInfo(req dto.OperationWithName, readOnly ...bool) (*dto.ContainerOperate, error)
|
||||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
ContainerListStats() ([]dto.ContainerListStats, error)
|
||||||
ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error)
|
ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||||
@@ -568,7 +568,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
return taskItem.Execute()
|
return taskItem.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
|
func (u *ContainerService) ContainerInfo(req dto.OperationWithName, readOnly ...bool) (*dto.ContainerOperate, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -612,6 +612,9 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
|||||||
data.Tty = oldContainer.Config.Tty
|
data.Tty = oldContainer.Config.Tty
|
||||||
data.Entrypoint = oldContainer.Config.Entrypoint
|
data.Entrypoint = oldContainer.Config.Entrypoint
|
||||||
data.Env = oldContainer.Config.Env
|
data.Env = oldContainer.Config.Env
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
data.Env = nil
|
||||||
|
}
|
||||||
data.CPUShares = oldContainer.HostConfig.CPUShares
|
data.CPUShares = oldContainer.HostConfig.CPUShares
|
||||||
for key, val := range oldContainer.Config.Labels {
|
for key, val := range oldContainer.Config.Labels {
|
||||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
|
|||||||
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||||
const composeCreatedBy = "createdBy"
|
const composeCreatedBy = "createdBy"
|
||||||
|
|
||||||
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (u *ContainerService) PageCompose(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||||
var (
|
var (
|
||||||
records []dto.ComposeInfo
|
records []dto.ComposeInfo
|
||||||
BackDatas []dto.ComposeInfo
|
BackDatas []dto.ComposeInfo
|
||||||
@@ -187,7 +187,10 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||||||
}
|
}
|
||||||
BackDatas = records[start:end]
|
BackDatas = records[start:end]
|
||||||
}
|
}
|
||||||
listItem := loadEnv(BackDatas)
|
listItem := BackDatas
|
||||||
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
listItem = loadEnv(BackDatas)
|
||||||
|
}
|
||||||
return int64(total), listItem, nil
|
return int64(total), listItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
type CronjobService struct{}
|
type CronjobService struct{}
|
||||||
|
|
||||||
type ICronjobService interface {
|
type ICronjobService interface {
|
||||||
SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
|
SearchWithPage(search dto.PageCronjob, readOnly ...bool) (int64, interface{}, error)
|
||||||
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
|
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
|
||||||
Create(cronjobDto dto.CronjobOperate, operator string) error
|
Create(cronjobDto dto.CronjobOperate, operator string) error
|
||||||
LoadNextHandle(spec string) ([]string, error)
|
LoadNextHandle(spec string) ([]string, error)
|
||||||
@@ -50,7 +50,7 @@ func NewICronjobService() ICronjobService {
|
|||||||
return &CronjobService{}
|
return &CronjobService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interface{}, error) {
|
func (u *CronjobService) SearchWithPage(search dto.PageCronjob, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, cronjobs, err := cronjobRepo.Page(search.Page,
|
total, cronjobs, err := cronjobRepo.Page(search.Page,
|
||||||
search.PageSize,
|
search.PageSize,
|
||||||
repo.WithByGroups(search.GroupIDs),
|
repo.WithByGroups(search.GroupIDs),
|
||||||
@@ -83,6 +83,9 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
|||||||
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
|
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
|
||||||
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
|
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Secret = ""
|
||||||
|
}
|
||||||
dtoCronjobs = append(dtoCronjobs, item)
|
dtoCronjobs = append(dtoCronjobs, item)
|
||||||
}
|
}
|
||||||
return total, dtoCronjobs, err
|
return total, dtoCronjobs, err
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import (
|
|||||||
type DatabaseService struct{}
|
type DatabaseService struct{}
|
||||||
|
|
||||||
type IDatabaseService interface {
|
type IDatabaseService interface {
|
||||||
Get(name string) (dto.DatabaseInfo, error)
|
Get(name string, readOnly ...bool) (dto.DatabaseInfo, error)
|
||||||
SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error)
|
SearchWithPage(search dto.DatabaseSearch, readOnly ...bool) (int64, interface{}, error)
|
||||||
CheckDatabase(req dto.DatabaseCreate) bool
|
CheckDatabase(req dto.DatabaseCreate) bool
|
||||||
Create(req dto.DatabaseCreate) error
|
Create(req dto.DatabaseCreate) error
|
||||||
Update(req dto.DatabaseUpdate) error
|
Update(req dto.DatabaseUpdate) error
|
||||||
@@ -39,7 +39,7 @@ func NewIDatabaseService() IDatabaseService {
|
|||||||
return &DatabaseService{}
|
return &DatabaseService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error) {
|
func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, dbs, err := databaseRepo.Page(search.Page, search.PageSize,
|
total, dbs, err := databaseRepo.Page(search.Page, search.PageSize,
|
||||||
databaseRepo.WithTypeList(search.Type),
|
databaseRepo.WithTypeList(search.Type),
|
||||||
repo.WithByLikeName(search.Info),
|
repo.WithByLikeName(search.Info),
|
||||||
@@ -52,12 +52,16 @@ func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, inte
|
|||||||
if err := copier.Copy(&item, &db); err != nil {
|
if err := copier.Copy(&item, &db); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Password = ""
|
||||||
|
item.ClientKey = ""
|
||||||
|
}
|
||||||
datas = append(datas, item)
|
datas = append(datas, item)
|
||||||
}
|
}
|
||||||
return total, datas, err
|
return total, datas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) {
|
func (u *DatabaseService) Get(name string, readOnly ...bool) (dto.DatabaseInfo, error) {
|
||||||
var data dto.DatabaseInfo
|
var data dto.DatabaseInfo
|
||||||
remote, err := databaseRepo.Get(repo.WithByName(name))
|
remote, err := databaseRepo.Get(repo.WithByName(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,6 +70,10 @@ func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) {
|
|||||||
if err := copier.Copy(&data, &remote); err != nil {
|
if err := copier.Copy(&data, &remote); err != nil {
|
||||||
return data, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return data, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
data.Password = ""
|
||||||
|
data.ClientKey = ""
|
||||||
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
type MongodbService struct{}
|
type MongodbService struct{}
|
||||||
|
|
||||||
type IMongodbService interface {
|
type IMongodbService interface {
|
||||||
SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error)
|
SearchWithPage(search dto.MongodbDBSearch, readOnly ...bool) (int64, interface{}, error)
|
||||||
Create(ctx context.Context, req dto.MongodbDBCreate) (*model.DatabaseMongodb, error)
|
Create(ctx context.Context, req dto.MongodbDBCreate) (*model.DatabaseMongodb, error)
|
||||||
LoadFromRemote(req dto.MongodbLoadDB) error
|
LoadFromRemote(req dto.MongodbLoadDB) error
|
||||||
UpdateDescription(req dto.UpdateDescription) error
|
UpdateDescription(req dto.UpdateDescription) error
|
||||||
@@ -40,7 +40,7 @@ func NewIMongodbService() IMongodbService {
|
|||||||
return &MongodbService{}
|
return &MongodbService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error) {
|
func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, mongodbs, err := mongodbRepo.Page(
|
total, mongodbs, err := mongodbRepo.Page(
|
||||||
search.Page,
|
search.Page,
|
||||||
search.PageSize,
|
search.PageSize,
|
||||||
@@ -54,6 +54,9 @@ func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch) (int64, inte
|
|||||||
if err := copier.Copy(&item, &mongodb); err != nil {
|
if err := copier.Copy(&item, &mongodb); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Password = ""
|
||||||
|
}
|
||||||
dtoMongodbs = append(dtoMongodbs, item)
|
dtoMongodbs = append(dtoMongodbs, item)
|
||||||
}
|
}
|
||||||
return total, dtoMongodbs, err
|
return total, dtoMongodbs, err
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ type IMysqlService interface {
|
|||||||
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
|
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
|
||||||
Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
||||||
|
|
||||||
ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, error)
|
ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]dto.MysqlUser, error)
|
||||||
ListGrants(req dto.MysqlUserSearch) ([]dto.MysqlGrant, error)
|
ListGrants(req dto.MysqlUserSearch) ([]dto.MysqlGrant, error)
|
||||||
ListGrantSummary(req dto.MysqlGrantSummarySearch) (map[string][]dto.MysqlUser, error)
|
ListGrantSummary(req dto.MysqlGrantSummarySearch) (map[string][]dto.MysqlUser, error)
|
||||||
CreateUser(req dto.MysqlUserCreate) error
|
CreateUser(req dto.MysqlUserCreate) error
|
||||||
@@ -506,7 +506,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
|||||||
return &createItem, nil
|
return &createItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *MysqlService) ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, error) {
|
func (u *MysqlService) ListUsers(req dto.MysqlUserSearch, readOnly ...bool) ([]dto.MysqlUser, error) {
|
||||||
dbType, err := resolveDatabaseUserType(req.Database)
|
dbType, err := resolveDatabaseUserType(req.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -520,10 +520,14 @@ func (u *MysqlService) ListUsers(req dto.MysqlUserSearch) ([]dto.MysqlUser, erro
|
|||||||
if isMysqlSystemUser(user.Username) {
|
if isMysqlSystemUser(user.Username) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
password := user.Password
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
password = ""
|
||||||
|
}
|
||||||
res = append(res, dto.MysqlUser{
|
res = append(res, dto.MysqlUser{
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Host: user.Host,
|
Host: user.Host,
|
||||||
Password: user.Password,
|
Password: password,
|
||||||
Description: user.Description,
|
Description: user.Description,
|
||||||
IsDelete: user.IsDelete,
|
IsDelete: user.IsDelete,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
type PostgresqlService struct{}
|
type PostgresqlService struct{}
|
||||||
|
|
||||||
type IPostgresqlService interface {
|
type IPostgresqlService interface {
|
||||||
SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error)
|
SearchWithPage(search dto.PostgresqlDBSearch, readOnly ...bool) (int64, interface{}, error)
|
||||||
ListDBOption() ([]dto.PostgresqlOption, error)
|
ListDBOption() ([]dto.PostgresqlOption, error)
|
||||||
BindUser(req dto.PostgresqlBindUser) error
|
BindUser(req dto.PostgresqlBindUser) error
|
||||||
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
|
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
|
||||||
@@ -42,7 +42,7 @@ func NewIPostgresqlService() IPostgresqlService {
|
|||||||
return &PostgresqlService{}
|
return &PostgresqlService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error) {
|
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize,
|
total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize,
|
||||||
postgresqlRepo.WithByPostgresqlName(search.Database),
|
postgresqlRepo.WithByPostgresqlName(search.Database),
|
||||||
repo.WithByLikeName(search.Info),
|
repo.WithByLikeName(search.Info),
|
||||||
@@ -54,6 +54,9 @@ func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64
|
|||||||
if err := copier.Copy(&item, &pg); err != nil {
|
if err := copier.Copy(&item, &pg); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Password = ""
|
||||||
|
}
|
||||||
dtoPostgresqls = append(dtoPostgresqls, item)
|
dtoPostgresqls = append(dtoPostgresqls, item)
|
||||||
}
|
}
|
||||||
return total, dtoPostgresqls, err
|
return total, dtoPostgresqls, err
|
||||||
|
|||||||
@@ -61,18 +61,21 @@ func parseDevice(dev LsblkDevice) []response.DiskBasicInfo {
|
|||||||
|
|
||||||
var used, avail, totalSize string
|
var used, avail, totalSize string
|
||||||
var usePercent int
|
var usePercent int
|
||||||
isMounted := mountPoint != ""
|
isMounted := mountPoint != "" && mountPoint != "-"
|
||||||
isSystem := false
|
isSystem := false
|
||||||
|
|
||||||
if dev.Fstype == "LVM2_member" && len(dev.Children) > 0 {
|
if dev.Fstype == "LVM2_member" && len(dev.Children) > 0 {
|
||||||
for _, child := range dev.Children {
|
for _, child := range dev.Children {
|
||||||
if child.Type == "lvm" && child.Mountpoint != "" {
|
if child.Type == "lvm" && child.Mountpoint != "" && child.Mountpoint != "-" {
|
||||||
devicePath := "/dev/mapper/" + child.Name
|
totalSize, used, avail, usePercent, _ := getDiskUsageInfo(child.Mountpoint)
|
||||||
totalSize, used, avail, usePercent, _ := getDiskUsageInfo(devicePath)
|
childSize := child.Size
|
||||||
|
if totalSize != "" {
|
||||||
|
childSize = totalSize
|
||||||
|
}
|
||||||
|
|
||||||
childInfo := response.DiskBasicInfo{
|
childInfo := response.DiskBasicInfo{
|
||||||
Device: dev.Name,
|
Device: dev.Name,
|
||||||
Size: totalSize,
|
Size: childSize,
|
||||||
Model: dev.Model,
|
Model: dev.Model,
|
||||||
DiskType: diskType,
|
DiskType: diskType,
|
||||||
Filesystem: child.Fstype,
|
Filesystem: child.Fstype,
|
||||||
@@ -91,8 +94,7 @@ func parseDevice(dev LsblkDevice) []response.DiskBasicInfo {
|
|||||||
return list
|
return list
|
||||||
} else if isMounted {
|
} else if isMounted {
|
||||||
isSystem = isSystemDisk(mountPoint)
|
isSystem = isSystemDisk(mountPoint)
|
||||||
devicePath := "/dev/" + dev.Name
|
totalSize, used, avail, usePercent, _ = getDiskUsageInfo(mountPoint)
|
||||||
totalSize, used, avail, usePercent, _ = getDiskUsageInfo(devicePath)
|
|
||||||
if totalSize != "" {
|
if totalSize != "" {
|
||||||
size = totalSize
|
size = totalSize
|
||||||
}
|
}
|
||||||
@@ -229,9 +231,14 @@ func parseLsblkOutput(output string) ([]response.DiskBasicInfo, error) {
|
|||||||
size := fields["SIZE"]
|
size := fields["SIZE"]
|
||||||
|
|
||||||
if diskType == "lvm" {
|
if diskType == "lvm" {
|
||||||
total, used, avail, usePercent, _ := getDiskUsageInfo("/dev/mapper/" + name)
|
var total, used, avail string
|
||||||
if total != "" && fsType != "" {
|
var usePercent int
|
||||||
size = total
|
isMounted := mountPoint != "" && mountPoint != "-"
|
||||||
|
if isMounted {
|
||||||
|
total, used, avail, usePercent, _ = getDiskUsageInfo(mountPoint)
|
||||||
|
if total != "" && fsType != "" {
|
||||||
|
size = total
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lvmInfo := response.DiskBasicInfo{
|
lvmInfo := response.DiskBasicInfo{
|
||||||
@@ -246,7 +253,7 @@ func parseLsblkOutput(output string) ([]response.DiskBasicInfo, error) {
|
|||||||
Avail: avail,
|
Avail: avail,
|
||||||
UsePercent: usePercent,
|
UsePercent: usePercent,
|
||||||
MountPoint: mountPoint,
|
MountPoint: mountPoint,
|
||||||
IsMounted: mountPoint != "" && mountPoint != "-",
|
IsMounted: isMounted,
|
||||||
Serial: fields["SERIAL"],
|
Serial: fields["SERIAL"],
|
||||||
}
|
}
|
||||||
lvmMap[name] = lvmInfo
|
lvmMap[name] = lvmInfo
|
||||||
@@ -269,8 +276,8 @@ func parseLsblkOutput(output string) ([]response.DiskBasicInfo, error) {
|
|||||||
used, avail, totalSize string
|
used, avail, totalSize string
|
||||||
usePercent int
|
usePercent int
|
||||||
)
|
)
|
||||||
if mountPoint != "" {
|
if mountPoint != "" && mountPoint != "-" {
|
||||||
totalSize, used, avail, usePercent, _ = getDiskUsageInfo("/dev/" + name)
|
totalSize, used, avail, usePercent, _ = getDiskUsageInfo(mountPoint)
|
||||||
if totalSize != "" {
|
if totalSize != "" {
|
||||||
size = totalSize
|
size = totalSize
|
||||||
}
|
}
|
||||||
@@ -387,26 +394,39 @@ func getParentDevice(device string) string {
|
|||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDiskUsageInfo(device string) (size, used, avail string, usePercent int, err error) {
|
func getDiskUsageInfo(mountPoint string) (size, used, avail string, usePercent int, err error) {
|
||||||
output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("df", "-h", device)
|
// Query by mount point instead of a reconstructed device path. The mount table may record
|
||||||
|
// a different device alias such as /dev/root.
|
||||||
|
output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("df", "-h", "-P", mountPoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", 0, nil
|
return "", "", "", 0, nil
|
||||||
}
|
}
|
||||||
|
return parseDiskUsageOutput(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDiskUsageOutput(output string) (size, used, avail string, usePercent int, err error) {
|
||||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
if len(lines) > 1 {
|
for i := len(lines) - 1; i >= 0; i-- {
|
||||||
output = lines[len(lines)-1]
|
fields := strings.Fields(lines[i])
|
||||||
|
for index, field := range fields {
|
||||||
|
if index < 3 || !strings.HasSuffix(field, "%") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
percent, parseErr := strconv.Atoi(strings.TrimSuffix(field, "%"))
|
||||||
|
if parseErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fields[index-3], fields[index-2], fields[index-1], percent, nil
|
||||||
|
}
|
||||||
|
for index, field := range fields {
|
||||||
|
if index < 3 || index+1 >= len(fields) || field != "-" || !strings.HasPrefix(fields[index+1], "/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fields[index-3], fields[index-2], fields[index-1], 0, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Fields(output)
|
return "", "", "", 0, nil
|
||||||
if len(fields) >= 5 {
|
|
||||||
size = fields[1]
|
|
||||||
used = fields[2]
|
|
||||||
avail = fields[3]
|
|
||||||
usePercentStr := strings.TrimSuffix(fields[4], "%")
|
|
||||||
usePercent, _ = strconv.Atoi(usePercentStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return size, used, avail, usePercent, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatDisk(req dto.DiskFormatRequest) error {
|
func formatDisk(req dto.DiskFormatRequest) error {
|
||||||
|
|||||||
+79
-21
@@ -52,6 +52,8 @@ type FileService struct {
|
|||||||
|
|
||||||
const fileHistorySnapshotMaxSize = 10 * 1024 * 1024
|
const fileHistorySnapshotMaxSize = 10 * 1024 * 1024
|
||||||
|
|
||||||
|
var fileTransferLocks = newFileTransferLocks()
|
||||||
|
|
||||||
type IFileService interface {
|
type IFileService interface {
|
||||||
GetFileList(op request.FileOption) (response.FileInfo, error)
|
GetFileList(op request.FileOption) (response.FileInfo, error)
|
||||||
SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error)
|
SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error)
|
||||||
@@ -72,6 +74,7 @@ type IFileService interface {
|
|||||||
ChangeName(req request.FileRename) error
|
ChangeName(req request.FileRename) error
|
||||||
Wget(w request.FileWget) (string, error)
|
Wget(w request.FileWget) (string, error)
|
||||||
MvFile(m request.FileMove) error
|
MvFile(m request.FileMove) error
|
||||||
|
StopMvFile(taskID string) error
|
||||||
ChangeOwner(req request.FileRoleUpdate) error
|
ChangeOwner(req request.FileRoleUpdate) error
|
||||||
ChangeMode(op request.FileCreate) error
|
ChangeMode(op request.FileCreate) error
|
||||||
BatchChangeModeAndOwner(op request.FileRoleReq) error
|
BatchChangeModeAndOwner(op request.FileRoleReq) error
|
||||||
@@ -84,7 +87,7 @@ type IFileService interface {
|
|||||||
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
|
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
|
||||||
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
|
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
|
||||||
SetRemark(req request.FileRemarkUpdate) error
|
SetRemark(req request.FileRemarkUpdate) error
|
||||||
AISearch(req request.FileAISearch) (*response.FileAISearchResult, error)
|
AISearch(req request.FileAISearch, readOnly ...bool) (*response.FileAISearchResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -908,17 +911,62 @@ func (f *FileService) Wget(w request.FileWget) (string, error) {
|
|||||||
|
|
||||||
func (f *FileService) MvFile(m request.FileMove) error {
|
func (f *FileService) MvFile(m request.FileMove) error {
|
||||||
fo := files.NewFileOp()
|
fo := files.NewFileOp()
|
||||||
|
if err := validateFileMove(fo, m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if m.TaskID == "" {
|
||||||
|
m.TaskID = common.GetUuid()
|
||||||
|
}
|
||||||
|
if !fileTransferLocks.Acquire(m.TaskID, getFileTransferPaths(m)) {
|
||||||
|
return buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
|
taskItem, err := task.NewTask(m.NewPath, task.TaskExec, task.TaskScopeTask, m.TaskID, 1)
|
||||||
|
if err != nil {
|
||||||
|
fileTransferLocks.Release(m.TaskID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer fileTransferLocks.Release(m.TaskID)
|
||||||
|
taskItem.AddSubTaskWithOps(m.NewPath, func(t *task.Task) error {
|
||||||
|
t.LogStart(m.NewPath)
|
||||||
|
err := f.moveFileWithContext(t.TaskCtx, m)
|
||||||
|
if err != nil && t.TaskCtx.Err() != nil {
|
||||||
|
return t.TaskCtx.Err()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}, nil, 0, 0)
|
||||||
|
_ = taskItem.Execute()
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileService) StopMvFile(taskID string) error {
|
||||||
|
if cancel, ok := global.LoadTaskCancel(taskID); ok {
|
||||||
|
cancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return buserr.New("TaskNotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateFileMove(fo files.FileOp, m request.FileMove) error {
|
||||||
if !fo.Stat(m.NewPath) {
|
if !fo.Stat(m.NewPath) {
|
||||||
return buserr.New("ErrPathNotFound")
|
return buserr.New("ErrPathNotFound")
|
||||||
}
|
}
|
||||||
for _, oldPath := range m.OldPaths {
|
for _, oldPath := range append(append([]string{}, m.OldPaths...), m.CoverPaths...) {
|
||||||
if !fo.Stat(oldPath) {
|
if !fo.Stat(oldPath) {
|
||||||
return buserr.WithName("ErrFileNotFound", oldPath)
|
return buserr.WithName("ErrFileNotFound", oldPath)
|
||||||
}
|
}
|
||||||
if oldPath == m.NewPath || strings.Contains(m.NewPath, filepath.Clean(oldPath)+"/") {
|
oldPath = filepath.Clean(oldPath)
|
||||||
|
newPath := filepath.Clean(m.NewPath)
|
||||||
|
if oldPath == newPath || strings.HasPrefix(newPath, oldPath+string(filepath.Separator)) {
|
||||||
return buserr.New("ErrMovePathFailed")
|
return buserr.New("ErrMovePathFailed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileService) moveFileWithContext(ctx context.Context, m request.FileMove) error {
|
||||||
|
fo := files.NewFileOp()
|
||||||
type moveSnapshot struct {
|
type moveSnapshot struct {
|
||||||
path string
|
path string
|
||||||
content []byte
|
content []byte
|
||||||
@@ -934,13 +982,25 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
|||||||
}
|
}
|
||||||
if len(m.CoverPaths) > 0 {
|
if len(m.CoverPaths) > 0 {
|
||||||
for _, src := range m.CoverPaths {
|
for _, src := range m.CoverPaths {
|
||||||
if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fo.CopyAndReNameWithContext(ctx, src, m.NewPath, "", true); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
global.LOG.Errorf("cut copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
global.LOG.Errorf("cut copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fo.DeleteDir(src); err != nil {
|
||||||
|
removeErr := fmt.Errorf("remove merged source [%s] failed: %w", src, err)
|
||||||
|
errs = append(errs, removeErr)
|
||||||
|
global.LOG.Errorf("%s", removeErr.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := fo.Cut(m.OldPaths, m.NewPath, m.Name, m.Cover); err != nil {
|
if err := fo.CutWithContext(ctx, m.OldPaths, m.NewPath, m.Name, m.Cover); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, snapshot := range snapshots {
|
for _, snapshot := range snapshots {
|
||||||
@@ -951,18 +1011,18 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return aggregateFileMoveErrors(errs)
|
||||||
}
|
}
|
||||||
if m.Type == "copy" {
|
if m.Type == "copy" {
|
||||||
for _, src := range m.OldPaths {
|
for _, src := range m.OldPaths {
|
||||||
if err := fo.CopyAndReName(src, m.NewPath, m.Name, m.Cover); err != nil {
|
if err := fo.CopyAndReNameWithContext(ctx, src, m.NewPath, m.Name, m.Cover); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(m.CoverPaths) > 0 {
|
if len(m.CoverPaths) > 0 {
|
||||||
for _, src := range m.CoverPaths {
|
for _, src := range m.CoverPaths {
|
||||||
if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil {
|
if err := fo.CopyAndReNameWithContext(ctx, src, m.NewPath, "", true); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
||||||
}
|
}
|
||||||
@@ -970,14 +1030,7 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var errString string
|
return aggregateFileMoveErrors(errs)
|
||||||
for _, err := range errs {
|
|
||||||
errString += err.Error() + "\n"
|
|
||||||
}
|
|
||||||
if errString != "" {
|
|
||||||
return errors.New(errString)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readEditableFileHistoryContent(filePath string) ([]byte, os.FileMode, bool) {
|
func readEditableFileHistoryContent(filePath string) ([]byte, os.FileMode, bool) {
|
||||||
@@ -1507,7 +1560,7 @@ func (f *FileService) ConvertLog(req dto.PageInfo) (total int64, data []response
|
|||||||
return total, data, nil
|
return total, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileService) AISearch(req request.FileAISearch) (*response.FileAISearchResult, error) {
|
func (f *FileService) AISearch(req request.FileAISearch, readOnly ...bool) (*response.FileAISearchResult, error) {
|
||||||
root := filepath.Clean(strings.TrimSpace(req.Path))
|
root := filepath.Clean(strings.TrimSpace(req.Path))
|
||||||
if root == "" {
|
if root == "" {
|
||||||
return nil, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
return nil, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
||||||
@@ -1560,10 +1613,15 @@ func (f *FileService) AISearch(req request.FileAISearch) (*response.FileAISearch
|
|||||||
return nil, buserr.WithDetail("ErrFileAISearchBadPattern", err.Error(), nil)
|
return nil, buserr.WithDetail("ErrFileAISearchBadPattern", err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, timeout, err := terminalai.LoadFileAIRuntimeConfig()
|
var cfg terminalai.GeneratorConfig
|
||||||
aiEnabled := err == nil
|
var timeout time.Duration
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
aiEnabled := false
|
||||||
return nil, err
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
cfg, timeout, err = terminalai.LoadFileAIRuntimeConfig()
|
||||||
|
aiEnabled = err == nil
|
||||||
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items, truncated, err := files.CollectDirInventory(root, containSub, maxItems)
|
items, truncated, err := files.CollectDirInventory(root, containSub, maxItems)
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ var fileShareCodeRegexp = regexp.MustCompile(`^[A-Za-z0-9]{10,16}$`)
|
|||||||
|
|
||||||
type IFileShareService interface {
|
type IFileShareService interface {
|
||||||
Create(req request.FileShareCreate) (*response.FileShareInfo, error)
|
Create(req request.FileShareCreate) (*response.FileShareInfo, error)
|
||||||
Page(req dto.PageInfo) (int64, []response.FileShareInfo, error)
|
Page(req dto.PageInfo, readOnly ...bool) (int64, []response.FileShareInfo, error)
|
||||||
GetByPath(path string) (*response.FileShareInfo, error)
|
GetByPath(path string, readOnly ...bool) (*response.FileShareInfo, error)
|
||||||
GetByCode(code string) (*response.FileShareInfo, error)
|
GetByCode(code string, readOnly ...bool) (*response.FileShareInfo, error)
|
||||||
GetPublicByCode(code string) (*response.FileSharePublicInfo, error)
|
GetPublicByCode(code string, readOnly ...bool) (*response.FileSharePublicInfo, error)
|
||||||
DeleteByPath(path string) error
|
DeleteByPath(path string) error
|
||||||
SharePathCodeMap() (map[string]string, error)
|
SharePathCodeMap() (map[string]string, error)
|
||||||
Check(code, password string) error
|
Check(code, password string, readOnly ...bool) error
|
||||||
PrepareDownload(code, password string) (filePath, fileName string, err error)
|
PrepareDownload(code, password string, readOnly ...bool) (filePath, fileName string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIFileShareService() IFileShareService {
|
func NewIFileShareService() IFileShareService {
|
||||||
@@ -212,14 +212,14 @@ func (s *FileShareService) Create(req request.FileShareCreate) (*response.FileSh
|
|||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) Page(req dto.PageInfo) (int64, []response.FileShareInfo, error) {
|
func (s *FileShareService) Page(req dto.PageInfo, readOnly ...bool) (int64, []response.FileShareInfo, error) {
|
||||||
items, err := fileShareRepo.All()
|
items, err := fileShareRepo.All()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
result := make([]response.FileShareInfo, 0, len(items))
|
result := make([]response.FileShareInfo, 0, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if err := s.pruneInvalidShare(item); err != nil {
|
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||||
@@ -239,7 +239,7 @@ func (s *FileShareService) Page(req dto.PageInfo) (int64, []response.FileShareIn
|
|||||||
return int64(total), result[start:end], nil
|
return int64(total), result[start:end], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) GetByPath(path string) (*response.FileShareInfo, error) {
|
func (s *FileShareService) GetByPath(path string, readOnly ...bool) (*response.FileShareInfo, error) {
|
||||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByPath(strings.TrimSpace(path)))
|
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByPath(strings.TrimSpace(path)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -247,7 +247,7 @@ func (s *FileShareService) GetByPath(path string) (*response.FileShareInfo, erro
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.pruneInvalidShare(item); err != nil {
|
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||||
@@ -258,7 +258,7 @@ func (s *FileShareService) GetByPath(path string) (*response.FileShareInfo, erro
|
|||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) GetByCode(code string) (*response.FileShareInfo, error) {
|
func (s *FileShareService) GetByCode(code string, readOnly ...bool) (*response.FileShareInfo, error) {
|
||||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -266,7 +266,7 @@ func (s *FileShareService) GetByCode(code string) (*response.FileShareInfo, erro
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.pruneInvalidShare(item); err != nil {
|
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||||
@@ -276,7 +276,7 @@ func (s *FileShareService) GetByCode(code string) (*response.FileShareInfo, erro
|
|||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) GetPublicByCode(code string) (*response.FileSharePublicInfo, error) {
|
func (s *FileShareService) GetPublicByCode(code string, readOnly ...bool) (*response.FileSharePublicInfo, error) {
|
||||||
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
item, err := fileShareRepo.GetFirst(fileShareRepo.WithByCode(strings.TrimSpace(code)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -284,7 +284,7 @@ func (s *FileShareService) GetPublicByCode(code string) (*response.FileSharePubl
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.pruneInvalidShare(item); err != nil {
|
if err := s.pruneInvalidShare(item, readOnly...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && time.Now().Unix() > item.ExpiresUnix {
|
||||||
@@ -324,32 +324,38 @@ func (s *FileShareService) SharePathCodeMap() (map[string]string, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) Check(code, password string) error {
|
func (s *FileShareService) Check(code, password string, readOnly ...bool) error {
|
||||||
_, err := s.check(code, password)
|
_, err := s.check(code, password, readOnly...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) PrepareDownload(code, password string) (string, string, error) {
|
func (s *FileShareService) PrepareDownload(code, password string, readOnly ...bool) (string, string, error) {
|
||||||
item, err := s.check(code, password)
|
item, err := s.check(code, password, readOnly...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return item.Path, item.FileName, nil
|
return item.Path, item.FileName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) pruneInvalidShare(item model.FileShare) error {
|
func (s *FileShareService) pruneInvalidShare(item model.FileShare, readOnly ...bool) error {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||||
}
|
}
|
||||||
info, err := os.Stat(item.Path)
|
info, err := os.Stat(item.Path)
|
||||||
if err != nil || info.IsDir() {
|
if err != nil || info.IsDir() {
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
return fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileShareService) check(code, password string) (*model.FileShare, error) {
|
func (s *FileShareService) check(code, password string, readOnly ...bool) (*model.FileShare, error) {
|
||||||
code = strings.TrimSpace(code)
|
code = strings.TrimSpace(code)
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
if code == "" {
|
if code == "" {
|
||||||
@@ -366,7 +372,9 @@ func (s *FileShareService) check(code, password string) (*model.FileShare, error
|
|||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
if item.ExpiresUnix > 0 && now > item.ExpiresUnix {
|
||||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||||
|
}
|
||||||
return nil, buserr.New("ErrFileShareExpired")
|
return nil, buserr.New("ErrFileShareExpired")
|
||||||
}
|
}
|
||||||
if item.PasswordHash != "" {
|
if item.PasswordHash != "" {
|
||||||
@@ -377,7 +385,9 @@ func (s *FileShareService) check(code, password string) (*model.FileShare, error
|
|||||||
|
|
||||||
info, err := os.Stat(item.Path)
|
info, err := os.Stat(item.Path)
|
||||||
if err != nil || info.IsDir() {
|
if err != nil || info.IsDir() {
|
||||||
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
_ = fileShareRepo.Delete(repo.WithByID(item.ID))
|
||||||
|
}
|
||||||
return nil, buserr.New("ErrFileSharePath")
|
return nil, buserr.New("ErrFileSharePath")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileTransferLockSet struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
paths map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileTransferLocks() *fileTransferLockSet {
|
||||||
|
return &fileTransferLockSet{paths: make(map[string][]string)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fileTransferLockSet) Acquire(taskID string, transferPaths []string) bool {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
for _, activePaths := range s.paths {
|
||||||
|
for _, activePath := range activePaths {
|
||||||
|
for _, transferPath := range transferPaths {
|
||||||
|
if fileTransferPathsOverlap(activePath, transferPath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.paths[taskID] = transferPaths
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fileTransferLockSet) Release(taskID string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
delete(s.paths, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileTransferPaths(req request.FileMove) []string {
|
||||||
|
paths := make([]string, 0, 1+len(req.OldPaths)+len(req.CoverPaths))
|
||||||
|
paths = append(paths, req.NewPath)
|
||||||
|
paths = append(paths, req.OldPaths...)
|
||||||
|
paths = append(paths, req.CoverPaths...)
|
||||||
|
|
||||||
|
unique := make(map[string]struct{}, len(paths))
|
||||||
|
result := make([]string, 0, len(paths))
|
||||||
|
for _, item := range paths {
|
||||||
|
item = filepath.Clean(item)
|
||||||
|
if _, ok := unique[item]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unique[item] = struct{}{}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileTransferPathsOverlap(first, second string) bool {
|
||||||
|
return first == second || strings.HasPrefix(first, second+string(filepath.Separator)) || strings.HasPrefix(second, first+string(filepath.Separator))
|
||||||
|
}
|
||||||
|
|
||||||
|
func aggregateFileMoveErrors(errs []error) error {
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var errString strings.Builder
|
||||||
|
for _, err := range errs {
|
||||||
|
errString.WriteString(err.Error())
|
||||||
|
errString.WriteByte('\n')
|
||||||
|
}
|
||||||
|
return errors.New(errString.String())
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ type FtpService struct{}
|
|||||||
|
|
||||||
type IFtpService interface {
|
type IFtpService interface {
|
||||||
LoadBaseInfo() (dto.FtpBaseInfo, error)
|
LoadBaseInfo() (dto.FtpBaseInfo, error)
|
||||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||||
Operate(operation string) error
|
Operate(operation string) error
|
||||||
Create(req dto.FtpCreate) (uint, error)
|
Create(req dto.FtpCreate) (uint, error)
|
||||||
CreateWebsite(req dto.FtpCreate) (uint, error)
|
CreateWebsite(req dto.FtpCreate) (uint, error)
|
||||||
@@ -74,7 +74,7 @@ func (u *FtpService) Operate(operation string) error {
|
|||||||
return client.Operate(operation)
|
return client.Operate(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (f *FtpService) SearchWithPage(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderDesc("created_at"))
|
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderDesc("created_at"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
@@ -85,7 +85,11 @@ func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{},
|
|||||||
if err := copier.Copy(&item, &user); err != nil {
|
if err := copier.Copy(&item, &user); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
}
|
}
|
||||||
item.Password, _ = encrypt.StringDecrypt(item.Password)
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Password = ""
|
||||||
|
} else {
|
||||||
|
item.Password, _ = encrypt.StringDecrypt(item.Password)
|
||||||
|
}
|
||||||
users = append(users, item)
|
users = append(users, item)
|
||||||
}
|
}
|
||||||
return total, users, err
|
return total, users, err
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type IHostService interface {
|
|||||||
TestByInfo(req dto.HostConnTest) bool
|
TestByInfo(req dto.HostConnTest) bool
|
||||||
GetHostByID(id uint) (*dto.HostInfo, error)
|
GetHostByID(id uint) (*dto.HostInfo, error)
|
||||||
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
|
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
|
||||||
SearchWithPage(search dto.SearchPageWithGroup) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchPageWithGroup, readOnly ...bool) (int64, interface{}, error)
|
||||||
Create(req dto.HostOperate) (*dto.HostInfo, error)
|
Create(req dto.HostOperate) (*dto.HostInfo, error)
|
||||||
Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error)
|
Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error)
|
||||||
Delete(id []uint) error
|
Delete(id []uint) error
|
||||||
@@ -111,7 +111,7 @@ func (u *HostService) TestLocalConn(id uint) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup) (int64, interface{}, error) {
|
func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup, readOnly ...bool) (int64, interface{}, error) {
|
||||||
var options []repo.DBOption
|
var options []repo.DBOption
|
||||||
if len(req.Info) != 0 {
|
if len(req.Info) != 0 {
|
||||||
options = append(options, hostRepo.WithByInfo(req.Info))
|
options = append(options, hostRepo.WithByInfo(req.Info))
|
||||||
@@ -155,6 +155,11 @@ func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup) (int64, interf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.Password = ""
|
||||||
|
item.PrivateKey = ""
|
||||||
|
item.PassPhrase = ""
|
||||||
|
}
|
||||||
dtoHosts = append(dtoHosts, item)
|
dtoHosts = append(dtoHosts, item)
|
||||||
}
|
}
|
||||||
return total, dtoHosts, err
|
return total, dtoHosts, err
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
type ImageRepoService struct{}
|
type ImageRepoService struct{}
|
||||||
|
|
||||||
type IImageRepoService interface {
|
type IImageRepoService interface {
|
||||||
Page(search dto.SearchWithPage) (int64, interface{}, error)
|
Page(search dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||||
List() ([]dto.ImageRepoOption, error)
|
List() ([]dto.ImageRepoOption, error)
|
||||||
Login(req dto.OperateByID) error
|
Login(req dto.OperateByID) error
|
||||||
Create(req dto.ImageRepoCreate) error
|
Create(req dto.ImageRepoCreate) error
|
||||||
@@ -36,10 +36,13 @@ func NewIImageRepoService() IImageRepoService {
|
|||||||
return &ImageRepoService{}
|
return &ImageRepoService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ImageRepoService) Page(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (u *ImageRepoService) Page(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, ops, err := imageRepoRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderDesc("created_at"))
|
total, ops, err := imageRepoRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderDesc("created_at"))
|
||||||
var dtoOps []dto.ImageRepoInfo
|
var dtoOps []dto.ImageRepoInfo
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
op.Password = ""
|
||||||
|
}
|
||||||
var item dto.ImageRepoInfo
|
var item dto.ImageRepoInfo
|
||||||
if err := copier.Copy(&item, &op); err != nil {
|
if err := copier.Copy(&item, &op); err != nil {
|
||||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type IMcpServerService interface {
|
type IMcpServerService interface {
|
||||||
Page(req request.McpServerSearch) response.McpServersRes
|
Page(req request.McpServerSearch, readOnly ...bool) response.McpServersRes
|
||||||
Detail(req request.McpServerDetail) (response.McpServerDTO, error)
|
Detail(req request.McpServerDetail) (response.McpServerDTO, error)
|
||||||
Create(create request.McpServerCreate) error
|
Create(create request.McpServerCreate) error
|
||||||
Update(req request.McpServerUpdate) error
|
Update(req request.McpServerUpdate) error
|
||||||
@@ -65,7 +65,7 @@ func NewIMcpServerService() IMcpServerService {
|
|||||||
return &McpServerService{}
|
return &McpServerService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m McpServerService) Page(req request.McpServerSearch) response.McpServersRes {
|
func (m McpServerService) Page(req request.McpServerSearch, readOnly ...bool) response.McpServersRes {
|
||||||
var (
|
var (
|
||||||
res response.McpServersRes
|
res response.McpServersRes
|
||||||
items []response.McpServerDTO
|
items []response.McpServerDTO
|
||||||
@@ -74,6 +74,10 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR
|
|||||||
total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
|
total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
|
||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
normalizeMcpServerGateway(&item)
|
normalizeMcpServerGateway(&item)
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
item.DockerCompose = ""
|
||||||
|
item.Env = ""
|
||||||
|
}
|
||||||
items = append(items, response.McpServerDTO{
|
items = append(items, response.McpServerDTO{
|
||||||
McpServer: item,
|
McpServer: item,
|
||||||
Environments: make([]request.Environment, 0),
|
Environments: make([]request.Environment, 0),
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
|
|||||||
if (item.MaxPowerLimit == "" || item.MaxPowerLimit == "N/A") && (item.PowerDraw == "" || item.PowerDraw == "N/A") {
|
if (item.MaxPowerLimit == "" || item.MaxPowerLimit == "N/A") && (item.PowerDraw == "" || item.PowerDraw == "N/A") {
|
||||||
chartHide.Power = true
|
chartHide.Power = true
|
||||||
}
|
}
|
||||||
|
chartHide.PowerLimit = item.MaxPowerLimit == "" || item.MaxPowerLimit == "N/A"
|
||||||
chartHide.Temperature = item.Temperature == "" || item.Temperature == "N/A"
|
chartHide.Temperature = item.Temperature == "" || item.Temperature == "N/A"
|
||||||
chartHide.Speed = item.FanSpeed == "" || item.FanSpeed == "N/A"
|
chartHide.Speed = item.FanSpeed == "" || item.FanSpeed == "N/A"
|
||||||
data.ChartHide = append(data.ChartHide, chartHide)
|
data.ChartHide = append(data.ChartHide, chartHide)
|
||||||
@@ -174,6 +175,7 @@ func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
|
|||||||
var chartHide dto.GPUChartHide
|
var chartHide dto.GPUChartHide
|
||||||
chartHide.GPU = true
|
chartHide.GPU = true
|
||||||
chartHide.Speed = true
|
chartHide.Speed = true
|
||||||
|
chartHide.PowerLimit = true
|
||||||
chartHide.ProductName = fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName)
|
chartHide.ProductName = fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName)
|
||||||
if (item.Stats.MemoryUsed == "" || item.Stats.MemoryUsed == "N/A") && (item.Basic.Memory == "" || item.Basic.FreeMemory == "N/A") {
|
if (item.Stats.MemoryUsed == "" || item.Stats.MemoryUsed == "N/A") && (item.Basic.Memory == "" || item.Basic.FreeMemory == "N/A") {
|
||||||
chartHide.Memory = true
|
chartHide.Memory = true
|
||||||
@@ -344,6 +346,7 @@ func (m *MonitorService) Run() {
|
|||||||
_ = monitorRepo.DelMonitorBase(timeForDelete)
|
_ = monitorRepo.DelMonitorBase(timeForDelete)
|
||||||
_ = monitorRepo.DelMonitorIO(timeForDelete)
|
_ = monitorRepo.DelMonitorIO(timeForDelete)
|
||||||
_ = monitorRepo.DelMonitorNet(timeForDelete)
|
_ = monitorRepo.DelMonitorNet(timeForDelete)
|
||||||
|
_ = monitorRepo.DelMonitorGPU(timeForDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorService) loadDiskIO() {
|
func (m *MonitorService) loadDiskIO() {
|
||||||
@@ -599,6 +602,7 @@ func saveGPUDataToDB() {
|
|||||||
}
|
}
|
||||||
gpuInfo, err := client.LoadGpuInfo()
|
gpuInfo, err := client.LoadGpuInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load gpu monitor data failed, err: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var list []model.MonitorGPU
|
var list []model.MonitorGPU
|
||||||
@@ -631,6 +635,7 @@ func saveXPUDataToDB() {
|
|||||||
}
|
}
|
||||||
xpuInfo, err := client.LoadGpuInfo()
|
xpuInfo, err := client.LoadGpuInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load xpu monitor data failed, err: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var list []model.MonitorGPU
|
var list []model.MonitorGPU
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ type IRuntimeService interface {
|
|||||||
Create(create request.RuntimeCreate) (*model.Runtime, error)
|
Create(create request.RuntimeCreate) (*model.Runtime, error)
|
||||||
Delete(delete request.RuntimeDelete) error
|
Delete(delete request.RuntimeDelete) error
|
||||||
Update(req request.RuntimeUpdate) error
|
Update(req request.RuntimeUpdate) error
|
||||||
Get(id uint) (res *response.RuntimeDTO, err error)
|
Get(id uint, readOnly ...bool) (res *response.RuntimeDTO, err error)
|
||||||
GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error)
|
GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error)
|
||||||
OperateRuntime(req request.RuntimeOperate) error
|
OperateRuntime(req request.RuntimeOperate) error
|
||||||
GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error)
|
GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error)
|
||||||
@@ -70,7 +70,7 @@ type IRuntimeService interface {
|
|||||||
GetFPMConfig(id uint) (*request.FPMConfig, error)
|
GetFPMConfig(id uint) (*request.FPMConfig, error)
|
||||||
|
|
||||||
UpdatePHPContainer(req request.PHPContainerConfig) error
|
UpdatePHPContainer(req request.PHPContainerConfig) error
|
||||||
GetPHPContainerConfig(id uint) (*request.PHPContainerConfig, error)
|
GetPHPContainerConfig(id uint, readOnly ...bool) (*request.PHPContainerConfig, error)
|
||||||
|
|
||||||
GetSupervisorProcess(id uint) ([]response.SupervisorProcessConfig, error)
|
GetSupervisorProcess(id uint) ([]response.SupervisorProcessConfig, error)
|
||||||
OperateSupervisorProcess(req request.PHPSupervisorProcessConfig) error
|
OperateSupervisorProcess(req request.PHPSupervisorProcessConfig) error
|
||||||
@@ -332,8 +332,11 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
|
|||||||
if len(runtimes) == 0 {
|
if len(runtimes) == 0 {
|
||||||
return 0, res, nil
|
return 0, res, nil
|
||||||
}
|
}
|
||||||
if err = SyncRuntimesStatus(runtimes); err != nil {
|
readOnlyMode := isDemoReadOnly(req.ReadOnly)
|
||||||
return 0, nil, err
|
if !readOnlyMode {
|
||||||
|
if err = SyncRuntimesStatus(runtimes); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, runtime := range runtimes {
|
for _, runtime := range runtimes {
|
||||||
if runtime.Resource == constant.ResourceLocal {
|
if runtime.Resource == constant.ResourceLocal {
|
||||||
@@ -347,7 +350,7 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
|
|||||||
}
|
}
|
||||||
detail, _ := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
|
detail, _ := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
|
||||||
if detail.AppId == 0 {
|
if detail.AppId == 0 {
|
||||||
appID, appDetailID := handleRuntimeDetailID(runtime)
|
appID, appDetailID := handleRuntimeDetailID(runtime, !readOnlyMode)
|
||||||
runtimeDTO.AppDetailID = appDetailID
|
runtimeDTO.AppDetailID = appDetailID
|
||||||
runtimeDTO.AppID = appID
|
runtimeDTO.AppID = appID
|
||||||
} else {
|
} else {
|
||||||
@@ -358,6 +361,9 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
|
|||||||
runtimeDTO.Params[k] = v
|
runtimeDTO.Params[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if readOnlyMode {
|
||||||
|
redactSensitiveValues(runtimeDTO.Params)
|
||||||
|
}
|
||||||
runtimeDTO.ExposedPorts, _ = loadComposeExposedPortsFromEnv(envs, "", false)
|
runtimeDTO.ExposedPorts, _ = loadComposeExposedPortsFromEnv(envs, "", false)
|
||||||
res = append(res, runtimeDTO)
|
res = append(res, runtimeDTO)
|
||||||
}
|
}
|
||||||
@@ -538,7 +544,7 @@ func deleteRuntimeImages(runtime *model.Runtime, taskItem *task.Task) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
|
func (r *RuntimeService) Get(id uint, readOnly ...bool) (*response.RuntimeDTO, error) {
|
||||||
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id))
|
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -627,6 +633,11 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
redactSensitiveValues(res.Params)
|
||||||
|
redactSensitiveAppParams(res.AppParams)
|
||||||
|
redactSensitiveEnvironments(res.Environments)
|
||||||
|
}
|
||||||
|
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
@@ -1348,7 +1359,7 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuntimeService) GetPHPContainerConfig(id uint) (*request.PHPContainerConfig, error) {
|
func (r *RuntimeService) GetPHPContainerConfig(id uint, readOnly ...bool) (*request.PHPContainerConfig, error) {
|
||||||
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id))
|
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1365,6 +1376,9 @@ func (r *RuntimeService) GetPHPContainerConfig(id uint) (*request.PHPContainerCo
|
|||||||
Volumes: runtimeDTO.Volumes,
|
Volumes: runtimeDTO.Volumes,
|
||||||
ExtraHosts: runtimeDTO.ExtraHosts,
|
ExtraHosts: runtimeDTO.ExtraHosts,
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
redactSensitiveEnvironments(res.Environments)
|
||||||
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,9 @@ func SyncRuntimesStatus(runtimes []model.Runtime) error {
|
|||||||
case "restarting":
|
case "restarting":
|
||||||
runtimes[index].Status = constant.StatusRestarting
|
runtimes[index].Status = constant.StatusRestarting
|
||||||
}
|
}
|
||||||
_ = runtimeRepo.Save(&runtimes[index])
|
if !global.CONF.Base.IsDemo {
|
||||||
|
_ = runtimeRepo.Save(&runtimes[index])
|
||||||
|
}
|
||||||
delete(runtimeContainer, contain.Names[0])
|
delete(runtimeContainer, contain.Names[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,6 +297,9 @@ func SyncRuntimeContainerStatus(runtime *model.Runtime) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if global.CONF.Base.IsDemo {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return runtimeRepo.Save(runtime)
|
return runtimeRepo.Save(runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1148,7 +1153,7 @@ func getOperation(operate, pkgManager string) string {
|
|||||||
return ops[1]
|
return ops[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRuntimeDetailID(runtime model.Runtime) (uint, uint) {
|
func handleRuntimeDetailID(runtime model.Runtime, persist bool) (uint, uint) {
|
||||||
app, _ := appRepo.GetFirst(appRepo.WithKey(runtime.Type))
|
app, _ := appRepo.GetFirst(appRepo.WithKey(runtime.Type))
|
||||||
if app.ID == 0 {
|
if app.ID == 0 {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
@@ -1158,6 +1163,8 @@ func handleRuntimeDetailID(runtime model.Runtime) (uint, uint) {
|
|||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
runtime.AppDetailID = appDetail.ID
|
runtime.AppDetailID = appDetail.ID
|
||||||
_ = runtimeRepo.Save(&runtime)
|
if persist && !global.CONF.Base.IsDemo {
|
||||||
|
_ = runtimeRepo.Save(&runtime)
|
||||||
|
}
|
||||||
return app.ID, appDetail.ID
|
return app.ID, appDetail.ID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type ISettingService interface {
|
|||||||
SaveConnInfo(req dto.SSHConnData) error
|
SaveConnInfo(req dto.SSHConnData) error
|
||||||
SetDefaultIsConn(req dto.SSHDefaultConn) error
|
SetDefaultIsConn(req dto.SSHDefaultConn) error
|
||||||
GetSystemProxy() (*dto.SystemProxy, error)
|
GetSystemProxy() (*dto.SystemProxy, error)
|
||||||
GetLocalConn() dto.SSHConnData
|
GetLocalConn(readOnly ...bool) dto.SSHConnData
|
||||||
GetLocalConnForSSH() (dto.SSHConnData, error)
|
GetLocalConnForSSH() (dto.SSHConnData, error)
|
||||||
|
|
||||||
SaveDescription(req dto.CommonDescription) error
|
SaveDescription(req dto.CommonDescription) error
|
||||||
@@ -311,8 +311,14 @@ func (u *SettingService) loadLocalConn() dto.SSHConnData {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *SettingService) GetLocalConn() dto.SSHConnData {
|
func (u *SettingService) GetLocalConn(readOnly ...bool) dto.SSHConnData {
|
||||||
data := u.loadLocalConn()
|
data := u.loadLocalConn()
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
data.Password = ""
|
||||||
|
data.PrivateKey = ""
|
||||||
|
data.PassPhrase = ""
|
||||||
|
return data
|
||||||
|
}
|
||||||
if len(data.Password) != 0 {
|
if len(data.Password) != 0 {
|
||||||
data.Password = base64.StdEncoding.EncodeToString([]byte(data.Password))
|
data.Password = base64.StdEncoding.EncodeToString([]byte(data.Password))
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-10
@@ -57,7 +57,7 @@ type ISSHService interface {
|
|||||||
SyncRootCert() error
|
SyncRootCert() error
|
||||||
CreateRootCert(req dto.RootCertOperate) error
|
CreateRootCert(req dto.RootCertOperate) error
|
||||||
EditRootCert(req dto.RootCertOperate) error
|
EditRootCert(req dto.RootCertOperate) error
|
||||||
SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error)
|
SearchRootCerts(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error)
|
||||||
DeleteRootCerts(req dto.ForceDelete) error
|
DeleteRootCerts(req dto.ForceDelete) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +585,7 @@ func (u *SSHService) EditRootCert(req dto.RootCertOperate) error {
|
|||||||
return hostRepo.SaveCert(&cert)
|
return hostRepo.SaveCert(&cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *SSHService) SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (u *SSHService) SearchRootCerts(req dto.SearchWithPage, readOnly ...bool) (int64, interface{}, error) {
|
||||||
total, records, err := hostRepo.PageCert(req.Page, req.PageSize)
|
total, records, err := hostRepo.PageCert(req.Page, req.PageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
@@ -597,12 +597,15 @@ func (u *SSHService) SearchRootCerts(req dto.SearchWithPage) (int64, interface{}
|
|||||||
if err == nil && len(publicItem) != 0 {
|
if err == nil && len(publicItem) != 0 {
|
||||||
publicBase64 = base64.StdEncoding.EncodeToString(publicItem)
|
publicBase64 = base64.StdEncoding.EncodeToString(publicItem)
|
||||||
}
|
}
|
||||||
privateItem, _ := os.ReadFile(records[i].PrivateKeyPath)
|
|
||||||
var privateBase64 string
|
var privateBase64 string
|
||||||
if err == nil && len(publicItem) != 0 {
|
var passPhrase string
|
||||||
privateBase64 = base64.StdEncoding.EncodeToString(privateItem)
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
privateItem, _ := os.ReadFile(records[i].PrivateKeyPath)
|
||||||
|
if len(privateItem) != 0 {
|
||||||
|
privateBase64 = base64.StdEncoding.EncodeToString(privateItem)
|
||||||
|
}
|
||||||
|
passPhrase, _ = encrypt.StringDecryptWithBase64(records[i].PassPhrase)
|
||||||
}
|
}
|
||||||
passPhrase, _ := encrypt.StringDecryptWithBase64(records[i].PassPhrase)
|
|
||||||
datas = append(datas, dto.RootCert{
|
datas = append(datas, dto.RootCert{
|
||||||
ID: records[i].ID,
|
ID: records[i].ID,
|
||||||
CreatedAt: records[i].CreatedAt,
|
CreatedAt: records[i].CreatedAt,
|
||||||
@@ -1328,6 +1331,8 @@ func collectSSHLogItems(lines []string, filter, status string) []sshParsedLog {
|
|||||||
var items []sshParsedLog
|
var items []sshParsedLog
|
||||||
auxiliaryIndex := make(map[string]int)
|
auxiliaryIndex := make(map[string]int)
|
||||||
sessionHasAuthEvent := make(map[string]bool)
|
sessionHasAuthEvent := make(map[string]bool)
|
||||||
|
authenticatedEndpoints := make(map[string]bool)
|
||||||
|
matchedTerminalSessions := make(map[string]bool)
|
||||||
for lineIndex, line := range lines {
|
for lineIndex, line := range lines {
|
||||||
if !shouldParseSSHLogLine(line, status, filter) {
|
if !shouldParseSSHLogLine(line, status, filter) {
|
||||||
continue
|
continue
|
||||||
@@ -1338,11 +1343,32 @@ func collectSSHLogItems(lines []string, filter, status string) []sshParsedLog {
|
|||||||
}
|
}
|
||||||
item.Index = lineIndex
|
item.Index = lineIndex
|
||||||
item.Search = line
|
item.Search = line
|
||||||
|
endpointKey := loadSSHLogEndpointKey(item.History)
|
||||||
if isSSHAuthEvent(item) && item.SessionKey != "" {
|
if isSSHAuthEvent(item) && item.SessionKey != "" {
|
||||||
sessionHasAuthEvent[item.SessionKey] = true
|
sessionHasAuthEvent[item.SessionKey] = true
|
||||||
|
delete(matchedTerminalSessions, item.SessionKey)
|
||||||
|
if endpointKey != "" {
|
||||||
|
if item.History.Status == constant.StatusSuccess {
|
||||||
|
authenticatedEndpoints[endpointKey] = true
|
||||||
|
} else {
|
||||||
|
delete(authenticatedEndpoints, endpointKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if endpointKey != "" && isSSHTerminalEvent(item) && authenticatedEndpoints[endpointKey] {
|
||||||
|
// A successful authentication and its terminal log can be emitted by
|
||||||
|
// different sshd processes. Associate only this active connection by
|
||||||
|
// endpoint, then clear it so a reused client port starts a new session.
|
||||||
|
delete(authenticatedEndpoints, endpointKey)
|
||||||
|
matchedTerminalSessions[item.SessionKey] = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isSSHTerminalEvent(item) && matchedTerminalSessions[item.SessionKey] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(matchedTerminalSessions, item.SessionKey)
|
||||||
if index, ok := auxiliaryIndex[item.SessionKey]; ok {
|
if index, ok := auxiliaryIndex[item.SessionKey]; ok {
|
||||||
items[index].Search += "\n" + line
|
items[index].Search += "\n" + line
|
||||||
continue
|
continue
|
||||||
@@ -1381,6 +1407,13 @@ func isSSHAuthEvent(item sshParsedLog) bool {
|
|||||||
return item.History.Status == constant.StatusSuccess || item.History.AuthMode != ""
|
return item.History.Status == constant.StatusSuccess || item.History.AuthMode != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSSHTerminalEvent(item sshParsedLog) bool {
|
||||||
|
message := item.History.Message
|
||||||
|
return strings.HasPrefix(message, "Connection closed by ") ||
|
||||||
|
strings.HasPrefix(message, "Disconnected from ") ||
|
||||||
|
strings.HasPrefix(message, "Received disconnect from ")
|
||||||
|
}
|
||||||
|
|
||||||
func shouldParseSSHLogLine(line, status, filter string) bool {
|
func shouldParseSSHLogLine(line, status, filter string) bool {
|
||||||
if !strings.Contains(line, "sshd") {
|
if !strings.Contains(line, "sshd") {
|
||||||
return false
|
return false
|
||||||
@@ -1482,18 +1515,27 @@ func parseSSHLogHeader(line string) (sshLogHeader, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadSSHLogSessionKey(header sshLogHeader, data dto.SSHHistory, line string) string {
|
func loadSSHLogSessionKey(header sshLogHeader, data dto.SSHHistory, line string) string {
|
||||||
if header.Process == "sshd-session" && data.Address != "" && data.Port != "" {
|
if header.Process == "sshd-session" {
|
||||||
return data.Address + ":" + data.Port
|
if endpointKey := loadSSHLogEndpointKey(data); endpointKey != "" {
|
||||||
|
return endpointKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if header.PID != "" {
|
if header.PID != "" {
|
||||||
return "pid:" + header.PID
|
return "pid:" + header.PID
|
||||||
}
|
}
|
||||||
if data.Address != "" && data.Port != "" {
|
if endpointKey := loadSSHLogEndpointKey(data); endpointKey != "" {
|
||||||
return data.Address + ":" + data.Port
|
return endpointKey
|
||||||
}
|
}
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadSSHLogEndpointKey(data dto.SSHHistory) string {
|
||||||
|
if data.Address == "" || data.Port == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(data.Address, data.Port)
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeSSHLogDate(dateStr string) string {
|
func normalizeSSHLogDate(dateStr string) string {
|
||||||
if t, err := time.Parse(time.RFC3339Nano, dateStr); err == nil {
|
if t, err := time.Parse(time.RFC3339Nano, dateStr); err == nil {
|
||||||
return t.Format("2006 Jan 2 15:04:05")
|
return t.Format("2006 Jan 2 15:04:05")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
type TensorRTLLMService struct{}
|
type TensorRTLLMService struct{}
|
||||||
|
|
||||||
type ITensorRTLLMService interface {
|
type ITensorRTLLMService interface {
|
||||||
Page(req request.TensorRTLLMSearch) response.TensorRTLLMsRes
|
Page(req request.TensorRTLLMSearch, readOnly bool) response.TensorRTLLMsRes
|
||||||
Create(create request.TensorRTLLMCreate) error
|
Create(create request.TensorRTLLMCreate) error
|
||||||
Update(req request.TensorRTLLMUpdate) error
|
Update(req request.TensorRTLLMUpdate) error
|
||||||
Delete(id uint) error
|
Delete(id uint) error
|
||||||
@@ -39,18 +39,23 @@ func NewITensorRTLLMService() ITensorRTLLMService {
|
|||||||
return &TensorRTLLMService{}
|
return &TensorRTLLMService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorRTLLMsRes {
|
func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch, readOnly bool) response.TensorRTLLMsRes {
|
||||||
var (
|
var (
|
||||||
res response.TensorRTLLMsRes
|
res response.TensorRTLLMsRes
|
||||||
items []response.TensorRTLLMDTO
|
items []response.TensorRTLLMDTO
|
||||||
)
|
)
|
||||||
|
|
||||||
|
readOnlyMode := isDemoReadOnly(readOnly)
|
||||||
total, data, _ := tensorrtLLMRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
|
total, data, _ := tensorrtLLMRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
|
||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
_ = syncTensorRTLLMContainerStatus(&item)
|
_ = syncTensorRTLLMContainerStatus(&item, readOnlyMode)
|
||||||
serverDTO := response.TensorRTLLMDTO{
|
serverDTO := response.TensorRTLLMDTO{
|
||||||
TensorRTLLM: item,
|
TensorRTLLM: item,
|
||||||
}
|
}
|
||||||
|
if readOnlyMode {
|
||||||
|
serverDTO.DockerCompose = ""
|
||||||
|
serverDTO.Env = ""
|
||||||
|
}
|
||||||
envs, _ := gotenv.Unmarshal(item.Env)
|
envs, _ := gotenv.Unmarshal(item.Env)
|
||||||
serverDTO.Version = envs["VERSION"]
|
serverDTO.Version = envs["VERSION"]
|
||||||
serverDTO.ModelDir = envs["MODEL_PATH"]
|
serverDTO.ModelDir = envs["MODEL_PATH"]
|
||||||
@@ -63,6 +68,9 @@ func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorR
|
|||||||
composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml"))
|
composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
serverDTO.Environments, _ = getDockerComposeEnvironments(composeByte)
|
serverDTO.Environments, _ = getDockerComposeEnvironments(composeByte)
|
||||||
|
if readOnlyMode {
|
||||||
|
redactSensitiveEnvironments(serverDTO.Environments)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
volumes, err := getDockerComposeVolumes(composeByte)
|
volumes, err := getDockerComposeVolumes(composeByte)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -323,10 +331,10 @@ func startTensorRTLLM(tensorrtLLM *model.TensorRTLLM) {
|
|||||||
tensorrtLLM.Status = constant.StatusRunning
|
tensorrtLLM.Status = constant.StatusRunning
|
||||||
tensorrtLLM.Message = ""
|
tensorrtLLM.Message = ""
|
||||||
}
|
}
|
||||||
_ = syncTensorRTLLMContainerStatus(tensorrtLLM)
|
_ = syncTensorRTLLMContainerStatus(tensorrtLLM, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncTensorRTLLMContainerStatus(tensorrtLLM *model.TensorRTLLM) error {
|
func syncTensorRTLLMContainerStatus(tensorrtLLM *model.TensorRTLLM, readOnly bool) error {
|
||||||
containerNames := []string{tensorrtLLM.ContainerName}
|
containerNames := []string{tensorrtLLM.ContainerName}
|
||||||
cli, err := docker.NewClient()
|
cli, err := docker.NewClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -342,6 +350,9 @@ func syncTensorRTLLMContainerStatus(tensorrtLLM *model.TensorRTLLM) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tensorrtLLM.Status = constant.StatusStopped
|
tensorrtLLM.Status = constant.StatusStopped
|
||||||
|
if readOnly || global.CONF.Base.IsDemo {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return tensorrtLLMRepo.Save(tensorrtLLM)
|
return tensorrtLLMRepo.Save(tensorrtLLM)
|
||||||
}
|
}
|
||||||
container := containers[0]
|
container := containers[0]
|
||||||
@@ -359,6 +370,9 @@ func syncTensorRTLLMContainerStatus(tensorrtLLM *model.TensorRTLLM) error {
|
|||||||
tensorrtLLM.Status = constant.StatusStopped
|
tensorrtLLM.Status = constant.StatusStopped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if readOnly || global.CONF.Base.IsDemo {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return tensorrtLLMRepo.Save(tensorrtLLM)
|
return tensorrtLLMRepo.Save(tensorrtLLM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ type IWebsiteService interface {
|
|||||||
GetWebsiteNginxConfig(websiteId uint, configType string) (*response.FileInfo, error)
|
GetWebsiteNginxConfig(websiteId uint, configType string) (*response.FileInfo, error)
|
||||||
UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error
|
UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error
|
||||||
|
|
||||||
GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error)
|
GetWebsiteHTTPS(websiteId uint, readOnly ...bool) (response.WebsiteHTTPS, error)
|
||||||
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
|
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
|
||||||
|
|
||||||
LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error)
|
LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error)
|
||||||
@@ -117,7 +117,7 @@ type IWebsiteService interface {
|
|||||||
SetRealIPConfig(req request.WebsiteRealIP) error
|
SetRealIPConfig(req request.WebsiteRealIP) error
|
||||||
GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error)
|
GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error)
|
||||||
|
|
||||||
GetWebsiteResource(websiteID uint) ([]response.Resource, error)
|
GetWebsiteResource(websiteID uint, readOnly ...bool) ([]response.Resource, error)
|
||||||
ListDatabases() ([]response.Database, error)
|
ListDatabases() ([]response.Database, error)
|
||||||
ChangeDatabase(req request.ChangeDatabase) error
|
ChangeDatabase(req request.ChangeDatabase) error
|
||||||
|
|
||||||
@@ -914,7 +914,7 @@ func (w WebsiteService) GetWebsiteNginxConfig(websiteID uint, configType string)
|
|||||||
return &response.FileInfo{FileInfo: *info}, nil
|
return &response.FileInfo{FileInfo: *info}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) {
|
func (w WebsiteService) GetWebsiteHTTPS(websiteId uint, readOnly ...bool) (response.WebsiteHTTPS, error) {
|
||||||
website, err := websiteRepo.GetFirst(repo.WithByID(websiteId))
|
website, err := websiteRepo.GetFirst(repo.WithByID(websiteId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.WebsiteHTTPS{}, err
|
return response.WebsiteHTTPS{}, err
|
||||||
@@ -938,6 +938,10 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
|
|||||||
return response.WebsiteHTTPS{}, err
|
return response.WebsiteHTTPS{}, err
|
||||||
}
|
}
|
||||||
res.SSL = *websiteSSL
|
res.SSL = *websiteSSL
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
res.SSL.PrivateKey = ""
|
||||||
|
res.SSL.AcmeAccount.EabHmacKey = ""
|
||||||
|
}
|
||||||
res.Enable = true
|
res.Enable = true
|
||||||
if website.HttpConfig != "" {
|
if website.HttpConfig != "" {
|
||||||
res.HttpConfig = website.HttpConfig
|
res.HttpConfig = website.HttpConfig
|
||||||
@@ -2238,7 +2242,7 @@ func (w WebsiteService) GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) GetWebsiteResource(websiteID uint) ([]response.Resource, error) {
|
func (w WebsiteService) GetWebsiteResource(websiteID uint, readOnly ...bool) ([]response.Resource, error) {
|
||||||
website, err := websiteRepo.GetFirst(repo.WithByID(websiteID))
|
website, err := websiteRepo.GetFirst(repo.WithByID(websiteID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2307,6 +2311,11 @@ func (w WebsiteService) GetWebsiteResource(websiteID uint) ([]response.Resource,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
for i := range res {
|
||||||
|
res[i].Detail = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type WebsiteAcmeAccountService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IWebsiteAcmeAccountService interface {
|
type IWebsiteAcmeAccountService interface {
|
||||||
Page(search dto.PageInfo) (int64, []response.WebsiteAcmeAccountDTO, error)
|
Page(search dto.PageInfo, readOnly ...bool) (int64, []response.WebsiteAcmeAccountDTO, error)
|
||||||
Create(create request.WebsiteAcmeAccountCreate) (*response.WebsiteAcmeAccountDTO, error)
|
Create(create request.WebsiteAcmeAccountCreate) (*response.WebsiteAcmeAccountDTO, error)
|
||||||
Delete(id uint) error
|
Delete(id uint) error
|
||||||
Update(update request.WebsiteAcmeAccountUpdate) (*response.WebsiteAcmeAccountDTO, error)
|
Update(update request.WebsiteAcmeAccountUpdate) (*response.WebsiteAcmeAccountDTO, error)
|
||||||
@@ -24,10 +24,13 @@ func NewIWebsiteAcmeAccountService() IWebsiteAcmeAccountService {
|
|||||||
return &WebsiteAcmeAccountService{}
|
return &WebsiteAcmeAccountService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteAcmeAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteAcmeAccountDTO, error) {
|
func (w WebsiteAcmeAccountService) Page(search dto.PageInfo, readOnly ...bool) (int64, []response.WebsiteAcmeAccountDTO, error) {
|
||||||
total, accounts, err := websiteAcmeRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
total, accounts, err := websiteAcmeRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
||||||
var accountDTOs []response.WebsiteAcmeAccountDTO
|
var accountDTOs []response.WebsiteAcmeAccountDTO
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
account.EabHmacKey = ""
|
||||||
|
}
|
||||||
accountDTOs = append(accountDTOs, response.WebsiteAcmeAccountDTO{
|
accountDTOs = append(accountDTOs, response.WebsiteAcmeAccountDTO{
|
||||||
WebsiteAcmeAccount: account,
|
WebsiteAcmeAccount: account,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ type WebsiteCAService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IWebsiteCAService interface {
|
type IWebsiteCAService interface {
|
||||||
Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error)
|
Page(search request.WebsiteCASearch, readOnly ...bool) (int64, []response.WebsiteCADTO, error)
|
||||||
Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error)
|
Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error)
|
||||||
GetCA(id uint) (*response.WebsiteCADTO, error)
|
GetCA(id uint, readOnly ...bool) (*response.WebsiteCADTO, error)
|
||||||
Delete(id uint) error
|
Delete(id uint) error
|
||||||
ObtainSSL(req request.WebsiteCAObtain) (*model.WebsiteSSL, error)
|
ObtainSSL(req request.WebsiteCAObtain) (*model.WebsiteSSL, error)
|
||||||
DownloadFile(id uint) (*os.File, error)
|
DownloadFile(id uint) (*os.File, error)
|
||||||
@@ -49,13 +49,16 @@ func NewIWebsiteCAService() IWebsiteCAService {
|
|||||||
return &WebsiteCAService{}
|
return &WebsiteCAService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteCAService) Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error) {
|
func (w WebsiteCAService) Page(search request.WebsiteCASearch, readOnly ...bool) (int64, []response.WebsiteCADTO, error) {
|
||||||
total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
var caDTOs []response.WebsiteCADTO
|
var caDTOs []response.WebsiteCADTO
|
||||||
for _, ca := range cas {
|
for _, ca := range cas {
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
ca.PrivateKey = ""
|
||||||
|
}
|
||||||
caDTOs = append(caDTOs, response.WebsiteCADTO{
|
caDTOs = append(caDTOs, response.WebsiteCADTO{
|
||||||
WebsiteCA: ca,
|
WebsiteCA: ca,
|
||||||
})
|
})
|
||||||
@@ -125,12 +128,15 @@ func (w WebsiteCAService) Create(create request.WebsiteCACreate) (*request.Websi
|
|||||||
return &create, nil
|
return &create, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteCAService) GetCA(id uint) (*response.WebsiteCADTO, error) {
|
func (w WebsiteCAService) GetCA(id uint, readOnly ...bool) (*response.WebsiteCADTO, error) {
|
||||||
res := &response.WebsiteCADTO{}
|
res := &response.WebsiteCADTO{}
|
||||||
ca, err := websiteCARepo.GetFirst(repo.WithByID(id))
|
ca, err := websiteCARepo.GetFirst(repo.WithByID(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
ca.PrivateKey = ""
|
||||||
|
}
|
||||||
res.WebsiteCA = ca
|
res.WebsiteCA = ca
|
||||||
certBlock, _ := pem.Decode([]byte(ca.CSR))
|
certBlock, _ := pem.Decode([]byte(ca.CSR))
|
||||||
if certBlock == nil {
|
if certBlock == nil {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type WebsiteDnsAccountService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IWebsiteDnsAccountService interface {
|
type IWebsiteDnsAccountService interface {
|
||||||
Page(search dto.PageInfo) (int64, []response.WebsiteDnsAccountDTO, error)
|
Page(search dto.PageInfo, readOnly ...bool) (int64, []response.WebsiteDnsAccountDTO, error)
|
||||||
Create(create request.WebsiteDnsAccountCreate) (request.WebsiteDnsAccountCreate, error)
|
Create(create request.WebsiteDnsAccountCreate) (request.WebsiteDnsAccountCreate, error)
|
||||||
Update(update request.WebsiteDnsAccountUpdate) (request.WebsiteDnsAccountUpdate, error)
|
Update(update request.WebsiteDnsAccountUpdate) (request.WebsiteDnsAccountUpdate, error)
|
||||||
Delete(id uint) error
|
Delete(id uint) error
|
||||||
@@ -26,12 +26,14 @@ func NewIWebsiteDnsAccountService() IWebsiteDnsAccountService {
|
|||||||
return &WebsiteDnsAccountService{}
|
return &WebsiteDnsAccountService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteDnsAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteDnsAccountDTO, error) {
|
func (w WebsiteDnsAccountService) Page(search dto.PageInfo, readOnly ...bool) (int64, []response.WebsiteDnsAccountDTO, error) {
|
||||||
total, accounts, err := websiteDnsRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
total, accounts, err := websiteDnsRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
||||||
var accountDTOs []response.WebsiteDnsAccountDTO
|
var accountDTOs []response.WebsiteDnsAccountDTO
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
auth := make(map[string]string)
|
auth := make(map[string]string)
|
||||||
_ = json.Unmarshal([]byte(account.Authorization), &auth)
|
if !isDemoReadOnly(readOnly...) {
|
||||||
|
_ = json.Unmarshal([]byte(account.Authorization), &auth)
|
||||||
|
}
|
||||||
accountDTOs = append(accountDTOs, response.WebsiteDnsAccountDTO{
|
accountDTOs = append(accountDTOs, response.WebsiteDnsAccountDTO{
|
||||||
WebsiteDnsAccount: account,
|
WebsiteDnsAccount: account,
|
||||||
Authorization: auth,
|
Authorization: auth,
|
||||||
|
|||||||
@@ -95,12 +95,12 @@ func newWebsiteSSLLegoClient(ctx context.Context, acmeAccount *model.WebsiteAcme
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IWebsiteSSLService interface {
|
type IWebsiteSSLService interface {
|
||||||
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
|
Page(search request.WebsiteSSLSearch, readOnly ...bool) (int64, []response.WebsiteSSLDTO, error)
|
||||||
GetSSL(id uint) (*response.WebsiteSSLDTO, error)
|
GetSSL(id uint, readOnly ...bool) (*response.WebsiteSSLDTO, error)
|
||||||
Search(req request.WebsiteSSLListReq) ([]response.WebsiteSSLDTO, error)
|
Search(req request.WebsiteSSLListReq) ([]response.WebsiteSSLDTO, error)
|
||||||
Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error)
|
Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error)
|
||||||
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
|
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
|
||||||
GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error)
|
GetWebsiteSSL(websiteId uint, readOnly ...bool) (response.WebsiteSSLDTO, error)
|
||||||
Delete(ids []uint) error
|
Delete(ids []uint) error
|
||||||
Update(update request.WebsiteSSLUpdate) error
|
Update(update request.WebsiteSSLUpdate) error
|
||||||
Upload(req request.WebsiteSSLUpload) error
|
Upload(req request.WebsiteSSLUpload) error
|
||||||
@@ -116,7 +116,7 @@ func NewIWebsiteSSLService() IWebsiteSSLService {
|
|||||||
return &WebsiteSSLService{}
|
return &WebsiteSSLService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) {
|
func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch, readOnly ...bool) (int64, []response.WebsiteSSLDTO, error) {
|
||||||
var (
|
var (
|
||||||
result []response.WebsiteSSLDTO
|
result []response.WebsiteSSLDTO
|
||||||
opts []repo.DBOption
|
opts []repo.DBOption
|
||||||
@@ -134,6 +134,10 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
|
|||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
for _, model := range sslList {
|
for _, model := range sslList {
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
model.PrivateKey = ""
|
||||||
|
model.AcmeAccount.EabHmacKey = ""
|
||||||
|
}
|
||||||
result = append(result, response.WebsiteSSLDTO{
|
result = append(result, response.WebsiteSSLDTO{
|
||||||
WebsiteSSL: model,
|
WebsiteSSL: model,
|
||||||
LogPath: path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", model.PrimaryDomain, model.ID)),
|
LogPath: path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", model.PrimaryDomain, model.ID)),
|
||||||
@@ -142,12 +146,16 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
|
|||||||
return total, result, err
|
return total, result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
|
func (w WebsiteSSLService) GetSSL(id uint, readOnly ...bool) (*response.WebsiteSSLDTO, error) {
|
||||||
var res response.WebsiteSSLDTO
|
var res response.WebsiteSSLDTO
|
||||||
websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(id))
|
websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
websiteSSL.PrivateKey = ""
|
||||||
|
websiteSSL.AcmeAccount.EabHmacKey = ""
|
||||||
|
}
|
||||||
res.WebsiteSSL = *websiteSSL
|
res.WebsiteSSL = *websiteSSL
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
@@ -682,7 +690,7 @@ func (w WebsiteSSLService) GetDNSResolve(req request.WebsiteDNSReq) ([]response.
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteSSLService) GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error) {
|
func (w WebsiteSSLService) GetWebsiteSSL(websiteId uint, readOnly ...bool) (response.WebsiteSSLDTO, error) {
|
||||||
var res response.WebsiteSSLDTO
|
var res response.WebsiteSSLDTO
|
||||||
website, err := websiteRepo.GetFirst(repo.WithByID(websiteId))
|
website, err := websiteRepo.GetFirst(repo.WithByID(websiteId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -692,6 +700,10 @@ func (w WebsiteSSLService) GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
if isDemoReadOnly(readOnly...) {
|
||||||
|
websiteSSL.PrivateKey = ""
|
||||||
|
websiteSSL.AcmeAccount.EabHmacKey = ""
|
||||||
|
}
|
||||||
res.WebsiteSSL = *websiteSSL
|
res.WebsiteSSL = *websiteSSL
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const (
|
|||||||
TypeComposeCreate = "compose-create"
|
TypeComposeCreate = "compose-create"
|
||||||
|
|
||||||
InterruptedMsg = "the task was interrupted due to the restart of the 1panel service"
|
InterruptedMsg = "the task was interrupted due to the restart of the 1panel service"
|
||||||
|
DemoModeHeader = "X-Panel-Demo-Mode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
github.com/compose-spec/compose-go/v2 v2.14.0
|
github.com/compose-spec/compose-go/v2 v2.14.0
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/docker/cli v29.7.1+incompatible
|
github.com/docker/cli v29.7.2+incompatible
|
||||||
github.com/docker/docker v28.5.2+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/docker/go-connections v0.8.1
|
github.com/docker/go-connections v0.8.1
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
@@ -28,7 +28,7 @@ require (
|
|||||||
github.com/jackc/pgx/v5 v5.10.0
|
github.com/jackc/pgx/v5 v5.10.0
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/klauspost/compress v1.19.1
|
github.com/klauspost/compress v1.19.2
|
||||||
github.com/mattn/go-shellwords v1.0.14
|
github.com/mattn/go-shellwords v1.0.14
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||||
github.com/miekg/dns v1.1.72
|
github.com/miekg/dns v1.1.72
|
||||||
|
|||||||
+4
-4
@@ -228,8 +228,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/docker/cli v29.7.1+incompatible h1:ILZpP6B7fedIr6ANy824QkDp1WMJuouIq0O2SrBkB2w=
|
github.com/docker/cli v29.7.2+incompatible h1:dlkwallR8XqfeVnA2ELEhdwvb4lsSwuB4IgsG8Q9cLY=
|
||||||
github.com/docker/cli v29.7.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v29.7.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||||
@@ -523,8 +523,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
|
||||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
fileRouter.POST("/wget", baseApi.WgetFile)
|
fileRouter.POST("/wget", baseApi.WgetFile)
|
||||||
fileRouter.POST("/wget/stop", baseApi.StopWget)
|
fileRouter.POST("/wget/stop", baseApi.StopWget)
|
||||||
fileRouter.POST("/move", baseApi.MoveFile)
|
fileRouter.POST("/move", baseApi.MoveFile)
|
||||||
|
fileRouter.POST("/move/stop", baseApi.StopMoveFile)
|
||||||
fileRouter.GET("/download", baseApi.Download)
|
fileRouter.GET("/download", baseApi.Download)
|
||||||
fileRouter.POST("/share/search", baseApi.SearchFileShare)
|
fileRouter.POST("/share/search", baseApi.SearchFileShare)
|
||||||
fileRouter.POST("/share/detail", baseApi.GetFileShareDetail)
|
fileRouter.POST("/share/detail", baseApi.GetFileShareDetail)
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package gpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ascendVersionPattern = regexp.MustCompile(`(?i)\bVersion:\s*([^\s|]+)`)
|
||||||
|
ascendMemoryPattern = regexp.MustCompile(`([0-9]+(?:\.[0-9]+)?)\s*/\s*([0-9]+(?:\.[0-9]+)?)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type AscendSMI struct{}
|
||||||
|
|
||||||
|
func (a AscendSMI) LoadGpuInfo() (*common.GpuInfo, error) {
|
||||||
|
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second))
|
||||||
|
itemData, err := cmdMgr.RunWithStdout("npu-smi", "info")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("calling npu-smi failed, %v", err)
|
||||||
|
}
|
||||||
|
return parseAscendSMI(itemData), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAscendSMI(data string) *common.GpuInfo {
|
||||||
|
info := &common.GpuInfo{Type: "ascend"}
|
||||||
|
if match := ascendVersionPattern.FindStringSubmatch(data); len(match) == 2 {
|
||||||
|
info.DriverVersion = match[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
processSection := false
|
||||||
|
deviceIndexes := make(map[uint]int)
|
||||||
|
var pending *common.GPU
|
||||||
|
|
||||||
|
for _, line := range strings.Split(data, "\n") {
|
||||||
|
if strings.Contains(line, "Process id") && strings.Contains(line, "Process memory") {
|
||||||
|
processSection = true
|
||||||
|
pending = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cells := ascendTableCells(line)
|
||||||
|
if processSection {
|
||||||
|
if len(cells) != 4 && len(cells) != 5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deviceFields := strings.Fields(cells[0])
|
||||||
|
if len(deviceFields) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deviceID, err := strconv.ParseUint(deviceFields[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index, ok := deviceIndexes[uint(deviceID)]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
processOffset := len(cells) - 3
|
||||||
|
info.GPUs[index].Processes = append(info.GPUs[index].Processes, common.Process{
|
||||||
|
Pid: strings.TrimSpace(cells[processOffset]),
|
||||||
|
Type: "NPU",
|
||||||
|
ProcessName: strings.TrimSpace(cells[processOffset+1]),
|
||||||
|
UsedMemory: ascendValueWithUnit(cells[processOffset+2], "MB"),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cells) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pending == nil {
|
||||||
|
fields := strings.Fields(cells[0])
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deviceID, err := strconv.ParseUint(fields[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metrics := strings.Fields(cells[2])
|
||||||
|
if len(metrics) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pending = &common.GPU{
|
||||||
|
Type: "ascend",
|
||||||
|
Index: uint(deviceID),
|
||||||
|
ProductName: strings.Join(fields[1:], " "),
|
||||||
|
PersistenceMode: "N/A",
|
||||||
|
DisplayActive: "N/A",
|
||||||
|
ECC: "N/A",
|
||||||
|
FanSpeed: "N/A",
|
||||||
|
Temperature: ascendValueWithUnit(metrics[1], "C"),
|
||||||
|
PerformanceState: strings.TrimSpace(cells[1]),
|
||||||
|
PowerDraw: ascendValueWithUnit(metrics[0], "W"),
|
||||||
|
MaxPowerLimit: "N/A",
|
||||||
|
ComputeMode: "N/A",
|
||||||
|
MigMode: "N/A",
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics := strings.Fields(cells[2])
|
||||||
|
if len(metrics) == 0 {
|
||||||
|
pending = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pending.BusID = strings.TrimSpace(cells[1])
|
||||||
|
pending.GPUUtil = ascendValueWithUnit(metrics[0], "%")
|
||||||
|
pending.MemUsed, pending.MemTotal = ascendMemoryUsage(cells[2])
|
||||||
|
deviceIndexes[pending.Index] = len(info.GPUs)
|
||||||
|
info.GPUs = append(info.GPUs, *pending)
|
||||||
|
pending = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func ascendTableCells(line string) []string {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if !strings.HasPrefix(line, "|") || !strings.HasSuffix(line, "|") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
parts := strings.Split(line, "|")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cells := make([]string, 0, len(parts)-2)
|
||||||
|
for _, part := range parts[1 : len(parts)-1] {
|
||||||
|
cells = append(cells, strings.TrimSpace(part))
|
||||||
|
}
|
||||||
|
return cells
|
||||||
|
}
|
||||||
|
|
||||||
|
func ascendMemoryUsage(value string) (string, string) {
|
||||||
|
matches := ascendMemoryPattern.FindAllStringSubmatch(value, -1)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return "N/A", "N/A"
|
||||||
|
}
|
||||||
|
// 910B exposes both generic memory and HBM. Prefer the last memory pool
|
||||||
|
// with a non-zero capacity, which selects HBM while retaining compatibility
|
||||||
|
// with devices that only expose Memory-Usage.
|
||||||
|
selected := matches[len(matches)-1]
|
||||||
|
for i := len(matches) - 1; i >= 0; i-- {
|
||||||
|
if total, err := strconv.ParseFloat(matches[i][2], 64); err == nil && total > 0 {
|
||||||
|
selected = matches[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected[1] + " MB", selected[2] + " MB"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ascendValueWithUnit(value, unit string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" || strings.EqualFold(value, "N/A") || strings.EqualFold(value, "NA") {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
return value + " " + unit
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ type GpuInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GPU struct {
|
type GPU struct {
|
||||||
|
Type string `json:"type"`
|
||||||
Index uint `json:"index"`
|
Index uint `json:"index"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
PersistenceMode string `json:"persistenceMode"`
|
PersistenceMode string `json:"persistenceMode"`
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -18,8 +19,95 @@ import (
|
|||||||
|
|
||||||
type NvidiaSMI struct{}
|
type NvidiaSMI struct{}
|
||||||
|
|
||||||
func New() (bool, NvidiaSMI) {
|
type SMI interface {
|
||||||
return cmd.Which("nvidia-smi"), NvidiaSMI{}
|
LoadGpuInfo() (*common.GpuInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (bool, SMI) {
|
||||||
|
var clients []SMI
|
||||||
|
if cmd.Which("nvidia-smi") {
|
||||||
|
clients = append(clients, NvidiaSMI{})
|
||||||
|
}
|
||||||
|
if cmd.Which("npu-smi") {
|
||||||
|
clients = append(clients, AscendSMI{})
|
||||||
|
}
|
||||||
|
if len(clients) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if len(clients) == 1 {
|
||||||
|
return true, clients[0]
|
||||||
|
}
|
||||||
|
return true, multiSMI{clients: clients}
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiSMI struct {
|
||||||
|
clients []SMI
|
||||||
|
}
|
||||||
|
|
||||||
|
type smiResult struct {
|
||||||
|
info *common.GpuInfo
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiSMI) LoadGpuInfo() (*common.GpuInfo, error) {
|
||||||
|
results := make([]smiResult, len(m.clients))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for index, client := range m.clients {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
results[index].info, results[index].err = client.LoadGpuInfo()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
merged := &common.GpuInfo{}
|
||||||
|
var (
|
||||||
|
errs []error
|
||||||
|
types []string
|
||||||
|
driverVersions []string
|
||||||
|
)
|
||||||
|
for _, result := range results {
|
||||||
|
if result.err != nil {
|
||||||
|
errs = append(errs, result.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if result.info == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deviceType := result.info.Type
|
||||||
|
if deviceType != "" {
|
||||||
|
types = append(types, deviceType)
|
||||||
|
}
|
||||||
|
if result.info.DriverVersion != "" {
|
||||||
|
driverVersions = append(driverVersions, fmt.Sprintf("%s: %s", strings.ToUpper(deviceType), result.info.DriverVersion))
|
||||||
|
}
|
||||||
|
if result.info.CudaVersion != "" {
|
||||||
|
merged.CudaVersion = result.info.CudaVersion
|
||||||
|
}
|
||||||
|
for _, device := range result.info.GPUs {
|
||||||
|
if device.Type == "" {
|
||||||
|
device.Type = deviceType
|
||||||
|
}
|
||||||
|
merged.GPUs = append(merged.GPUs, device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(types) == 1 {
|
||||||
|
merged.Type = types[0]
|
||||||
|
} else if len(types) > 1 {
|
||||||
|
merged.Type = "mixed"
|
||||||
|
}
|
||||||
|
if len(driverVersions) == 1 {
|
||||||
|
parts := strings.SplitN(driverVersions[0], ": ", 2)
|
||||||
|
merged.DriverVersion = parts[len(parts)-1]
|
||||||
|
} else {
|
||||||
|
merged.DriverVersion = strings.Join(driverVersions, ";")
|
||||||
|
}
|
||||||
|
if len(merged.GPUs) == 0 && len(errs) > 0 {
|
||||||
|
return nil, fmt.Errorf("calling GPU monitoring tools failed: %w", errors.Join(errs...))
|
||||||
|
}
|
||||||
|
return merged, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) {
|
func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func Parse(buf []byte, version string) (*common.GpuInfo, error) {
|
|||||||
}
|
}
|
||||||
for i := 0; i < len(s.Gpu); i++ {
|
for i := 0; i < len(s.Gpu); i++ {
|
||||||
var gpuItem common.GPU
|
var gpuItem common.GPU
|
||||||
|
gpuItem.Type = "nvidia"
|
||||||
gpuItem.Index = uint(i)
|
gpuItem.Index = uint(i)
|
||||||
gpuItem.ProductName = s.Gpu[i].ProductName
|
gpuItem.ProductName = s.Gpu[i].ProductName
|
||||||
gpuItem.PersistenceMode = s.Gpu[i].PersistenceMode
|
gpuItem.PersistenceMode = s.Gpu[i].PersistenceMode
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func Up(filePath string, projectName ...string) (string, error) {
|
|||||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(base, args...)
|
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(base, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpWithoutPull(filePath string, projectName ...string) (string, error) {
|
func UpWithoutBuild(filePath string, projectName ...string) (string, error) {
|
||||||
if err := checkCmd(); err != nil {
|
if err := checkCmd(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -58,11 +58,11 @@ func UpWithoutPull(filePath string, projectName ...string) (string, error) {
|
|||||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(base, args...)
|
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(base, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func upArgs(filePath string, withoutPull bool) []string {
|
func upArgs(filePath string, withoutBuild bool) []string {
|
||||||
args := loadFiles(filePath)
|
args := loadFiles(filePath)
|
||||||
args = append(args, "up", "-d")
|
args = append(args, "up", "-d")
|
||||||
if withoutPull {
|
if withoutBuild {
|
||||||
args = append(args, "--pull", "never", "--no-build")
|
args = append(args, "--no-build")
|
||||||
}
|
}
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||||
"github.com/compose-spec/compose-go/v2/loader"
|
"github.com/compose-spec/compose-go/v2/loader"
|
||||||
|
"github.com/compose-spec/compose-go/v2/template"
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@@ -91,7 +92,7 @@ func (e *Environment) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetImagesFromDockerCompose(env, yml []byte) ([]string, error) {
|
func GetImagesFromDockerCompose(env, yml []byte) ([]string, error) {
|
||||||
envVars, err := loadEnvFile(env)
|
envVars, err := dotenv.Parse(bytes.NewReader(env))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load env failed: %v", err)
|
return nil, fmt.Errorf("load env failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -104,43 +105,16 @@ func GetImagesFromDockerCompose(env, yml []byte) ([]string, error) {
|
|||||||
var images []string
|
var images []string
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
if service.Image != "" {
|
if service.Image != "" {
|
||||||
resolvedImage := replaceEnvVars(service.Image, envVars)
|
resolvedImage, err := template.Substitute(service.Image, func(key string) (string, bool) {
|
||||||
|
value, ok := envVars[key]
|
||||||
|
return value, ok
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve image failed: %v", err)
|
||||||
|
}
|
||||||
images = append(images, resolvedImage)
|
images = append(images, resolvedImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEnvFile(env []byte) (map[string]string, error) {
|
|
||||||
envVars := make(map[string]string)
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(bytes.NewReader(env))
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
|
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(line, "=", 2)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
key := strings.TrimSpace(parts[0])
|
|
||||||
value := strings.TrimSpace(parts[1])
|
|
||||||
value = strings.Trim(value, `"'`)
|
|
||||||
envVars[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return envVars, scanner.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceEnvVars(input string, envVars map[string]string) string {
|
|
||||||
return re.GetRegex(re.ComposeEnvVarPattern).ReplaceAllStringFunc(input, func(match string) string {
|
|
||||||
varName := match[2 : len(match)-1]
|
|
||||||
if value, exists := envVars[varName]; exists {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -594,6 +594,12 @@ func (f FileOp) DownloadFile(url, dst string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), cmdRecursiveTimeout)
|
||||||
|
defer cancel()
|
||||||
|
return f.CutWithContext(ctx, oldPaths, dst, name, cover)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileOp) CutWithContext(ctx context.Context, oldPaths []string, dst, name string, cover bool) error {
|
||||||
if len(oldPaths) == 0 {
|
if len(oldPaths) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -617,7 +623,7 @@ func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
|||||||
}
|
}
|
||||||
args = append(args, oldPaths...)
|
args = append(args, oldPaths...)
|
||||||
args = append(args, dstPath)
|
args = append(args, dstPath)
|
||||||
if err := cmd.NewCommandMgr(cmd.WithTimeout(cmdRecursiveTimeout)).Run("mv", args...); err != nil {
|
if err := cmd.NewCommandMgr(cmd.WithContext(ctx)).Run("mv", args...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -49,8 +49,10 @@ func Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
if err = settingRepo.Update("Language", info.Language); err != nil {
|
if !global.CONF.Base.IsDemo {
|
||||||
return nil, "", err
|
if err = settingRepo.Update("Language", info.Language); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if mfaSetting.Value == constant.StatusEnable {
|
if mfaSetting.Value == constant.StatusEnable {
|
||||||
return BeginMFALogin(c, nameSetting.Value, entrance, mfaSetting.Value), "", nil
|
return BeginMFALogin(c, nameSetting.Value, entrance, mfaSetting.Value), "", nil
|
||||||
|
|||||||
@@ -99,13 +99,17 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
|
|||||||
}
|
}
|
||||||
if info.Edition == "" {
|
if info.Edition == "" {
|
||||||
info.Edition = "cn"
|
info.Edition = "cn"
|
||||||
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
|
if !global.CONF.Base.IsDemo {
|
||||||
|
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if info.MenuAccordion == "" {
|
if info.MenuAccordion == "" {
|
||||||
info.MenuAccordion = constant.StatusDisable
|
info.MenuAccordion = constant.StatusDisable
|
||||||
_ = settingRepo.UpdateOrCreate("MenuAccordion", info.MenuAccordion)
|
if !global.CONF.Base.IsDemo {
|
||||||
|
_ = settingRepo.UpdateOrCreate("MenuAccordion", info.MenuAccordion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if info.ProxyPasswdKeep != constant.StatusEnable {
|
if global.CONF.Base.IsDemo || info.ProxyPasswdKeep != constant.StatusEnable {
|
||||||
info.ProxyPasswd = ""
|
info.ProxyPasswd = ""
|
||||||
} else {
|
} else {
|
||||||
info.ProxyPasswd, _ = encrypt.StringDecrypt(info.ProxyPasswd)
|
info.ProxyPasswd, _ = encrypt.StringDecrypt(info.ProxyPasswd)
|
||||||
@@ -142,11 +146,15 @@ func (u *SettingService) GetSettingBaseInfo() (*dto.SettingBaseInfo, error) {
|
|||||||
}
|
}
|
||||||
if info.Edition == "" {
|
if info.Edition == "" {
|
||||||
info.Edition = "cn"
|
info.Edition = "cn"
|
||||||
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
|
if !global.CONF.Base.IsDemo {
|
||||||
|
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if info.MenuAccordion == "" {
|
if info.MenuAccordion == "" {
|
||||||
info.MenuAccordion = constant.StatusDisable
|
info.MenuAccordion = constant.StatusDisable
|
||||||
_ = settingRepo.UpdateOrCreate("MenuAccordion", info.MenuAccordion)
|
if !global.CONF.Base.IsDemo {
|
||||||
|
_ = settingRepo.UpdateOrCreate("MenuAccordion", info.MenuAccordion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &info, err
|
return &info, err
|
||||||
@@ -516,6 +524,9 @@ func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
|
|||||||
data.SSLID = uint(id)
|
data.SSLID = uint(id)
|
||||||
data.Timeout = ssl.ExpireDate.Format(constant.DateTimeLayout)
|
data.Timeout = ssl.ExpireDate.Format(constant.DateTimeLayout)
|
||||||
}
|
}
|
||||||
|
if global.CONF.Base.IsDemo {
|
||||||
|
data.Key = ""
|
||||||
|
}
|
||||||
return &data, nil
|
return &data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const (
|
|||||||
DefaultDate = "1970-01-01"
|
DefaultDate = "1970-01-01"
|
||||||
DateTimeLayout = "2006-01-02 15:04:05" // or use time.DateTime while go version >= 1.20
|
DateTimeLayout = "2006-01-02 15:04:05" // or use time.DateTime while go version >= 1.20
|
||||||
DateTimeSlimLayout = "20060102150405"
|
DateTimeSlimLayout = "20060102150405"
|
||||||
|
DemoModeHeader = "X-Panel-Demo-Mode"
|
||||||
|
|
||||||
OrderDesc = "descending"
|
OrderDesc = "descending"
|
||||||
OrderAsc = "ascending"
|
OrderAsc = "ascending"
|
||||||
@@ -59,6 +60,7 @@ var WebUrlMap = map[string]struct{}{
|
|||||||
|
|
||||||
"/ai": {},
|
"/ai": {},
|
||||||
"/ai/ai-proxy": {},
|
"/ai/ai-proxy": {},
|
||||||
|
"/ai/ai-proxy/overview": {},
|
||||||
"/ai/ai-proxy/model-pool": {},
|
"/ai/ai-proxy/model-pool": {},
|
||||||
"/ai/ai-proxy/api-keys": {},
|
"/ai/ai-proxy/api-keys": {},
|
||||||
"/ai/ai-proxy/groups": {},
|
"/ai/ai-proxy/groups": {},
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/sftp v1.13.11
|
github.com/pkg/sftp v1.13.11
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/goxmldsig v1.6.0
|
github.com/russellhaering/goxmldsig v1.6.1
|
||||||
github.com/shirou/gopsutil/v4 v4.26.7
|
github.com/shirou/gopsutil/v4 v4.26.7
|
||||||
github.com/sirupsen/logrus v1.9.4
|
github.com/sirupsen/logrus v1.9.4
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
|
|||||||
+2
-2
@@ -234,8 +234,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks=
|
github.com/russellhaering/goxmldsig v1.6.1 h1:SB7R5ttvrGIDB2juJAK/i7DQ2Ivr7agG+ohfNJjwyYU=
|
||||||
github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM=
|
github.com/russellhaering/goxmldsig v1.6.1/go.mod h1:haZkRcLs9W/Xp989fIjP3BrTdbFQveRF0QNZSYoH09w=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ UpdatePortFirewallReload: "Failed to reload the firewall, {{ .err }}, manually r
|
|||||||
# mobile app
|
# mobile app
|
||||||
ErrVerifyToken: 'Token verification failed. Reset and scan again.'
|
ErrVerifyToken: 'Token verification failed. Reset and scan again.'
|
||||||
ErrInvalidToken: 'Invalid token. Reset and scan again.'
|
ErrInvalidToken: 'Invalid token. Reset and scan again.'
|
||||||
ErrExpiredToken: 'Token expired. Reset and scan again.'
|
ErrExpiredToken: 'QR code is no longer valid. Generate a new one and scan again.'
|
||||||
|
|
||||||
# command export
|
# command export
|
||||||
Name: "Name"
|
Name: "Name"
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ UpdatePortFirewallReload: "Fallo al recargar el firewall, {{ .err }}, recargue e
|
|||||||
# aplicación móvil
|
# aplicación móvil
|
||||||
ErrVerifyToken: 'Error al verificar el token, por favor restablezca y escanee de nuevo.'
|
ErrVerifyToken: 'Error al verificar el token, por favor restablezca y escanee de nuevo.'
|
||||||
ErrInvalidToken: 'Token inválido, por favor restablezca y escanee de nuevo.'
|
ErrInvalidToken: 'Token inválido, por favor restablezca y escanee de nuevo.'
|
||||||
ErrExpiredToken: 'El token ha expirado, por favor restablezca y escanee de nuevo.'
|
ErrExpiredToken: 'El código QR ya no es válido. Genere uno nuevo y vuelva a escanearlo.'
|
||||||
|
|
||||||
# exportación de comandos
|
# exportación de comandos
|
||||||
Name: "Nombre"
|
Name: "Nombre"
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ UpdatePortFirewallReload: "بارگذاری مجدد دیواره آتش نام
|
|||||||
# برنامه موبایل
|
# برنامه موبایل
|
||||||
ErrVerifyToken: 'تأیید توکن ناموفق بود. بازنشانی کرده و دوباره اسکن کنید.'
|
ErrVerifyToken: 'تأیید توکن ناموفق بود. بازنشانی کرده و دوباره اسکن کنید.'
|
||||||
ErrInvalidToken: 'توکن نامعتبر است. بازنشانی کرده و دوباره اسکن کنید.'
|
ErrInvalidToken: 'توکن نامعتبر است. بازنشانی کرده و دوباره اسکن کنید.'
|
||||||
ErrExpiredToken: 'توکن منقضی شده است. بازنشانی کرده و دوباره اسکن کنید.'
|
ErrExpiredToken: 'کد QR دیگر معتبر نیست. یک کد جدید ایجاد و دوباره اسکن کنید.'
|
||||||
|
|
||||||
# خروجی فرمان
|
# خروجی فرمان
|
||||||
Name: "نام"
|
Name: "نام"
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ UpdatePortFirewallReload: "ファイアウォールのリロードに失敗し
|
|||||||
# モバイルアプリ
|
# モバイルアプリ
|
||||||
ErrVerifyToken: 'トークンの検証エラーです。リセット後、再度QRコードをスキャンしてください。'
|
ErrVerifyToken: 'トークンの検証エラーです。リセット後、再度QRコードをスキャンしてください。'
|
||||||
ErrInvalidToken: '無効なトークンです。リセット後、再度QRコードをスキャンしてください。'
|
ErrInvalidToken: '無効なトークンです。リセット後、再度QRコードをスキャンしてください。'
|
||||||
ErrExpiredToken: 'トークンの有効期限が切れました。リセット後、再度QRコードをスキャンしてください。'
|
ErrExpiredToken: 'QRコードが無効になりました。再生成してからもう一度スキャンしてください。'
|
||||||
|
|
||||||
# コマンドエクスポート
|
# コマンドエクスポート
|
||||||
Name: "名前"
|
Name: "名前"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user