mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 08:00:34 +00:00
Compare commits
233
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c986b04667 | ||
|
|
754e016f20 | ||
|
|
f67ce75492 | ||
|
|
e236ca5607 | ||
|
|
10e74c4fe4 | ||
|
|
473c0a5ff2 | ||
|
|
7fbe6e4091 | ||
|
|
65dbb53a07 | ||
|
|
c21910c203 | ||
|
|
1728511bad | ||
|
|
e63f6672f8 | ||
|
|
7fa9754ce7 | ||
|
|
475e4ca629 | ||
|
|
32dee74edf | ||
|
|
6715559925 | ||
|
|
2f380c9f2f | ||
|
|
0aebb0d2db | ||
|
|
b7cf9aab2b | ||
|
|
ea2f1875bc | ||
|
|
83524940ed | ||
|
|
8c33eb8409 | ||
|
|
644655af86 | ||
|
|
8082f4d2ca | ||
|
|
3c896bc5f8 | ||
|
|
5c7ce6d8d4 | ||
|
|
222702f66b | ||
|
|
b3b3feb1b9 | ||
|
|
133140040b | ||
|
|
d70f903432 | ||
|
|
f77f305316 | ||
|
|
13b5908442 | ||
|
|
d1335db986 | ||
|
|
1e8836ad95 | ||
|
|
d155217ce3 | ||
|
|
9ddb996a44 | ||
|
|
226eccf5f1 | ||
|
|
4b2d6f0ef2 | ||
|
|
f1c9ad45a7 | ||
|
|
93489c85fa | ||
|
|
8cead06d7b | ||
|
|
625cd9f32f | ||
|
|
9aab3b42af | ||
|
|
6684cd57d7 | ||
|
|
00d95d6e19 | ||
|
|
36f03e5daf | ||
|
|
7c395a6bf8 | ||
|
|
e9a7cac5fd | ||
|
|
9f00b431d4 | ||
|
|
1705d44378 | ||
|
|
537e2c730c | ||
|
|
b4dc6b1ab0 | ||
|
|
1f22285cf4 | ||
|
|
1d546236e4 | ||
|
|
982e0bbd3e | ||
|
|
5a67a62733 | ||
|
|
961ebd30a1 | ||
|
|
9819546694 | ||
|
|
5c8d391a73 | ||
|
|
0e84b9a2a7 | ||
|
|
1f58da501e | ||
|
|
0041184a3e | ||
|
|
112468949d | ||
|
|
7e03e94460 | ||
|
|
fd9b299b14 | ||
|
|
37932d7fb2 | ||
|
|
f0f2087f54 | ||
|
|
f3de09e5d7 | ||
|
|
1a8c1613a4 | ||
|
|
5209275f28 | ||
|
|
924e59d948 | ||
|
|
87a39cab3a | ||
|
|
f9a70757a1 | ||
|
|
741acee393 | ||
|
|
c964a02088 | ||
|
|
ba71613ae6 | ||
|
|
5493b41381 | ||
|
|
c3f45d4787 | ||
|
|
a8a92da483 | ||
|
|
a77e61a861 | ||
|
|
e589728987 | ||
|
|
cc3cdef5f3 | ||
|
|
7101af0594 | ||
|
|
df6cda7143 | ||
|
|
62beb534d2 | ||
|
|
9a70bf1c2d | ||
|
|
6f4559c7cd | ||
|
|
8b2bcdc814 | ||
|
|
0f6ddf26fd | ||
|
|
ebe472a9db | ||
|
|
c1a87baad7 | ||
|
|
c7f185a184 | ||
|
|
4ca2ba5482 | ||
|
|
727b74895d | ||
|
|
6c3f56516f | ||
|
|
4833cf2d3c | ||
|
|
b5ca015632 | ||
|
|
1c82034111 | ||
|
|
e45d723f3d | ||
|
|
627e6852db | ||
|
|
1725767789 | ||
|
|
3e1f2b4934 | ||
|
|
140d20a21d | ||
|
|
6e933fc83c | ||
|
|
79ff565995 | ||
|
|
39691698c9 | ||
|
|
4bedddb557 | ||
|
|
f9547752f5 | ||
|
|
96b37cb024 | ||
|
|
30d874bc91 | ||
|
|
32bc8d922b | ||
|
|
a95d9aa27a | ||
|
|
7f4f5a0a47 | ||
|
|
65a7193bc6 | ||
|
|
64f79b4fdd | ||
|
|
31d2af554e | ||
|
|
e0f2910710 | ||
|
|
f89ab607b4 | ||
|
|
61fa07b8f2 | ||
|
|
9653df185a | ||
|
|
8f7d3df76d | ||
|
|
2aa89699c1 | ||
|
|
46b8d3bd80 | ||
|
|
8a7be481b0 | ||
|
|
21bd94d3a3 | ||
|
|
d337190b59 | ||
|
|
7073e83d94 | ||
|
|
537ff49c0b | ||
|
|
4b1f643c70 | ||
|
|
b8b873836d | ||
|
|
021334c2c8 | ||
|
|
635d86a1a5 | ||
|
|
607eae3ca8 | ||
|
|
66428c0a8e | ||
|
|
c0404a4ec5 | ||
|
|
77ef3b7096 | ||
|
|
498052f4c9 | ||
|
|
958c55eab8 | ||
|
|
d3cddd175a | ||
|
|
6cd130677e | ||
|
|
33b90e3979 | ||
|
|
e1e2b97e88 | ||
|
|
8e4e1dc749 | ||
|
|
1eb5830238 | ||
|
|
5db2ccbb22 | ||
|
|
47e04a14ad | ||
|
|
a13c58eb18 | ||
|
|
15d77ceb46 | ||
|
|
af8fa6763f | ||
|
|
ed66d22bdb | ||
|
|
5209db3abc | ||
|
|
35ea1a1d6c | ||
|
|
b0a053b397 | ||
|
|
a8b3390ce9 | ||
|
|
4266e39d84 | ||
|
|
5620b6e736 | ||
|
|
289e4190ff | ||
|
|
9250412296 | ||
|
|
9c5370508e | ||
|
|
a145bfdc5b | ||
|
|
db2b38eedb | ||
|
|
9303ee399f | ||
|
|
d64cb3dc58 | ||
|
|
62871acfbb | ||
|
|
7401906b03 | ||
|
|
5aa3c29684 | ||
|
|
91745f592f | ||
|
|
564333ea03 | ||
|
|
7b543ea5fe | ||
|
|
fc5e583481 | ||
|
|
1767b1a9c1 | ||
|
|
930c340e37 | ||
|
|
58531d3995 | ||
|
|
4b7c1390fb | ||
|
|
2337d6f13a | ||
|
|
cb8ecd55ed | ||
|
|
33ad31dba2 | ||
|
|
294c3f128b | ||
|
|
0899f4e9db | ||
|
|
788f4d8307 | ||
|
|
2451d6bed2 | ||
|
|
c547de875a | ||
|
|
ffead783aa | ||
|
|
d742c7d333 | ||
|
|
1025cd3535 | ||
|
|
de402ea724 | ||
|
|
3af07a42d6 | ||
|
|
e5b7e99cce | ||
|
|
365936bf57 | ||
|
|
2b7562f546 | ||
|
|
e7f4e312b9 | ||
|
|
98d3870477 | ||
|
|
b1ceb2e53d | ||
|
|
075d2468ea | ||
|
|
8b9e7aef4a | ||
|
|
15e499c1e9 | ||
|
|
55ccb9fe61 | ||
|
|
a5816f1dc9 | ||
|
|
95a613f59f | ||
|
|
6777941dee | ||
|
|
e55128b7fa | ||
|
|
9ac11002f8 | ||
|
|
1cc7bfb4a9 | ||
|
|
e59534e54f | ||
|
|
8c2368c1d2 | ||
|
|
8e8f181cae | ||
|
|
2fcab77565 | ||
|
|
d29f0c6c31 | ||
|
|
79f1b1458b | ||
|
|
b279ee6b82 | ||
|
|
c64a58d814 | ||
|
|
57c60ab3f8 | ||
|
|
e37d90d6e1 | ||
|
|
22055ed99a | ||
|
|
e263331b94 | ||
|
|
eb3d613e3d | ||
|
|
cc23444776 | ||
|
|
1931d3f262 | ||
|
|
79cbbbdf0a | ||
|
|
38cd785aa3 | ||
|
|
dabec3f87a | ||
|
|
6c94cb2bdf | ||
|
|
f67bdf0c22 | ||
|
|
6c51b31270 | ||
|
|
ef6e342737 | ||
|
|
ac0ef98dde | ||
|
|
523775e56b | ||
|
|
b6e741a150 | ||
|
|
54357fbe2b | ||
|
|
040dda5bec | ||
|
|
77b4d58fb8 | ||
|
|
0c9e551492 | ||
|
|
2ba1e2e22e | ||
|
|
d8a08a6c0c |
+25
-1
@@ -1,33 +1,43 @@
|
||||
name: Create Release And Upload Cloudflare R2
|
||||
name: Build And Publish (OSS + R2)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.19.0'
|
||||
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache-dependency-path: |
|
||||
core/go.sum
|
||||
agent/go.sum
|
||||
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: '~> v2'
|
||||
args: release --skip=publish --clean
|
||||
|
||||
- name: Upload Assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
@@ -36,10 +46,24 @@ jobs:
|
||||
files: |
|
||||
dist/*.tar.gz
|
||||
dist/checksums.txt
|
||||
|
||||
- name: Setup OSSUTIL
|
||||
uses: yizhoumo/setup-ossutil@v2
|
||||
with:
|
||||
endpoint: ${{ secrets.OSS_ENDPOINT }}
|
||||
access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }}
|
||||
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
|
||||
ossutil-version: '1.7.18'
|
||||
|
||||
- name: Upload Assets to OSS
|
||||
run: |
|
||||
ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload to Cloudflare R2
|
||||
run: |
|
||||
rclone copy dist/ cloudflare_r2:package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --progress
|
||||
@@ -1,47 +0,0 @@
|
||||
name: Create Release And Upload assets
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.19.0'
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: '~> v2'
|
||||
args: release --skip=publish --clean
|
||||
- name: Upload Assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
dist/*.tar.gz
|
||||
dist/checksums.txt
|
||||
- name: Setup OSSUTIL
|
||||
uses: yizhoumo/setup-ossutil@v2
|
||||
with:
|
||||
endpoint: ${{ secrets.OSS_ENDPOINT }}
|
||||
access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }}
|
||||
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
|
||||
ossutil-version: '1.7.18'
|
||||
- name: Upload Assets to OSS
|
||||
run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force
|
||||
+13
-2
@@ -44,12 +44,17 @@ frontend/src/xpack
|
||||
agent/xpack
|
||||
agent/router/entry_xpack.go
|
||||
agent/server/init_xpack.go
|
||||
agent/utils/xpack/xpack.go
|
||||
agent/utils/xpack/xpack_xpack.go
|
||||
core/xpack
|
||||
core/router/entry_xpack.go
|
||||
core/server/init_xpack.go
|
||||
core/utils/xpack/xpack.go
|
||||
core/utils/xpack/xpack_xpack.go
|
||||
xpack
|
||||
core/xpack-ee
|
||||
core/router/entry_xpackee.go
|
||||
core/server/init_xpackee.go
|
||||
core/utils/xpack/xpack_xpackee.go
|
||||
|
||||
.history/
|
||||
dist/
|
||||
@@ -62,4 +67,10 @@ cmd/server/fileList.txt
|
||||
1Panel.code-workspace
|
||||
|
||||
core/.golangci.yml
|
||||
agent/.golangci.yml
|
||||
agent/.golangci.yml
|
||||
|
||||
.opencode
|
||||
openspec
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
opencode.json
|
||||
@@ -1,5 +1,5 @@
|
||||
<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"><b>Top-Rated Web-based Linux Server Management Tool</b><br>Best VPS control panel<br>新一代的 Linux 服务器运维管理面板</p>
|
||||
<h3 align="center">Take control of your VPS with 1Panel</h3>
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 240px; height: auto;" /></a>
|
||||
</p>
|
||||
@@ -30,27 +30,27 @@
|
||||
</p>
|
||||
|
||||
------------------------------
|
||||
## What is 1Panel?
|
||||
|
||||
1Panel is an open-source, modern web-based control panel for Linux server management.
|
||||
1Panel is a modern, open-source VPS control panel that streamlines administration through an intuitive, clean web interface.
|
||||
|
||||
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, container management, LLMs management.
|
||||
- **Rapid Website Deployment**: With deep integration of the popular open-source website building software WordPress, 1Panel streamlines the process of domain binding and SSL certificate configuration, all achievable with just one click.
|
||||
- **Application Store**: 1Panel curates a wide range of high-quality open-source tools and applications, facilitating easy installation and updates for its users.
|
||||
- **Security and Reliability**: By leveraging containerization and secure application deployment practices, 1Panel minimizes vulnerability exposure. It further enhances security through integrated firewall management and log auditing capabilities.
|
||||
- **One-Click Backup & Restore**: Data protection is made simple with 1Panel's one-click backup and restore functionality, supporting various cloud storage solutions to ensure data integrity and availability.
|
||||
## Key Features
|
||||
|
||||
- **One-Click OpenClaw Deployment**: Seamlessly deploy OpenClaw agents, host local LLMs using Ollama, and monitor GPU utilization—all within a unified workspace.
|
||||
- **One-Click Website Deployment**: Launch production-ready websites instantly with automatic domain binding, SSL certificate provisioning, and configuration—zero manual setup required.
|
||||
- **Curated App Marketplace**: Discover, install, and update trusted open-source applications (Nextcloud, Bitwarden, Umami, and more) with a single click.
|
||||
- **Centralized Server Management**: Monitor system resources and manage files, databases, containers, and services effortlessly through a unified, intuitive dashboard.
|
||||
- **Enterprise-Grade Security**: Strengthen your defenses with container isolation, firewall controls, audit logging, and secure deployment practices—ensuring compliance and peace of mind.
|
||||
- **Intelligent Backup & Restore**: Schedule automated backups or perform instant restores to local storage or major cloud platforms (AWS S3, Cloudflare R2, and more), with flexible retention and destination options.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Execute the script below and follow the prompts to install 1Panel:
|
||||
|
||||
```bash
|
||||
curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh
|
||||
bash -c "$(curl -sSL https://resource.1panel.pro/v2/quick_start.sh)"
|
||||
```
|
||||
|
||||
Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for more details.
|
||||
|
||||
中国用户请使用这个 [安装脚本](https://1panel.cn/docs/installation/online_installation/),其应用数量比国际版本更丰富。
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||
@@ -73,4 +73,4 @@ Licensed under The GNU General Public License version 3 (GPLv3) (the "License")
|
||||
|
||||
<https://www.gnu.org/licenses/gpl-3.0.html>
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
@@ -0,0 +1,476 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Create Agent
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentCreateReq true "request"
|
||||
// @Success 200 {object} dto.AgentItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents [post]
|
||||
func (b *BaseApi) CreateAgent(c *gin.Context) {
|
||||
var req dto.AgentCreateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := agentService.Create(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Agents
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/search [post]
|
||||
func (b *BaseApi) PageAgents(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := agentService.Page(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Delete Agent
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentDeleteReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/delete [post]
|
||||
func (b *BaseApi) DeleteAgent(c *gin.Context) {
|
||||
var req dto.AgentDeleteReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.Delete(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Reset Agent token
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentTokenResetReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/token/reset [post]
|
||||
func (b *BaseApi) ResetAgentToken(c *gin.Context) {
|
||||
var req dto.AgentTokenResetReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.ResetToken(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent model config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentModelConfigUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/model/update [post]
|
||||
func (b *BaseApi) UpdateAgentModelConfig(c *gin.Context) {
|
||||
var req dto.AgentModelConfigUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateModelConfig(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Providers
|
||||
// @Success 200 {array} dto.ProviderInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/providers [get]
|
||||
func (b *BaseApi) GetAgentProviders(c *gin.Context) {
|
||||
list, err := agentService.GetProviders()
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Create Agent account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountCreateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts [post]
|
||||
func (b *BaseApi) CreateAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountCreateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.CreateAccount(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/update [post]
|
||||
func (b *BaseApi) UpdateAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateAccount(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Agent accounts
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/search [post]
|
||||
func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
||||
var req dto.AgentAccountSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := agentService.PageAccounts(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Verify Agent account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountVerifyReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/verify [post]
|
||||
func (b *BaseApi) VerifyAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountVerifyReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.VerifyAccount(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Delete Agent account
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountDeleteReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/delete [post]
|
||||
func (b *BaseApi) DeleteAgentAccount(c *gin.Context) {
|
||||
var req dto.AgentAccountDeleteReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.DeleteAccount(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Feishu channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentFeishuConfigReq true "request"
|
||||
// @Success 200 {object} dto.AgentFeishuConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/feishu/get [post]
|
||||
func (b *BaseApi) GetAgentFeishuConfig(c *gin.Context) {
|
||||
var req dto.AgentFeishuConfigReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetFeishuConfig(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent Feishu channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentFeishuConfigUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/feishu/update [post]
|
||||
func (b *BaseApi) UpdateAgentFeishuConfig(c *gin.Context) {
|
||||
var req dto.AgentFeishuConfigUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateFeishuConfig(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Telegram channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentTelegramConfigReq true "request"
|
||||
// @Success 200 {object} dto.AgentTelegramConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/telegram/get [post]
|
||||
func (b *BaseApi) GetAgentTelegramConfig(c *gin.Context) {
|
||||
var req dto.AgentTelegramConfigReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetTelegramConfig(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent Telegram channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentTelegramConfigUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/telegram/update [post]
|
||||
func (b *BaseApi) UpdateAgentTelegramConfig(c *gin.Context) {
|
||||
var req dto.AgentTelegramConfigUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateTelegramConfig(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Discord channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentDiscordConfigReq true "request"
|
||||
// @Success 200 {object} dto.AgentDiscordConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/discord/get [post]
|
||||
func (b *BaseApi) GetAgentDiscordConfig(c *gin.Context) {
|
||||
var req dto.AgentDiscordConfigReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetDiscordConfig(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent Discord channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentDiscordConfigUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/discord/update [post]
|
||||
func (b *BaseApi) UpdateAgentDiscordConfig(c *gin.Context) {
|
||||
var req dto.AgentDiscordConfigUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateDiscordConfig(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Browser config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentBrowserConfigReq true "request"
|
||||
// @Success 200 {object} dto.AgentBrowserConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/browser/get [post]
|
||||
func (b *BaseApi) GetAgentBrowserConfig(c *gin.Context) {
|
||||
var req dto.AgentBrowserConfigReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetBrowserConfig(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent Browser config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentBrowserConfigUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/browser/update [post]
|
||||
func (b *BaseApi) UpdateAgentBrowserConfig(c *gin.Context) {
|
||||
var req dto.AgentBrowserConfigUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateBrowserConfig(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Other config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentOtherConfigReq true "request"
|
||||
// @Success 200 {object} dto.AgentOtherConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/other/get [post]
|
||||
func (b *BaseApi) GetAgentOtherConfig(c *gin.Context) {
|
||||
var req dto.AgentOtherConfigReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetOtherConfig(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent Other config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentOtherConfigUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/other/update [post]
|
||||
func (b *BaseApi) UpdateAgentOtherConfig(c *gin.Context) {
|
||||
var req dto.AgentOtherConfigUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateOtherConfig(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Approve Agent Feishu pairing code
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentFeishuPairingApproveReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/feishu/approve [post]
|
||||
func (b *BaseApi) ApproveAgentFeishuPairing(c *gin.Context) {
|
||||
var req dto.AgentFeishuPairingApproveReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.ApproveFeishuPairing(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Approve Agent channel pairing code
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentChannelPairingApproveReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/pairing/approve [post]
|
||||
func (b *BaseApi) ApproveAgentChannelPairing(c *gin.Context) {
|
||||
var req dto.AgentChannelPairingApproveReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.ApproveChannelPairing(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
+22
-9
@@ -2,12 +2,12 @@ package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/appicon"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -164,7 +164,7 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
install, err := appService.Install(req)
|
||||
install, err := appService.Install(req, true)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -203,22 +203,35 @@ func (b *BaseApi) GetAppListUpdate(c *gin.Context) {
|
||||
// @Success 200 {file} file "app icon"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/icon/:appId [get]
|
||||
// @Router /apps/icon/:key [get]
|
||||
func (b *BaseApi) GetAppIcon(c *gin.Context) {
|
||||
appID, err := helper.GetIntParamByKey(c, "appID")
|
||||
appKey, err := helper.GetStrParamByKey(c, "key")
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
iconBytes, err := appService.GetAppIcon(appID)
|
||||
iconBytes, filename, etag, err := appService.GetAppIcon(appKey)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
c.Header("Content-Type", "image/png")
|
||||
c.Header("Cache-Control", "public, max-age=31536000, immutable")
|
||||
c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
||||
c.Data(http.StatusOK, "image/png", iconBytes)
|
||||
|
||||
if len(iconBytes) == 0 {
|
||||
c.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Cache-Control", "public, max-age=2592000")
|
||||
|
||||
if etag != "" && filename != "" {
|
||||
c.Header("ETag", etag)
|
||||
if c.GetHeader("If-None-Match") == etag {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, appicon.ContentTypePNG, iconBytes)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
|
||||
@@ -109,7 +109,7 @@ func (b *BaseApi) LoadPort(c *gin.Context) {
|
||||
// @Success 200 {object} response.DatabaseConn
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/installed/conninfo [POST]
|
||||
// @Router /apps/installed/conninfo [post]
|
||||
func (b *BaseApi) LoadConnInfo(c *gin.Context) {
|
||||
var req dto.OperationWithNameAndType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -294,7 +294,7 @@ func (b *BaseApi) GetParams(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/installed/params/update [post]
|
||||
// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用参数修改 [installId]","formatEN":"Application param update [installId]"}
|
||||
// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"installId","isList":false,"db":"app_installs","output_column":"name","output_value":"name"}],"formatZH":"应用参数修改 [name]","formatEN":"Application param update [name]"}
|
||||
func (b *BaseApi) UpdateInstalled(c *gin.Context) {
|
||||
var req request.AppInstalledUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -349,3 +349,15 @@ func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UpdateAppInstallSort(c *gin.Context) {
|
||||
var req request.AppInstallSort
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := appInstallService.UpdateSort(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
@@ -306,27 +306,6 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Create container by command
|
||||
// @Accept json
|
||||
// @Param request body dto.ContainerCreateByCommand true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/command [post]
|
||||
func (b *BaseApi) ContainerCreateByCommand(c *gin.Context) {
|
||||
var req dto.ContainerCreateByCommand
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := containerService.ContainerCreateByCommand(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Upgrade container
|
||||
// @Accept json
|
||||
@@ -529,7 +508,7 @@ func (b *BaseApi) DownloadContainerLogs(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), c)
|
||||
err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), req.Timestamp, c)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
}
|
||||
@@ -731,12 +710,33 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container Compose
|
||||
// @Summary Load compose environment variables
|
||||
// @Accept json
|
||||
// @Param request body dto.FilePath true "request"
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/compose/env [post]
|
||||
func (b *BaseApi) LoadComposeEnv(c *gin.Context) {
|
||||
var req dto.FilePath
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := containerService.LoadComposeEnv(req.Path)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Container logs
|
||||
// @Param container query string false "容器名称"
|
||||
// @Param since query string false "时间筛选"
|
||||
// @Param follow query string false "是否追踪"
|
||||
// @Param tail query string false "显示行号"
|
||||
// @Param timestamp query string false "是否显示时间"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -750,6 +750,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
||||
since := c.Query("since")
|
||||
follow := c.Query("follow") == "true"
|
||||
tail := c.Query("tail")
|
||||
timestamp := c.Query("timestamp") == "true"
|
||||
|
||||
container := c.Query("container")
|
||||
compose := c.Query("compose")
|
||||
@@ -759,6 +760,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
||||
Since: since,
|
||||
Follow: follow,
|
||||
Tail: tail,
|
||||
Timestamp: timestamp,
|
||||
Type: "container",
|
||||
}
|
||||
if compose != "" {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// @Tags Container Docker
|
||||
// @Summary Load docker status
|
||||
// @Produce json
|
||||
// @Success 200 {string} dto.DockerStatus
|
||||
// @Success 200 {object} dto.DockerStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/docker/status [get]
|
||||
@@ -24,7 +24,7 @@ func (b *BaseApi) LoadDockerStatus(c *gin.Context) {
|
||||
// @Tags Container Docker
|
||||
// @Summary Load docker daemon.json
|
||||
// @Produce json
|
||||
// @Success 200 {object} string
|
||||
// @Success 200 {string} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/daemonjson/file [get]
|
||||
|
||||
@@ -20,6 +20,7 @@ var (
|
||||
aiToolService = service.NewIAIToolService()
|
||||
mcpServerService = service.NewIMcpServerService()
|
||||
tensorrtLLMService = service.NewITensorRTLLMService()
|
||||
agentService = service.NewIAgentService()
|
||||
|
||||
containerService = service.NewIContainerService()
|
||||
composeTemplateService = service.NewIComposeTemplateService()
|
||||
|
||||
@@ -978,3 +978,40 @@ func (b *BaseApi) ConvertLog(c *gin.Context) {
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Batch get file remarks
|
||||
// @Accept json
|
||||
// @Param request body request.FileRemarkBatch true "request"
|
||||
// @Success 200 {object} response.FileRemarksRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/remarks [post]
|
||||
func (b *BaseApi) BatchGetFileRemarks(c *gin.Context) {
|
||||
var req request.FileRemarkBatch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
remarks := fileService.BatchGetRemarks(req)
|
||||
helper.SuccessWithData(c, response.FileRemarksRes{Remarks: remarks})
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Set file remark
|
||||
// @Accept json
|
||||
// @Param request body request.FileRemarkUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/remark [post]
|
||||
func (b *BaseApi) SetFileRemark(c *gin.Context) {
|
||||
var req request.FileRemarkUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := fileService.SetRemark(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
@@ -57,3 +57,18 @@ func (b *BaseApi) GetProcessInfoByPID(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Process
|
||||
// @Summary Get Listening Process
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /process/listening [post]
|
||||
func (b *BaseApi) GetListeningProcess(c *gin.Context) {
|
||||
procs, err := processService.GetListeningProcess(c)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, procs)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@ func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (b *BaseApi) CheckLocalConn(c *gin.Context) {
|
||||
_, err := loadLocalConn()
|
||||
client, err := loadLocalConn()
|
||||
if err == nil && client != nil {
|
||||
client.Close()
|
||||
}
|
||||
helper.SuccessWithData(c, err == nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func (b *BaseApi) PageTasks(c *gin.Context) {
|
||||
|
||||
// @Tags TaskLog
|
||||
// @Summary Get the number of executing tasks
|
||||
// @Success 200 {object} int64
|
||||
// @Success 200 {integer} int64
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/tasks/executing/count [get]
|
||||
|
||||
@@ -506,7 +506,7 @@ func (b *BaseApi) GetProxyConfig(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/proxies/update [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置 ","formatEN":"Update domain [domain] proxy config"}
|
||||
// @x-panel-log {"bodyKeys":["id","name","operate"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改 [operate] 网站 [domain] 反向代理配置 [name] ","formatEN":"Update [operate] domain [domain] proxy config [name]"}
|
||||
func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
|
||||
var req request.WebsiteProxyConfig
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -520,6 +520,50 @@ func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Delete proxy config
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteProxyDel true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/proxies/delete [post]
|
||||
// @x-panel-log {"bodyKeys":["id","name"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain] 反向代理配置 [name] ","formatEN":"Delete domain [domain] proxy config [name]"}
|
||||
func (b *BaseApi) DeleteProxyConfig(c *gin.Context) {
|
||||
var req request.WebsiteProxyDel
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := websiteService.DeleteProxy(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Update proxy config status
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteProxyStatusUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/proxies/status [post]
|
||||
// @x-panel-log {"bodyKeys":["id","name","status"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置 [name] 状态 [status] ","formatEN":"Update domain [domain] proxy config [name] status [status]"}
|
||||
func (b *BaseApi) UpdateProxyConfigStatus(c *gin.Context) {
|
||||
var req request.WebsiteProxyStatusUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := websiteService.UpdateProxyStatus(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Update proxy file
|
||||
// @Accept json
|
||||
@@ -528,7 +572,7 @@ func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/proxies/file [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新反向代理文件 [domain]","formatEN":"Nginx conf proxy file update [domain]"}
|
||||
// @x-panel-log {"bodyKeys":["websiteID","name"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置文件 [name] ","formatEN":"Update domain [domain] proxy config file [name]"}
|
||||
func (b *BaseApi) UpdateProxyConfigFile(c *gin.Context) {
|
||||
var req request.NginxProxyUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -26,27 +25,39 @@ import (
|
||||
// @Router /websites/ssl/search [post]
|
||||
func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
|
||||
var req request.WebsiteSSLSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, accounts, err := websiteSSLService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithDataGzipped(c, dto.PageResult{
|
||||
Total: total,
|
||||
Items: accounts,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
// @Summary List website ssl
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteSSLListReq true "request"
|
||||
// @Success 200 {array} response.WebsiteSSLDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/ssl/list [post]
|
||||
func (b *BaseApi) ListWebsiteSSL(c *gin.Context) {
|
||||
var req request.WebsiteSSLListReq
|
||||
if err := helper.CheckBind(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(req.PageInfo, dto.PageInfo{}) {
|
||||
total, accounts, err := websiteSSLService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithDataGzipped(c, dto.PageResult{
|
||||
Total: total,
|
||||
Items: accounts,
|
||||
})
|
||||
} else {
|
||||
list, err := websiteSSLService.Search(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithDataGzipped(c, list)
|
||||
list, err := websiteSSLService.Search(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithDataGzipped(c, list)
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type AgentCreateReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
AppVersion string `json:"appVersion" validate:"required"`
|
||||
WebUIPort int `json:"webUIPort" validate:"required"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
AgentType string `json:"agentType"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
AccountID uint `json:"accountId"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Token string `json:"token"`
|
||||
TaskID string `json:"taskID"`
|
||||
Advanced bool `json:"advanced"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AllowPort bool `json:"allowPort"`
|
||||
SpecifyIP string `json:"specifyIP"`
|
||||
RestartPolicy string `json:"restartPolicy"`
|
||||
CpuQuota float64 `json:"cpuQuota"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
MemoryUnit string `json:"memoryUnit"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
EditCompose bool `json:"editCompose"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
}
|
||||
|
||||
type AgentItem struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AgentType string `json:"agentType"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
AccountID uint `json:"accountId"`
|
||||
AppVersion string `json:"appVersion"`
|
||||
Container string `json:"containerName"`
|
||||
WebUIPort int `json:"webUIPort"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
Path string `json:"path"`
|
||||
ConfigPath string `json:"configPath"`
|
||||
Upgradable bool `json:"upgradable"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type AgentDeleteReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
TaskID string `json:"taskID"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
}
|
||||
|
||||
type AgentTokenResetReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentModelConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountCreateReq struct {
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type AgentAccountUpdateReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Remark string `json:"remark"`
|
||||
SyncAgents bool `json:"syncAgents"`
|
||||
}
|
||||
|
||||
type AgentAccountVerifyReq struct {
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
}
|
||||
|
||||
type AgentAccountDeleteReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountSearch struct {
|
||||
PageInfo
|
||||
Provider string `json:"provider"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AgentAccountInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ProviderModelInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ProviderInfo struct {
|
||||
Sort uint `json:"-"`
|
||||
Provider string `json:"provider"`
|
||||
DisplayName string `json:"displayName"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Models []ProviderModelInfo `json:"models"`
|
||||
}
|
||||
|
||||
type AgentFeishuConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentFeishuConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
BotName string `json:"botName" validate:"required"`
|
||||
AppID string `json:"appId" validate:"required"`
|
||||
AppSecret string `json:"appSecret" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
DmPolicy string `json:"dmPolicy" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentFeishuPairingApproveReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
PairingCode string `json:"pairingCode" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentFeishuConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
DmPolicy string `json:"dmPolicy"`
|
||||
BotName string `json:"botName"`
|
||||
AppID string `json:"appId"`
|
||||
AppSecret string `json:"appSecret"`
|
||||
}
|
||||
|
||||
type AgentTelegramConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentTelegramConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
DmPolicy string `json:"dmPolicy" validate:"required"`
|
||||
BotToken string `json:"botToken" validate:"required"`
|
||||
Proxy string `json:"proxy"`
|
||||
}
|
||||
|
||||
type AgentTelegramConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
DmPolicy string `json:"dmPolicy"`
|
||||
BotToken string `json:"botToken"`
|
||||
Proxy string `json:"proxy"`
|
||||
}
|
||||
|
||||
type AgentChannelPairingApproveReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=feishu telegram discord"`
|
||||
PairingCode string `json:"pairingCode" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentDiscordConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentDiscordConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
DmPolicy string `json:"dmPolicy" validate:"required"`
|
||||
GroupPolicy string `json:"groupPolicy" validate:"required,oneof=open allowlist disabled"`
|
||||
Token string `json:"token" validate:"required"`
|
||||
Proxy string `json:"proxy"`
|
||||
}
|
||||
|
||||
type AgentDiscordConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
DmPolicy string `json:"dmPolicy"`
|
||||
GroupPolicy string `json:"groupPolicy"`
|
||||
Token string `json:"token"`
|
||||
Proxy string `json:"proxy"`
|
||||
}
|
||||
|
||||
type AgentBrowserConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentBrowserConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Headless bool `json:"headless"`
|
||||
NoSandbox bool `json:"noSandbox"`
|
||||
DefaultProfile string `json:"defaultProfile" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentBrowserConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ExecutablePath string `json:"executablePath"`
|
||||
Headless bool `json:"headless"`
|
||||
NoSandbox bool `json:"noSandbox"`
|
||||
DefaultProfile string `json:"defaultProfile"`
|
||||
}
|
||||
|
||||
type AgentOtherConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentOtherConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
UserTimezone string `json:"userTimezone" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentOtherConfig struct {
|
||||
UserTimezone string `json:"userTimezone"`
|
||||
}
|
||||
@@ -330,3 +330,13 @@ type AlertEmailConfig struct {
|
||||
Encryption string `json:"encryption"` // "ssl" / "tls" / "none"
|
||||
Recipient string `json:"recipient"`
|
||||
}
|
||||
|
||||
type AgentInfo struct {
|
||||
NodeName string `json:"nodeName"`
|
||||
NodeAddr string `json:"nodeAddr"`
|
||||
}
|
||||
|
||||
type AlertWebhookConfig struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
@@ -65,12 +65,13 @@ type UploadForRecover struct {
|
||||
}
|
||||
|
||||
type CommonBackup struct {
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
Secret string `json:"secret"`
|
||||
TaskID string `json:"taskID"`
|
||||
FileName string `json:"fileName"`
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
Secret string `json:"secret"`
|
||||
TaskID string `json:"taskID"`
|
||||
FileName string `json:"fileName"`
|
||||
Args []string `json:"args"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -95,10 +95,16 @@ type ContainerOperate struct {
|
||||
Privileged bool `json:"privileged"`
|
||||
AutoRemove bool `json:"autoRemove"`
|
||||
Volumes []VolumeHelper `json:"volumes"`
|
||||
ExtraHosts []ExtraHost `json:"extraHosts"`
|
||||
Labels []string `json:"labels"`
|
||||
Env []string `json:"env"`
|
||||
RestartPolicy string `json:"restartPolicy"`
|
||||
}
|
||||
|
||||
type ExtraHost struct {
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
type ContainerNetwork struct {
|
||||
Network string `json:"network"`
|
||||
Ipv4 string `json:"ipv4"`
|
||||
@@ -272,13 +278,14 @@ type ComposeContainer struct {
|
||||
Ports []string `json:"ports"`
|
||||
}
|
||||
type ComposeCreate struct {
|
||||
TaskID string `json:"taskID"`
|
||||
Name string `json:"name"`
|
||||
From string `json:"from" validate:"required,oneof=edit path template"`
|
||||
File string `json:"file"`
|
||||
Path string `json:"path"`
|
||||
Template uint `json:"template"`
|
||||
Env string `json:"env"`
|
||||
TaskID string `json:"taskID"`
|
||||
Name string `json:"name"`
|
||||
From string `json:"from" validate:"required,oneof=edit path template"`
|
||||
File string `json:"file"`
|
||||
Path string `json:"path"`
|
||||
Template uint `json:"template"`
|
||||
Env string `json:"env"`
|
||||
ForcePull bool `json:"forcePull"`
|
||||
}
|
||||
type ComposeOperation struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
@@ -288,11 +295,13 @@ type ComposeOperation struct {
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
type ComposeUpdate struct {
|
||||
TaskID string `json:"taskID"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
DetailPath string `json:"detailPath"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Env string `json:"env"`
|
||||
ForcePull bool `json:"forcePull"`
|
||||
}
|
||||
type ComposeLogClean struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
@@ -304,6 +313,7 @@ type ContainerLog struct {
|
||||
Container string `json:"container" validate:"required"`
|
||||
Since string `json:"since"`
|
||||
Tail uint `json:"tail"`
|
||||
Timestamp bool `json:"timestamp"`
|
||||
ContainerType string `json:"containerType"`
|
||||
}
|
||||
|
||||
@@ -313,5 +323,6 @@ type StreamLog struct {
|
||||
Since string
|
||||
Follow bool
|
||||
Tail string
|
||||
Timestamp bool
|
||||
Type string
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ type CronjobOperate struct {
|
||||
Timeout uint `json:"timeout" validate:"number,min=1"`
|
||||
IgnoreErr bool `json:"ignoreErr"`
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
@@ -123,6 +124,7 @@ type CronjobInfo struct {
|
||||
LastRecordTime string `json:"lastRecordTime"`
|
||||
Status string `json:"status"`
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
}
|
||||
@@ -162,6 +164,7 @@ type CronjobTrans struct {
|
||||
IgnoreErr bool `json:"ignoreErr"`
|
||||
SnapshotRule SnapshotTransHelper `json:"snapshotRule"`
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
|
||||
@@ -21,8 +21,8 @@ type ImageInfo struct {
|
||||
}
|
||||
|
||||
type ImageLoad struct {
|
||||
TaskID string `json:"taskID"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
TaskID string `json:"taskID"`
|
||||
Paths []string `json:"paths" validate:"required,dive,required"`
|
||||
}
|
||||
|
||||
type ImageBuild struct {
|
||||
|
||||
@@ -54,13 +54,14 @@ type AppContainerConfig struct {
|
||||
|
||||
type AppInstalledSearch struct {
|
||||
dto.PageInfo
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Tags []string `json:"tags"`
|
||||
Update bool `json:"update"`
|
||||
Unused bool `json:"unused"`
|
||||
All bool `json:"all"`
|
||||
Sync bool `json:"sync"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Tags []string `json:"tags"`
|
||||
Update bool `json:"update"`
|
||||
Unused bool `json:"unused"`
|
||||
All bool `json:"all"`
|
||||
Sync bool `json:"sync"`
|
||||
CheckUpdate bool `json:"checkUpdate"`
|
||||
}
|
||||
|
||||
type AppInstalledInfo struct {
|
||||
@@ -137,3 +138,12 @@ type AppUpdateVersion struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
UpdateVersion string `json:"updateVersion"`
|
||||
}
|
||||
|
||||
type AppInstallSortItem struct {
|
||||
InstallID uint `json:"installID"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
}
|
||||
|
||||
type AppInstallSort struct {
|
||||
Items []AppInstallSortItem `json:"items" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -163,3 +163,12 @@ type FileConvertRequest struct {
|
||||
DeleteSource bool `json:"deleteSource"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type FileRemarkBatch struct {
|
||||
Paths []string `json:"paths" validate:"required"`
|
||||
}
|
||||
|
||||
type FileRemarkUpdate struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
@@ -272,6 +272,17 @@ type WebsiteProxyConfig struct {
|
||||
CorsConfig
|
||||
}
|
||||
|
||||
type WebsiteProxyDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteProxyStatusUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Status string `json:"status" validate:"required"`
|
||||
}
|
||||
|
||||
type CorsConfig struct {
|
||||
Cors bool `json:"cors"`
|
||||
AllowOrigins string `json:"allowOrigins"`
|
||||
|
||||
@@ -6,8 +6,12 @@ type WebsiteSSLSearch struct {
|
||||
dto.PageInfo
|
||||
AcmeAccountID string `json:"acmeAccountID"`
|
||||
Domain string `json:"domain"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=expire_date"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
OrderBy string `json:"orderBy" validate:"omitempty,oneof=created_at expire_date"`
|
||||
Order string `json:"order" validate:"omitempty,oneof=null ascending descending"`
|
||||
}
|
||||
|
||||
type WebsiteSSLListReq struct {
|
||||
AcmeAccountID string `json:"acmeAccountID"`
|
||||
}
|
||||
|
||||
type WebsiteSSLCreate struct {
|
||||
@@ -63,6 +67,7 @@ type WebsiteAcmeAccountCreate struct {
|
||||
EabHmacKey string `json:"eabHmacKey"`
|
||||
UseProxy bool `json:"useProxy"`
|
||||
CaDirURL string `json:"caDirURL"`
|
||||
UseEAB bool `json:"useEAB"`
|
||||
}
|
||||
|
||||
type WebsiteAcmeAccountUpdate struct {
|
||||
|
||||
@@ -126,6 +126,7 @@ type AppInstallDTO struct {
|
||||
WebUI string `json:"webUI"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Favorite bool `json:"favorite"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
App AppDetail `json:"app"`
|
||||
Container string `json:"container"`
|
||||
IsEdit bool `json:"isEdit"`
|
||||
|
||||
@@ -81,3 +81,7 @@ type FileConvertLog struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type FileRemarksRes struct {
|
||||
Remarks map[string]string `json:"remarks"`
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ type WebsiteRes struct {
|
||||
SSLStatus string `json:"sslStatus"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
ChildSites []string `json:"childSites"`
|
||||
ParentSite string `json:"parentSite"`
|
||||
RuntimeType string `json:"runtimeType"`
|
||||
Favorite bool `json:"favorite"`
|
||||
IPV6 bool `json:"IPV6"`
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package model
|
||||
|
||||
type Agent struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;unique"`
|
||||
AgentType string `json:"agentType" gorm:"default:openclaw"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
AccountID uint `json:"accountId"`
|
||||
ConfigPath string `json:"configPath"`
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package model
|
||||
|
||||
type AgentAccount struct {
|
||||
BaseModel
|
||||
Provider string `json:"provider"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func (AgentAccount) TableName() string {
|
||||
return "agent_provider_accounts"
|
||||
}
|
||||
@@ -11,7 +11,7 @@ type Alert struct {
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Method string `gorm:"type:varchar(64);not null" json:"method"`
|
||||
SendCount uint `gorm:"type:integer" json:"sendCount"`
|
||||
AdvancedParams string `gorm:"type:longText" json:"advancedParam"`
|
||||
AdvancedParams string `gorm:"type:longText" json:"advancedParams"`
|
||||
}
|
||||
|
||||
type AlertTask struct {
|
||||
|
||||
@@ -26,6 +26,7 @@ type AppInstall struct {
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
WebUI string `json:"webUI"`
|
||||
Favorite bool `json:"favorite"`
|
||||
SortOrder int `json:"sortOrder" gorm:"default:0"`
|
||||
|
||||
App App `json:"app" gorm:"-:migration"`
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ type Cronjob struct {
|
||||
Timeout uint `json:"timeout"`
|
||||
IgnoreErr bool `json:"ignoreErr"`
|
||||
RetainCopies uint64 `json:"retainCopies"`
|
||||
Args string `json:"args"`
|
||||
|
||||
IsExecuting bool `json:"isExecuting"`
|
||||
Status string `json:"status"`
|
||||
|
||||
@@ -11,6 +11,7 @@ type WebsiteAcmeAccount struct {
|
||||
KeyType string `gorm:"not null;default:2048" json:"keyType"`
|
||||
UseProxy bool `gorm:"default:false" json:"useProxy"`
|
||||
CaDirURL string `json:"caDirURL"`
|
||||
UseEAB bool `json:"useEAB"`
|
||||
}
|
||||
|
||||
func (w WebsiteAcmeAccount) TableName() string {
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
Key string
|
||||
DisplayName string
|
||||
Sort uint
|
||||
DefaultBaseURL string
|
||||
EnvKey string
|
||||
Models []Model
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var catalog = map[string]Meta{
|
||||
"ollama": {
|
||||
Key: "ollama",
|
||||
DisplayName: "Ollama",
|
||||
Sort: 10,
|
||||
Enabled: true,
|
||||
},
|
||||
"deepseek": {
|
||||
Key: "deepseek",
|
||||
DisplayName: "DeepSeek",
|
||||
Sort: 20,
|
||||
DefaultBaseURL: "https://api.deepseek.com/v1",
|
||||
EnvKey: "DEEPSEEK_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "deepseek/deepseek-chat", Name: "DeepSeek Chat"},
|
||||
{ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner"},
|
||||
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B"},
|
||||
},
|
||||
},
|
||||
"custom": {
|
||||
Key: "custom",
|
||||
DisplayName: "Custom",
|
||||
Sort: 30,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "CUSTOM_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{},
|
||||
},
|
||||
"openai": {
|
||||
Key: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
Sort: 40,
|
||||
DefaultBaseURL: "https://api.openai.com/v1",
|
||||
EnvKey: "OPENAI_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "openai/codex-mini-latest", Name: "Codex Mini"},
|
||||
{ID: "openai/gpt-4.1", Name: "GPT-4.1"},
|
||||
{ID: "openai/gpt-4o", Name: "GPT-4o"},
|
||||
{ID: "openai/gpt-4o-mini", Name: "GPT-4o Mini"},
|
||||
{ID: "openai/gpt-5", Name: "GPT-5"},
|
||||
{ID: "openai/gpt-5-mini", Name: "GPT-5 Mini"},
|
||||
},
|
||||
},
|
||||
"anthropic": {
|
||||
Key: "anthropic",
|
||||
DisplayName: "Anthropic",
|
||||
Sort: 50,
|
||||
DefaultBaseURL: "https://api.anthropic.com",
|
||||
EnvKey: "ANTHROPIC_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "anthropic/claude-3-haiku-20240307", Name: "Claude 3 Haiku"},
|
||||
{ID: "anthropic/claude-3-5-haiku-latest", Name: "Claude 3.5 Haiku"},
|
||||
{ID: "anthropic/claude-3-5-sonnet-20241022", Name: "Claude 3.5 Sonnet"},
|
||||
{ID: "anthropic/claude-3-7-sonnet-20250219", Name: "Claude 3.7 Sonnet"},
|
||||
{ID: "anthropic/claude-opus-4-1", Name: "Claude Opus 4.1"},
|
||||
},
|
||||
},
|
||||
"gemini": {
|
||||
Key: "gemini",
|
||||
DisplayName: "Gemini",
|
||||
Sort: 60,
|
||||
DefaultBaseURL: "https://generativelanguage.googleapis.com",
|
||||
EnvKey: "GEMINI_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "google/gemini-1.5-flash", Name: "Gemini 1.5 Flash"},
|
||||
{ID: "google/gemini-1.5-pro", Name: "Gemini 1.5 Pro"},
|
||||
{ID: "google/gemini-2.0-flash", Name: "Gemini 2.0 Flash"},
|
||||
{ID: "google/gemini-2.5-flash", Name: "Gemini 2.5 Flash"},
|
||||
{ID: "google/gemini-2.5-pro", Name: "Gemini 2.5 Pro"},
|
||||
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"},
|
||||
},
|
||||
},
|
||||
"minimax": {
|
||||
Key: "minimax",
|
||||
DisplayName: "MiniMax (CN)",
|
||||
Sort: 70,
|
||||
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
||||
EnvKey: "MINIMAX_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "minimax/MiniMax-M2.1", Name: "MiniMax M2.1"},
|
||||
{ID: "minimax/MiniMax-M2.1-lightning", Name: "MiniMax M2.1 Lightning"},
|
||||
},
|
||||
},
|
||||
"moonshot": {
|
||||
Key: "moonshot",
|
||||
DisplayName: "Moonshot (Global)",
|
||||
Sort: 80,
|
||||
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
||||
EnvKey: "MOONSHOT_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||
},
|
||||
},
|
||||
"kimi": {
|
||||
Key: "kimi",
|
||||
DisplayName: "Kimi (CN)",
|
||||
Sort: 90,
|
||||
DefaultBaseURL: "https://api.moonshot.cn/v1",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "kimi/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||
},
|
||||
},
|
||||
"zai": {
|
||||
Key: "zai",
|
||||
DisplayName: "Z.ai",
|
||||
Sort: 95,
|
||||
DefaultBaseURL: "https://open.bigmodel.cn/api/paas/v4",
|
||||
EnvKey: "ZAI_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "zai/glm-5", Name: "GLM-5"},
|
||||
{ID: "zai/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "zai/glm-4.7-flash", Name: "GLM-4.7-Flash"},
|
||||
{ID: "zai/glm-4.7-flashx", Name: "GLM-4.7-FlashX"},
|
||||
},
|
||||
},
|
||||
"kimi-coding": {
|
||||
Key: "kimi-coding",
|
||||
DisplayName: "Kimi Coding",
|
||||
Sort: 100,
|
||||
DefaultBaseURL: "https://api.kimi.com/coding/",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5"},
|
||||
},
|
||||
},
|
||||
"bailian-coding-plan": {
|
||||
Key: "bailian-coding-plan",
|
||||
DisplayName: "阿里云百炼 Coding Plan",
|
||||
Sort: 11,
|
||||
DefaultBaseURL: "https://coding.dashscope.aliyuncs.com/v1",
|
||||
EnvKey: "QWEN_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "bailian-coding-plan/qwen3.5-plus", Name: "Qwen3.5-Plus"},
|
||||
{ID: "bailian-coding-plan/qwen3-max", Name: "Qwen3-Max"},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-next", Name: "Qwen3-Coder-Next"},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-plus", Name: "Qwen3-Coder-Plus"},
|
||||
{ID: "bailian-coding-plan/minimax-m2.5", Name: "MiniMax M2.5"},
|
||||
{ID: "bailian-coding-plan/glm-5", Name: "GLM-5"},
|
||||
{ID: "bailian-coding-plan/kimi-k2.5", Name: "Kimi-k2.5"},
|
||||
{ID: "bailian-coding-plan/glm-4.7", Name: "GLM-4.7"},
|
||||
},
|
||||
},
|
||||
"ark-coding-plan": {
|
||||
Key: "ark-coding-plan",
|
||||
DisplayName: "方舟 Coding Plan",
|
||||
Sort: 12,
|
||||
DefaultBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
||||
EnvKey: "ARK_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "ark-coding-plan/doubao-seed-2.0-code", Name: "Doubao-Seed-2.0-Code"},
|
||||
{ID: "ark-coding-plan/doubao-seed-code", Name: "Doubao-Seed-Code"},
|
||||
{ID: "ark-coding-plan/kimi-k2.5", Name: "Kimi-K2.5"},
|
||||
{ID: "ark-coding-plan/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "ark-coding-plan/deepseek-v3.2", Name: "DeepSeek-V3.2"},
|
||||
{ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi-K2-thinking"},
|
||||
},
|
||||
},
|
||||
"qwen": {
|
||||
Key: "qwen",
|
||||
DisplayName: "Qwen",
|
||||
Sort: 110,
|
||||
DefaultBaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
EnvKey: "QWEN_API_KEY",
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
func Get(key string) (Meta, bool) {
|
||||
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
|
||||
if !ok {
|
||||
return Meta{}, false
|
||||
}
|
||||
return cloneMeta(meta), true
|
||||
}
|
||||
|
||||
func All() map[string]Meta {
|
||||
result := make(map[string]Meta, len(catalog))
|
||||
for key, meta := range catalog {
|
||||
result[key] = cloneMeta(meta)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func IsEnabled(key string) bool {
|
||||
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
|
||||
return ok && meta.Enabled
|
||||
}
|
||||
|
||||
func DefaultBaseURL(key string) (string, bool) {
|
||||
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
|
||||
if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" {
|
||||
return "", false
|
||||
}
|
||||
return meta.DefaultBaseURL, true
|
||||
}
|
||||
|
||||
func EnvKey(key string) string {
|
||||
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return meta.EnvKey
|
||||
}
|
||||
|
||||
func DisplayName(key string) string {
|
||||
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
if strings.TrimSpace(meta.DisplayName) == "" {
|
||||
return key
|
||||
}
|
||||
return meta.DisplayName
|
||||
}
|
||||
|
||||
func cloneMeta(meta Meta) Meta {
|
||||
clone := meta
|
||||
if len(meta.Models) > 0 {
|
||||
clone.Models = make([]Model, len(meta.Models))
|
||||
copy(clone.Models, meta.Models)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
type AgentRepo struct{}
|
||||
|
||||
type IAgentRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Agent, error)
|
||||
GetFirst(opts ...DBOption) (*model.Agent, error)
|
||||
Create(agent *model.Agent) error
|
||||
Save(agent *model.Agent) error
|
||||
DeleteByID(id uint) error
|
||||
DeleteByAppInstallID(appInstallID uint) error
|
||||
DeleteByAppInstallIDWithCtx(ctx context.Context, appInstallID uint) error
|
||||
List(opts ...DBOption) ([]model.Agent, error)
|
||||
}
|
||||
|
||||
func NewIAgentRepo() IAgentRepo {
|
||||
return &AgentRepo{}
|
||||
}
|
||||
|
||||
func (a AgentRepo) Page(page, size int, opts ...DBOption) (int64, []model.Agent, error) {
|
||||
var agents []model.Agent
|
||||
db := getDb(opts...).Model(&model.Agent{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&agents).Error
|
||||
return count, agents, err
|
||||
}
|
||||
|
||||
func (a AgentRepo) GetFirst(opts ...DBOption) (*model.Agent, error) {
|
||||
var agent model.Agent
|
||||
if err := getDb(opts...).First(&agent).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &agent, nil
|
||||
}
|
||||
|
||||
func (a AgentRepo) Create(agent *model.Agent) error {
|
||||
return getDb().Create(agent).Error
|
||||
}
|
||||
|
||||
func (a AgentRepo) Save(agent *model.Agent) error {
|
||||
return getDb().Save(agent).Error
|
||||
}
|
||||
|
||||
func (a AgentRepo) DeleteByID(id uint) error {
|
||||
return getDb().Delete(&model.Agent{}, id).Error
|
||||
}
|
||||
|
||||
func (a AgentRepo) DeleteByAppInstallID(appInstallID uint) error {
|
||||
if appInstallID == 0 {
|
||||
return nil
|
||||
}
|
||||
return getDb().Where("app_install_id = ?", appInstallID).Delete(&model.Agent{}).Error
|
||||
}
|
||||
|
||||
func (a AgentRepo) DeleteByAppInstallIDWithCtx(ctx context.Context, appInstallID uint) error {
|
||||
if appInstallID == 0 {
|
||||
return nil
|
||||
}
|
||||
return getTx(ctx).Where("app_install_id = ?", appInstallID).Delete(&model.Agent{}).Error
|
||||
}
|
||||
|
||||
func (a AgentRepo) List(opts ...DBOption) ([]model.Agent, error) {
|
||||
var agents []model.Agent
|
||||
if err := getDb(opts...).Find(&agents).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agents, nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package repo
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
|
||||
type AgentAccountRepo struct{}
|
||||
|
||||
type IAgentAccountRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.AgentAccount, error)
|
||||
GetFirst(opts ...DBOption) (*model.AgentAccount, error)
|
||||
Create(account *model.AgentAccount) error
|
||||
Save(account *model.AgentAccount) error
|
||||
DeleteByID(id uint) error
|
||||
List(opts ...DBOption) ([]model.AgentAccount, error)
|
||||
}
|
||||
|
||||
func NewIAgentAccountRepo() IAgentAccountRepo {
|
||||
return &AgentAccountRepo{}
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.AgentAccount, error) {
|
||||
var accounts []model.AgentAccount
|
||||
db := getDb(opts...).Model(&model.AgentAccount{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&accounts).Error
|
||||
return count, accounts, err
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) GetFirst(opts ...DBOption) (*model.AgentAccount, error) {
|
||||
var account model.AgentAccount
|
||||
if err := getDb(opts...).First(&account).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) Create(account *model.AgentAccount) error {
|
||||
return getDb().Create(account).Error
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) Save(account *model.AgentAccount) error {
|
||||
return getDb().Save(account).Error
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) DeleteByID(id uint) error {
|
||||
return getDb().Delete(&model.AgentAccount{}, id).Error
|
||||
}
|
||||
|
||||
func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) {
|
||||
var accounts []model.AgentAccount
|
||||
if err := getDb(opts...).Find(&accounts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
@@ -20,6 +20,7 @@ type IAppRepo interface {
|
||||
WithResource(resource string) DBOption
|
||||
WithNotLocal() DBOption
|
||||
WithByLikeName(name string) DBOption
|
||||
WithKeyIn(keys []string) DBOption
|
||||
WithArch(arch string) DBOption
|
||||
WithPanelVersion(panelVersion string) DBOption
|
||||
|
||||
@@ -55,6 +56,12 @@ func (a AppRepo) WithKey(key string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppRepo) WithKeyIn(keys []string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("`key` in (?)", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppRepo) WithType(typeStr string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("type = ?", typeStr)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -72,6 +73,33 @@ func WithByDetailName(detailName string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithByProvider(provider string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(provider) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("provider = ?", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func WithByModel(model string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(model) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("model = ?", model)
|
||||
}
|
||||
}
|
||||
|
||||
func WithByAccountID(accountID uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if accountID == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("account_id = ?", accountID)
|
||||
}
|
||||
}
|
||||
|
||||
func WithByType(tp string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("`type` = ?", tp)
|
||||
@@ -124,23 +152,30 @@ func WithByCreatedAt(startTime, endTime time.Time) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithOrderBy(orderStr string) DBOption {
|
||||
if orderStr == "createdAt" {
|
||||
orderStr = "created_at"
|
||||
}
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Order(orderStr)
|
||||
}
|
||||
func WithOrderDesc(orderBy string) DBOption {
|
||||
return WithOrderRuleBy(orderBy, constant.Desc)
|
||||
}
|
||||
|
||||
func WithOrderAsc(orderBy string) DBOption {
|
||||
return WithOrderRuleBy(orderBy, constant.Asc)
|
||||
}
|
||||
|
||||
func WithOrderRuleBy(orderBy, order string) DBOption {
|
||||
if orderBy == "createdAt" {
|
||||
orderBy = "created_at"
|
||||
}
|
||||
if !re.GetRegex(re.OrderByValidationPattern).MatchString(orderBy) {
|
||||
orderBy = "created_at"
|
||||
}
|
||||
switch order {
|
||||
case constant.OrderDesc:
|
||||
order = "desc"
|
||||
case constant.OrderAsc:
|
||||
order = "asc"
|
||||
case constant.Desc:
|
||||
order = "desc"
|
||||
case constant.Asc:
|
||||
order = "asc"
|
||||
default:
|
||||
orderBy = "created_at"
|
||||
order = "desc"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
if search.Type != "" {
|
||||
opts = append(opts, alertRepo.WithByType(search.Type))
|
||||
}
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
opts = append(opts, repo.WithOrderDesc("created_at"))
|
||||
|
||||
total, alerts, err := alertRepo.Page(search.Page, search.PageSize, opts...)
|
||||
if err != nil {
|
||||
@@ -343,7 +343,7 @@ func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.Ale
|
||||
if search.Count != 0 {
|
||||
opts = append(opts, alertRepo.WithByCount(search.Count))
|
||||
}
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
opts = append(opts, repo.WithOrderDesc("created_at"))
|
||||
|
||||
total, alerts, err := alertRepo.PageLog(search.Page, search.PageSize, opts...)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
@@ -110,7 +111,7 @@ func (m *AlertTaskHelper) getClassifiedAlerts() (baseAlerts, resourceAlerts []dt
|
||||
|
||||
func handleBaseAlerts(baseAlerts []dto.AlertDTO) {
|
||||
if len(baseAlerts) == 0 {
|
||||
stopResourceJob()
|
||||
stopBaseJob()
|
||||
return
|
||||
}
|
||||
if global.AlertBaseJobID == 0 {
|
||||
@@ -227,11 +228,30 @@ func loadSSLInfo(alert dto.AlertDTO) {
|
||||
return
|
||||
}
|
||||
sender := NewAlertSender(alert, projectJSON)
|
||||
minDays := math.MaxInt
|
||||
maxDays := 0
|
||||
allDomains := make([]string, 0)
|
||||
for daysDiff, domains := range daysDiffMap {
|
||||
domainStr := strings.Join(domains, ",")
|
||||
params := createAlertBaseParams(strconv.Itoa(len(domains)), strconv.Itoa(daysDiff))
|
||||
sender.Send(domainStr, params)
|
||||
allDomains = append(allDomains, domains...)
|
||||
if daysDiff < minDays {
|
||||
minDays = daysDiff
|
||||
}
|
||||
if daysDiff > maxDays {
|
||||
maxDays = daysDiff
|
||||
}
|
||||
}
|
||||
if len(allDomains) == 0 {
|
||||
return
|
||||
}
|
||||
var daysStr string
|
||||
if len(allDomains) == 1 {
|
||||
daysStr = strconv.Itoa(minDays)
|
||||
} else {
|
||||
daysStr = strconv.Itoa(minDays) + "-" + strconv.Itoa(maxDays)
|
||||
}
|
||||
domainStr := strings.Join(allDomains, ",")
|
||||
params := createAlertBaseParams(strconv.Itoa(len(daysDiffMap)), daysStr)
|
||||
sender.Send(domainStr, params)
|
||||
}
|
||||
|
||||
func loadWebsiteInfo(alert dto.AlertDTO) {
|
||||
@@ -247,11 +267,30 @@ func loadWebsiteInfo(alert dto.AlertDTO) {
|
||||
return
|
||||
}
|
||||
sender := NewAlertSender(alert, projectJSON)
|
||||
minDays := math.MaxInt
|
||||
maxDays := 0
|
||||
allDomains := make([]string, 0)
|
||||
for daysDiff, domains := range daysDiffMap {
|
||||
domainStr := strings.Join(domains, ",")
|
||||
params := createAlertBaseParams(strconv.Itoa(len(domains)), strconv.Itoa(daysDiff))
|
||||
sender.Send(domainStr, params)
|
||||
allDomains = append(allDomains, domains...)
|
||||
if daysDiff < minDays {
|
||||
minDays = daysDiff
|
||||
}
|
||||
if daysDiff > maxDays {
|
||||
maxDays = daysDiff
|
||||
}
|
||||
}
|
||||
if len(allDomains) == 0 {
|
||||
return
|
||||
}
|
||||
var daysStr string
|
||||
if len(allDomains) == 1 {
|
||||
daysStr = strconv.Itoa(minDays)
|
||||
} else {
|
||||
daysStr = strconv.Itoa(minDays) + "-" + strconv.Itoa(maxDays)
|
||||
}
|
||||
domainStr := strings.Join(allDomains, ",")
|
||||
params := createAlertBaseParams(strconv.Itoa(len(daysDiffMap)), daysStr)
|
||||
sender.Send(domainStr, params)
|
||||
}
|
||||
|
||||
func loadPanelPwd(alert dto.AlertDTO) {
|
||||
@@ -580,10 +619,12 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
_ = xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
|
||||
alertErr := xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.SMS)
|
||||
global.LOG.Infof("%s alert sms push successful", alertType)
|
||||
|
||||
case constant.Email:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.Email)
|
||||
if !isValid {
|
||||
@@ -599,9 +640,32 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
|
||||
transport := xpack.LoadRequestTransport()
|
||||
_ = alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport)
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.Email)
|
||||
global.LOG.Infof("%s alert email push successful", alertType)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, m)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
err := xpack.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, m, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, m, err)
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, m)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -641,7 +705,9 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
|
||||
projectMap := make(map[uint][]time.Time)
|
||||
|
||||
for _, ssl := range sslList {
|
||||
daysDiff := int(ssl.ExpireDate.Sub(currentDate).Hours() / 24)
|
||||
daysDiff := int(math.Ceil(
|
||||
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
||||
))
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff {
|
||||
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
||||
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
||||
@@ -656,7 +722,9 @@ func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][
|
||||
projectMap := make(map[uint][]time.Time)
|
||||
|
||||
for _, website := range websites {
|
||||
daysDiff := int(website.ExpireDate.Sub(currentDate).Hours() / 24)
|
||||
daysDiff := int(math.Ceil(
|
||||
website.ExpireDate.Sub(currentDate).Hours() / 24,
|
||||
))
|
||||
if daysDiff > 0 && int(cycle) >= daysDiff {
|
||||
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], website.PrimaryDomain)
|
||||
projectMap[website.ID] = append(projectMap[website.ID], website.ExpireDate)
|
||||
@@ -820,30 +888,36 @@ func processAllDisks(alert dto.AlertDTO) error {
|
||||
global.LOG.Errorf("error getting disk list, err: %v", err)
|
||||
return err
|
||||
}
|
||||
var errMsgs []string
|
||||
for _, item := range diskList {
|
||||
if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success {
|
||||
global.LOG.Infof("disk alert pushed successfully for %s", item.Path)
|
||||
err := checkAndCreateDiskAlert(alert, item.Path)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("disk path %s process failed: %v", item.Path, err)
|
||||
errMsgs = append(errMsgs, errMsg)
|
||||
global.LOG.Errorf(errMsg)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(errMsgs) > 0 {
|
||||
return fmt.Errorf("batch process disks failed, error count: %d, details: %s", len(errMsgs), strings.Join(errMsgs, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processSingleDisk(alert dto.AlertDTO) error {
|
||||
success, err := checkAndCreateDiskAlert(alert, alert.Project)
|
||||
err := checkAndCreateDiskAlert(alert, alert.Project)
|
||||
if err != nil {
|
||||
global.LOG.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
if success {
|
||||
global.LOG.Infof("disk alert pushed successfully for %s", alert.Project)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
|
||||
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) error {
|
||||
usageStat, err := psutil.DISK.GetUsage(path, false)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
|
||||
@@ -852,13 +926,12 @@ func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
|
||||
commonTotal *= 1024 * 1024 * 1024
|
||||
}
|
||||
if usedTotal < commonTotal {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
global.LOG.Infof("disk「 %s 」usage: %s", path, usedStr)
|
||||
params := createAlertDiskParams(path, usedStr)
|
||||
sender := NewAlertSender(alert, alert.Project)
|
||||
sender.ResourceSend(path, params)
|
||||
return true, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
|
||||
|
||||
@@ -30,6 +30,8 @@ func (s *AlertSender) Send(quota string, params []dto.Param) {
|
||||
s.sendSMS(quota, params)
|
||||
case constant.Email:
|
||||
s.sendEmail(quota, params)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
s.sendWebhook(quota, params, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +45,8 @@ func (s *AlertSender) ResourceSend(quota string, params []dto.Param) {
|
||||
s.sendResourceSMS(quota, params)
|
||||
case constant.Email:
|
||||
s.sendResourceEmail(quota, params)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
s.sendResourceWebhook(quota, params, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,9 +68,12 @@ func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
|
||||
Type: s.alert.Type,
|
||||
}
|
||||
|
||||
_ = xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
|
||||
err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
|
||||
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
|
||||
@@ -85,9 +92,35 @@ func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
|
||||
}
|
||||
|
||||
transport := xpack.LoadRequestTransport()
|
||||
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport)
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
|
||||
global.LOG.Infof("%s alert email push successful", s.alert.Type)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||
totalCount, isValid := s.canSendAlert(method)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
}
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
|
||||
@@ -112,7 +145,6 @@ func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
|
||||
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
@@ -131,12 +163,33 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
}
|
||||
|
||||
transport := xpack.LoadRequestTransport()
|
||||
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport); err != nil {
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
|
||||
global.LOG.Errorf("failed to send Email alert: %v", err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
|
||||
global.LOG.Infof("%s alert email push successful", s.alert.Type)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, method string) {
|
||||
todayCount, isValid := s.canResourceSendAlert(method)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
}
|
||||
transport := xpack.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.GetAgentInfo()
|
||||
if err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo); err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
|
||||
}
|
||||
|
||||
func (s *AlertSender) canSendAlert(method string) (uint, bool) {
|
||||
|
||||
+101
-259
@@ -12,6 +12,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/appicon"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
@@ -33,6 +35,11 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
appStoreSyncMu sync.Mutex
|
||||
appStoreSyncing bool
|
||||
)
|
||||
|
||||
type AppService struct {
|
||||
}
|
||||
|
||||
@@ -41,12 +48,12 @@ type IAppService interface {
|
||||
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
||||
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
||||
Install(req request.AppInstallCreate) (*model.AppInstall, error)
|
||||
Install(req request.AppInstallCreate, executeScript bool) (*model.AppInstall, error)
|
||||
SyncAppListFromRemote(taskID string) error
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
|
||||
SyncAppListFromLocal(taskID string)
|
||||
GetAppIcon(appID uint) ([]byte, error)
|
||||
GetAppIcon(key string) ([]byte, string, string, error)
|
||||
GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error)
|
||||
}
|
||||
|
||||
@@ -332,7 +339,7 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.AppInstall, err error) {
|
||||
func (a AppService) Install(req request.AppInstallCreate, executeScript bool) (appInstall *model.AppInstall, err error) {
|
||||
if err = docker.CreateDefaultDockerNetwork(); err != nil {
|
||||
err = buserr.WithDetail("Err1PanelNetworkFailed", err.Error(), nil)
|
||||
return
|
||||
@@ -409,7 +416,7 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
|
||||
}
|
||||
} else {
|
||||
if appDetail.DockerCompose == "" {
|
||||
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode, app.Key, appDetail.Version)
|
||||
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
|
||||
_, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -526,8 +533,10 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
|
||||
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = runScript(t, appInstall, "init"); err != nil {
|
||||
return err
|
||||
if executeScript {
|
||||
if err = runScript(t, appInstall, "init"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if app.Key == "openresty" {
|
||||
if err = handleSiteDir(app, appDetail, req, t); err != nil {
|
||||
@@ -802,8 +811,13 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
|
||||
res := &response.AppUpdateRes{
|
||||
CanUpdate: false,
|
||||
}
|
||||
mysql, _ := appRepo.GetFirst(appRepo.WithKey("mysql"))
|
||||
if !mysql.BatchInstallSupport {
|
||||
res.CanUpdate = true
|
||||
return res, nil
|
||||
}
|
||||
|
||||
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)
|
||||
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.AppRepoURL(), global.CONF.Base.Mode)
|
||||
_, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -834,6 +848,13 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
|
||||
res.CanUpdate = true
|
||||
return res, err
|
||||
}
|
||||
if appicon.IsIconFile(app.Icon) {
|
||||
fileName, _ := appicon.ParseIconField(app.Icon)
|
||||
if fileName == "" || !appicon.IconFileExists(fileName) {
|
||||
res.CanUpdate = true
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list, err := getAppList()
|
||||
@@ -868,7 +889,7 @@ func getAppFromRepo(downloadPath string) error {
|
||||
|
||||
func getAppList() (*dto.AppList, error) {
|
||||
list := &dto.AppList{}
|
||||
if err := getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)); err != nil {
|
||||
if err := getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.AppRepoURL(), global.CONF.Base.Mode)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listFile := filepath.Join(global.Dir.ResourceDir, "1panel.json")
|
||||
@@ -891,295 +912,116 @@ var InitTypes = map[string]struct{}{
|
||||
}
|
||||
|
||||
func deleteCustomApp() {
|
||||
installs, err := appInstallRepo.ListBy(context.Background())
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to list installs, skipping: %v", err)
|
||||
return
|
||||
}
|
||||
var appIDS []uint
|
||||
installs, _ := appInstallRepo.ListBy(context.Background())
|
||||
for _, install := range installs {
|
||||
appIDS = append(appIDS, install.AppId)
|
||||
}
|
||||
var ops []repo.DBOption
|
||||
ops = append(ops, repo.WithByIDNotIn(appIDS))
|
||||
if len(appIDS) > 0 {
|
||||
ops = append(ops, repo.WithByIDNotIn(appIDS))
|
||||
}
|
||||
apps, _ := appRepo.GetBy(ops...)
|
||||
apps, err := appRepo.GetBy(ops...)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to get apps, skipping: %v", err)
|
||||
return
|
||||
}
|
||||
var deleteIDS []uint
|
||||
for _, app := range apps {
|
||||
if app.Resource == constant.AppResourceCustom {
|
||||
deleteIDS = append(deleteIDS, app.ID)
|
||||
}
|
||||
}
|
||||
_ = appRepo.DeleteByIDs(context.Background(), deleteIDS)
|
||||
_ = appDetailRepo.DeleteByAppIds(context.Background(), deleteIDS)
|
||||
if len(deleteIDS) == 0 {
|
||||
return
|
||||
}
|
||||
if err = appRepo.DeleteByIDs(context.Background(), deleteIDS); err != nil {
|
||||
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to delete apps: %v", err)
|
||||
}
|
||||
if err = appDetailRepo.DeleteByAppIds(context.Background(), deleteIDS); err != nil {
|
||||
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to delete app details: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
|
||||
if xpack.IsUseCustomApp() {
|
||||
return nil
|
||||
}
|
||||
|
||||
appStoreSyncMu.Lock()
|
||||
global.LOG.Info("[AppStore] sync app from remote task create start")
|
||||
if appStoreSyncing {
|
||||
appStoreSyncMu.Unlock()
|
||||
global.LOG.Info("[AppStore] sync already in progress, skipping")
|
||||
return nil
|
||||
}
|
||||
appStoreSyncing = true
|
||||
appStoreSyncMu.Unlock()
|
||||
|
||||
syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore, taskID, 0)
|
||||
if err != nil {
|
||||
appStoreSyncMu.Lock()
|
||||
appStoreSyncing = false
|
||||
appStoreSyncMu.Unlock()
|
||||
return err
|
||||
}
|
||||
syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore), func(t *task.Task) (err error) {
|
||||
updateRes, err := a.GetAppUpdate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !updateRes.CanUpdate {
|
||||
if updateRes.IsSyncing {
|
||||
t.Log(i18n.GetMsgByKey("AppStoreIsSyncing"))
|
||||
return nil
|
||||
}
|
||||
t.Log(i18n.GetMsgByKey("AppStoreIsUpToDate"))
|
||||
return nil
|
||||
}
|
||||
list := &dto.AppList{}
|
||||
if updateRes.AppList == nil {
|
||||
list, err = getAppList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
list = updateRes.AppList
|
||||
}
|
||||
settingService := NewISettingService()
|
||||
_ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncing)
|
||||
|
||||
setting, err := settingService.GetSettingInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
appTags []*model.AppTag
|
||||
oldAppIds []uint
|
||||
)
|
||||
if err = SyncTags(list.Extra); err != nil {
|
||||
return err
|
||||
}
|
||||
deleteCustomApp()
|
||||
oldApps, err := appRepo.GetBy(appRepo.WithNotLocal())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, old := range oldApps {
|
||||
oldAppIds = append(oldAppIds, old.ID)
|
||||
}
|
||||
var sharedCtx *appSyncContext
|
||||
|
||||
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)
|
||||
|
||||
appsMap := getApps(oldApps, list.Apps, setting.SystemVersion, t)
|
||||
|
||||
t.LogStart(i18n.GetMsgByKey("SyncAppDetail"))
|
||||
for _, l := range list.Apps {
|
||||
app, ok := appsMap[l.AppProperty.Key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
iconStr := ""
|
||||
_, iconRes, err := req_helper.HandleRequest(l.Icon, http.MethodGet, constant.TimeOut20s)
|
||||
if err == nil {
|
||||
if !strings.Contains(string(iconRes), "<xml>") {
|
||||
iconStr = base64.StdEncoding.EncodeToString(iconRes)
|
||||
}
|
||||
}
|
||||
app.Icon = iconStr
|
||||
app.TagsKey = l.AppProperty.Tags
|
||||
if l.AppProperty.Recommend > 0 {
|
||||
app.Recommend = l.AppProperty.Recommend
|
||||
} else {
|
||||
app.Recommend = 9999
|
||||
}
|
||||
app.ReadMe = l.ReadMe
|
||||
app.LastModified = l.LastModified
|
||||
versions := l.Versions
|
||||
detailsMap := getAppDetails(app.Details, versions)
|
||||
for _, v := range versions {
|
||||
version := v.Name
|
||||
detail := detailsMap[version]
|
||||
versionUrl := fmt.Sprintf("%s/%s/%s", baseRemoteUrl, app.Key, version)
|
||||
paramByte, _ := json.Marshal(v.AppForm)
|
||||
var appForm dto.AppForm
|
||||
_ = json.Unmarshal(paramByte, &appForm)
|
||||
if appForm.SupportVersion > 0 && common.CompareVersion(strconv.FormatFloat(appForm.SupportVersion, 'f', -1, 64), setting.SystemVersion) {
|
||||
delete(detailsMap, version)
|
||||
continue
|
||||
}
|
||||
if _, ok := InitTypes[app.Type]; ok {
|
||||
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
|
||||
_, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if err == nil {
|
||||
detail.DockerCompose = string(composeRes)
|
||||
}
|
||||
} else {
|
||||
detail.DockerCompose = ""
|
||||
}
|
||||
|
||||
detail.Params = string(paramByte)
|
||||
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz")
|
||||
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
|
||||
detail.Update = true
|
||||
detail.LastModified = v.LastModified
|
||||
detailsMap[version] = detail
|
||||
}
|
||||
var newDetails []model.AppDetail
|
||||
for _, detail := range detailsMap {
|
||||
newDetails = append(newDetails, detail)
|
||||
}
|
||||
app.Details = newDetails
|
||||
appsMap[l.AppProperty.Key] = app
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("SyncAppDetail"))
|
||||
|
||||
tags, _ := tagRepo.All()
|
||||
var (
|
||||
addAppArray []model.App
|
||||
updateAppArray []model.App
|
||||
deleteAppArray []model.App
|
||||
deleteIds []uint
|
||||
tagMap = make(map[string]uint, len(tags))
|
||||
)
|
||||
|
||||
for _, v := range appsMap {
|
||||
if v.ID == 0 {
|
||||
addAppArray = append(addAppArray, v)
|
||||
} else {
|
||||
if v.Status == constant.AppTakeDown {
|
||||
installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(v.ID))
|
||||
if len(installs) > 0 {
|
||||
updateAppArray = append(updateAppArray, v)
|
||||
continue
|
||||
}
|
||||
deleteAppArray = append(deleteAppArray, v)
|
||||
deleteIds = append(deleteIds, v.ID)
|
||||
} else {
|
||||
updateAppArray = append(updateAppArray, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx, ctx := getTxAndContext()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
}()
|
||||
if len(addAppArray) > 0 {
|
||||
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(deleteAppArray) > 0 {
|
||||
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
|
||||
return
|
||||
}
|
||||
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, tag := range tags {
|
||||
tagMap[tag.Key] = tag.ID
|
||||
}
|
||||
for _, update := range updateAppArray {
|
||||
if err = appRepo.Save(ctx, &update); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
apps := append(addAppArray, updateAppArray...)
|
||||
|
||||
var (
|
||||
addDetails []model.AppDetail
|
||||
updateDetails []model.AppDetail
|
||||
deleteDetails []model.AppDetail
|
||||
)
|
||||
for _, app := range apps {
|
||||
for _, tag := range app.TagsKey {
|
||||
tagId, ok := tagMap[tag]
|
||||
if ok {
|
||||
exist, _ := appTagRepo.GetFirst(ctx, appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID))
|
||||
if exist == nil {
|
||||
appTags = append(appTags, &model.AppTag{
|
||||
AppId: app.ID,
|
||||
TagId: tagId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, d := range app.Details {
|
||||
d.AppId = app.ID
|
||||
if d.ID == 0 {
|
||||
addDetails = append(addDetails, d)
|
||||
} else {
|
||||
if d.Status == constant.AppTakeDown {
|
||||
runtime, _ := runtimeRepo.GetFirst(ctx, runtimeRepo.WithDetailId(d.ID))
|
||||
if runtime != nil {
|
||||
updateDetails = append(updateDetails, d)
|
||||
continue
|
||||
}
|
||||
installs, _ := appInstallRepo.ListBy(ctx, appInstallRepo.WithDetailIdsIn([]uint{d.ID}))
|
||||
if len(installs) > 0 {
|
||||
updateDetails = append(updateDetails, d)
|
||||
continue
|
||||
}
|
||||
deleteDetails = append(deleteDetails, d)
|
||||
} else {
|
||||
updateDetails = append(updateDetails, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(addDetails) > 0 {
|
||||
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(deleteDetails) > 0 {
|
||||
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, u := range updateDetails {
|
||||
if err = appDetailRepo.Update(ctx, u); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(oldAppIds) > 0 {
|
||||
if err = appTagRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(appTags) > 0 {
|
||||
if err = appTagRepo.BatchCreate(ctx, appTags); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
_ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncSuccess)
|
||||
_ = settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified))
|
||||
return nil
|
||||
}, nil)
|
||||
syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore), a.createSyncAppStoreTask(&sharedCtx), nil)
|
||||
syncTask.AddSubTask(i18n.GetMsgByKey("SyncAppDetail"), a.createSyncAppStoreMetaTask(&sharedCtx), nil)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.LOG.Errorf("[AppStore] sync goroutine recovered from panic: %v", r)
|
||||
if updateErr := NewISettingService().Update("AppStoreSyncStatus", constant.StatusError); updateErr != nil {
|
||||
global.LOG.Warnf("[AppStore] failed to update sync status after panic: %v", updateErr)
|
||||
}
|
||||
}
|
||||
appStoreSyncMu.Lock()
|
||||
appStoreSyncing = false
|
||||
appStoreSyncMu.Unlock()
|
||||
}()
|
||||
if err := syncTask.Execute(); err != nil {
|
||||
_ = NewISettingService().Update("AppStoreLastModified", "0")
|
||||
_ = NewISettingService().Update("AppStoreSyncStatus", constant.StatusError)
|
||||
if updateErr := NewISettingService().Update("AppStoreLastModified", "0"); updateErr != nil {
|
||||
global.LOG.Warnf("[AppStore] failed to reset last modified: %v", updateErr)
|
||||
}
|
||||
if updateErr := NewISettingService().Update("AppStoreSyncStatus", constant.StatusError); updateErr != nil {
|
||||
global.LOG.Warnf("[AppStore] failed to update sync status to error: %v", updateErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
global.LOG.Info("[AppStore] sync app from remote task create ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppIcon(appID uint) ([]byte, error) {
|
||||
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
||||
func (a AppService) GetAppIcon(key string) ([]byte, string, string, error) {
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
if appicon.IsIconFile(app.Icon) {
|
||||
fileName, etag := appicon.ParseIconField(app.Icon)
|
||||
iconBytes, err := appicon.ReadIconFile(fileName)
|
||||
if err != nil {
|
||||
global.LOG.Warnf("[AppIcon] read icon file failed key=%s, file=%s, err=%v", key, fileName, err)
|
||||
return nil, "", "", nil
|
||||
}
|
||||
return iconBytes, fileName, etag, nil
|
||||
}
|
||||
|
||||
iconBytes, err := base64.StdEncoding.DecodeString(app.Icon)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
global.LOG.Warnf("[AppIcon] decode base64 icon failed key=%s, err=%v", key, err)
|
||||
return nil, "", "", nil
|
||||
}
|
||||
return iconBytes, nil
|
||||
return iconBytes, "", "", nil
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ type IAppInstallService interface {
|
||||
UpdateAppConfig(req request.AppConfigUpdate) error
|
||||
GetInstallList() ([]dto.AppInstallInfo, error)
|
||||
GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error)
|
||||
UpdateSort(req request.AppInstallSort) error
|
||||
}
|
||||
|
||||
func NewIAppInstalledService() IAppInstallService {
|
||||
@@ -83,6 +84,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
|
||||
err error
|
||||
)
|
||||
opts = append(opts, repo.WithOrderRuleBy("favorite", "descending"))
|
||||
opts = append(opts, repo.WithOrderRuleBy("sort_order", "ascending"))
|
||||
|
||||
if req.Name != "" {
|
||||
opts = append(opts, repo.WithByLikeName(req.Name))
|
||||
@@ -121,7 +123,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
|
||||
}
|
||||
}
|
||||
|
||||
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync)
|
||||
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync, req.CheckUpdate)
|
||||
if req.Update {
|
||||
total = int64(len(installDTOs))
|
||||
}
|
||||
@@ -238,7 +240,7 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]
|
||||
}
|
||||
}
|
||||
|
||||
return handleInstalled(installs, false, true)
|
||||
return handleInstalled(installs, false, true, false)
|
||||
}
|
||||
|
||||
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
@@ -300,6 +302,9 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
return opNginx(install.ContainerName, constant.NginxReload)
|
||||
case constant.Favorite:
|
||||
install.Favorite = req.Favorite
|
||||
var maxSort int
|
||||
global.DB.Model(&model.AppInstall{}).Where("favorite = ?", req.Favorite).Select("COALESCE(MAX(sort_order),0)").Scan(&maxSort)
|
||||
install.SortOrder = maxSort + 1
|
||||
return appInstallRepo.Save(context.Background(), &install)
|
||||
default:
|
||||
return errors.New("operate not support")
|
||||
@@ -318,6 +323,15 @@ func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error {
|
||||
return appInstallRepo.Save(context.Background(), &installed)
|
||||
}
|
||||
|
||||
func (a *AppInstallService) UpdateSort(req request.AppInstallSort) error {
|
||||
for _, item := range req.Items {
|
||||
if err := appInstallRepo.BatchUpdateBy(map[string]interface{}{"sort_order": item.SortOrder}, repo.WithByID(item.InstallID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallId))
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/appicon"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
)
|
||||
|
||||
type appSyncContext struct {
|
||||
task *task.Task
|
||||
httpClient http.Client
|
||||
baseRemoteUrl string
|
||||
systemVersion string
|
||||
appsMap map[string]model.App
|
||||
settingService ISettingService
|
||||
list *dto.AppList
|
||||
oldAppIds []uint
|
||||
appTags []*model.AppTag
|
||||
skipMetaSync bool
|
||||
pendingIcons map[string]string
|
||||
}
|
||||
|
||||
func (a AppService) createSyncAppStoreTask(sharedCtx **appSyncContext) func(t *task.Task) error {
|
||||
return func(t *task.Task) (err error) {
|
||||
t.LogStart(i18n.GetMsgByKey("AppStore") + " " + i18n.GetMsgByKey("TaskSync"))
|
||||
|
||||
updateRes, err := a.GetAppUpdate()
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("CheckAppStoreUpdate"), err)
|
||||
return err
|
||||
}
|
||||
if !updateRes.CanUpdate {
|
||||
if updateRes.IsSyncing {
|
||||
t.Log(i18n.GetMsgByKey("AppStoreIsSyncing"))
|
||||
return nil
|
||||
}
|
||||
global.LOG.Infof("[AppStore] Appstore is up to date")
|
||||
t.Log(i18n.GetMsgByKey("AppStoreIsUpToDate"))
|
||||
*sharedCtx = &appSyncContext{skipMetaSync: true}
|
||||
t.LogSuccess(i18n.GetMsgByKey("AppStore") + " " + i18n.GetMsgByKey("TaskSync"))
|
||||
return nil
|
||||
}
|
||||
|
||||
list := &dto.AppList{}
|
||||
if updateRes.AppList == nil {
|
||||
list, err = getAppList()
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("DownloadAppList"), err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
list = updateRes.AppList
|
||||
}
|
||||
|
||||
settingService := NewISettingService()
|
||||
if err := settingService.Update("AppStoreSyncStatus", constant.StatusSyncing); err != nil {
|
||||
global.LOG.Warnf("[AppStore] failed to update sync status to syncing: %v", err)
|
||||
}
|
||||
|
||||
setting, err := settingService.GetSettingInfo()
|
||||
if err != nil {
|
||||
t.LogFailedWithErr("GetSettingInfo", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := &appSyncContext{
|
||||
task: t,
|
||||
httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.LoadRequestTransport()},
|
||||
baseRemoteUrl: fmt.Sprintf("%s/%s/1panel", global.AppRepoURL(), global.CONF.Base.Mode),
|
||||
systemVersion: setting.SystemVersion,
|
||||
settingService: settingService,
|
||||
list: list,
|
||||
appTags: make([]*model.AppTag, 0),
|
||||
pendingIcons: make(map[string]string),
|
||||
}
|
||||
|
||||
if err = SyncTags(list.Extra); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("SyncTags"), err)
|
||||
return err
|
||||
}
|
||||
deleteCustomApp()
|
||||
|
||||
oldApps, err := appRepo.GetBy(appRepo.WithNotLocal())
|
||||
if err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("LoadLocalApps"), err)
|
||||
return err
|
||||
}
|
||||
ctx.oldAppIds = make([]uint, 0, len(oldApps))
|
||||
for _, old := range oldApps {
|
||||
ctx.oldAppIds = append(ctx.oldAppIds, old.ID)
|
||||
}
|
||||
|
||||
ctx.appsMap, ctx.pendingIcons = getApps(oldApps, list.Apps, setting.SystemVersion, t)
|
||||
|
||||
var addCount, updateCount, deleteCount int
|
||||
if err = ctx.classifyAndPersistAppsWithStats(&addCount, &updateCount, &deleteCount); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("PersistApps"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settingService.Update("AppStoreSyncStatus", constant.StatusSyncSuccess); err != nil {
|
||||
global.LOG.Warnf("[AppStore] failed to update sync status to success: %v", err)
|
||||
}
|
||||
if err := settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
|
||||
global.LOG.Warnf("[AppStore] failed to update last modified: %v", err)
|
||||
}
|
||||
global.LOG.Infof("[AppStore] Appstore sync completed")
|
||||
|
||||
*sharedCtx = ctx
|
||||
t.LogSuccess(i18n.GetMsgByKey("AppStore") + " " + i18n.GetMsgByKey("TaskSync"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type appWorkItem struct {
|
||||
appDef dto.AppDefine
|
||||
app model.App
|
||||
iconUrl string
|
||||
hadIcon bool
|
||||
}
|
||||
|
||||
type appWorkResult struct {
|
||||
appKey string
|
||||
app model.App
|
||||
iconStatus int
|
||||
hadIcon bool
|
||||
httpFailed bool
|
||||
}
|
||||
|
||||
func (c *appSyncContext) processOneApp(item appWorkItem) appWorkResult {
|
||||
app := item.app
|
||||
l := item.appDef
|
||||
result := appWorkResult{
|
||||
appKey: l.AppProperty.Key,
|
||||
hadIcon: item.hadIcon,
|
||||
}
|
||||
|
||||
if item.hadIcon {
|
||||
status, iconField := c.downloadAppIcon(item.iconUrl, l.AppProperty.Key, app.Icon)
|
||||
result.iconStatus = status
|
||||
switch status {
|
||||
case http.StatusOK:
|
||||
app.Icon = iconField
|
||||
case http.StatusNotModified:
|
||||
default:
|
||||
result.httpFailed = true
|
||||
}
|
||||
}
|
||||
|
||||
app.TagsKey = l.AppProperty.Tags
|
||||
if l.AppProperty.Recommend > 0 {
|
||||
app.Recommend = l.AppProperty.Recommend
|
||||
} else {
|
||||
app.Recommend = 9999
|
||||
}
|
||||
app.ReadMe = l.ReadMe
|
||||
app.LastModified = l.LastModified
|
||||
|
||||
versions := l.Versions
|
||||
detailsMap := getAppDetails(app.Details, versions)
|
||||
for _, v := range versions {
|
||||
version := v.Name
|
||||
detail := detailsMap[version]
|
||||
versionUrl := fmt.Sprintf("%s/%s/%s", c.baseRemoteUrl, app.Key, version)
|
||||
|
||||
paramByte, _ := json.Marshal(v.AppForm)
|
||||
var appForm dto.AppForm
|
||||
_ = json.Unmarshal(paramByte, &appForm)
|
||||
|
||||
if appForm.SupportVersion > 0 && common.CompareVersion(strconv.FormatFloat(appForm.SupportVersion, 'f', -1, 64), c.systemVersion) {
|
||||
delete(detailsMap, version)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := InitTypes[app.Type]; ok {
|
||||
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
|
||||
_, composeRes, err := req_helper.HandleRequestWithClient(&c.httpClient, dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if err == nil {
|
||||
detail.DockerCompose = string(composeRes)
|
||||
} else {
|
||||
result.httpFailed = true
|
||||
}
|
||||
} else {
|
||||
detail.DockerCompose = ""
|
||||
}
|
||||
|
||||
detail.Params = string(paramByte)
|
||||
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz")
|
||||
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
|
||||
detail.Update = true
|
||||
detail.LastModified = v.LastModified
|
||||
detailsMap[version] = detail
|
||||
}
|
||||
|
||||
newDetails := make([]model.AppDetail, 0, len(detailsMap))
|
||||
for _, detail := range detailsMap {
|
||||
newDetails = append(newDetails, detail)
|
||||
}
|
||||
app.Details = newDetails
|
||||
|
||||
result.app = app
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *appSyncContext) syncAppIconsAndDetails() error {
|
||||
total := len(c.list.Apps)
|
||||
global.LOG.Infof("[AppStore] sync app detail start, total apps: %d", total)
|
||||
|
||||
workItems := make([]appWorkItem, 0, total)
|
||||
for _, l := range c.list.Apps {
|
||||
app, ok := c.appsMap[l.AppProperty.Key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
iconUrl, hasPending := c.pendingIcons[l.AppProperty.Key]
|
||||
workItems = append(workItems, appWorkItem{
|
||||
appDef: l,
|
||||
app: app,
|
||||
iconUrl: iconUrl,
|
||||
hadIcon: hasPending,
|
||||
})
|
||||
}
|
||||
|
||||
totalWork := len(workItems)
|
||||
if totalWork == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxWorkers = 4
|
||||
var (
|
||||
failFlag atomic.Bool
|
||||
workCh = make(chan int, maxWorkers)
|
||||
resultCh = make(chan appWorkResult, maxWorkers)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
wg.Add(maxWorkers)
|
||||
for range maxWorkers {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for idx := range workCh {
|
||||
result := c.processOneApp(workItems[idx])
|
||||
resultCh <- result
|
||||
if result.httpFailed {
|
||||
failFlag.Store(true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
go func() { wg.Wait(); close(resultCh) }()
|
||||
|
||||
var fed atomic.Int32
|
||||
go func() {
|
||||
for i := range workItems {
|
||||
if failFlag.Load() {
|
||||
break
|
||||
}
|
||||
workCh <- i
|
||||
fed.Store(int32(i + 1))
|
||||
}
|
||||
close(workCh)
|
||||
}()
|
||||
|
||||
var (
|
||||
completed int
|
||||
icon200Count int
|
||||
icon304Count int
|
||||
iconFailCount int
|
||||
)
|
||||
milestones := [4]int{totalWork / 4, totalWork / 2, totalWork * 3 / 4, totalWork}
|
||||
nextMS := 0
|
||||
checkProgress := func() {
|
||||
if nextMS < len(milestones) && completed >= milestones[nextMS] {
|
||||
c.task.LogWithProgress(i18n.GetMsgByKey("SyncAppDetail"), completed, totalWork)
|
||||
nextMS++
|
||||
}
|
||||
}
|
||||
|
||||
applyResult := func(result appWorkResult) {
|
||||
c.appsMap[result.appKey] = result.app
|
||||
if result.hadIcon {
|
||||
switch result.iconStatus {
|
||||
case http.StatusOK:
|
||||
icon200Count++
|
||||
case http.StatusNotModified:
|
||||
icon304Count++
|
||||
default:
|
||||
iconFailCount++
|
||||
}
|
||||
}
|
||||
completed++
|
||||
checkProgress()
|
||||
}
|
||||
|
||||
for result := range resultCh {
|
||||
applyResult(result)
|
||||
}
|
||||
|
||||
fedCount := int(fed.Load())
|
||||
if failFlag.Load() && fedCount < len(workItems) {
|
||||
global.LOG.Warnf("[AppStore] HTTP failure detected, falling back to serial for remaining %d apps", len(workItems)-fedCount)
|
||||
for i := fedCount; i < len(workItems); i++ {
|
||||
result := c.processOneApp(workItems[i])
|
||||
applyResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
global.LOG.Infof("[AppStore] icon download completed - total: %d, success(200): %d, cached(304): %d, failed: %d",
|
||||
total, icon200Count, icon304Count, iconFailCount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appSyncContext) downloadAppIcon(iconUrl, appKey, oldIcon string) (status int, iconField string) {
|
||||
iconFileName, existingEtag := appicon.ParseIconField(oldIcon)
|
||||
|
||||
reqHeaders := make(map[string]string)
|
||||
if existingEtag != "" && iconFileName != "" && appicon.IconFileExists(iconFileName) {
|
||||
reqHeaders["If-None-Match"] = existingEtag
|
||||
}
|
||||
|
||||
resp, err := req_helper.HandleRequestWithHeaders(&c.httpClient, iconUrl, http.MethodGet, constant.TimeOut20s, reqHeaders)
|
||||
if err != nil {
|
||||
global.LOG.Warnf("[AppStore] request icon failed url=%s, err=%v", iconUrl, err)
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotModified {
|
||||
return http.StatusNotModified, ""
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
global.LOG.Warnf("[AppStore] download icon failed url=%s, status=%d", iconUrl, resp.StatusCode)
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
if len(resp.Body) == 0 {
|
||||
global.LOG.Warnf("[AppStore] download icon empty body url=%s", iconUrl)
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
if resp.Body[0] == '<' {
|
||||
global.LOG.Warnf("[AppStore] download icon got HTML response url=%s", iconUrl)
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
ct := strings.TrimSpace(strings.Split(contentType, ";")[0])
|
||||
if strings.ToLower(ct) != "image/png" {
|
||||
global.LOG.Warnf("[AppStore] unexpected icon content-type: %s, expected image/png, url=%s", ct, iconUrl)
|
||||
}
|
||||
|
||||
fileName, err := appicon.WriteIconFile(appKey, resp.Body)
|
||||
if err != nil {
|
||||
global.LOG.Warnf("[AppStore] write icon file failed appKey=%s, err=%v", appKey, err)
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
newEtag := resp.Header.Get("ETag")
|
||||
iconField = appicon.BuildIconField(fileName, newEtag)
|
||||
|
||||
return http.StatusOK, iconField
|
||||
}
|
||||
|
||||
func (a AppService) createSyncAppStoreMetaTask(sharedCtx **appSyncContext) func(t *task.Task) error {
|
||||
return func(t *task.Task) (err error) {
|
||||
t.LogStart(i18n.GetMsgByKey("SyncAppDetail"))
|
||||
ctx := *sharedCtx
|
||||
if ctx == nil {
|
||||
global.LOG.Warnf("[AppStore] meta sync skipped: shared context is nil")
|
||||
t.Log(i18n.GetMsgByKey("SyncAppDetail") + " skipped: shared context is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
if ctx.skipMetaSync {
|
||||
global.LOG.Infof("[AppStore] meta sync skipped: no update needed")
|
||||
t.Log(i18n.GetMsgByKey("SyncAppDetail") + " skipped: no update needed")
|
||||
return nil
|
||||
}
|
||||
|
||||
if ctx.list == nil || ctx.appsMap == nil {
|
||||
global.LOG.Errorf("[AppStore] meta sync failed: shared context data not initialized")
|
||||
err := fmt.Errorf("shared context data not initialized")
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("SyncAppDetail"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
t.Logf("%s: %d apps", i18n.GetMsgByKey("SyncAppDetail"), len(ctx.list.Apps))
|
||||
|
||||
ctx.task = t
|
||||
ctx.appTags = make([]*model.AppTag, 0)
|
||||
|
||||
if err = ctx.syncAppIconsAndDetails(); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("SyncAppDetail"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ctx.classifyAndPersistApps(); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("PersistAppDetails"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
global.LOG.Infof("[AppStore] Appstore meta sync completed")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appSyncContext) classifyAndPersistApps() (err error) {
|
||||
var addCount, updateCount, deleteCount int
|
||||
return c.classifyAndPersistAppsWithStats(&addCount, &updateCount, &deleteCount)
|
||||
}
|
||||
|
||||
func (c *appSyncContext) classifyAndPersistAppsWithStats(addCount, updateCount, deleteCount *int) (err error) {
|
||||
tags, _ := tagRepo.All()
|
||||
var (
|
||||
addAppArray []model.App
|
||||
updateAppArray []model.App
|
||||
deleteAppArray []model.App
|
||||
deleteIds []uint
|
||||
tagMap = make(map[string]uint, len(tags))
|
||||
)
|
||||
|
||||
for _, v := range c.appsMap {
|
||||
if v.ID == 0 {
|
||||
addAppArray = append(addAppArray, v)
|
||||
} else {
|
||||
if v.Status == constant.AppTakeDown {
|
||||
installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(v.ID))
|
||||
if len(installs) > 0 {
|
||||
updateAppArray = append(updateAppArray, v)
|
||||
continue
|
||||
}
|
||||
deleteAppArray = append(deleteAppArray, v)
|
||||
deleteIds = append(deleteIds, v.ID)
|
||||
} else {
|
||||
updateAppArray = append(updateAppArray, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(addAppArray) > 0 {
|
||||
addKeys := make([]string, 0, len(addAppArray))
|
||||
for _, app := range addAppArray {
|
||||
addKeys = append(addKeys, app.Key)
|
||||
}
|
||||
existingApps, _ := appRepo.GetBy(appRepo.WithKeyIn(addKeys))
|
||||
if len(existingApps) > 0 {
|
||||
existingMap := make(map[string]model.App, len(existingApps))
|
||||
for _, e := range existingApps {
|
||||
existingMap[e.Key] = e
|
||||
}
|
||||
filteredAdd := make([]model.App, 0, len(addAppArray))
|
||||
for _, app := range addAppArray {
|
||||
if existing, ok := existingMap[app.Key]; ok {
|
||||
app.ID = existing.ID
|
||||
if len(app.Details) == 0 {
|
||||
app.Details = existing.Details
|
||||
}
|
||||
updateAppArray = append(updateAppArray, app)
|
||||
} else {
|
||||
filteredAdd = append(filteredAdd, app)
|
||||
}
|
||||
}
|
||||
addAppArray = filteredAdd
|
||||
}
|
||||
}
|
||||
|
||||
*addCount = len(addAppArray)
|
||||
*updateCount = len(updateAppArray)
|
||||
*deleteCount = len(deleteAppArray)
|
||||
|
||||
tx, ctx := getTxAndContext()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if len(addAppArray) > 0 {
|
||||
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(deleteAppArray) > 0 {
|
||||
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
|
||||
return
|
||||
}
|
||||
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
tagMap[tag.Key] = tag.ID
|
||||
}
|
||||
|
||||
if len(updateAppArray) > 0 {
|
||||
for _, update := range updateAppArray {
|
||||
if err = appRepo.Save(ctx, &update); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apps := append(addAppArray, updateAppArray...)
|
||||
|
||||
var (
|
||||
addDetails []model.AppDetail
|
||||
updateDetails []model.AppDetail
|
||||
deleteDetails []model.AppDetail
|
||||
)
|
||||
|
||||
totalDetails := 0
|
||||
for _, app := range apps {
|
||||
for _, tag := range app.TagsKey {
|
||||
tagId, ok := tagMap[tag]
|
||||
if ok {
|
||||
exist, _ := appTagRepo.GetFirst(ctx, appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID))
|
||||
if exist == nil {
|
||||
c.appTags = append(c.appTags, &model.AppTag{
|
||||
AppId: app.ID,
|
||||
TagId: tagId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range app.Details {
|
||||
totalDetails++
|
||||
d.AppId = app.ID
|
||||
if d.ID == 0 {
|
||||
addDetails = append(addDetails, d)
|
||||
} else {
|
||||
if d.Status == constant.AppTakeDown {
|
||||
runtime, _ := runtimeRepo.GetFirst(ctx, runtimeRepo.WithDetailId(d.ID))
|
||||
if runtime != nil {
|
||||
updateDetails = append(updateDetails, d)
|
||||
continue
|
||||
}
|
||||
installs, _ := appInstallRepo.ListBy(ctx, appInstallRepo.WithDetailIdsIn([]uint{d.ID}))
|
||||
if len(installs) > 0 {
|
||||
updateDetails = append(updateDetails, d)
|
||||
continue
|
||||
}
|
||||
deleteDetails = append(deleteDetails, d)
|
||||
} else {
|
||||
updateDetails = append(updateDetails, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(addDetails) > 0 {
|
||||
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(deleteDetails) > 0 {
|
||||
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(updateDetails) > 0 {
|
||||
for _, u := range updateDetails {
|
||||
if err = appDetailRepo.Update(ctx, u); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.oldAppIds) > 0 {
|
||||
if err = appTagRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.appTags) > 0 {
|
||||
if err = appTagRepo.BatchCreate(ctx, c.appTags); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -392,6 +392,10 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
if err = appInstallRepo.Delete(ctx, install); err != nil {
|
||||
return err
|
||||
}
|
||||
appKey := install.App.Key
|
||||
if appKey == constant.AppOpenclaw || appKey == constant.AppCopaw {
|
||||
_ = agentRepo.DeleteByAppInstallIDWithCtx(ctx, install.ID)
|
||||
}
|
||||
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
if len(resources) > 0 {
|
||||
@@ -764,6 +768,30 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
|
||||
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
|
||||
_, _ = command.CombinedOutput()
|
||||
if install.App.Key == constant.AppOpenresty {
|
||||
installBuildDir := path.Join(install.GetPath(), "build")
|
||||
detailBuildDir := path.Join(detailDir, "build")
|
||||
if !fileOp.Stat(installBuildDir) {
|
||||
if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := fileOp.DeleteDir(path.Join(installBuildDir, "tmp")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.CopyDir(path.Join(detailBuildDir, "tmp"), installBuildDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.vh.default.conf"), installBuildDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sourceScripts := path.Join(detailDir, "scripts")
|
||||
if fileOp.Stat(sourceScripts) {
|
||||
dstScripts := path.Join(install.GetPath(), "scripts")
|
||||
@@ -1271,8 +1299,9 @@ func getAppDetails(details []model.AppDetail, versions []dto.AppConfigVersion) m
|
||||
return appDetails
|
||||
}
|
||||
|
||||
func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, task *task.Task) map[string]model.App {
|
||||
func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, task *task.Task) (map[string]model.App, map[string]string) {
|
||||
apps := make(map[string]model.App, len(oldApps))
|
||||
pendingIcons := make(map[string]string, len(items))
|
||||
for _, old := range oldApps {
|
||||
old.Status = constant.AppTakeDown
|
||||
apps[old.Key] = old
|
||||
@@ -1313,9 +1342,12 @@ func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, t
|
||||
app.Architectures = strings.Join(config.Architectures, ",")
|
||||
app.GpuSupport = config.GpuSupport
|
||||
app.BatchInstallSupport = config.BatchInstallSupport
|
||||
if item.Icon != "" {
|
||||
pendingIcons[key] = item.Icon
|
||||
}
|
||||
apps[key] = app
|
||||
}
|
||||
return apps
|
||||
return apps, pendingIcons
|
||||
}
|
||||
|
||||
func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
|
||||
@@ -1531,7 +1563,7 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate bool) ([]response.AppInstallDTO, error) {
|
||||
var (
|
||||
res []response.AppInstallDTO
|
||||
containersMap map[string]container.Summary
|
||||
@@ -1554,6 +1586,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
if updated && ignoreUpdate(installed) {
|
||||
continue
|
||||
}
|
||||
|
||||
if sync && !doNotNeedSync(installed) {
|
||||
synAppInstall(containersMap, &installed, false)
|
||||
}
|
||||
@@ -1580,23 +1613,34 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
Document: installed.App.Document,
|
||||
},
|
||||
Favorite: installed.Favorite,
|
||||
SortOrder: installed.SortOrder,
|
||||
Container: installed.ContainerName,
|
||||
ServiceName: strings.ToLower(installed.ServiceName),
|
||||
}
|
||||
if !updated {
|
||||
|
||||
if !updated && !checkUpdate {
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
res = append(res, installDTO)
|
||||
continue
|
||||
}
|
||||
|
||||
if installed.Version == "latest" {
|
||||
if checkUpdate {
|
||||
installDTO.CanUpdate = false
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
installDTO.DockerCompose = installed.DockerCompose
|
||||
installDTO.IsEdit = isEditCompose(installed)
|
||||
|
||||
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var versions []string
|
||||
for _, appDetail := range details {
|
||||
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(appDetail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
||||
@@ -1608,11 +1652,19 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
}
|
||||
versions = append(versions, appDetail.Version)
|
||||
}
|
||||
|
||||
if len(versions) == 0 {
|
||||
if checkUpdate {
|
||||
installDTO.CanUpdate = false
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
versions = common.GetSortedVersions(versions)
|
||||
lastVersion := versions[0]
|
||||
|
||||
if installed.App.Key == constant.AppMysql || installed.App.Key == constant.AppMysqlCluster {
|
||||
for _, version := range versions {
|
||||
majorVersion := getMajorVersion(installed.Version)
|
||||
@@ -1624,15 +1676,23 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if common.IsCrossVersion(installed.Version, lastVersion) {
|
||||
installDTO.CanUpdate = installed.App.CrossVersionUpdate
|
||||
} else {
|
||||
installDTO.CanUpdate = common.CompareVersion(lastVersion, installed.Version)
|
||||
}
|
||||
if installDTO.CanUpdate {
|
||||
|
||||
if updated {
|
||||
if installDTO.CanUpdate {
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
} else if checkUpdate {
|
||||
installDTO.LinkDB = hasLinkDB(installed.ID)
|
||||
res = append(res, installDTO)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -1961,23 +2021,26 @@ func handleSiteDir(app model.App, appDetail model.AppDetail, req request.AppInst
|
||||
req.Params["WEBSITE_DIR"] = siteDir
|
||||
oldWebStePath, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
fileOp := files.NewFileOp()
|
||||
movePath := func(src, dst string) error {
|
||||
return cmd.NewCommandMgr().Run("mv", src, dst)
|
||||
}
|
||||
if oldWebStePath != "" && oldWebStePath != siteDir && fileOp.Stat(oldWebStePath) {
|
||||
t.Log(i18n.GetWithName("MoveSiteDir", siteDir))
|
||||
if fileOp.Stat(siteDir) {
|
||||
if fileOp.Stat(path.Join(siteDir, "conf.d")) {
|
||||
_ = fileOp.Rename(path.Join(siteDir, "conf.d"), path.Join(siteDir, "conf.d.bak"))
|
||||
_ = movePath(path.Join(siteDir, "conf.d"), path.Join(siteDir, "conf.d.bak"))
|
||||
}
|
||||
if fileOp.Stat(path.Join(siteDir, "sites")) {
|
||||
_ = fileOp.Rename(path.Join(siteDir, "sites"), path.Join(siteDir, "sites.bak"))
|
||||
_ = movePath(path.Join(siteDir, "sites"), path.Join(siteDir, "sites.bak"))
|
||||
}
|
||||
if err := fileOp.Rename(path.Join(oldWebStePath, "sites"), path.Join(siteDir, "sites")); err != nil {
|
||||
if err := movePath(path.Join(oldWebStePath, "sites"), path.Join(siteDir, "sites")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.Rename(path.Join(oldWebStePath, "conf.d"), path.Join(siteDir, "conf.d")); err != nil {
|
||||
if err := movePath(path.Join(oldWebStePath, "conf.d"), path.Join(siteDir, "conf.d")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := fileOp.Rename(oldWebStePath, siteDir)
|
||||
err := movePath(oldWebStePath, siteDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2092,15 +2155,21 @@ func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRe
|
||||
return nil
|
||||
}
|
||||
|
||||
func SyncTags(remoteProperties dto.ExtraProperties) error {
|
||||
func SyncTags(remoteProperties dto.ExtraProperties) (err error) {
|
||||
tx, ctx := getTxAndContext()
|
||||
defer tx.Rollback()
|
||||
localTags, _ := tagRepo.All()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
localTags, err := tagRepo.All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localTagsMap := make(map[string]*model.Tag)
|
||||
for i := range localTags {
|
||||
localTagsMap[localTags[i].Key] = &localTags[i]
|
||||
}
|
||||
var err error
|
||||
remoteTagsMap := make(map[string]*dto.Tag)
|
||||
for i := range remoteProperties.Tags {
|
||||
remoteTagsMap[remoteProperties.Tags[i].Key] = &remoteProperties.Tags[i]
|
||||
@@ -2108,7 +2177,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error {
|
||||
|
||||
for key, localTag := range localTagsMap {
|
||||
if _, exists := remoteTagsMap[key]; !exists {
|
||||
_ = tagRepo.DeleteByID(ctx, localTag.ID)
|
||||
if err = tagRepo.DeleteByID(ctx, localTag.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2138,7 +2209,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error {
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ func (u *BackupService) GetLocalDir() (string, error) {
|
||||
}
|
||||
|
||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
|
||||
options := []repo.DBOption{repo.WithOrderBy("created_at desc")}
|
||||
options := []repo.DBOption{repo.WithOrderDesc("created_at")}
|
||||
if len(req.Type) != 0 {
|
||||
options = append(options, repo.WithByType(req.Type))
|
||||
}
|
||||
@@ -372,7 +372,7 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
|
||||
}
|
||||
|
||||
func (u *BackupService) LoadBackupOptions() ([]dto.BackupOption, error) {
|
||||
accounts, err := backupRepo.List(repo.WithOrderBy("created_at desc"))
|
||||
accounts, err := backupRepo.List(repo.WithOrderDesc("created_at"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -535,7 +535,7 @@ func loadRefreshTokenByCode(backup *model.BackupAccount) (string, error) {
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return "", fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||
}
|
||||
if _, ok := varMap["refresh_token"]; ok {
|
||||
if token, ok := varMap["refresh_token"]; ok && len(token.(string)) != 0 {
|
||||
return "", nil
|
||||
}
|
||||
refreshToken := ""
|
||||
|
||||
+153
-39
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/subosito/gotenv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
@@ -179,6 +180,9 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
||||
return err
|
||||
}
|
||||
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||
if err := fileOp.TarGzExtractPro(tmpPath+"/app.tar.gz", tmpPath, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
|
||||
@@ -187,19 +191,26 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
||||
if !fileOp.Stat(tmpPath+"/app.json") || !fileOp.Stat(tmpPath+"/app.tar.gz") {
|
||||
return errors.New(i18n.GetMsgByKey("AppBackupFileIncomplete"))
|
||||
}
|
||||
var oldInstall model.AppInstall
|
||||
var backupInstall model.AppInstall
|
||||
appJson, err := os.ReadFile(tmpPath + "/app.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(appJson, &oldInstall); err != nil {
|
||||
if err := json.Unmarshal(appJson, &backupInstall); err != nil {
|
||||
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
|
||||
}
|
||||
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name {
|
||||
if backupInstall.App.Key != install.App.Key || backupInstall.Name != install.Name {
|
||||
return errors.New(i18n.GetMsgByKey("AppAttributesNotMatch"))
|
||||
}
|
||||
|
||||
newEnvFile := ""
|
||||
backupEnvMap, err := getEnvMapByPath(path.Join(tmpPath, install.Name, ".env"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installedEnvMap, err := getEnvMapByPath(install.GetEnvPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var mergedEnvContent string
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
for _, resource := range resources {
|
||||
var database model.Database
|
||||
@@ -225,11 +236,20 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newDB, err := reCreatePostgresqlDB(db.ID, database, backupEnvMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupInstall.Env, mergedEnvContent, err = buildRecoverEnv(backupInstall.Env, backupEnvMap, installedEnvMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, repo.WithByID(resource.ID))
|
||||
taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase)
|
||||
t.LogStart(taskName)
|
||||
if err := handlePostgresqlRecover(dto.CommonRecover{
|
||||
Name: database.Name,
|
||||
DetailName: db.Name,
|
||||
Name: newDB.PostgresqlName,
|
||||
DetailName: newDB.Name,
|
||||
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
|
||||
}, parentTask, true); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
@@ -241,15 +261,11 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newDB, envMap, err := reCreateDB(db.ID, database, oldInstall.Env)
|
||||
newDB, err := reCreateDB(db.ID, database, backupEnvMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
|
||||
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", database.Address)
|
||||
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
|
||||
envMap["PANEL_DB_HOST"] = database.Address
|
||||
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
|
||||
backupInstall.Env, mergedEnvContent, err = buildRecoverEnv(backupInstall.Env, backupEnvMap, installedEnvMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -284,22 +300,22 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
||||
t.LogSuccess(deCompressName)
|
||||
_ = fileOp.DeleteDir(backPath)
|
||||
|
||||
if len(newEnvFile) != 0 {
|
||||
if len(mergedEnvContent) != 0 {
|
||||
envPath := fmt.Sprintf("%s/%s/.env", install.GetAppPath(), install.Name)
|
||||
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, _ = file.WriteString(newEnvFile)
|
||||
_, _ = file.WriteString(mergedEnvContent)
|
||||
}
|
||||
|
||||
oldInstall.ID = install.ID
|
||||
oldInstall.Status = constant.StatusRunning
|
||||
oldInstall.AppId = install.AppId
|
||||
oldInstall.AppDetailId = install.AppDetailId
|
||||
oldInstall.App.ID = install.AppId
|
||||
if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil {
|
||||
backupInstall.ID = install.ID
|
||||
backupInstall.Status = constant.StatusRunning
|
||||
backupInstall.AppId = install.AppId
|
||||
backupInstall.AppDetailId = install.AppDetailId
|
||||
backupInstall.App.ID = install.AppId
|
||||
if err := appInstallRepo.Save(context.Background(), &backupInstall); err != nil {
|
||||
global.LOG.Errorf("save db app install failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
@@ -373,33 +389,131 @@ func doAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fi
|
||||
return nil
|
||||
}
|
||||
|
||||
func reCreateDB(dbID uint, database model.Database, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
|
||||
func reCreateDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabaseMysql, error) {
|
||||
mysqlService := NewIMysqlService()
|
||||
ctx := context.Background()
|
||||
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true})
|
||||
|
||||
envMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil {
|
||||
return nil, envMap, err
|
||||
}
|
||||
oldName, _ := envMap["PANEL_DB_NAME"].(string)
|
||||
oldUser, _ := envMap["PANEL_DB_USER"].(string)
|
||||
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
|
||||
dbInfo := getDBCreateInfoFromEnv(envMap, "utf8mb4")
|
||||
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
|
||||
Name: oldName,
|
||||
Name: dbInfo.Name,
|
||||
From: database.From,
|
||||
Database: database.Name,
|
||||
Format: "utf8mb4",
|
||||
Username: oldUser,
|
||||
Password: oldPassword,
|
||||
Format: dbInfo.Format,
|
||||
Username: dbInfo.User,
|
||||
Password: dbInfo.Password,
|
||||
Permission: "%",
|
||||
})
|
||||
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDbName(fmt.Sprintf("%v", dbID)))
|
||||
for _, job := range cronjobs {
|
||||
_ = cronjobRepo.Update(job.ID, map[string]interface{}{"db_name": fmt.Sprintf("%v", createDB.ID)})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, envMap, err
|
||||
return nil, err
|
||||
}
|
||||
return createDB, envMap, nil
|
||||
updateCronjobsDBRef(dbID, createDB.ID)
|
||||
return createDB, nil
|
||||
}
|
||||
|
||||
func reCreatePostgresqlDB(dbID uint, database model.Database, envMap map[string]interface{}) (*model.DatabasePostgresql, error) {
|
||||
postgresqlService := NewIPostgresqlService()
|
||||
_ = postgresqlService.Delete(context.Background(), dto.PostgresqlDBDelete{
|
||||
ID: dbID,
|
||||
Type: database.Type,
|
||||
Database: database.Name,
|
||||
DeleteBackup: false,
|
||||
ForceDelete: true,
|
||||
})
|
||||
|
||||
dbInfo := getDBCreateInfoFromEnv(envMap, "UTF8")
|
||||
createDB, err := postgresqlService.Create(context.Background(), dto.PostgresqlDBCreate{
|
||||
Name: dbInfo.Name,
|
||||
From: database.From,
|
||||
Database: database.Name,
|
||||
Format: dbInfo.Format,
|
||||
Username: dbInfo.User,
|
||||
Password: dbInfo.Password,
|
||||
SuperUser: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateCronjobsDBRef(dbID, createDB.ID)
|
||||
return createDB, nil
|
||||
}
|
||||
|
||||
type dbRecreateInfo struct {
|
||||
Name string
|
||||
User string
|
||||
Password string
|
||||
Format string
|
||||
}
|
||||
|
||||
func getDBCreateInfoFromEnv(envMap map[string]interface{}, defaultFormat string) dbRecreateInfo {
|
||||
name, _ := envMap["PANEL_DB_NAME"].(string)
|
||||
user, _ := envMap["PANEL_DB_USER"].(string)
|
||||
password, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
|
||||
format, _ := envMap["format"].(string)
|
||||
if len(format) == 0 {
|
||||
format = defaultFormat
|
||||
}
|
||||
return dbRecreateInfo{
|
||||
Name: name,
|
||||
User: user,
|
||||
Password: password,
|
||||
Format: format,
|
||||
}
|
||||
}
|
||||
|
||||
func updateCronjobsDBRef(oldDBID, newDBID uint) {
|
||||
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDbName(fmt.Sprintf("%v", oldDBID)))
|
||||
for _, job := range cronjobs {
|
||||
_ = cronjobRepo.Update(job.ID, map[string]interface{}{"db_name": fmt.Sprintf("%v", newDBID)})
|
||||
}
|
||||
}
|
||||
|
||||
func buildRecoverEnv(appEnv string, backupEnvMap, installedEnvMap map[string]interface{}) (string, string, error) {
|
||||
currentHostVal, hasCurrentHost := installedEnvMap["PANEL_DB_HOST"]
|
||||
if hasCurrentHost && fmt.Sprintf("%v", currentHostVal) != "" {
|
||||
backupHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", backupEnvMap["PANEL_DB_HOST"])
|
||||
currentHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", currentHostVal)
|
||||
appEnv = strings.ReplaceAll(appEnv, backupHost, currentHost)
|
||||
if _, ok := backupEnvMap["CASDOOR_DATASOURCE_NAME"]; ok {
|
||||
backupEnvMap["CASDOOR_DATASOURCE_NAME"] = strings.ReplaceAll(
|
||||
fmt.Sprintf("%v", backupEnvMap["CASDOOR_DATASOURCE_NAME"]),
|
||||
fmt.Sprintf("%v", backupEnvMap["PANEL_DB_HOST"]),
|
||||
fmt.Sprintf("%v", currentHostVal),
|
||||
)
|
||||
}
|
||||
backupEnvMap["PANEL_DB_HOST"] = currentHostVal
|
||||
}
|
||||
newEnvMap := make(map[string]string, len(backupEnvMap))
|
||||
handleMap(backupEnvMap, newEnvMap)
|
||||
mergedEnvContent, err := gotenv.Marshal(newEnvMap)
|
||||
if err != nil {
|
||||
return appEnv, "", err
|
||||
}
|
||||
return appEnv, mergedEnvContent, nil
|
||||
}
|
||||
|
||||
func getEnvMapByPath(envPath string) (map[string]interface{}, error) {
|
||||
envMap := make(map[string]interface{})
|
||||
envFile, err := os.ReadFile(envPath)
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
lines := strings.Split(string(envFile), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
value := strings.TrimSpace(parts[1])
|
||||
if len(value) >= 2 {
|
||||
if (value[0] == '"' && value[len(value)-1] == '"') || (value[0] == '\'' && value[len(value)-1] == '\'') {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
}
|
||||
envMap[parts[0]] = value
|
||||
}
|
||||
}
|
||||
return envMap, nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
return err
|
||||
}
|
||||
|
||||
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName}
|
||||
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName, Args: req.Args}
|
||||
if err := handleMysqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil {
|
||||
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
return err
|
||||
@@ -87,7 +87,12 @@ func handleMysqlBackup(db DatabaseHelper, parentTask *task.Task, recordID uint,
|
||||
}
|
||||
}
|
||||
|
||||
itemHandler := func() error { return doMysqlBackup(db, targetDir, fileName, secret) }
|
||||
itemHandler := func() error {
|
||||
if len(db.Args) != 0 {
|
||||
backupTask.Logf("%s: %v", i18n.GetMsgByKey("Arg"), db.Args)
|
||||
}
|
||||
return doMysqlBackup(db, targetDir, fileName, secret)
|
||||
}
|
||||
if parentTask != nil {
|
||||
return itemHandler()
|
||||
}
|
||||
@@ -225,6 +230,7 @@ func doMysqlBackup(db DatabaseHelper, targetDir, fileName, secret string) error
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: targetDir,
|
||||
FileName: fileName,
|
||||
Args: db.Args,
|
||||
}
|
||||
if err := cli.Backup(backupInfo); err != nil {
|
||||
return err
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewIBackupRecordService() IBackupRecordService {
|
||||
func (u *BackupRecordService) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) {
|
||||
total, records, err := backupRepo.PageRecord(
|
||||
search.Page, search.PageSize,
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
repo.WithByName(search.Name),
|
||||
repo.WithByType(search.Type),
|
||||
repo.WithByDetailName(search.DetailName),
|
||||
@@ -69,7 +69,7 @@ func (u *BackupRecordService) SearchRecordsWithPage(search dto.RecordSearch) (in
|
||||
func (u *BackupRecordService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
||||
total, records, err := backupRepo.PageRecord(
|
||||
search.Page, search.PageSize,
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
backupRepo.WithByCronID(search.CronjobID),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -166,7 +166,7 @@ func (u *BackupRecordService) BatchDeleteRecord(ids []uint) error {
|
||||
|
||||
func (u *BackupRecordService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
|
||||
records, err := backupRepo.ListRecord(
|
||||
repo.WithOrderBy("created_at asc"),
|
||||
repo.WithOrderAsc("created_at"),
|
||||
repo.WithByName(name),
|
||||
repo.WithByType("app"),
|
||||
backupRepo.WithFileNameStartWith(fileName),
|
||||
@@ -221,7 +221,7 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
|
||||
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)})
|
||||
}
|
||||
case "cronjob":
|
||||
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, repo.WithOrderBy("created_at desc"), backupRepo.WithByCronID(req.CronjobID))
|
||||
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, repo.WithOrderDesc("created_at"), backupRepo.WithByCronID(req.CronjobID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
|
||||
default:
|
||||
_, records, err := backupRepo.PageRecord(
|
||||
req.Page, req.PageSize,
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
repo.WithByName(req.Name),
|
||||
repo.WithByType(req.Type),
|
||||
repo.WithByDetailName(req.DetailName),
|
||||
|
||||
@@ -96,7 +96,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_ = clam.StopAllClamJob(false, clamRepo)
|
||||
_ = clam.CheckWithStopAll(false, clamRepo)
|
||||
}
|
||||
if baseInfo.FreshIsActive {
|
||||
version, err := cmd.RunDefaultWithStdoutBashC("freshclam --version")
|
||||
@@ -313,7 +313,7 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
|
||||
}
|
||||
|
||||
func (c *ClamService) HandleOnce(id uint) error {
|
||||
if active := clam.StopAllClamJob(true, clamRepo); !active {
|
||||
if active := clam.CheckWithStopAll(true, clamRepo); !active {
|
||||
return buserr.New("ErrClamdscanNotFound")
|
||||
}
|
||||
clamItem, _ := clamRepo.Get(repo.WithByID(id))
|
||||
@@ -337,9 +337,9 @@ func (c *ClamService) HandleOnce(id uint) error {
|
||||
clamRepo.EndRecords(record, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
handleAlert(record.InfectedFiles, clamItem.Name, clamItem.ID)
|
||||
clam.AnalysisFromLog(taskItem.LogFile, &record)
|
||||
clamRepo.EndRecords(record, constant.StatusDone, "")
|
||||
handleAlert(record.InfectedFiles, clamItem.Name, clamItem.ID)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/gin-gonic/gin"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
)
|
||||
@@ -59,11 +58,16 @@ type IContainerService interface {
|
||||
ListNetwork() ([]dto.Options, error)
|
||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListVolume() ([]dto.Options, error)
|
||||
|
||||
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
LoadComposeEnv(name string) (string, error)
|
||||
CreateCompose(req dto.ComposeCreate) error
|
||||
ComposeOperation(req dto.ComposeOperation) error
|
||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||
ComposeUpdate(req dto.ComposeUpdate) error
|
||||
ComposeLogClean(req dto.ComposeLogClean) error
|
||||
|
||||
ContainerCreate(req dto.ContainerOperate, inThread bool) error
|
||||
ContainerCreateByCommand(req dto.ContainerCreateByCommand) error
|
||||
ContainerUpdate(req dto.ContainerOperate) error
|
||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||
@@ -74,16 +78,14 @@ type IContainerService interface {
|
||||
ContainerCommit(req dto.ContainerCommit) error
|
||||
ContainerLogClean(req dto.OperationWithName) error
|
||||
ContainerOperation(req dto.ContainerOperation) error
|
||||
DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error
|
||||
DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error
|
||||
ContainerStats(id string) (*dto.ContainerStats, error)
|
||||
|
||||
Inspect(req dto.InspectReq) (string, error)
|
||||
DeleteNetwork(req dto.BatchDelete) error
|
||||
CreateNetwork(req dto.NetworkCreate) error
|
||||
DeleteVolume(req dto.BatchDelete) error
|
||||
CreateVolume(req dto.VolumeCreate) error
|
||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||
ComposeUpdate(req dto.ComposeUpdate) error
|
||||
ComposeLogClean(req dto.ComposeLogClean) error
|
||||
Prune(req dto.ContainerPrune) error
|
||||
|
||||
LoadUsers(req dto.OperationWithName) []string
|
||||
@@ -100,7 +102,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
options := container.ListOptions{All: true}
|
||||
if len(req.Filters) != 0 {
|
||||
options.Filters = filters.NewArgs()
|
||||
@@ -298,42 +300,6 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerCreateByCommand(req dto.ContainerCreateByCommand) error {
|
||||
if cmd.CheckIllegal(req.Command) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if !strings.HasPrefix(strings.TrimSpace(req.Command), "docker run ") {
|
||||
return errors.New("error command format")
|
||||
}
|
||||
containerName := ""
|
||||
commands := strings.Split(req.Command, " ")
|
||||
for index, val := range commands {
|
||||
if val == "--name" && len(commands) > index+1 {
|
||||
containerName = commands[index+1]
|
||||
}
|
||||
}
|
||||
if !strings.Contains(req.Command, " -d ") {
|
||||
req.Command = strings.ReplaceAll(req.Command, "docker run", "docker run -d")
|
||||
}
|
||||
if len(containerName) == 0 {
|
||||
containerName = fmt.Sprintf("1Panel-%s-%s", common.RandStr(5), common.RandStrAndNum(4))
|
||||
req.Command += fmt.Sprintf(" --name %s", containerName)
|
||||
}
|
||||
taskItem, err := task.NewTaskWithOps(containerName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", containerName), func(t *task.Task) error {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem), cmd.WithTimeout(5*time.Minute))
|
||||
return cmdMgr.RunBashC(req.Command)
|
||||
}, nil)
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
@@ -435,7 +401,6 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
}
|
||||
}
|
||||
taskItem.Log(i18n.GetMsgByKey("PruneStart"))
|
||||
DeletedNumber := 0
|
||||
SpaceReclaimed := 0
|
||||
switch req.PruneType {
|
||||
case "container":
|
||||
@@ -443,21 +408,18 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.ContainersDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "image":
|
||||
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.ImagesDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "network":
|
||||
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
|
||||
_, err := client.NetworksPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.NetworksDeleted)
|
||||
case "volume":
|
||||
versions, err := client.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
@@ -470,7 +432,6 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.VolumesDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "buildcache":
|
||||
opts := build.CachePruneOptions{}
|
||||
@@ -479,10 +440,9 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.CachesDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
}
|
||||
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "count": DeletedNumber, "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
|
||||
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
|
||||
return nil
|
||||
}, nil)
|
||||
go func() {
|
||||
@@ -623,6 +583,16 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
data.ExposedPorts = loadContainerPortForInfo(exposePorts)
|
||||
data.Hostname = oldContainer.Config.Hostname
|
||||
data.DNS = oldContainer.HostConfig.DNS
|
||||
for _, item := range oldContainer.HostConfig.ExtraHosts {
|
||||
parts := strings.SplitN(item, ":", 2)
|
||||
if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 {
|
||||
continue
|
||||
}
|
||||
data.ExtraHosts = append(data.ExtraHosts, dto.ExtraHost{
|
||||
Hostname: parts[0],
|
||||
IP: parts[1],
|
||||
})
|
||||
}
|
||||
data.DomainName = oldContainer.Config.Domainname
|
||||
|
||||
data.Cmd = oldContainer.Config.Cmd
|
||||
@@ -958,6 +928,9 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
|
||||
if params.Follow {
|
||||
cmdArgs = append(cmdArgs, "-f")
|
||||
}
|
||||
if params.Timestamp {
|
||||
cmdArgs = append(cmdArgs, "-t")
|
||||
}
|
||||
if params.Tail != "0" {
|
||||
cmdArgs = append(cmdArgs, "--tail", params.Tail)
|
||||
}
|
||||
@@ -1054,7 +1027,7 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
|
||||
_ = dockerCmd.Wait()
|
||||
}
|
||||
|
||||
func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error {
|
||||
func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error {
|
||||
if cmd.CheckIllegal(container, since, tail) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
@@ -1082,6 +1055,9 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
|
||||
commandArg = append(commandArg, "--since")
|
||||
commandArg = append(commandArg, since)
|
||||
}
|
||||
if timestamp {
|
||||
commandArg = append(commandArg, "-t")
|
||||
}
|
||||
var dockerCmd *exec.Cmd
|
||||
if containerType == "compose" && dockerCommand == "docker-compose" {
|
||||
dockerCmd = exec.Command("docker-compose", commandArg...)
|
||||
@@ -1177,7 +1153,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
|
||||
|
||||
func (u *ContainerService) LoadUsers(req dto.OperationWithName) []string {
|
||||
var users []string
|
||||
std, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s cat /etc/passwd", req.Name)
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("docker", "exec", req.Name, "cat", "/etc/passwd")
|
||||
if err != nil {
|
||||
return users
|
||||
}
|
||||
@@ -1484,6 +1460,13 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *conta
|
||||
hostConf.Binds = []string{}
|
||||
hostConf.Mounts = []mount.Mount{}
|
||||
hostConf.DNS = req.DNS
|
||||
hostConf.ExtraHosts = []string{}
|
||||
for _, item := range req.ExtraHosts {
|
||||
if len(item.Hostname) == 0 || len(item.IP) == 0 {
|
||||
continue
|
||||
}
|
||||
hostConf.ExtraHosts = append(hostConf.ExtraHosts, fmt.Sprintf("%s:%s", item.Hostname, item.IP))
|
||||
}
|
||||
config.Volumes = make(map[string]struct{})
|
||||
for _, volume := range req.Volumes {
|
||||
item := mount.Mount{
|
||||
|
||||
@@ -205,7 +205,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
|
||||
err := compose.UpWithTask(req.Path, t)
|
||||
err := compose.UpWithTask(req.Path, t, req.ForcePull)
|
||||
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
|
||||
if err != nil {
|
||||
_, _ = compose.Down(req.Path)
|
||||
@@ -258,31 +258,43 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
||||
if cmd.CheckIllegal(req.Name, req.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
oldFile, err := os.ReadFile(req.DetailPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.DetailPath, err)
|
||||
}
|
||||
file, err := os.OpenFile(req.DetailPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpdate, task.TaskScopeCompose, req.TaskID, 1)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for update compose failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.Content)
|
||||
write.Flush()
|
||||
go func() {
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskUpdate"), func(t *task.Task) error {
|
||||
oldFile, err := os.ReadFile(req.DetailPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.DetailPath, err)
|
||||
}
|
||||
file, err := os.OpenFile(req.DetailPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.Content)
|
||||
write.Flush()
|
||||
|
||||
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.DetailPath)
|
||||
if err := newComposeEnv(req.DetailPath, req.Env); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.DetailPath)
|
||||
if err := newComposeEnv(req.DetailPath, req.Env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
global.LOG.Errorf("update failed when handle compose up, std: %s, err: %s, now try to recreate the old compose file", stdout, err)
|
||||
if err := recreateCompose(string(oldFile), req.Path); err != nil {
|
||||
return fmt.Errorf("update failed and recreate old compose file also failed, err: %v", err)
|
||||
}
|
||||
return fmt.Errorf("update failed when handle compose up, std: %v, err: %s", stdout, err)
|
||||
}
|
||||
if err := compose.UpWithTask(req.Path, t, req.ForcePull); err != nil {
|
||||
global.LOG.Errorf("update failed when handle compose up, err: %s, now try to recreate the old compose file", err)
|
||||
if err := recreateCompose(string(oldFile), req.Path); err != nil {
|
||||
return fmt.Errorf("update failed and recreate old compose file also failed, err: %v", err)
|
||||
}
|
||||
return fmt.Errorf("update failed when handle compose up, err: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil)
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -338,6 +350,15 @@ func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ContainerService) LoadComposeEnv(name string) (string, error) {
|
||||
envFilePath := path.Join(path.Dir(name), ".env")
|
||||
file, err := os.ReadFile(envFilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(file), nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
||||
if req.From == "template" || req.From == "edit" {
|
||||
dir := fmt.Sprintf("%s/docker/compose/%s", global.Dir.DataDir, req.Name)
|
||||
|
||||
@@ -143,6 +143,7 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
Timeout: cronjob.Timeout,
|
||||
IgnoreErr: cronjob.IgnoreErr,
|
||||
Secret: cronjob.Secret,
|
||||
Args: cronjob.Args,
|
||||
}
|
||||
switch cronjob.Type {
|
||||
case "app":
|
||||
@@ -237,6 +238,7 @@ func (u *CronjobService) Import(req []dto.CronjobTrans) error {
|
||||
Timeout: item.Timeout,
|
||||
IgnoreErr: item.IgnoreErr,
|
||||
Secret: item.Secret,
|
||||
Args: item.Args,
|
||||
}
|
||||
hasNotFound := false
|
||||
switch item.Type {
|
||||
@@ -733,6 +735,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
|
||||
upMap["timeout"] = req.Timeout
|
||||
upMap["ignore_err"] = req.IgnoreErr
|
||||
upMap["secret"] = req.Secret
|
||||
upMap["args"] = req.Args
|
||||
err = cronjobRepo.Update(id, upMap)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -762,7 +765,7 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
)
|
||||
|
||||
if status == constant.StatusEnable {
|
||||
entryIDs, err = u.StartJob(&cronjob, false)
|
||||
entryIDs, err = u.StartJob(&cronjob, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -379,6 +379,7 @@ type DatabaseHelper struct {
|
||||
DBType string
|
||||
Database string
|
||||
Name string
|
||||
Args []string
|
||||
}
|
||||
|
||||
func addSkipTask(source string, taskItem *task.Task) {
|
||||
@@ -400,6 +401,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysql.Database,
|
||||
Name: mysql.Name,
|
||||
Args: strings.Split(cronjob.Args, ","),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -410,6 +412,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
DBType: cronjob.DBType,
|
||||
Database: pg.PostgresqlName,
|
||||
Name: pg.Name,
|
||||
Args: strings.Split(cronjob.Args, ","),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -425,6 +428,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
Args: strings.Split(cronjob.Args, ","),
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(repo.WithByID(uint(itemID)))
|
||||
@@ -433,6 +437,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
Args: strings.Split(cronjob.Args, ","),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
|
||||
var opts []repo.DBOption
|
||||
opts = append(opts, repo.WithByFrom("cronjob"))
|
||||
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
opts = append(opts, repo.WithOrderDesc("created_at"))
|
||||
if record.ID != 0 {
|
||||
opts = append(opts, repo.WithByType(record.Type))
|
||||
opts = append(opts, repo.WithByName(record.Name))
|
||||
@@ -461,7 +461,7 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
|
||||
}
|
||||
|
||||
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
|
||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), repo.WithOrderBy("created_at desc"))
|
||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), repo.WithOrderDesc("created_at"))
|
||||
if len(records) <= int(cronjob.RetainCopies) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent {
|
||||
|
||||
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
|
||||
var baseInfo dto.DashboardBase
|
||||
hostInfo, err := psutil.HOST.GetHostInfo(false)
|
||||
hostInfo, err := psutil.HOST.GetHostInfo(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
@@ -43,6 +44,10 @@ func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.D
|
||||
|
||||
func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
|
||||
filePath := ""
|
||||
safeName := filepath.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return "", buserr.New("ErrInvalidParams")
|
||||
}
|
||||
switch req.Type {
|
||||
case "mysql-cluster-conf":
|
||||
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql-cluster/%s/conf/my.cnf", req.Name))
|
||||
|
||||
@@ -109,7 +109,7 @@ func (u *DeviceService) CheckDNS(key, value string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func (u *DeviceService) Update(key, value string) error {
|
||||
if cmd.CheckIllegal(value) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if err := cmd.RunDefaultBashCf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value); err != nil {
|
||||
if err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run("hostnamectl", "set-hostname", value); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = psutil.HOST.GetHostInfo(true)
|
||||
@@ -245,7 +245,7 @@ func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error {
|
||||
}
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem))
|
||||
if !req.IsNew {
|
||||
if err := cmdMgr.RunBashCf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path); err != nil {
|
||||
if err := cmdMgr.Run("swapoff", req.Path); err != nil {
|
||||
return fmt.Errorf("handle swapoff %s failed, %v", req.Path, err)
|
||||
}
|
||||
}
|
||||
@@ -256,22 +256,22 @@ func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error {
|
||||
return operateSwapWithFile(true, req)
|
||||
}
|
||||
taskItem.LogStart(i18n.GetMsgByKey("CreateSwap"))
|
||||
if err := cmdMgr.RunBashCf("%s dd if=/dev/zero of=%s bs=1024 count=%d", cmd.SudoHandleCmd(), req.Path, req.Size); err != nil {
|
||||
if err := cmdMgr.Run("dd", "if=/dev/zero", fmt.Sprintf("of=%s", req.Path), "bs=1024", fmt.Sprintf("count=%d", req.Size)); err != nil {
|
||||
return fmt.Errorf("handle dd %s failed, %v", req.Path, err)
|
||||
}
|
||||
|
||||
taskItem.Log("chmod 0600 " + req.Path)
|
||||
if err := cmdMgr.RunBashCf("%s chmod 0600 %s", cmd.SudoHandleCmd(), req.Path); err != nil {
|
||||
if err := cmdMgr.Run("chmod", "0600", req.Path); err != nil {
|
||||
return fmt.Errorf("handle chmod 0600 %s failed, %v", req.Path, err)
|
||||
}
|
||||
taskItem.LogStart(i18n.GetMsgByKey("FormatSwap"))
|
||||
if err := cmdMgr.RunBashCf("%s mkswap -f %s", cmd.SudoHandleCmd(), req.Path); err != nil {
|
||||
if err := cmdMgr.Run("mkswap", "-f", req.Path); err != nil {
|
||||
return fmt.Errorf("handle mkswap -f %s failed, %v", req.Path, err)
|
||||
}
|
||||
|
||||
taskItem.LogStart(i18n.GetMsgByKey("EnableSwap"))
|
||||
if err := cmdMgr.RunBashCf("%s swapon %s", cmd.SudoHandleCmd(), req.Path); err != nil {
|
||||
_ = cmdMgr.RunBashCf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path)
|
||||
if err := cmdMgr.Run("swapon", req.Path); err != nil {
|
||||
_ = cmdMgr.Run("swapoff", req.Path)
|
||||
return fmt.Errorf("handle swapoff %s failed, %v", req.Path, err)
|
||||
}
|
||||
return operateSwapWithFile(false, req)
|
||||
|
||||
@@ -105,7 +105,11 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
case "tmp_backup":
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp"))
|
||||
case "unknown_backup":
|
||||
dropFileOrDir(item.Name)
|
||||
if strings.HasPrefix(item.Name, path.Join(global.Dir.LocalBackupDir, "log/website")) {
|
||||
dropFileOrDir(item.Name)
|
||||
} else {
|
||||
dropFile(item.Name)
|
||||
}
|
||||
|
||||
case "rollback":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app"))
|
||||
@@ -182,6 +186,7 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
}
|
||||
}
|
||||
|
||||
_ = cleanEmptyDirs(global.Dir.LocalBackupDir)
|
||||
_ = settingRepo.Update("LastCleanTime", time.Now().Format(constant.DateTimeLayout))
|
||||
_ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size))
|
||||
_ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", len(req)))
|
||||
@@ -295,7 +300,7 @@ func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
itemTree := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "agent_packages",
|
||||
IsCheck: true,
|
||||
IsCheck: false,
|
||||
IsRecommend: true,
|
||||
Type: "agent",
|
||||
}
|
||||
@@ -314,17 +319,19 @@ func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
Type: "agent",
|
||||
})
|
||||
} else {
|
||||
if strings.HasPrefix(file.Name(), fmt.Sprintf("1panel-agent_%s_a", global.CONF.Base.Version)) {
|
||||
itemSize, _ := file.Info()
|
||||
itemName := file.Name()
|
||||
isCurrentVersion := strings.HasPrefix(itemName, fmt.Sprintf("1panel-agent_%s_", global.CONF.Base.Version))
|
||||
if isCurrentVersion {
|
||||
continue
|
||||
}
|
||||
itemSize, _ := file.Info()
|
||||
itemTree.Size += uint64(itemSize.Size())
|
||||
itemTree.Children = append(itemTree.Children, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: file.Name(),
|
||||
Name: file.Name(),
|
||||
Label: itemName,
|
||||
Name: itemName,
|
||||
Size: uint64(itemSize.Size()),
|
||||
IsCheck: true,
|
||||
IsCheck: !isCurrentVersion,
|
||||
IsRecommend: true,
|
||||
Type: "agent",
|
||||
})
|
||||
@@ -363,7 +370,7 @@ func loadBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
treeData = append(treeData, loadUnknownDbs(fileOp, recordMap))
|
||||
treeData = append(treeData, loadUnknownWebsites(fileOp, recordMap))
|
||||
treeData = append(treeData, loadUnknownSnapshot(fileOp))
|
||||
treeData = append(treeData, loadUnknownWebsiteLog(fileOp, recordMap))
|
||||
treeData = append(treeData, loadUnknownWebsiteLog(fileOp))
|
||||
return treeData
|
||||
}
|
||||
|
||||
@@ -376,70 +383,68 @@ func loadUnknownApps(fileOp fileUtils.FileOp, recordMap map[string][]string) dto
|
||||
for _, item := range val {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
|
||||
}
|
||||
} else {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName))
|
||||
}
|
||||
}
|
||||
backupPath := path.Join(global.Dir.LocalBackupDir, "app")
|
||||
treeData := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "unknown_app",
|
||||
IsCheck: false,
|
||||
IsRecommend: false,
|
||||
Name: backupPath,
|
||||
Type: "unknown_backup",
|
||||
}
|
||||
_ = loadFileOrDirWithExclude(fileOp, 0, path.Join(global.Dir.LocalBackupDir, "app"), &treeData, excludePaths)
|
||||
loadBackupIsCheck(&treeData)
|
||||
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
|
||||
return treeData
|
||||
}
|
||||
func loadUnknownDbs(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
|
||||
apps, _ := appInstallRepo.ListBy(context.Background())
|
||||
dbs, _ := databaseRepo.GetList()
|
||||
var excludePaths []string
|
||||
appMap := make(map[string]struct{})
|
||||
for _, app := range apps {
|
||||
appMap[fmt.Sprintf("database/%s/%s", app.App.Key, app.Name)] = struct{}{}
|
||||
dbMap := make(map[string]struct{})
|
||||
for _, db := range dbs {
|
||||
dbMap[fmt.Sprintf("database/%s/%s", db.Type, db.Name)] = struct{}{}
|
||||
}
|
||||
for key, val := range recordMap {
|
||||
itemName := path.Dir(key)
|
||||
if _, ok := appMap[itemName]; ok {
|
||||
if _, ok := dbMap[itemName]; ok {
|
||||
for _, item := range val {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, key, item))
|
||||
}
|
||||
}
|
||||
}
|
||||
backupPath := path.Join(global.Dir.LocalBackupDir, "database")
|
||||
treeData := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "unknown_database",
|
||||
Name: backupPath,
|
||||
IsCheck: false,
|
||||
IsRecommend: false,
|
||||
Type: "unknown_backup",
|
||||
}
|
||||
_ = loadFileOrDirWithExclude(fileOp, 0, path.Join(global.Dir.LocalBackupDir, "database"), &treeData, excludePaths)
|
||||
|
||||
loadBackupIsCheck(&treeData)
|
||||
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
|
||||
return treeData
|
||||
}
|
||||
func loadUnknownWebsites(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
|
||||
websites, _ := websiteRepo.List()
|
||||
var excludePaths []string
|
||||
for _, website := range websites {
|
||||
itemName := fmt.Sprintf("website/%s", website.PrimaryDomain)
|
||||
itemName := fmt.Sprintf("website/%s", website.Alias)
|
||||
if val, ok := recordMap[itemName]; ok {
|
||||
for _, item := range val {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
|
||||
}
|
||||
} else {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName))
|
||||
}
|
||||
}
|
||||
backupPath := path.Join(global.Dir.LocalBackupDir, "website")
|
||||
treeData := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "unknown_website",
|
||||
Name: backupPath,
|
||||
IsCheck: false,
|
||||
IsRecommend: false,
|
||||
Type: "unknown_backup",
|
||||
}
|
||||
_ = loadFileOrDirWithExclude(fileOp, 0, path.Join(global.Dir.LocalBackupDir, "website"), &treeData, excludePaths)
|
||||
loadBackupIsCheck(&treeData)
|
||||
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
|
||||
return treeData
|
||||
}
|
||||
func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
@@ -448,21 +453,21 @@ func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
for _, item := range snaps {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, "system_snapshot", item.Name+".tar.gz"))
|
||||
}
|
||||
backupPath := path.Join(global.Dir.LocalBackupDir, "system_snapshot")
|
||||
treeData := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "unknown_snapshot",
|
||||
Name: backupPath,
|
||||
IsCheck: false,
|
||||
IsRecommend: false,
|
||||
Type: "unknown_backup",
|
||||
}
|
||||
dir := path.Join(global.Dir.LocalBackupDir, "system_snapshot")
|
||||
entries, _ := os.ReadDir(dir)
|
||||
entries, _ := os.ReadDir(backupPath)
|
||||
for _, entry := range entries {
|
||||
childPath := filepath.Join(dir, entry.Name())
|
||||
childPath := filepath.Join(backupPath, entry.Name())
|
||||
if isExactPathMatch(childPath, excludePaths) {
|
||||
continue
|
||||
}
|
||||
|
||||
childNode := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: entry.Name(),
|
||||
@@ -488,7 +493,7 @@ func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
return treeData
|
||||
}
|
||||
|
||||
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
|
||||
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
treeData := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "unknown_website_log",
|
||||
@@ -497,14 +502,19 @@ func loadUnknownWebsiteLog(fileOp fileUtils.FileOp, recordMap map[string][]strin
|
||||
Type: "unknown_backup",
|
||||
}
|
||||
dir := path.Join(global.Dir.LocalBackupDir, "log/website")
|
||||
websites, _ := websiteRepo.List()
|
||||
websiteMap := make(map[string]struct{})
|
||||
for _, website := range websites {
|
||||
websiteMap[website.Alias] = struct{}{}
|
||||
}
|
||||
|
||||
entries, _ := os.ReadDir(dir)
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
dirName := entry.Name()
|
||||
itemName := fmt.Sprintf("log/website/%s", dirName)
|
||||
if _, ok := recordMap[itemName]; !ok {
|
||||
if _, ok := websiteMap[dirName]; !ok {
|
||||
dirPath := path.Join(dir, dirName)
|
||||
itemSize, _ := fileOp.GetDirSize(dirPath)
|
||||
childData := dto.CleanTree{
|
||||
@@ -537,7 +547,6 @@ func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, r
|
||||
if isExactPathMatch(childPath, excludes) {
|
||||
continue
|
||||
}
|
||||
|
||||
childNode := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: entry.Name(),
|
||||
@@ -572,26 +581,6 @@ func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, r
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadBackupIsCheck(treeData *dto.CleanTree) {
|
||||
for i := 0; i < len(treeData.Children); i++ {
|
||||
if len(treeData.Children[i].Children) == 0 {
|
||||
treeData.Children[i].IsCheck = true
|
||||
treeData.Children[i].IsRecommend = true
|
||||
continue
|
||||
}
|
||||
if treeData.Label != "unknown_database" && treeData.Label != "unknown_app" {
|
||||
continue
|
||||
}
|
||||
for j := 0; j < len(treeData.Children[i].Children); j++ {
|
||||
if len(treeData.Children[i].Children[j].Children) == 0 {
|
||||
treeData.Children[i].Children[j].IsCheck = true
|
||||
treeData.Children[i].Children[j].IsRecommend = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isExactPathMatch(path string, excludePaths []string) bool {
|
||||
cleanPath := filepath.Clean(path)
|
||||
|
||||
@@ -903,6 +892,18 @@ func dropFileOrDir(itemPath string) {
|
||||
global.LOG.Errorf("drop file %s failed, err %v", itemPath, err)
|
||||
}
|
||||
}
|
||||
func dropFile(itemPath string) {
|
||||
info, err := os.Stat(itemPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if info.IsDir() {
|
||||
return
|
||||
}
|
||||
if err := os.Remove(itemPath); err != nil {
|
||||
global.LOG.Errorf("drop file %s failed, err %v", itemPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func dropBuildCache() (int, int) {
|
||||
client, err := docker.NewDockerClient()
|
||||
@@ -1101,3 +1102,36 @@ func scanFile(pathItem string, size *int64, count *int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanEmptyDirs(root string) error {
|
||||
dirsToCheck := make([]string, 0)
|
||||
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
dirsToCheck = append(dirsToCheck, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := len(dirsToCheck) - 1; i >= 0; i-- {
|
||||
dir := dirsToCheck[i]
|
||||
if dir == root {
|
||||
continue
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
_ = os.Remove(dir)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
@@ -50,6 +51,9 @@ func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error)
|
||||
}
|
||||
|
||||
func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, error) {
|
||||
if cmd.CheckIllegal(req.Device, req.Filesystem, req.MountPoint, req.Label) {
|
||||
return "", buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if !strings.HasPrefix("/dev", req.Device) {
|
||||
req.Device = "/dev/" + req.Device
|
||||
}
|
||||
@@ -62,19 +66,19 @@ func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, e
|
||||
}
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
|
||||
if err := cmdMgr.Run("partprobe", req.Device); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mklabel gpt", req.Device)); err != nil {
|
||||
if err := cmdMgr.Run("parted", "-s", req.Device, "mklabel", "gpt"); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mkpart primary 1MiB 100%%", req.Device)); err != nil {
|
||||
if err := cmdMgr.Run("parted", "-s", req.Device, "mkpart", "primary", "1MiB", "100%"); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
|
||||
if err := cmdMgr.Run("partprobe", req.Device); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
partition := req.Device + "1"
|
||||
@@ -104,6 +108,9 @@ func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, e
|
||||
}
|
||||
|
||||
func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
|
||||
if cmd.CheckIllegal(req.Device, req.MountPoint, req.Filesystem) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if !deviceExists(req.Device) {
|
||||
return buserr.WithName("DeviceNotFound", req.Device)
|
||||
}
|
||||
@@ -127,7 +134,7 @@ func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
|
||||
}
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Minute))
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("mount -t %s %s %s", req.Filesystem, req.Device, req.MountPoint)); err != nil {
|
||||
if err := cmdMgr.Run("mount", "-t", req.Filesystem, req.Device, req.MountPoint); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
if req.AutoMount {
|
||||
@@ -144,10 +151,13 @@ func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
|
||||
}
|
||||
|
||||
func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error {
|
||||
if cmd.CheckIllegal(req.MountPoint) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if !isPointMounted(req.MountPoint) {
|
||||
return buserr.New("MountDiskErr")
|
||||
}
|
||||
if err := cmd.RunDefaultBashC(fmt.Sprintf("umount -f %s", req.MountPoint)); err != nil {
|
||||
if err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run("umount", "-f", req.MountPoint); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
if err := removeFromFstab(req.MountPoint); err != nil {
|
||||
|
||||
@@ -12,9 +12,11 @@ var (
|
||||
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
|
||||
appIgnoreUpgradeRepo = repo.NewIAppIgnoreUpgradeRepo()
|
||||
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
tensorrtLLMRepo = repo.NewITensorRTLLMRepo()
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
tensorrtLLMRepo = repo.NewITensorRTLLMRepo()
|
||||
agentRepo = repo.NewIAgentRepo()
|
||||
agentAccountRepo = repo.NewIAgentAccountRepo()
|
||||
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
postgresqlRepo = repo.NewIPostgresqlRepo()
|
||||
|
||||
+103
-14
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -75,12 +76,19 @@ type IFileService interface {
|
||||
GetUsersAndGroups() (*response.UserGroupResponse, error)
|
||||
Convert(req request.FileConvertRequest)
|
||||
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
|
||||
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
|
||||
SetRemark(req request.FileRemarkUpdate) error
|
||||
}
|
||||
|
||||
var filteredPaths = []string{
|
||||
"/.1panel_clash",
|
||||
}
|
||||
|
||||
const (
|
||||
fileRemarkXattr = "user.1panel.remark"
|
||||
fileRemarkEncodedMaxLen = 256
|
||||
)
|
||||
|
||||
func NewIFileService() IFileService {
|
||||
return &FileService{}
|
||||
}
|
||||
@@ -255,7 +263,7 @@ func (f *FileService) Create(op request.FileCreate) error {
|
||||
func (f *FileService) Delete(op request.FileDelete) error {
|
||||
if op.IsDir {
|
||||
excludeDir := global.Dir.DataDir
|
||||
if filepath.Base(op.Path) == ".1panel_clash" || op.Path == excludeDir {
|
||||
if path.Base(op.Path) == ".1panel_clash" || op.Path == excludeDir {
|
||||
return buserr.New("ErrPathNotDelete")
|
||||
}
|
||||
}
|
||||
@@ -307,19 +315,18 @@ func (f *FileService) ChangeMode(op request.FileCreate) error {
|
||||
|
||||
func (f *FileService) BatchChangeModeAndOwner(op request.FileRoleReq) error {
|
||||
fo := files.NewFileOp()
|
||||
for _, path := range op.Paths {
|
||||
if !fo.Stat(path) {
|
||||
for _, p := range op.Paths {
|
||||
if !fo.Stat(p) {
|
||||
return buserr.New("ErrPathNotFound")
|
||||
}
|
||||
if err := fo.ChownR(path, op.User, op.Group, op.Sub); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fo.ChmodR(path, op.Mode, op.Sub); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := fo.ChownRPaths(op.Paths, op.User, op.Group, op.Sub); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fo.ChmodRPaths(op.Paths, op.Mode, op.Sub); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error {
|
||||
@@ -585,6 +592,12 @@ func (f *FileService) DepthDirSize(req request.DirSizeReq) ([]response.DepthDirS
|
||||
func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) {
|
||||
logFilePath := ""
|
||||
taskStatus := ""
|
||||
if len(req.Name) != 0 {
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return nil, buserr.New("ErrInvalidParams")
|
||||
}
|
||||
}
|
||||
switch req.Type {
|
||||
case constant.TypeWebsite:
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
|
||||
@@ -642,9 +655,9 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
logFilePath = taskModel.LogFile
|
||||
taskStatus = taskModel.Status
|
||||
case "mysql-slow-logs":
|
||||
logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
|
||||
logFilePath = path.Join(global.Dir.DataDir, "apps", "mysql", req.Name, "data", "1Panel-slow.log")
|
||||
case "mariadb-slow-logs":
|
||||
logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
|
||||
logFilePath = path.Join(global.Dir.DataDir, "apps", "mariadb", req.Name, "db", "data", "1Panel-slow.log")
|
||||
case "php-fpm-slow-logs":
|
||||
php, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
@@ -659,8 +672,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
}
|
||||
logFilePath, _ = ini_conf.GetIniValue(configPath, "supervisord", "logfile")
|
||||
case constant.Supervisor:
|
||||
logDir := path.Join(global.Dir.DataDir, "tools", "supervisord", "log")
|
||||
logFilePath = path.Join(logDir, req.Name)
|
||||
logFilePath = path.Join(global.Dir.DataDir, "tools", "supervisord", "log", req.Name)
|
||||
}
|
||||
|
||||
file, err := os.Open(logFilePath)
|
||||
@@ -760,6 +772,52 @@ func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *FileService) BatchGetRemarks(req request.FileRemarkBatch) map[string]string {
|
||||
remarks := make(map[string]string)
|
||||
for _, filePath := range req.Paths {
|
||||
remark, err := getFileRemark(filePath)
|
||||
if err != nil {
|
||||
if isXattrNotSupported(err) {
|
||||
return map[string]string{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if remark == "" {
|
||||
continue
|
||||
}
|
||||
remarks[filePath] = remark
|
||||
}
|
||||
|
||||
return remarks
|
||||
}
|
||||
|
||||
func (f *FileService) SetRemark(req request.FileRemarkUpdate) error {
|
||||
if req.Remark == "" {
|
||||
if err := unix.Lremovexattr(req.Path, fileRemarkXattr); err != nil {
|
||||
if isXattrNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if isXattrNotSupported(err) {
|
||||
return buserr.WithDetail("ErrInvalidParams", "xattr not supported", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(req.Remark))
|
||||
if len(encoded) >= fileRemarkEncodedMaxLen {
|
||||
return buserr.WithDetail("ErrInvalidParams", "remark length must be less than 256", nil)
|
||||
}
|
||||
if err := unix.Lsetxattr(req.Path, fileRemarkXattr, []byte(encoded), 0); err != nil {
|
||||
if isXattrNotSupported(err) {
|
||||
return buserr.WithDetail("ErrInvalidParams", "xattr not supported", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getValidGroups() (map[string]bool, error) {
|
||||
groupFile, err := os.Open("/etc/group")
|
||||
if err != nil {
|
||||
@@ -786,6 +844,37 @@ func getValidGroups() (map[string]bool, error) {
|
||||
return groupMap, nil
|
||||
}
|
||||
|
||||
func getFileRemark(filePath string) (string, error) {
|
||||
size, err := unix.Lgetxattr(filePath, fileRemarkXattr, nil)
|
||||
if err != nil {
|
||||
if isXattrNotFound(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if size == 0 {
|
||||
return "", nil
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
n, err := unix.Lgetxattr(filePath, fileRemarkXattr, buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(string(buf[:n]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(decoded), nil
|
||||
}
|
||||
|
||||
func isXattrNotSupported(err error) bool {
|
||||
return errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.EOPNOTSUPP)
|
||||
}
|
||||
|
||||
func isXattrNotFound(err error) bool {
|
||||
return errors.Is(err, unix.ENODATA)
|
||||
}
|
||||
|
||||
func getValidUsers(validGroups map[string]bool) ([]response.UserInfo, map[string]struct{}, error) {
|
||||
passwdFile, err := os.Open("/etc/passwd")
|
||||
if err != nil {
|
||||
|
||||
@@ -112,29 +112,15 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
|
||||
}
|
||||
}
|
||||
|
||||
var datasFilterStatus []fireClient.FireInfo
|
||||
if len(req.Status) != 0 {
|
||||
for _, data := range datas {
|
||||
if req.Status == "free" && len(data.UsedStatus) == 0 {
|
||||
datasFilterStatus = append(datasFilterStatus, data)
|
||||
}
|
||||
if req.Status == "used" && len(data.UsedStatus) != 0 {
|
||||
datasFilterStatus = append(datasFilterStatus, data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
datasFilterStatus = datas
|
||||
}
|
||||
|
||||
var datasFilterStrategy []fireClient.FireInfo
|
||||
if len(req.Strategy) != 0 {
|
||||
for _, data := range datasFilterStatus {
|
||||
for _, data := range datas {
|
||||
if req.Strategy == data.Strategy {
|
||||
datasFilterStrategy = append(datasFilterStrategy, data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
datasFilterStrategy = datasFilterStatus
|
||||
datasFilterStrategy = datas
|
||||
}
|
||||
|
||||
total, start, end := len(datasFilterStrategy), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
@@ -203,15 +189,15 @@ func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
|
||||
needRestartDocker = true
|
||||
case "disableBanPing":
|
||||
if err := firewall.UpdatePingStatus("0"); err != nil {
|
||||
_ = settingRepo.Update("BanPing", constant.StatusDisable)
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.Update("BanPing", constant.StatusDisable)
|
||||
return nil
|
||||
case "enableBanPing":
|
||||
if err := firewall.UpdatePingStatus("1"); err != nil {
|
||||
_ = settingRepo.Update("BanPing", constant.StatusEnable)
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.Update("BanPing", constant.StatusEnable)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("not supported operation: %s", req.Operation)
|
||||
@@ -720,16 +706,9 @@ func checkPortUsed(ports, proto string, apps []portOfApp) string {
|
||||
|
||||
for _, app := range apps {
|
||||
if app.HttpPort == ports || app.HttpsPort == ports {
|
||||
return fmt.Sprintf("(%s)", app.AppName)
|
||||
return app.AppName
|
||||
}
|
||||
}
|
||||
port, err := strconv.Atoi(ports)
|
||||
if err != nil {
|
||||
global.LOG.Errorf(" convert string %v to int failed, err: %v", port, err)
|
||||
return ""
|
||||
}
|
||||
if common.ScanPortWithProto(port, proto) {
|
||||
return "inUsed"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func (u *FtpService) Operate(operation string) error {
|
||||
}
|
||||
|
||||
func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderBy("created_at desc"))
|
||||
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderDesc("created_at"))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ func NewIGroupService() IGroupService {
|
||||
|
||||
func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) {
|
||||
options := []repo.DBOption{
|
||||
repo.WithOrderBy("is_default desc"),
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
repo.WithOrderDesc("is_default"),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
}
|
||||
if len(req.Type) != 0 {
|
||||
options = append(options, repo.WithByType(req.Type))
|
||||
|
||||
@@ -278,6 +278,10 @@ func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcess
|
||||
}
|
||||
|
||||
func handleProcess(supervisordDir string, req request.SupervisorProcessConfig, containerName string) error {
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
var (
|
||||
fileOp = files.NewFileOp()
|
||||
logDir = path.Join(supervisordDir, "log")
|
||||
|
||||
+30
-22
@@ -321,36 +321,44 @@ func (u *ImageService) ImagePull(req dto.ImagePull) error {
|
||||
}
|
||||
|
||||
func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
||||
taskItem, err := task.NewTaskWithOps(req.Path, task.TaskImport, task.TaskScopeImage, req.TaskID, 1)
|
||||
taskItem, err := task.NewTaskWithOps(strings.Join(req.Paths, ","), task.TaskImport, task.TaskScopeImage, req.TaskID, 1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new task for image import failed, err: %v", err)
|
||||
}
|
||||
taskItem.AddSubTask(i18n.GetWithName("TaskImport", req.Path), func(t *task.Task) error {
|
||||
file, err := os.Open(req.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
go func() {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
taskItem.Log("Failed to create Docker client: " + err.Error())
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
res, err := client.ImageLoad(context.TODO(), file)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
for _, itemPath := range req.Paths {
|
||||
currentPath := itemPath
|
||||
itemName := path.Base(currentPath)
|
||||
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
||||
taskItem.Logf("----------------- %s -----------------", itemName)
|
||||
file, err := os.Open(currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
res, err := client.ImageLoad(context.TODO(), file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(string(content), "Error") {
|
||||
return errors.New(string(content))
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(string(content), "Error") {
|
||||
return errors.New(string(content))
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
go func() {
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
return nil
|
||||
|
||||
@@ -37,7 +37,7 @@ func NewIImageRepoService() IImageRepoService {
|
||||
}
|
||||
|
||||
func (u *ImageRepoService) Page(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, ops, err := imageRepoRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderBy("created_at desc"))
|
||||
total, ops, err := imageRepoRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderDesc("created_at"))
|
||||
var dtoOps []dto.ImageRepoInfo
|
||||
for _, op := range ops {
|
||||
var item dto.ImageRepoInfo
|
||||
@@ -65,7 +65,7 @@ func (u *ImageRepoService) Login(req dto.OperateByID) error {
|
||||
}
|
||||
|
||||
func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
|
||||
ops, err := imageRepoRepo.List(repo.WithOrderBy("created_at desc"))
|
||||
ops, err := imageRepoRepo.List(repo.WithOrderDesc("created_at"))
|
||||
var dtoOps []dto.ImageRepoOption
|
||||
for _, op := range ops {
|
||||
if op.Status == constant.StatusSuccess {
|
||||
|
||||
@@ -230,6 +230,18 @@ func (s *IptablesService) Operate(req dto.IptablesOp) error {
|
||||
}
|
||||
_ = settingRepo.Update("IptablesStatus", constant.StatusEnable)
|
||||
return nil
|
||||
case "bind-base-without-init":
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.Update("IptablesStatus", constant.StatusEnable)
|
||||
return nil
|
||||
case "unbind-base":
|
||||
if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter); err != nil {
|
||||
return err
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
@@ -23,9 +27,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type McpServerService struct{}
|
||||
@@ -458,7 +459,12 @@ func addProxy(server *model.McpServer) {
|
||||
} else {
|
||||
proxyPath = server.StreamableHttpPath
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
|
||||
safePass, err := nginx.NginxSafeString(fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath), nginx.ModeURL)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("mcp add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{safePass})
|
||||
location.ChangePath("^~", proxyPath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr("ErrUpdateBuWebsite", err))
|
||||
@@ -516,7 +522,12 @@ func addMCPProxy(websiteID uint) error {
|
||||
} else {
|
||||
proxyPath = server.StreamableHttpPath
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
|
||||
safePass, err := nginx.NginxSafeString(fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath), nginx.ModeURL)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("mcp add proxy failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{safePass})
|
||||
location.ChangePath("^~", proxyPath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
|
||||
@@ -2,15 +2,19 @@ package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/websocket"
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/websocket"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
)
|
||||
|
||||
type ProcessService struct{}
|
||||
@@ -18,6 +22,7 @@ type ProcessService struct{}
|
||||
type IProcessService interface {
|
||||
StopProcess(req request.ProcessReq) error
|
||||
GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error)
|
||||
GetListeningProcess(c context.Context) ([]ListeningProcess, error)
|
||||
}
|
||||
|
||||
func NewIProcessService() IProcessService {
|
||||
@@ -35,6 +40,55 @@ func (ps *ProcessService) StopProcess(req request.ProcessReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListeningProcess struct {
|
||||
PID int32
|
||||
Port map[uint32]struct{}
|
||||
Protocol uint32
|
||||
Name string
|
||||
}
|
||||
|
||||
func (ps *ProcessService) GetListeningProcess(c context.Context) ([]ListeningProcess, error) {
|
||||
conn, err := net.ConnectionsMaxWithContext(c, "inet", 32768)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
procCache := make(map[int32]ListeningProcess, 64)
|
||||
|
||||
for _, conn := range conn {
|
||||
if conn.Pid == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if (conn.Status == "LISTEN" && conn.Type == syscall.SOCK_STREAM) || (conn.Type == syscall.SOCK_DGRAM && conn.Raddr.Port == 0) {
|
||||
if _, exists := procCache[conn.Pid]; !exists {
|
||||
proc, err := process.NewProcess(conn.Pid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procData := ListeningProcess{
|
||||
PID: conn.Pid,
|
||||
}
|
||||
procData.Name, _ = proc.Name()
|
||||
procData.Port = make(map[uint32]struct{})
|
||||
procData.Port[conn.Laddr.Port] = struct{}{}
|
||||
procData.Protocol = conn.Type
|
||||
procCache[conn.Pid] = procData
|
||||
} else {
|
||||
p := procCache[conn.Pid]
|
||||
p.Port[conn.Laddr.Port] = struct{}{}
|
||||
procCache[conn.Pid] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
procs := make([]ListeningProcess, 0, len(procCache))
|
||||
for _, proc := range procCache {
|
||||
procs = append(procs, proc)
|
||||
}
|
||||
|
||||
return procs, nil
|
||||
}
|
||||
|
||||
func (ps *ProcessService) GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error) {
|
||||
p, err := process.NewProcess(pid)
|
||||
if err != nil {
|
||||
|
||||
@@ -677,7 +677,7 @@ func (r *RuntimeService) GetPHPExtensions(runtimeID uint) (response.PHPExtension
|
||||
return res, err
|
||||
}
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
out, err := cmdMgr.RunWithStdoutBashCf("docker exec -i %s php -m", runtime.ContainerName)
|
||||
out, err := cmdMgr.RunWithStdout("docker", "exec", "-i", runtime.ContainerName, "php", "-m")
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -722,7 +722,7 @@ func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq)
|
||||
}
|
||||
installTask.AddSubTask("", func(t *task.Task) error {
|
||||
err = cmd.NewCommandMgr(cmd.WithTask(*installTask), cmd.WithTimeout(20*time.Minute)).
|
||||
RunBashCf("docker exec -i %s install-ext %s", runtime.ContainerName, req.Name)
|
||||
Run("docker", "exec", "-i", runtime.ContainerName, "install-ext", req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -736,7 +736,7 @@ func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq)
|
||||
return err
|
||||
}
|
||||
err = cmd.NewCommandMgr(cmd.WithTask(*installTask), cmd.WithTimeout(15*time.Minute)).
|
||||
RunBashCf("docker commit %s %s", runtime.ContainerName, runtime.Image)
|
||||
Run("docker", "commit", runtime.ContainerName, runtime.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -101,15 +101,15 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
}()
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDownload" || req.ReDownload {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverDownload",
|
||||
func(t *task.Task) error { return handleDownloadSnapshot(&itemHelper, snap, rootDir) },
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDecompress" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverDecompress",
|
||||
func(t *task.Task) error {
|
||||
itemHelper.Task.Log("---------------------- 2 / 11 ----------------------")
|
||||
@@ -118,54 +118,46 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "BackupBeforeRecover" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"BackupBeforeRecover",
|
||||
func(t *task.Task) error { return backupBeforeRecover(snap.Name, &itemHelper) },
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
var snapJson SnapshotJson
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"Readjson",
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"ReadJson",
|
||||
func(t *task.Task) error {
|
||||
snapJson, err = readFromJson(path.Join(rootDir, snap.Name), &itemHelper)
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
if req.IsNew || snap.InterruptStep == "RecoverApp" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverApp",
|
||||
func(t *task.Task) error { return recoverAppData(path.Join(rootDir, snap.Name), &itemHelper) },
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverBaseData" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverBaseData",
|
||||
func(t *task.Task) error { return recoverBaseData(path.Join(rootDir, snap.Name, "base"), &itemHelper) },
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDBData" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverDBData",
|
||||
func(t *task.Task) error { return recoverDBData(path.Join(rootDir, snap.Name, "db"), &itemHelper) },
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverBackups" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverBackups",
|
||||
func(t *task.Task) error {
|
||||
itemHelper.Task.Log("---------------------- 8 / 11 ----------------------")
|
||||
@@ -174,12 +166,12 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverWebsite" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverWebsite",
|
||||
func(t *task.Task) error {
|
||||
itemHelper.Task.Log("---------------------- 9 / 11 ----------------------")
|
||||
@@ -193,12 +185,12 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverPanelData" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverPanelData",
|
||||
func(t *task.Task) error {
|
||||
itemHelper.Task.Log("---------------------- 10 / 11 ----------------------")
|
||||
@@ -218,17 +210,28 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
}
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverCompose" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverCompose",
|
||||
func(t *task.Task) error {
|
||||
return restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"), &itemHelper)
|
||||
},
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDBData" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverDBData",
|
||||
func(t *task.Task) error { return recoverDBData(path.Join(rootDir, snap.Name, "db"), &itemHelper) },
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverDBData",
|
||||
func(t *task.Task) error {
|
||||
return restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"), &itemHelper)
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
@@ -277,14 +280,15 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(global.Dir.DataDir, rootDir, []string{"cache", "tmp"})
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.DataDir), err)
|
||||
dataDir := global.Dir.DataDir
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(dataDir, rootDir, []string{"cache", "tmp", "backup"})
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", dataDir), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, global.Dir.DataDir) {
|
||||
if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, dataDir) {
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(openrestyDir, rootDir, nil)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", openrestyDir), err)
|
||||
if err != nil {
|
||||
@@ -292,9 +296,10 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(global.Dir.LocalBackupDir) != 0 && !strings.Contains(global.Dir.LocalBackupDir, global.Dir.DataDir) {
|
||||
err = itemHelper.FileOp.CopyDirWithExclude(global.Dir.LocalBackupDir, rootDir, []string{"system_snapshot"})
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir), err)
|
||||
backupDir := global.Dir.LocalBackupDir
|
||||
if len(backupDir) != 0 && (backupDir == dataDir+"/backup" || !strings.HasPrefix(backupDir, dataDir)) {
|
||||
err = itemHelper.FileOp.CopyDirWithExclude(backupDir, rootDir, []string{"system_snapshot", "tmp"})
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", backupDir), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -341,22 +346,22 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
|
||||
|
||||
func readFromJson(rootDir string, itemHelper *snapRecoverHelper) (SnapshotJson, error) {
|
||||
itemHelper.Task.Log("---------------------- 4 / 11 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("Readjson"))
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("ReadJson"))
|
||||
|
||||
snapJsonPath := path.Join(rootDir, "base/snapshot.json")
|
||||
var snap SnapshotJson
|
||||
_, err := os.Stat(snapJsonPath)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonPath"), err)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadJsonPath"), err)
|
||||
if err != nil {
|
||||
return snap, err
|
||||
}
|
||||
fileByte, err := os.ReadFile(snapJsonPath)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonContent"), err)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadJsonContent"), err)
|
||||
if err != nil {
|
||||
return snap, err
|
||||
}
|
||||
err = json.Unmarshal(fileByte, &snap)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonMarshal"), err)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadJsonMarshal"), err)
|
||||
if err != nil {
|
||||
return snap, err
|
||||
}
|
||||
@@ -437,17 +442,8 @@ func recoverBaseData(src string, itemHelper *snapRecoverHelper) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverDBData(src string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 7 / 11 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDBData"))
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(src, global.Dir.DataDir, nil)
|
||||
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
func restartCompose(composePath string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 11 / 11 ----------------------")
|
||||
itemHelper.Task.Log("---------------------- 7 / 11 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverCompose"))
|
||||
|
||||
composes, err := composeRepo.ListRecord()
|
||||
@@ -470,3 +466,64 @@ func restartCompose(composePath string, itemHelper *snapRecoverHelper) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverDBData(src string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 11 / 11 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDBData"))
|
||||
dbDir := path.Join(global.Dir.DataDir, "db")
|
||||
if err := os.RemoveAll(dbDir); err != nil {
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(src, global.Dir.DataDir, nil)
|
||||
if err != nil {
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), err)
|
||||
return err
|
||||
}
|
||||
if cleanErr := cleanOrphanSQLiteSidecars(dbDir); cleanErr != nil {
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), cleanErr)
|
||||
return cleanErr
|
||||
}
|
||||
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanOrphanSQLiteSidecars(dbDir string) error {
|
||||
entries, err := os.ReadDir(dbDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := entry.Name()
|
||||
var mainDB string
|
||||
switch {
|
||||
case strings.HasSuffix(name, ".db-wal"):
|
||||
mainDB = strings.TrimSuffix(name, "-wal")
|
||||
case strings.HasSuffix(name, ".db-shm"):
|
||||
mainDB = strings.TrimSuffix(name, "-shm")
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if _, statErr := os.Stat(path.Join(dbDir, mainDB)); statErr != nil {
|
||||
if !os.IsNotExist(statErr) {
|
||||
return statErr
|
||||
}
|
||||
if removeErr := os.Remove(path.Join(dbDir, name)); removeErr != nil && !os.IsNotExist(removeErr) {
|
||||
return removeErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
@@ -88,7 +89,7 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyDir(path.Join(rootDir, "backup"), global.Dir.LocalBackupDir)
|
||||
return FileOp.CopyDir(path.Join(rootDir, "backup"), strings.TrimSuffix(global.Dir.LocalBackupDir, "/backup"))
|
||||
},
|
||||
nil,
|
||||
)
|
||||
@@ -97,7 +98,7 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", openrestyDir),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyDir(path.Join(rootDir, "www"), openrestyDir)
|
||||
return FileOp.CopyDir(path.Join(rootDir, "www"), strings.TrimSuffix(openrestyDir, "/www"))
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
+52
-13
@@ -269,16 +269,26 @@ func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error {
|
||||
publicPath := fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name)
|
||||
authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys"
|
||||
|
||||
authFileItem, _ := os.ReadFile(authFilePath)
|
||||
authFile := string(authFileItem)
|
||||
if authFile != "" && !strings.HasSuffix(authFile, "\n") {
|
||||
file, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if info, err := os.Stat(authFilePath); err == nil && info.Size() > 0 {
|
||||
f, err := os.Open(authFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err := file.WriteString("\n"); err != nil {
|
||||
return err
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
if _, err := f.Seek(-1, 2); err == nil {
|
||||
buf := make([]byte, 1)
|
||||
if _, err := f.Read(buf); err == nil && buf[0] != '\n' {
|
||||
appendFile, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := appendFile.Write([]byte("\n")); err != nil {
|
||||
_ = appendFile.Close()
|
||||
return err
|
||||
}
|
||||
_ = appendFile.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,17 +300,43 @@ func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
command := fmt.Sprintf("ssh-keygen -t %s -f %s/.ssh/%s -N ''", req.EncryptionMode, currentUser.HomeDir, req.Name)
|
||||
if len(req.PassPhrase) != 0 {
|
||||
command = fmt.Sprintf("ssh-keygen -t %s -P %s -f %s/.ssh/%s | echo y", req.EncryptionMode, req.PassPhrase, currentUser.HomeDir, req.Name)
|
||||
tmpPrivatePath := privatePath + ".tmp"
|
||||
tmpPublicPath := privatePath + ".tmp.pub"
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Minute))
|
||||
args := []string{
|
||||
"-t", req.EncryptionMode,
|
||||
"-f", tmpPrivatePath,
|
||||
"-N", req.PassPhrase,
|
||||
"-q",
|
||||
}
|
||||
if err := cmd.RunDefaultBashC(command); err != nil {
|
||||
|
||||
if err := cmdMgr.Run("ssh-keygen", args...); err != nil {
|
||||
_ = os.Remove(tmpPrivatePath)
|
||||
_ = os.Remove(tmpPublicPath)
|
||||
return fmt.Errorf("generate failed, %v", err)
|
||||
}
|
||||
if err := os.Rename(tmpPrivatePath, privatePath); err != nil {
|
||||
return fmt.Errorf("replace private key failed, %v", err)
|
||||
}
|
||||
if err := os.Rename(tmpPublicPath, publicPath); err != nil {
|
||||
return fmt.Errorf("replace public key failed, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.RunDefaultBashCf("cat %s >> %s", publicPath, authFilePath); err != nil {
|
||||
return fmt.Errorf("generate failed, %v", err)
|
||||
publicKeyBytes, err := os.ReadFile(publicPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read public key failed, %v", err)
|
||||
}
|
||||
|
||||
cleanKey := strings.TrimRight(string(publicKeyBytes), "\n") + "\n"
|
||||
authFile, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open authorized_keys failed, %v", err)
|
||||
}
|
||||
defer func() { _ = authFile.Close() }()
|
||||
|
||||
if _, err := authFile.Write([]byte(cleanKey)); err != nil {
|
||||
return fmt.Errorf("append authorized_keys failed, %v", err)
|
||||
}
|
||||
|
||||
cert.PrivateKeyPath = privatePath
|
||||
@@ -465,6 +501,9 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d
|
||||
|
||||
command := ""
|
||||
if len(req.Info) != 0 {
|
||||
if cmd.CheckIllegal(req.Info) {
|
||||
return 0, data, buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
command = fmt.Sprintf(" | grep '%s'", req.Info)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewITaskService() ITaskLogService {
|
||||
|
||||
func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) {
|
||||
opts := []repo.DBOption{
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
}
|
||||
if req.Status != "" {
|
||||
opts = append(opts, repo.WithByStatus(req.Status))
|
||||
|
||||
@@ -110,6 +110,7 @@ type IWebsiteService interface {
|
||||
GetProxyCache(id uint) (res response.NginxProxyCache, err error)
|
||||
ClearProxyCache(req request.NginxCommonReq) error
|
||||
DeleteProxy(req request.WebsiteProxyDel) (err error)
|
||||
UpdateProxyStatus(req request.WebsiteProxyStatusUpdate) (err error)
|
||||
|
||||
CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error)
|
||||
GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error)
|
||||
@@ -173,11 +174,10 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
switch web.Type {
|
||||
case constant.Deployment:
|
||||
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(web.AppInstallID))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
if err == nil {
|
||||
appName = appInstall.Name
|
||||
appInstallID = appInstall.ID
|
||||
}
|
||||
appName = appInstall.Name
|
||||
appInstallID = appInstall.ID
|
||||
case constant.Runtime:
|
||||
runtime, _ := runtimeRepo.GetFirst(context.Background(), repo.WithByID(web.RuntimeID))
|
||||
if runtime != nil {
|
||||
@@ -208,6 +208,13 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
IPV6: web.IPV6,
|
||||
}
|
||||
|
||||
if siteDTO.Type == constant.Subsite {
|
||||
parentWeb, _ := websiteRepo.GetFirst(repo.WithByID(web.ParentWebsiteID))
|
||||
if parentWeb.ID != 0 {
|
||||
siteDTO.ParentSite = parentWeb.PrimaryDomain
|
||||
}
|
||||
}
|
||||
|
||||
sites, _ := websiteRepo.List(websiteRepo.WithParentID(web.ID))
|
||||
if len(sites) > 0 {
|
||||
for _, site := range sites {
|
||||
@@ -376,7 +383,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
req.AppDetailId = create.AppInstall.AppDetailId
|
||||
req.Params = create.AppInstall.Params
|
||||
req.AppContainerConfig = create.AppInstall.AppContainerConfig
|
||||
install, err = NewIAppService().Install(req)
|
||||
install, err = NewIAppService().Install(req, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1318,7 +1325,7 @@ func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermiss
|
||||
}
|
||||
absoluteIndexPath := GetSitePath(website, SiteIndexDir)
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
|
||||
if err := cmdMgr.RunBashCf("%s chown -R %s:%s %s", cmd.SudoHandleCmd(), req.User, req.Group, absoluteIndexPath); err != nil {
|
||||
if err := cmdMgr.Run("chown", "-R", fmt.Sprintf("%s:%s", req.User, req.Group), absoluteIndexPath); err != nil {
|
||||
return err
|
||||
}
|
||||
website.User = req.User
|
||||
@@ -1780,7 +1787,7 @@ func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig
|
||||
continue
|
||||
}
|
||||
fileName := entry.Name()
|
||||
if !strings.HasSuffix(fileName, ".conf") && !strings.HasSuffix(fileName, ".conf.bak") {
|
||||
if !strings.HasSuffix(fileName, ".conf") && !strings.HasSuffix(fileName, ".bak") {
|
||||
continue
|
||||
}
|
||||
redirectConfig := response.NginxRedirectConfig{
|
||||
@@ -1899,6 +1906,10 @@ func (w WebsiteService) UpdateRedirectFile(req request.NginxRedirectUpdate) (err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
absolutePath := path.Join(GetSitePath(website, SiteRedirectDir), req.Name+".conf")
|
||||
fileOp := files.NewFileOp()
|
||||
oldRewriteContent, err = fileOp.GetContent(absolutePath)
|
||||
@@ -1936,25 +1947,40 @@ func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*res
|
||||
return nil, err
|
||||
}
|
||||
res.Dirs = []string{"/"}
|
||||
for _, file := range indexFiles {
|
||||
if !file.IsDir() || file.Name() == "node_modules" || file.Name() == "vendor" {
|
||||
continue
|
||||
}
|
||||
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name()))
|
||||
fileInfo, _ := file.Info()
|
||||
if fileInfo.Sys().(*syscall.Stat_t).Uid != 1000 || fileInfo.Sys().(*syscall.Stat_t).Gid != 1000 {
|
||||
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
|
||||
}
|
||||
childFiles, _ := os.ReadDir(absoluteIndexPath + "/" + file.Name())
|
||||
for _, childFile := range childFiles {
|
||||
if !childFile.IsDir() {
|
||||
checkAndAppendDirs := func(relPath string, entries []os.DirEntry) {
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() || entry.Name() == "node_modules" || entry.Name() == "vendor" {
|
||||
continue
|
||||
}
|
||||
childInfo, _ := childFile.Info()
|
||||
if childInfo.Sys().(*syscall.Stat_t).Uid != 1000 || childInfo.Sys().(*syscall.Stat_t).Gid != 1000 {
|
||||
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
|
||||
nextRelPath := path.Join(relPath, entry.Name())
|
||||
res.Dirs = append(res.Dirs, "/"+strings.TrimPrefix(nextRelPath, "/"))
|
||||
entryInfo, _ := entry.Info()
|
||||
if entryInfo != nil {
|
||||
if stat, ok := entryInfo.Sys().(*syscall.Stat_t); ok {
|
||||
if stat.Uid != 1000 || stat.Gid != 1000 {
|
||||
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
|
||||
}
|
||||
}
|
||||
}
|
||||
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s/%s", file.Name(), childFile.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
checkAndAppendDirs("", indexFiles)
|
||||
for _, firstDir := range indexFiles {
|
||||
if !firstDir.IsDir() || firstDir.Name() == "node_modules" || firstDir.Name() == "vendor" {
|
||||
continue
|
||||
}
|
||||
secondLevelPath := path.Join(absoluteIndexPath, firstDir.Name())
|
||||
secondLevelDirs, _ := os.ReadDir(secondLevelPath)
|
||||
checkAndAppendDirs(firstDir.Name(), secondLevelDirs)
|
||||
|
||||
for _, secondDir := range secondLevelDirs {
|
||||
if !secondDir.IsDir() || secondDir.Name() == "node_modules" || secondDir.Name() == "vendor" {
|
||||
continue
|
||||
}
|
||||
thirdLevelPath := path.Join(secondLevelPath, secondDir.Name())
|
||||
thirdLevelDirs, _ := os.ReadDir(thirdLevelPath)
|
||||
checkAndAppendDirs(path.Join(firstDir.Name(), secondDir.Name()), thirdLevelDirs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2275,6 +2301,9 @@ func (w WebsiteService) OperateCrossSiteAccess(req request.CrossSiteAccessOp) er
|
||||
}
|
||||
|
||||
func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error {
|
||||
if cmd.CheckIllegal(req.User, req.Mirror, req.Command, req.ExtCommand) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2308,9 +2337,25 @@ func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error {
|
||||
siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR"))
|
||||
execDir := strings.ReplaceAll(req.Dir, siteDir.Value, "/www")
|
||||
composerTask.AddSubTask("", func(t *task.Task) error {
|
||||
cmdStr := fmt.Sprintf("docker exec -u %s %s sh -c 'composer config -g repo.packagist composer %s && composer %s --working-dir=%s'", req.User, runtime.ContainerName, req.Mirror, command, execDir)
|
||||
err = cmdMgr.RunBashC(cmdStr)
|
||||
if err != nil {
|
||||
if err := cmdMgr.Run("docker", "exec",
|
||||
"-u", req.User,
|
||||
runtime.ContainerName,
|
||||
"composer",
|
||||
"config", "-g",
|
||||
"repo.packagist",
|
||||
"composer",
|
||||
req.Mirror,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cmdMgr.Run("docker", "exec",
|
||||
"-u", req.User,
|
||||
runtime.ContainerName,
|
||||
"composer",
|
||||
command,
|
||||
"--working-dir="+execDir,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -25,7 +25,7 @@ func NewIWebsiteAcmeAccountService() IWebsiteAcmeAccountService {
|
||||
}
|
||||
|
||||
func (w WebsiteAcmeAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteAcmeAccountDTO, error) {
|
||||
total, accounts, err := websiteAcmeRepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc"))
|
||||
total, accounts, err := websiteAcmeRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
||||
var accountDTOs []response.WebsiteAcmeAccountDTO
|
||||
for _, account := range accounts {
|
||||
accountDTOs = append(accountDTOs, response.WebsiteAcmeAccountDTO{
|
||||
@@ -45,9 +45,10 @@ func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreat
|
||||
Type: create.Type,
|
||||
KeyType: create.KeyType,
|
||||
UseProxy: create.UseProxy,
|
||||
UseEAB: create.UseEAB,
|
||||
}
|
||||
|
||||
if create.Type == "google" || create.Type == "freessl" {
|
||||
if create.Type == "google" || create.Type == "freessl" || (create.Type == "custom" && create.UseEAB) {
|
||||
if create.EabKid == "" || create.EabHmacKey == "" {
|
||||
return nil, buserr.New("ErrEabKidOrEabHmacKeyCannotBlank")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ package service
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
@@ -11,14 +15,12 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) {
|
||||
@@ -260,6 +262,10 @@ func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) err
|
||||
if !fileOp.Stat(passDir) {
|
||||
_ = fileOp.CreateDir(passDir, constant.DirPerm)
|
||||
}
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
confPath := path.Join(authDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
passPath := path.Join(passDir, fmt.Sprintf("%s.pass", req.Name))
|
||||
var config *components.Config
|
||||
@@ -289,7 +295,12 @@ func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) err
|
||||
config.FilePath = confPath
|
||||
directives := config.Directives
|
||||
location, _ := directives[0].(*components.Location)
|
||||
location.UpdateDirective("auth_basic_user_file", []string{fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name))})
|
||||
safePass, err := nginx.NginxSafeString(fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name)), nginx.ModePath)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("invalid auth_basic_user_file path, err: %v", err)
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("auth_basic_user_file", []string{safePass})
|
||||
location.ChangePath("~*", fmt.Sprintf("^%s", req.Path))
|
||||
var passwdHash []byte
|
||||
passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
@@ -306,7 +317,7 @@ func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) err
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/path_auth/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func NewIWebsiteCAService() IWebsiteCAService {
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error) {
|
||||
total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc"))
|
||||
total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func NewIWebsiteDnsAccountService() IWebsiteDnsAccountService {
|
||||
}
|
||||
|
||||
func (w WebsiteDnsAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteDnsAccountDTO, error) {
|
||||
total, accounts, err := websiteDnsRepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc"))
|
||||
total, accounts, err := websiteDnsRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
|
||||
var accountDTOs []response.WebsiteDnsAccountDTO
|
||||
for _, account := range accounts {
|
||||
auth := make(map[string]string)
|
||||
|
||||
@@ -3,6 +3,10 @@ package service
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
@@ -13,9 +17,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) {
|
||||
@@ -87,6 +88,10 @@ func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error {
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, constant.DirPerm)
|
||||
}
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if fileOp.Stat(filePath) {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
@@ -133,6 +138,10 @@ func (w WebsiteService) UpdateLoadBalance(req request.WebsiteLBUpdate) error {
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
fileOp := files.NewFileOp()
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if !fileOp.Stat(filePath) {
|
||||
return nil
|
||||
@@ -191,6 +200,10 @@ func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error {
|
||||
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
fileOp := files.NewFileOp()
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if !fileOp.Stat(filePath) {
|
||||
return nil
|
||||
@@ -211,6 +224,10 @@ func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) e
|
||||
return err
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
fileOp := files.NewFileOp()
|
||||
oldContent, err := fileOp.GetContent(filePath)
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
@@ -17,12 +21,24 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
|
||||
switch req.Operate {
|
||||
case "delete":
|
||||
return w.DeleteProxy(request.WebsiteProxyDel{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
})
|
||||
case "disable":
|
||||
fallthrough
|
||||
case "enable":
|
||||
return w.UpdateProxyStatus(request.WebsiteProxyStatusUpdate{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
Status: req.Operate,
|
||||
})
|
||||
}
|
||||
var (
|
||||
website model.Website
|
||||
par *parser.Parser
|
||||
@@ -80,16 +96,8 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "delete":
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
_ = fileOp.DeleteFile(backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "disable":
|
||||
_ = fileOp.Rename(includePath, backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "enable":
|
||||
_ = fileOp.Rename(backPath, includePath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
default:
|
||||
return errors.New("unknown operate")
|
||||
}
|
||||
|
||||
config.FilePath = includePath
|
||||
@@ -106,8 +114,16 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
err = errors.New("invalid proxy config, no location found")
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{req.ProxyPass})
|
||||
location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
|
||||
safePass, err := nginx.NginxSafeString(req.ProxyPass, nginx.ModeURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
safeHeader, err := nginx.NginxSafeString(req.ProxyHost, nginx.ModeHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{safePass})
|
||||
location.UpdateDirective("proxy_set_header", []string{"Host", safeHeader})
|
||||
location.ChangePath(req.Modifier, req.Match)
|
||||
// Server Cache Settings
|
||||
if req.Cache {
|
||||
@@ -119,8 +135,13 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
location.RemoveServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias))
|
||||
}
|
||||
// Browser Cache Settings
|
||||
if req.CacheTime != 0 {
|
||||
// cacheTime > 0: enable expires;
|
||||
// cacheTime == 0: use upstream cache-control, remove self-set;
|
||||
// cacheTime < 0: force no-cache
|
||||
if req.CacheTime > 0 {
|
||||
location.AddBrowserCache(req.CacheTime, req.CacheUnit)
|
||||
} else if req.CacheTime < 0 {
|
||||
location.AddBroswerNoCache()
|
||||
} else {
|
||||
location.RemoveBrowserCache()
|
||||
}
|
||||
@@ -141,14 +162,26 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
}
|
||||
// CORS Settings
|
||||
if req.Cors {
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"})
|
||||
safeAllowOrigins, err := nginx.NginxSafeString(req.AllowOrigins, nginx.ModeGeneric)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", safeAllowOrigins, "always"})
|
||||
if req.AllowMethods != "" {
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"})
|
||||
safeAllowMethods, err := nginx.NginxSafeString(req.AllowMethods, nginx.ModeAllowMethods)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", safeAllowMethods, "always"})
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
|
||||
}
|
||||
if req.AllowHeaders != "" {
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"})
|
||||
safeAllowHeaders, err := nginx.NginxSafeString(req.AllowHeaders, nginx.ModeGeneric)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", safeAllowHeaders, "always"})
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
|
||||
}
|
||||
@@ -385,24 +418,47 @@ func (w WebsiteService) ClearProxyCache(req request.NginxCommonReq) error {
|
||||
}
|
||||
|
||||
func (w WebsiteService) DeleteProxy(req request.WebsiteProxyDel) (err error) {
|
||||
fileOp := files.NewFileOp()
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
includeDir := GetSitePath(website, SiteProxyDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, 0755)
|
||||
return
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.conf", req.Name)
|
||||
includePath := path.Join(includeDir, fileName)
|
||||
backName := fmt.Sprintf("%s.bak", req.Name)
|
||||
backPath := path.Join(includeDir, backName)
|
||||
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
_ = fileOp.DeleteFile(backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateProxyStatus(req request.WebsiteProxyStatusUpdate) (err error) {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteProxyDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
return
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.conf", req.Name)
|
||||
includePath := path.Join(includeDir, fileName)
|
||||
backName := fmt.Sprintf("%s.bak", req.Name)
|
||||
backPath := path.Join(includeDir, backName)
|
||||
|
||||
switch req.Status {
|
||||
case "disable":
|
||||
_ = fileOp.Rename(includePath, backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "enable":
|
||||
_ = fileOp.Rename(backPath, includePath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
return errors.New("unknown status")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
@@ -12,9 +16,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error {
|
||||
@@ -73,6 +74,10 @@ func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response
|
||||
contentByte, _ = nginx_conf.Rewrites.ReadFile(rewriteFile)
|
||||
if contentByte == nil {
|
||||
customRewriteDir := GetOpenrestyDir(DefaultRewriteDir)
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return nil, buserr.New("ErrInvalidParams")
|
||||
}
|
||||
customRewriteFile := path.Join(customRewriteDir, fmt.Sprintf("%s.conf", strings.ToLower(req.Name)))
|
||||
contentByte, err = files.NewFileOp().GetContent(customRewriteFile)
|
||||
}
|
||||
@@ -90,6 +95,10 @@ func (w WebsiteService) OperateCustomRewrite(req request.CustomRewriteOperate) e
|
||||
return err
|
||||
}
|
||||
}
|
||||
safeName := path.Base(req.Name)
|
||||
if safeName != req.Name || strings.Contains(safeName, "..") {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
rewriteFile := path.Join(rewriteDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
|
||||
@@ -37,7 +37,7 @@ type WebsiteSSLService struct {
|
||||
type IWebsiteSSLService interface {
|
||||
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
|
||||
GetSSL(id uint) (*response.WebsiteSSLDTO, error)
|
||||
Search(req request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error)
|
||||
Search(req request.WebsiteSSLListReq) ([]response.WebsiteSSLDTO, error)
|
||||
Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error)
|
||||
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
|
||||
GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error)
|
||||
@@ -62,7 +62,7 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
|
||||
if search.OrderBy != "" && search.Order != "null" {
|
||||
opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order))
|
||||
} else {
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
opts = append(opts, repo.WithOrderDesc("created_at"))
|
||||
}
|
||||
if search.Domain != "" {
|
||||
opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain))
|
||||
@@ -90,12 +90,12 @@ func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error) {
|
||||
func (w WebsiteSSLService) Search(search request.WebsiteSSLListReq) ([]response.WebsiteSSLDTO, error) {
|
||||
var (
|
||||
opts []repo.DBOption
|
||||
result []response.WebsiteSSLDTO
|
||||
)
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
opts = append(opts, repo.WithOrderDesc("created_at"))
|
||||
if search.AcmeAccountID != "" {
|
||||
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
|
||||
if err != nil {
|
||||
|
||||
@@ -849,6 +849,17 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W
|
||||
nginxParams[i].Params = []string{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED"}
|
||||
}
|
||||
}
|
||||
if param.Name == "error_page" {
|
||||
if len(param.Params) < 2 {
|
||||
continue
|
||||
}
|
||||
code := param.Params[0]
|
||||
if code == "497" {
|
||||
if sslPort != 443 && param.Params[1] == "https://$host$request_uri" {
|
||||
param.Params[1] = fmt.Sprintf("https://$host:%d$request_uri", sslPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if req.Hsts {
|
||||
var hstsValue string
|
||||
@@ -966,6 +977,8 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
server.RemoveDirective("include", []string{proxyInclude})
|
||||
rewriteInclude := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
|
||||
server.RemoveDirective("include", []string{rewriteInclude})
|
||||
redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias)
|
||||
server.RemoveDirective("include", []string{redirectInclude})
|
||||
|
||||
switch website.Type {
|
||||
case constant.Deployment:
|
||||
@@ -996,6 +1009,11 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
if fileOp.Stat(absoluteRewritePath) {
|
||||
server.UpdateDirective("include", []string{rewriteInclude})
|
||||
}
|
||||
redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias)
|
||||
absoluteRedirectPath := GetSitePath(*website, SiteRedirectDir)
|
||||
if fileOp.Stat(absoluteRedirectPath) {
|
||||
server.UpdateDirective("include", []string{redirectInclude})
|
||||
}
|
||||
rootIndex := path.Join("/www/sites", website.Alias, "index")
|
||||
if website.SiteDir != "/" {
|
||||
rootIndex = path.Join(rootIndex, website.SiteDir)
|
||||
@@ -1030,6 +1048,31 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
proxy := fmt.Sprintf("http://%s", website.Proxy)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
}
|
||||
case constant.Subsite:
|
||||
parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(website.ParentWebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website.Proxy = parentWebsite.Proxy
|
||||
rootIndex = path.Join("/www/sites", parentWebsite.Alias, "index", website.SiteDir)
|
||||
if parentWebsite.Type == constant.Runtime {
|
||||
parentRuntime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(parentWebsite.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website.RuntimeID = parentRuntime.ID
|
||||
if parentRuntime.Type == constant.RuntimePHP {
|
||||
server.UpdateRoot(rootIndex)
|
||||
localPath := ""
|
||||
if parentRuntime.Resource == constant.ResourceLocal {
|
||||
localPath = path.Join(rootIndex, "index.php")
|
||||
}
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||
}
|
||||
}
|
||||
if parentWebsite.Type == constant.Static {
|
||||
server.UpdateRoot(rootIndex)
|
||||
}
|
||||
}
|
||||
website.Status = constant.WebRunning
|
||||
now := time.Now()
|
||||
@@ -1115,7 +1158,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, default
|
||||
if domain.Domain == "" {
|
||||
continue
|
||||
}
|
||||
if !common.IsValidNginxServerName(domain.Domain) {
|
||||
if !(common.IsValidNginxServerName(domain.Domain) || common.IsValidIP(domain.Domain)) {
|
||||
err = buserr.WithName("ErrDomainFormat", domain.Domain)
|
||||
return
|
||||
}
|
||||
@@ -1226,7 +1269,7 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
|
||||
func GetSystemSSL() (bool, uint) {
|
||||
var sslSetting model.Setting
|
||||
_ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SSL").First(&sslSetting).Error
|
||||
if sslSetting.Value == "Enable" {
|
||||
if sslSetting.Value == "Enable" || sslSetting.Value == "Mux" {
|
||||
var sslIDSetting model.Setting
|
||||
_ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SSLID").First(&sslIDSetting).Error
|
||||
idValue, _ := strconv.Atoi(sslIDSetting.Value)
|
||||
|
||||
+15
-1
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
@@ -353,7 +354,20 @@ func (t *Task) LogSuccessWithOps(operate, msg string) {
|
||||
}
|
||||
|
||||
func (t *Task) LogFailedWithOps(operate, msg string, err error) {
|
||||
t.Logger.Printf("%s%s%s : %s ", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Failed"), err.Error())
|
||||
t.Logger.Printf("%s%s : %s ", msg, i18n.GetMsgByKey("Failed"), err.Error())
|
||||
}
|
||||
|
||||
func (t *Task) LogWithProgress(msg string, current int, total int) {
|
||||
const barWidth = 10
|
||||
filled := int(float64(current) / float64(total) * 100 / 10)
|
||||
if filled > barWidth {
|
||||
filled = barWidth
|
||||
}
|
||||
if filled < 0 {
|
||||
filled = 0
|
||||
}
|
||||
bar := strings.Repeat("=", filled) + strings.Repeat("-", barWidth-filled)
|
||||
t.Logger.Printf("%s [%s] %.2f%%", msg, bar, float64(current)/float64(total)*100)
|
||||
}
|
||||
|
||||
type SimpleFormatter struct{}
|
||||
|
||||
@@ -4,11 +4,6 @@ base:
|
||||
is_demo: false
|
||||
is_offline: false
|
||||
|
||||
remote_url:
|
||||
app_repo: https://apps-assets.fit2cloud.com
|
||||
repo_url: https://resource.fit2cloud.com/1panel/package/v2
|
||||
resource_url: https://resource.fit2cloud.com/1panel/resource/v2
|
||||
|
||||
log:
|
||||
level: debug
|
||||
time_zone: Asia/Shanghai
|
||||
|
||||
@@ -24,4 +24,5 @@ const (
|
||||
WeCom = "weCom"
|
||||
DingTalk = "dingTalk"
|
||||
FeiShu = "feiShu"
|
||||
Custom = "custom"
|
||||
)
|
||||
|
||||
@@ -7,6 +7,8 @@ const (
|
||||
AppTakeDown = "TakeDown"
|
||||
|
||||
AppOpenresty = "openresty"
|
||||
AppOpenclaw = "openclaw"
|
||||
AppCopaw = "copaw"
|
||||
AppMysql = "mysql"
|
||||
AppMariaDB = "mariadb"
|
||||
AppPostgresql = "postgresql"
|
||||
|
||||
@@ -40,4 +40,6 @@ const (
|
||||
|
||||
OrderDesc = "descending"
|
||||
OrderAsc = "ascending"
|
||||
Desc = "desc"
|
||||
Asc = "asc"
|
||||
)
|
||||
|
||||
+13
-2
@@ -1,8 +1,9 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
mathRand "math/rand"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -43,7 +44,17 @@ func Run() {
|
||||
if _, err := global.Cron.AddJob("@daily", job.NewSSLJob()); err != nil {
|
||||
global.LOG.Errorf("can not add ssl corn job: %s", err.Error())
|
||||
}
|
||||
if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), job.NewAppStoreJob()); err != nil {
|
||||
minuteRand, err := rand.Int(rand.Reader, big.NewInt(60))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("generate random minute failed: %v", err)
|
||||
minuteRand = big.NewInt(0)
|
||||
}
|
||||
hourRand, err := rand.Int(rand.Reader, big.NewInt(3))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("generate random hour failed: %v", err)
|
||||
hourRand = big.NewInt(0)
|
||||
}
|
||||
if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", minuteRand.Int64(), hourRand.Int64()), job.NewAppStoreJob()); err != nil {
|
||||
global.LOG.Errorf("can not add appstore corn job: %s", err.Error())
|
||||
}
|
||||
if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil {
|
||||
|
||||
@@ -2,13 +2,13 @@ package global
|
||||
|
||||
type ServerConfig struct {
|
||||
Base Base `mapstructure:"base"`
|
||||
RemoteURL RemoteURL `mapstructure:"remote_url"`
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
DockerConfig DockerConfig
|
||||
}
|
||||
|
||||
type Base struct {
|
||||
Port string `mapstructure:"port"`
|
||||
Edition string `mapstructure:"edition"` // [ cn / intl]
|
||||
Version string `mapstructure:"version"`
|
||||
EncryptKey string `mapstructure:"encrypt_key"`
|
||||
Mode string `mapstructure:"mode"` // xpack [ Enable / Disable ]
|
||||
@@ -17,12 +17,6 @@ type Base struct {
|
||||
IsOffLine bool `mapstructure:"is_offline"`
|
||||
}
|
||||
|
||||
type RemoteURL struct {
|
||||
AppRepo string `mapstructure:"app_repo"`
|
||||
RepoUrl string `mapstructure:"repo_url"`
|
||||
ResourceUrl string `mapstructure:"resource_url"`
|
||||
}
|
||||
|
||||
type SystemDir struct {
|
||||
BaseDir string
|
||||
DbDir string
|
||||
@@ -34,6 +28,7 @@ type SystemDir struct {
|
||||
|
||||
AppDir string
|
||||
ResourceDir string
|
||||
IconCacheDir string
|
||||
AppResourceDir string
|
||||
AppInstallDir string
|
||||
LocalAppResourceDir string
|
||||
|
||||
@@ -40,3 +40,22 @@ var (
|
||||
|
||||
TaskCtxMap = make(map[string]context.CancelFunc)
|
||||
)
|
||||
|
||||
func RepoURL() string {
|
||||
if CONF.Base.Edition != "intl" {
|
||||
return "https://resource.fit2cloud.com/1panel/package/v2"
|
||||
}
|
||||
return "https://resource.1panel.pro/v2"
|
||||
}
|
||||
func ResourceURL() string {
|
||||
if CONF.Base.Edition != "intl" {
|
||||
return "https://resource.fit2cloud.com/1panel/resource/v2"
|
||||
}
|
||||
return "https://resource.1panel.pro/v2/resource"
|
||||
}
|
||||
func AppRepoURL() string {
|
||||
if CONF.Base.Edition != "intl" {
|
||||
return "https://apps-assets.fit2cloud.com"
|
||||
}
|
||||
return "https://apps.1panel.pro"
|
||||
}
|
||||
|
||||
+9
-7
@@ -50,7 +50,7 @@ require (
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
golang.org/x/sys v0.39.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.org/x/text v0.32.0
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
@@ -94,6 +94,7 @@ require (
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
@@ -195,6 +196,7 @@ require (
|
||||
github.com/nrdcg/freemyip v0.3.0 // indirect
|
||||
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||
github.com/nrdcg/namesilo v0.5.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||
@@ -235,14 +237,14 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
|
||||
+16
-15
@@ -31,8 +31,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
@@ -189,7 +189,6 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -764,6 +763,8 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
|
||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||
github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE=
|
||||
github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw=
|
||||
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
||||
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
@@ -1023,8 +1024,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
@@ -1033,14 +1034,14 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
@@ -1306,8 +1307,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
||||
+288
-284
@@ -1,102 +1,115 @@
|
||||
ErrInvalidParams: 'Request parameter error: {{ .detail }}'
|
||||
ErrTokenParse: 'Token generation error: {{ .detail }}'
|
||||
ErrInvalidParams: 'Bad request: {{ .detail }}'
|
||||
ErrTokenParse: 'Token error: {{ .detail }}'
|
||||
ErrInitialPassword: 'Original password is incorrect'
|
||||
ErrInternalServer: 'Internal server error: {{ .detail }}'
|
||||
ErrRecordExist: 'Record already exists'
|
||||
ErrRecordNotFound: 'Record not found'
|
||||
ErrStructTransform: 'Type conversion failed: {{ .err }}'
|
||||
ErrNotLogin: 'User not logged in: {{ .detail }}'
|
||||
ErrPasswordExpired: 'The current password has expired: {{ .detail }}'
|
||||
ErrNotSupportType: 'The system does not support the current type: {{ .name }}'
|
||||
ErrProxy: 'Request error, please check the node status: {{ .detail }}'
|
||||
ErrApiConfigStatusInvalid: 'Access to the API interface is prohibited: {{ .detail }}'
|
||||
ErrApiConfigKeyInvalid: 'API interface key error: {{ .detail }}'
|
||||
ErrApiConfigIPInvalid: 'The IP used to call the API interface is not in the whitelist: {{ .detail }}'
|
||||
ErrApiConfigDisable: 'This interface prohibits the use of API interface calls: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'API interface timestamp error: {{ .detail }}'
|
||||
ErrShutDown: "Command manually terminated!"
|
||||
ErrNotLogin: 'Not logged in: {{ .detail }}'
|
||||
ErrPasswordExpired: 'Password expired: {{ .detail }}'
|
||||
ErrNotSupportType: 'Unsupported type: {{ .name }}'
|
||||
ErrProxy: 'Request failed: {{ .detail }}'
|
||||
ErrApiConfigStatusInvalid: 'API access disabled: {{ .detail }}'
|
||||
ErrApiConfigKeyInvalid: 'Invalid API key: {{ .detail }}'
|
||||
ErrApiConfigIPInvalid: 'IP not in API allowlist: {{ .detail }}'
|
||||
ErrApiConfigDisable: 'Endpoint not allowed via API: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'Invalid API timestamp: {{ .detail }}'
|
||||
ErrShutDown: 'Command stopped manually'
|
||||
|
||||
ErrMinQuickJump: "Please set at least one quick jump entry!"
|
||||
ErrMaxQuickJump: "You can set up to four quick jump entries!"
|
||||
ErrMinQuickJump: 'Set at least one quick jump entry'
|
||||
ErrMaxQuickJump: 'At most 4 quick jump entries'
|
||||
|
||||
#common
|
||||
ErrUsernameIsExist: 'Username already exists'
|
||||
ErrNameIsExist: 'Name already exists'
|
||||
ErrDemoEnvironment: 'Demo server, this operation is forbidden!'
|
||||
ErrCmdTimeout: 'Command execution timed out!'
|
||||
ErrCmdIllegal: 'There are illegal characters in the execution command, please modify it and try again!'
|
||||
ErrDemoEnvironment: 'Disabled in demo environment'
|
||||
ErrCmdTimeout: 'Command timed out'
|
||||
ErrCmdIllegal: 'Command has invalid characters'
|
||||
ErrPortExist: '{{ .port }} port is already occupied by {{ .type }} [{{ .name }}]'
|
||||
TYPE_APP: 'Application'
|
||||
TYPE_RUNTIME: 'Runtime environment'
|
||||
TYPE_DOMAIN: 'Domain Name'
|
||||
ErrTypePort: 'Port {{ .name }} format is incorrect'
|
||||
ErrTypePortRange: 'The port range needs to be between 1-65535'
|
||||
TYPE_RUNTIME: 'Runtime'
|
||||
TYPE_DOMAIN: 'Domain'
|
||||
ErrTypePort: 'Invalid port format: {{ .name }}'
|
||||
ErrTypePortRange: 'Port range must be 1-65535'
|
||||
Success: 'Success'
|
||||
Failed: 'Failed'
|
||||
SystemRestart: 'Task interrupted due to system restart'
|
||||
SystemRestart: 'Task interrupted by system restart'
|
||||
ErrGroupIsDefault: 'Default group, cannot be deleted'
|
||||
ErrGroupIsInWebsiteUse: 'The group is being used by another website and cannot be deleted.'
|
||||
ErrGroupIsInWebsiteUse: 'Group is in use by a website'
|
||||
Decrypt: "Decrypt"
|
||||
|
||||
#agent
|
||||
ErrAgentAccountBound: 'Account is bound to an agent and cannot be deleted'
|
||||
ErrAgentAccountUnavailable: 'Account connection unavailable: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: 'Unsupported agent provider'
|
||||
ErrAgentAccountRequired: 'Select an agent account first'
|
||||
ErrAgentAccountNotVerified: 'Agent account not verified'
|
||||
ErrAgentProviderMismatch: 'Agent provider mismatch'
|
||||
ErrAgentBaseURLRequired: 'Base URL is required'
|
||||
ErrAgentApiKeyRequired: 'API key is required'
|
||||
ErrAgentComposeRequired: 'Compose content is required'
|
||||
ErrAgentIDRequired: 'Agent ID is required'
|
||||
ErrAgentAccountIDRequired: 'Account ID is required'
|
||||
|
||||
#backup
|
||||
Localhost: 'Local Machine'
|
||||
ErrBackupInUsed: 'The backup account has been used in the scheduled task and cannot be deleted.'
|
||||
ErrBackupCheck: 'Backup account test connection failed {{ .err }}'
|
||||
ErrBackupLocalDelete: 'Deleting the local server backup account is not supported yet'
|
||||
ErrBackupLocalCreate: 'Creating local server backup accounts is not supported yet'
|
||||
Localhost: 'Local'
|
||||
ErrBackupInUsed: 'Backup account is in use by a scheduled task'
|
||||
ErrBackupCheck: 'Backup account connectivity test failed: {{ .err }}'
|
||||
ErrBackupLocalDelete: 'Local backup accounts cannot be deleted'
|
||||
ErrBackupLocalCreate: 'Local backup accounts cannot be created'
|
||||
|
||||
#app
|
||||
ErrPortInUsed: '{{ .detail }} port is already occupied!'
|
||||
ErrAppLimit: 'The number of applications installed has exceeded the limit'
|
||||
ErrAppLimit: 'App install limit reached'
|
||||
ErrNotInstall: 'Application not installed'
|
||||
ErrPortInOtherApp: '{{ .port }} port is already occupied by application {{ .apps }}!'
|
||||
ErrDbUserNotValid: 'Existing database, username and password do not match!'
|
||||
ErrUpdateBuWebsite: 'The application was updated successfully, but the website configuration file modification failed. Please check the configuration! '
|
||||
ErrDbUserNotValid: 'Database username/password mismatch'
|
||||
ErrUpdateBuWebsite: 'App updated, but website config update failed'
|
||||
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
|
||||
ErrFileParse: 'Application docker-compose file parsing failed!'
|
||||
ErrInstallDirNotFound: 'The installation directory does not exist. If you need to uninstall, please select Force Uninstall'
|
||||
ErrFileParse: 'Failed to parse app docker-compose file'
|
||||
ErrInstallDirNotFound: 'Install directory not found; use Force Uninstall'
|
||||
AppStoreIsUpToDate: 'The app store is already the latest version'
|
||||
LocalAppVersionNull: '{{ .name }} application is not synchronized to the version! Cannot be added to the application list'
|
||||
LocalAppVersionErr: '{{ .name }} sync version {{ .version }} failed! {{ .err }}'
|
||||
LocalAppVersionNull: 'App {{ .name }} has no synced version'
|
||||
LocalAppVersionErr: 'Failed to sync {{ .name }} version {{ .version }}: {{ .err }}'
|
||||
ErrFileNotFound: '{{ .name }} file does not exist'
|
||||
ErrFileParseApp: '{{ .name }} file parsing failed {{ .err }}'
|
||||
ErrFileParseApp: 'Failed to parse {{ .name }}: {{ .err }}'
|
||||
ErrAppDirNull: 'The version folder does not exist'
|
||||
LocalAppErr: 'Application {{ .name }} sync failed! {{ .err }}'
|
||||
LocalAppErr: 'Failed to sync app {{ .name }}: {{ .err }}'
|
||||
ErrContainerName: 'Container name already exists'
|
||||
ErrCreateHttpClient: 'Failed to create request {{ .err }}'
|
||||
ErrHttpReqTimeOut: 'Request timed out {{ .err }}'
|
||||
ErrHttpReqFailed: 'Request failed {{ .err }}'
|
||||
ErrNoSuchHost: 'Unable to find the requested server {{ .err }}'
|
||||
ErrHttpReqNotFound: 'The requested resource {{ .err }} could not be found'
|
||||
ErrCreateHttpClient: 'Failed to create HTTP request: {{ .err }}'
|
||||
ErrHttpReqTimeOut: 'HTTP request timed out: {{ .err }}'
|
||||
ErrHttpReqFailed: 'HTTP request failed: {{ .err }}'
|
||||
ErrNoSuchHost: 'Host resolve failed: {{ .err }}'
|
||||
ErrHttpReqNotFound: 'Requested resource not found: {{ .err }}'
|
||||
ErrContainerNotFound: '{{ .name }} container does not exist'
|
||||
ErrContainerMsg: '{{ .name }} container is abnormal. Please check the log on the container page for details'
|
||||
ErrAppBackup: '{{ .name }} application backup failed {{ .err }}'
|
||||
ErrVersionTooLow: 'The current 1Panel version is too low to update the App Store. Please upgrade the version before operating.'
|
||||
ErrContainerMsg: 'Container {{ .name }} is abnormal; check logs'
|
||||
ErrAppBackup: 'App backup failed: {{ .name }} {{ .err }}'
|
||||
ErrVersionTooLow: '1Panel version too low to update App Store'
|
||||
ErrAppNameExist: 'The application name already exists'
|
||||
AppStoreIsSyncing: 'The App Store is syncing, please try again later'
|
||||
ErrGetCompose: 'Failed to obtain the docker-compose.yml file! {{ .detail }}'
|
||||
ErrAppWarn: 'Abnormal status, please check the log'
|
||||
ErrAppParamKey: 'Parameter {{ .name }} field is abnormal'
|
||||
ErrAppUpgrade: 'Application {{ .name }} upgrade failed {{ .err }}'
|
||||
AppStoreIsSyncing: 'App Store is syncing; try again later'
|
||||
ErrGetCompose: 'Failed to read docker-compose.yml: {{ .detail }}'
|
||||
ErrAppWarn: 'App status abnormal; check logs'
|
||||
ErrAppParamKey: 'Invalid app parameter: {{ .name }}'
|
||||
ErrAppUpgrade: 'App upgrade failed: {{ .name }} {{ .err }}'
|
||||
AppRecover: 'Rollback application {{ .name }}'
|
||||
PullImageStart: 'Start pulling image [{{ .name }}]'
|
||||
PullImageSuccess: 'Image pull successful'
|
||||
AppStoreIsLastVersion: 'The App Store is already the latest version'
|
||||
AppStoreSyncSuccess: 'App Store synchronization successful'
|
||||
SyncAppDetail: 'Synchronize application configuration'
|
||||
AppVersionNotMatch: '{{ .name }} application requires a higher 1Panel version, skipping synchronization'
|
||||
MoveSiteDir: "Detected website directory change. Need to migrate OpenResty website directory to {{ .name }}"
|
||||
MoveSiteDirSuccess: 'Successful migration of website directory'
|
||||
PullImageStart: 'Pull image [{{ .name }}]'
|
||||
PullImageSuccess: 'Image pulled'
|
||||
AppStoreSyncSuccess: 'App Store synced'
|
||||
SyncAppDetail: 'Sync app config'
|
||||
AppVersionNotMatch: 'Skip {{ .name }}: requires newer 1Panel'
|
||||
MoveSiteDir: 'Website dir changed; migrate OpenResty dir to {{ .name }}'
|
||||
MoveSiteDirSuccess: 'Website dir migrated'
|
||||
DeleteRuntimePHP: 'Delete PHP runtime'
|
||||
CustomAppStoreFileValid: 'App store packages need to be in .tar.gz format'
|
||||
PullImageTimeout: 'Pull image timeout, please try to increase the image acceleration or change to another image acceleration'
|
||||
ErrAppIsDown: '{{ .name }} application status is abnormal, please check'
|
||||
ErrCustomApps: 'There is an installed application, please uninstall it first'
|
||||
ErrCustomRuntimes: 'There is an installed runtime environment, please delete it first'
|
||||
CustomAppStoreFileValid: 'App Store package must be .tar.gz'
|
||||
PullImageTimeout: 'Image pull timed out; try another mirror'
|
||||
ErrAppIsDown: 'App {{ .name }} is abnormal'
|
||||
ErrCustomApps: 'Installed apps detected; uninstall first'
|
||||
ErrCustomRuntimes: 'Installed runtimes detected; delete first'
|
||||
ErrAppVersionDeprecated: "The {{ .name }} application is not compatible with the current 1Panel version, skipped"
|
||||
ErrDockerFailed: "The state of Docker is abnormal, please check the service status"
|
||||
ErrDockerComposeCmdNotFound: "The Docker Compose command does not exist, please install this command on the host machine first"
|
||||
UseExistImage: 'Failed to pull image, using existing image'
|
||||
ErrDockerFailed: 'Docker is abnormal; check service status'
|
||||
ErrDockerComposeCmdNotFound: 'Docker Compose not found on host'
|
||||
UseExistImage: 'Image exists; using existing image'
|
||||
ErrDatabaseNotFound: 'Database not found: {{ .name }}'
|
||||
|
||||
#ssh
|
||||
ExportIP: "Login IP"
|
||||
@@ -108,42 +121,42 @@ ExportStatus: "Login Status"
|
||||
ExportDate: "Time"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: 'This file does not support preview'
|
||||
ErrFileToLarge: 'The file is larger than 10M and cannot be opened'
|
||||
ErrFileCanNotRead: 'File preview is not supported'
|
||||
ErrFileToLarge: 'File is larger than 10 MB'
|
||||
ErrPathNotFound: 'Directory does not exist'
|
||||
ErrMovePathFailed: 'The target path cannot contain the original path!'
|
||||
ErrMovePathFailed: 'Target path cannot include source path'
|
||||
ErrLinkPathNotFound: 'The target path does not exist!'
|
||||
ErrFileIsExist: 'The file or folder already exists!'
|
||||
ErrFileUpload: '{{ .name }} failed to upload file {{ .detail }}'
|
||||
ErrFileDownloadDir: 'Download folder is not supported'
|
||||
ErrCmdNotFound: '{{ .name}} command does not exist, please install this command on the host first'
|
||||
ErrFileUpload: 'Upload failed: {{ .name }} {{ .detail }}'
|
||||
ErrFileDownloadDir: 'Directory download is not supported'
|
||||
ErrCmdNotFound: 'Command not found: {{ .name }}'
|
||||
ErrSourcePathNotFound: 'Source directory does not exist'
|
||||
ErrFavoriteExist: 'This path has already been favorited'
|
||||
ErrFavoriteExist: 'Path already in favorites'
|
||||
ErrInvalidChar: 'Illegal characters are not allowed'
|
||||
ErrPathNotDelete: 'The selected directory cannot be deleted'
|
||||
ErrLogFileToLarge: "The log file exceeds 500MB and cannot be opened"
|
||||
FileDropFailed: "Failed to clean up file {{ .name }}: {{ .err }}"
|
||||
FileDropSuccess: "Successfully cleaned up file {{ .name }}, cleaned {{ .count }} files, freed {{ .size }} disk space"
|
||||
FileDropSum: "File cleanup completed successfully, total {{ .count }} files cleaned, total {{ .size }} disk space freed"
|
||||
ErrBadDecrypt: "Compressed package decryption password error, decompression failed, please check and try again!"
|
||||
ErrLogFileToLarge: 'Log file exceeds 500 MB'
|
||||
FileDropFailed: 'File cleanup failed: {{ .name }} {{ .err }}'
|
||||
FileDropSuccess: 'Cleaned {{ .name }}; files: {{ .count }}, freed: {{ .size }}'
|
||||
FileDropSum: 'Cleanup done; files: {{ .count }}, freed: {{ .size }}'
|
||||
ErrBadDecrypt: 'Wrong archive password; extract failed'
|
||||
|
||||
#website
|
||||
ErrAliasIsExist: 'Alias already exists'
|
||||
ErrBackupMatch: 'The backup file does not match some of the current website data {{ .detail }}'
|
||||
ErrBackupExist: 'The corresponding part of the source data in the backup file does not exist {{ .detail }}'
|
||||
ErrPHPResource: 'The local operating environment does not support switching! '
|
||||
ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the index directory, which may cause an Access denied error on the website. Please click the Save button above'
|
||||
ErrBackupMatch: 'Backup data mismatch: {{ .detail }}'
|
||||
ErrBackupExist: 'Backup source data missing: {{ .detail }}'
|
||||
ErrPHPResource: 'Switching local runtime is not supported'
|
||||
ErrPathPermission: 'Non-1000:1000 folder in index dir may cause Access denied'
|
||||
ErrDomainIsUsed: 'The domain name is already used by the website [{{ .name }}]'
|
||||
ErrDomainFormat: '{{ .name }} domain name format is incorrect'
|
||||
ErrDefaultAlias: 'default is a reserved code, please use another code'
|
||||
ErrParentWebsite: 'You need to delete the subsite {{ .name }} first'
|
||||
ErrDomainFormat: 'Invalid domain format: {{ .name }}'
|
||||
ErrDefaultAlias: 'default is reserved; use another alias'
|
||||
ErrParentWebsite: 'Delete subsite {{ .name }} first'
|
||||
ErrBuildDirNotFound: 'The build directory does not exist'
|
||||
ErrImageNotExist: 'The operating environment {{ .name }} image does not exist, please re-edit the operating environment'
|
||||
ErrProxyIsUsed: "Load balancing has been used by reverse proxy, cannot be deleted"
|
||||
ErrSSLValid: 'Certificate file is abnormal, please check the certificate status!'
|
||||
ErrWebsiteDir: "Please select a directory within the website directory."
|
||||
ErrImageNotExist: 'Runtime image not found: {{ .name }}'
|
||||
ErrProxyIsUsed: 'Load balancer is used by reverse proxy'
|
||||
ErrSSLValid: 'Certificate file is invalid'
|
||||
ErrWebsiteDir: 'Select a subdirectory under the website directory'
|
||||
ErrComposerFileNotFound: "composer.json file does not exist"
|
||||
ErrRuntimeNoPort: "The runtime environment is not set with a port, please edit the runtime environment first."
|
||||
ErrRuntimeNoPort: 'Runtime port is not set'
|
||||
Status: 'Status'
|
||||
start: 'Start'
|
||||
stop: 'Stop'
|
||||
@@ -153,156 +166,157 @@ pause: 'Pause'
|
||||
unpause: 'Resume'
|
||||
remove: 'Delete'
|
||||
delete: 'Delete'
|
||||
ErrDefaultWebsite: 'Default website has been set, please cancel it before setting!'
|
||||
ErrDefaultWebsite: 'Default website already set; unset it first'
|
||||
SSL: 'Certificate'
|
||||
Set: 'Settings'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: 'The {{ .name }} certificate is being used by a website and cannot be deleted'
|
||||
ErrAccountCannotDelete: 'The account is associated with a certificate and cannot be deleted'
|
||||
ErrSSLApply: 'Certificate renewal successful, openresty reload failed, please check the configuration!'
|
||||
ErrEmailIsExist: 'The mailbox already exists'
|
||||
ErrSSLCannotDelete: 'Certificate {{ .name }} is in use by a website'
|
||||
ErrAccountCannotDelete: 'Account is linked to a certificate and cannot be deleted'
|
||||
ErrSSLApply: 'Certificate renewed; OpenResty reload failed'
|
||||
ErrEmailIsExist: 'Email already exists'
|
||||
ErrSSLKeyNotFound: 'The private key file does not exist'
|
||||
ErrSSLCertificateNotFound: 'The certificate file does not exist'
|
||||
ErrSSLKeyFormat: 'Private key file verification failed'
|
||||
ErrSSLCertificateFormat: 'The certificate file format is incorrect, please use pem format'
|
||||
ErrSSLCertificateFormat: 'Invalid certificate format; use PEM'
|
||||
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be blank'
|
||||
ErrOpenrestyNotFound: 'Http mode requires Openresty to be installed first'
|
||||
ApplySSLStart: 'Start applying for a certificate, domain name [{{ .domain }}] application method [{{ .type }}] '
|
||||
ErrOpenrestyNotFound: 'HTTP mode requires OpenResty'
|
||||
ApplySSLStart: 'Starting certificate request. Domain: [{{ .domain }}], method: [{{ .type }}]'
|
||||
dnsAccount: 'DNS Auto'
|
||||
dnsManual: 'DNS Manual'
|
||||
http: 'HTTP'
|
||||
ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{ .detail }} '
|
||||
ApplySSLSuccess: 'Apply for [{{ .domain }}] certificate successfully! !'
|
||||
DNSAccountName: 'DNS account [{{ .name }}] vendor [{{ .type }}]'
|
||||
ApplySSLFailed: 'Failed to request certificate for [{{ .domain }}]: {{ .detail }}'
|
||||
ApplySSLSuccess: 'Certificate issued for [{{ .domain }}]'
|
||||
DNSAccountName: 'DNS account [{{ .name }}], provider [{{ .type }}]'
|
||||
PushDirLog: 'Certificate pushed to directory [{{ .path }}] {{ .status }}'
|
||||
ErrDeleteCAWithSSL: 'The current organization has a certificate that has been issued and cannot be deleted.'
|
||||
ErrDeleteWithPanelSSL: 'Panel SSL configuration uses this certificate and cannot be deleted'
|
||||
ErrDeleteCAWithSSL: 'CA has issued certificates; cannot delete'
|
||||
ErrDeleteWithPanelSSL: 'Certificate is in use by Panel SSL settings'
|
||||
ErrDefaultCA: 'The default authority cannot be deleted'
|
||||
ApplyWebSiteSSLLog: 'Starting to renew the {{ .name }} website certificate'
|
||||
ApplyWebSiteSSLLog: 'Updating website certificate: {{ .name }}'
|
||||
ErrUpdateWebsiteSSL: '{{ .name }} website certificate update failed: {{ .err }}'
|
||||
ApplyWebSiteSSLSuccess: 'Update website certificate successfully'
|
||||
ApplyWebSiteSSLSuccess: 'Website certificate updated'
|
||||
ErrExecShell: 'Failed to execute script {{ .err }}'
|
||||
ExecShellStart: 'Start executing the script'
|
||||
ExecShellSuccess: 'Script execution successful'
|
||||
StartUpdateSystemSSL: 'Start updating system certificate'
|
||||
UpdateSystemSSLSuccess: 'Update system certificate successfully'
|
||||
ErrWildcardDomain: 'Unable to apply for wildcard domain name certificate in HTTP mode'
|
||||
ErrApplySSLCanNotDelete: "The certificate {{.name}} being applied for cannot be deleted, please try again later."
|
||||
StartPushSSLToNode: "Starting to push certificate to node"
|
||||
ExecShellStart: 'Run script'
|
||||
ExecShellSuccess: 'Script succeeded'
|
||||
StartUpdateSystemSSL: 'Update system certificate'
|
||||
UpdateSystemSSLSuccess: 'System certificate updated'
|
||||
ErrWildcardDomain: 'Wildcard certificates are not supported in HTTP mode'
|
||||
ErrApplySSLCanNotDelete: 'Certificate {{ .name }} is being issued and cannot be deleted yet'
|
||||
StartPushSSLToNode: 'Push certificate to node'
|
||||
PushSSLToNodeFailed: "Failed to push certificate to node: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Successfully pushed certificate to node"
|
||||
PushSSLToNodeSuccess: 'Certificate pushed to node'
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: 'The current user already exists, please re-enter'
|
||||
ErrDatabaseIsExist: 'The current database already exists, please re-enter'
|
||||
ErrExecTimeOut: 'SQL execution timed out, please check the database'
|
||||
ErrRemoteExist: 'The remote database already exists with this name, please modify it and try again'
|
||||
ErrLocalExist: 'The name already exists in the local database, please modify it and try again'
|
||||
RemoteBackup: "To back up the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..."
|
||||
RemoteRecover: "To restore the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..."
|
||||
ErrUserIsExist: 'User already exists'
|
||||
ErrDatabaseIsExist: 'Database already exists'
|
||||
ErrExecTimeOut: 'SQL execution timed out'
|
||||
ErrRemoteExist: 'Remote database name already exists'
|
||||
ErrLocalExist: 'Local database name already exists'
|
||||
RemoteBackup: 'Start local DB container with image {{ .name }} before backing up remote DB'
|
||||
RemoteRecover: 'Start local DB container with image {{ .name }} before restoring remote DB'
|
||||
Arg: "Argument"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: 'The recovery file type does not match the current persistence method, please modify it and try again'
|
||||
ErrTypeOfRedis: 'Backup file type does not match current persistence mode'
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} is in use and cannot be deleted!"
|
||||
ErrObjectInUsed: "This object is in use and cannot be deleted!"
|
||||
ErrObjectBeDependent: "This image is depended on by other images and cannot be deleted!"
|
||||
ErrPortRules: 'Port number does not match, please re-enter!'
|
||||
ErrPgImagePull: 'Image pull timed out, please configure image acceleration or manually pull the {{ .name }} image and try again'
|
||||
PruneHelper: "This cleanup removed {{ .name }} {{ .count }} items, freeing {{ .size }} disk space"
|
||||
ImageRemoveHelper: "Deleted image {{ .name }}, freeing {{ .size }} disk space"
|
||||
BuildCache: "Build cache"
|
||||
Volume: "Storage volume"
|
||||
ErrObjectBeDependent: "This image is referenced by other images and cannot be deleted!"
|
||||
ErrPortRules: 'Port count does not match'
|
||||
ErrPgImagePull: 'Image pull timed out; configure acceleration or pull {{ .name }} manually'
|
||||
PruneHelper: "Cleanup {{ .name }} released {{ .size }} disk space"
|
||||
ImageRemoveHelper: "Removed image {{ .name }}, released {{ .size }} disk space"
|
||||
BuildCache: 'Build Cache'
|
||||
Volume: 'Volume'
|
||||
Network: "Network"
|
||||
PruneStart: "Cleaning in progress, please wait..."
|
||||
PruneStart: 'Cleaning in progress'
|
||||
|
||||
#runtime
|
||||
ErrFileNotExist: '{{ .detail }} file does not exist! Please check the integrity of the source file!'
|
||||
ErrFileNotExist: 'Source file not found: {{ .detail }}'
|
||||
ErrImageBuildErr: 'Image build failed'
|
||||
ErrImageExist: "Image already exists! Please modify the image name."
|
||||
ErrDelWithWebsite: 'The operating environment is already associated with a website and cannot be deleted'
|
||||
ErrImageExist: "Image already exists. Use a different image name."
|
||||
ErrDelWithWebsite: 'Runtime is linked to a website and cannot be deleted'
|
||||
ErrRuntimeStart: 'Startup failed'
|
||||
ErrPackageJsonNotFound: 'package.json file does not exist'
|
||||
ErrScriptsNotFound: 'The scripts configuration item was not found in package.json'
|
||||
ErrContainerNameNotFound: 'Unable to get container name, please check .env file'
|
||||
ErrNodeModulesNotFound: 'The node_modules folder does not exist! Please edit the runtime environment or wait for the runtime environment to start successfully'
|
||||
ErrContainerNameIsNull: 'Container name does not exist'
|
||||
ErrPHPPortIsDefault: "Port 9000 is the default port, please modify and try again"
|
||||
ErrPHPRuntimePortFailed: "The port {{ .name }} is already used by the current runtime environment, please modify and try again"
|
||||
ErrScriptsNotFound: 'No scripts section found in package.json'
|
||||
ErrContainerNameNotFound: 'Container name not found; check .env'
|
||||
ErrNodeModulesNotFound: 'node_modules not found; edit runtime or wait for startup'
|
||||
ErrContainerNameIsNull: 'Container name is empty'
|
||||
ErrPHPPortIsDefault: 'Port 9000 is reserved as default; use another port'
|
||||
ErrPHPRuntimePortFailed: 'Port {{ .name }} is already used by this runtime'
|
||||
|
||||
#tool
|
||||
ErrConfigNotFound: 'Configuration file does not exist'
|
||||
ErrConfigParse: 'The configuration file format is incorrect'
|
||||
ErrConfigParse: 'Invalid configuration file format'
|
||||
ErrConfigIsNull: 'Configuration file cannot be empty'
|
||||
ErrConfigDirNotFound: 'The running directory does not exist'
|
||||
ErrConfigAlreadyExist: 'A configuration file with the same name already exists'
|
||||
ErrUserFindErr: 'User {{ .name }} search failed {{ .err }}'
|
||||
ErrConfigDirNotFound: 'Run directory not found'
|
||||
ErrConfigAlreadyExist: 'Configuration file with the same name already exists'
|
||||
ErrUserFindErr: 'Failed to find user {{ .name }}: {{ .err }}'
|
||||
|
||||
#cronjob
|
||||
CutWebsiteLogSuccess: '{{ .name }} website log cut successfully, backup path {{ .path }}'
|
||||
HandleShell: 'Execute script {{ .name }}'
|
||||
HandleCurl: "Access URL {{ .name }}"
|
||||
HandleNtpSync: 'System time synchronization'
|
||||
HandleSystemClean: 'System cache cleanup'
|
||||
CutWebsiteLogSuccess: 'Website log rotated: {{ .name }} (backup: {{ .path }})'
|
||||
HandleShell: 'Run script {{ .name }}'
|
||||
HandleCurl: 'Request URL {{ .name }}'
|
||||
HandleNtpSync: 'Sync system time'
|
||||
HandleSystemClean: 'Clean system cache'
|
||||
SystemLog: 'System Log'
|
||||
CutWebsiteLog: 'Rotate Website Log'
|
||||
FileOrDir: 'Directory / File'
|
||||
UploadFile: 'Uploading backup file {{ .file }} to {{ .backup }}'
|
||||
UploadFile: 'Upload backup {{ .file }} to {{ .backup }}'
|
||||
Upload: "Upload"
|
||||
IgnoreBackupErr: "Backup failed, error: {{ .detail }}, ignoring this error..."
|
||||
IgnoreUploadErr: "Upload failed, error: {{ .detail }}, ignoring this error..."
|
||||
LoadBackupFailed: "Failed to get backup account connection, error: {{ .detail }}"
|
||||
InExecuting: "The current task is being executed, please do not repeat the execution!"
|
||||
NoSuchResource: "No backup content found in the database, skipping..."
|
||||
CleanLog: "Clean Log"
|
||||
CleanLogByName: "Clean {{.name}} Log"
|
||||
WafIpGroupNotFound: "WAF IP group not found. Please go to [Advanced Features - WAF - Black/White List - IP Group] to add an IP group via remote download method."
|
||||
IgnoreBackupErr: 'Backup failed: {{ .detail }} (ignored)'
|
||||
IgnoreUploadErr: 'Upload failed: {{ .detail }} (ignored)'
|
||||
LoadBackupFailed: 'Failed to get backup account connection info: {{ .detail }}'
|
||||
InExecuting: 'Task is already running'
|
||||
NoSuchResource: 'No backup records found; skipped'
|
||||
CleanLog: 'Clean logs'
|
||||
CleanLogByName: 'Clean {{ .name }} logs'
|
||||
WafIpGroupNotFound: "WAF IP group not found. Go to [Advanced Features -> WAF -> Black/White List -> IP Group] and add one using Remote Download."
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: 'The current user does not exist, please modify and try again!'
|
||||
ErrBanAction: 'Setting failed. The current {{ .name }} service is unavailable. Please check and try again!'
|
||||
ErrClamdscanNotFound: 'The clamdscan command was not detected, please refer to the documentation to install it!'
|
||||
TaskSwapSet: "Set Swap"
|
||||
ErrNotExistUser: 'User does not exist'
|
||||
ErrBanAction: 'Config failed: service {{ .name }} unavailable'
|
||||
ErrClamdscanNotFound: 'clamdscan command not found'
|
||||
TaskSwapSet: 'Set swap'
|
||||
SetSwap: "Set Swap {{ .path }} - {{ .size }}"
|
||||
CreateSwap: "Create Swap File"
|
||||
FormatSwap: "Format Swap File"
|
||||
EnableSwap: "Enable Swap"
|
||||
CreateSwap: 'Create swap file'
|
||||
FormatSwap: 'Format swap file'
|
||||
EnableSwap: 'Enable swap'
|
||||
|
||||
#tamper
|
||||
CleanTamperSetting: "Clean up historical protection settings"
|
||||
SetTamperWithRules: "Start directory protection according to rules"
|
||||
CleanTamperSetting: 'Clean historical protection settings'
|
||||
SetTamperWithRules: 'Enable directory protection by rules'
|
||||
ProtectDir: "Protect directory {{ .name }}"
|
||||
IgnoreFile: "Protect file {{ .name }}"
|
||||
TamperSetSuccessful: "Protection settings completed, start monitoring the following directories:"
|
||||
TamperSetSuccessful: 'Protection set. Monitoring these directories:'
|
||||
|
||||
#waf
|
||||
ErrScope: 'Modifying this configuration is not supported'
|
||||
ErrStateChange: 'State change failed'
|
||||
ErrScope: 'This config cannot be changed'
|
||||
ErrStateChange: 'Failed to change state'
|
||||
ErrRuleExist: 'Rule already exists'
|
||||
ErrRuleNotExist: 'Rule does not exist'
|
||||
ErrParseIP: 'Wrong IP format'
|
||||
ErrDefaultIP: 'default is a reserved name, please change it to another name'
|
||||
ErrGroupInUse: 'IP group is used by blacklist/whitelist and cannot be deleted'
|
||||
ErrIPGroupAclUse: "IP group is used by custom rules of website {{ .name }}, cannot be deleted"
|
||||
ErrParseIP: 'Invalid IP format'
|
||||
ErrDefaultIP: 'default is reserved; use another name'
|
||||
ErrGroupInUse: 'IP group is used by blacklist/whitelist rules'
|
||||
ErrIPGroupAclUse: 'IP group is used by website {{ .name }} custom rules'
|
||||
ErrGroupExist: 'IP group name already exists'
|
||||
ErrIPRange: 'Wrong IP range'
|
||||
ErrIPRange: 'Invalid IP range'
|
||||
ErrIPExist: 'IP already exists'
|
||||
urlDefense: 'URL rules'
|
||||
urlHelper: 'Prohibited URL'
|
||||
urlHelper: 'Blocked URL'
|
||||
dirFilter: 'Directory filter'
|
||||
xss: 'XSS'
|
||||
phpExec: 'PHP script execution'
|
||||
oneWordTrojan: 'One Word Trojan'
|
||||
appFilter: 'Apply dangerous directory filtering'
|
||||
webshell: 'Webshell'
|
||||
appFilter: 'App dangerous directory filtering'
|
||||
webShell: 'WebShell'
|
||||
args: 'Parameter rules'
|
||||
protocolFilter: 'Protocol filtering'
|
||||
javaFileter: 'Java dangerous file filter'
|
||||
javaFilter: 'Java dangerous file filtering'
|
||||
scannerFilter: 'Scanner filter'
|
||||
escapeFilter: 'escape filter'
|
||||
customRule: 'Custom rule'
|
||||
escapeFilter: 'Escape filtering'
|
||||
customRule: 'Custom rules'
|
||||
httpMethod: 'HTTP method filtering'
|
||||
fileExt: 'File upload restrictions'
|
||||
defaultIpBlack: 'Malicious IP group'
|
||||
@@ -317,8 +331,8 @@ notFoundCount: '404 Rate Limit'
|
||||
headerDefense: 'Header rules'
|
||||
defaultUaBlack: 'User-Agent rules'
|
||||
methodWhite: 'HTTP rules'
|
||||
captcha: 'human-machine verification'
|
||||
fiveSeconds: '5 seconds verification'
|
||||
captcha: 'Human verification'
|
||||
fiveSeconds: '5-second verification'
|
||||
vulnCheck: 'Supplementary rules'
|
||||
acl: 'Custom rules'
|
||||
sql: 'SQL injection'
|
||||
@@ -326,19 +340,20 @@ cc: 'Access frequency limit'
|
||||
defaultUrlBlack: 'URL rules'
|
||||
sqlInject: 'SQL injection'
|
||||
ErrDBNotExist: 'Database does not exist'
|
||||
allow: 'allow'
|
||||
deny: 'deny'
|
||||
OpenrestyNotFound: 'Openresty is not installed'
|
||||
allow: 'Allow'
|
||||
deny: 'Deny'
|
||||
OpenrestyNotFound: 'OpenResty is not installed'
|
||||
remoteIpIsNull: "IP list is empty"
|
||||
OpenrestyVersionErr: "Openresty version is too low, please upgrade Openresty to 1.27.1.2-2-2-focal"
|
||||
OpenrestyVersionErr: 'OpenResty version too low; upgrade to 1.27.1.2-2-2-focal'
|
||||
ErrFileTooLarge: 'File is larger than 1 MB and cannot be uploaded'
|
||||
|
||||
#task
|
||||
TaskStart: '{{ .name }} task starts [START]'
|
||||
TaskEnd: '{{ .name }} Task completed [COMPLETED]'
|
||||
TaskStart: '{{ .name }} [START]'
|
||||
TaskEnd: '{{ .name }} [DONE]'
|
||||
TaskFailed: '{{ .name }} task failed'
|
||||
TaskTimeout: '{{ .name }} timed out'
|
||||
TaskSuccess: '{{ .name }} Task succeeded'
|
||||
TaskRetry: 'Start {{ .name }}th retry'
|
||||
TaskSuccess: '{{ .name }} succeeded'
|
||||
TaskRetry: 'Retry #{{ .name }}'
|
||||
SubTaskSuccess: '{{ .name }} succeeded'
|
||||
SubTaskFailed: '{{ .name }} failed: {{ .err }}'
|
||||
TaskInstall: 'Install'
|
||||
@@ -362,34 +377,34 @@ TaskImport: "Import"
|
||||
TaskExport: "Export"
|
||||
Website: 'Website'
|
||||
App: 'Application'
|
||||
Runtime: 'Runtime environment'
|
||||
Runtime: 'Runtime'
|
||||
Database: 'Database'
|
||||
ConfigFTP: 'Create FTP user {{ .name }}'
|
||||
ConfigOpenresty: 'Create Openresty configuration file'
|
||||
InstallAppSuccess: 'Application {{ .name }} installed successfully'
|
||||
ConfigRuntime: 'Configure the runtime environment'
|
||||
ConfigApp: 'Configuration Application'
|
||||
ConfigOpenresty: 'Create OpenResty config file'
|
||||
InstallAppSuccess: 'App {{ .name }} installed'
|
||||
ConfigRuntime: 'Configure runtime'
|
||||
ConfigApp: 'Configure app'
|
||||
SuccessStatus: '{{ .name }} succeeded'
|
||||
FailedStatus: '{{ .name }} failed {{ .err }}'
|
||||
HandleLink: 'Handle application association'
|
||||
HandleDatabaseApp: 'Handling application parameters'
|
||||
ExecShell: 'Execute {{ .name }} script'
|
||||
FailedStatus: '{{ .name }} failed: {{ .err }}'
|
||||
HandleLink: 'Process app links'
|
||||
HandleDatabaseApp: 'Process app parameters'
|
||||
ExecShell: 'Run script {{ .name }}'
|
||||
PullImage: 'Pull image'
|
||||
Start: 'Start'
|
||||
Run: 'Start'
|
||||
Stop: 'Stop'
|
||||
Image: 'Mirror'
|
||||
Compose: 'Orchestration'
|
||||
Image: 'Image'
|
||||
Compose: 'Compose'
|
||||
Container: 'Container'
|
||||
AppLink: 'Linked Application'
|
||||
EnableSSL: 'Enable HTTPS'
|
||||
AppStore: 'App Store'
|
||||
TaskSync: 'Synchronize'
|
||||
TaskSync: 'Sync'
|
||||
LocalApp: 'Local Application'
|
||||
SubTask: 'Subtask'
|
||||
RuntimeExtension: 'Runtime Environment Extension'
|
||||
RuntimeExtension: 'Runtime extension'
|
||||
TaskIsExecuting: 'Task is running'
|
||||
CustomAppstore: 'Custom application warehouse'
|
||||
CustomAppstore: 'Custom application repository'
|
||||
TaskExec: 'Execute'
|
||||
TaskBatch: "Batch Operation"
|
||||
FileConvert: 'File Conversion'
|
||||
@@ -400,130 +415,119 @@ TaskScan: "Scan"
|
||||
|
||||
# task - ai
|
||||
OllamaModelPull: 'Pull Ollama model {{ .name }}'
|
||||
OllamaModelSize: 'Get the size of Ollama model {{ .name }}'
|
||||
OllamaModelSize: 'Get Ollama model size: {{ .name }}'
|
||||
|
||||
# task-snapshot
|
||||
Snapshot: 'Snapshot'
|
||||
SnapDBInfo: 'Write 1Panel database information'
|
||||
SnapCopy: 'Copy files & directories {{ .name }}'
|
||||
SnapNewDB: 'Initialize database {{ .name }} connection'
|
||||
SnapDeleteOperationLog: 'Delete operation log'
|
||||
SnapDeleteLoginLog: 'Delete access log'
|
||||
SnapDBInfo: 'Write 1Panel DB info'
|
||||
SnapCopy: 'Copy files/directories {{ .name }}'
|
||||
SnapNewDB: 'Init DB connection {{ .name }}'
|
||||
SnapDeleteOperationLog: 'Delete operation logs'
|
||||
SnapDeleteLoginLog: 'Delete access logs'
|
||||
SnapDeleteMonitor: 'Delete monitoring data'
|
||||
SnapRemoveSystemIP: 'Remove system IP'
|
||||
SnapBaseInfo: 'Write 1Panel basic information'
|
||||
SnapInstallAppImageEmpty: 'No application images selected, skipping...'
|
||||
SnapInstallApp: 'Backup 1Panel installed applications'
|
||||
SnapBaseInfo: 'Write 1Panel base info'
|
||||
SnapInstallAppImageEmpty: 'No app images selected; skipped'
|
||||
SnapInstallApp: 'Backup installed apps'
|
||||
SnapDockerSave: 'Compress installed applications'
|
||||
SnapLocalBackup: 'Backup 1Panel local backup directory'
|
||||
SnapLocalBackup: 'Backup local backup directory'
|
||||
SnapCompressBackup: 'Compress local backup directory'
|
||||
SnapPanelData: 'Backup 1Panel data directory'
|
||||
SnapCompressPanel: 'Compressed Data Directory'
|
||||
SnapWebsite: 'Backup 1Panel website directory'
|
||||
SnapCloseDBConn: 'Close the database connection'
|
||||
SnapCompress: 'Make snapshot files'
|
||||
SnapCompressPanel: 'Compress data directory'
|
||||
SnapWebsite: 'Backup website directory'
|
||||
SnapCloseDBConn: 'Close DB connection'
|
||||
SnapCompress: 'Create snapshot file'
|
||||
SnapCompressFile: 'Compress snapshot file'
|
||||
SnapCheckCompress: 'Check snapshot compression file'
|
||||
SnapCompressSize: 'Snapshot file size {{ .name }}'
|
||||
SnapCheckCompress: 'Check snapshot archive'
|
||||
SnapCompressSize: 'Snapshot size {{ .name }}'
|
||||
SnapUpload: 'Upload snapshot file'
|
||||
SnapUploadTo: 'Upload snapshot file to {{ .name }}'
|
||||
SnapUploadRes: 'Upload snapshot file to {{ .name }}'
|
||||
SnapUploadTo: 'Upload snapshot to {{ .name }}'
|
||||
SnapUploadRes: 'Upload snapshot to {{ .name }}'
|
||||
|
||||
SnapshotRecover: 'Snapshot Restore'
|
||||
SnapshotRecover: 'Recover snapshot'
|
||||
RecoverDownload: 'Download snapshot file'
|
||||
Download: 'Download'
|
||||
RecoverDownloadAccount: 'Get snapshot download backup account {{ .name }}'
|
||||
RecoverDecompress: 'Decompress snapshot compressed files'
|
||||
RecoverDownloadAccount: 'Get snapshot download account {{ .name }}'
|
||||
RecoverDecompress: 'Decompress snapshot archive'
|
||||
Decompress: 'Decompression'
|
||||
BackupBeforeRecover: 'Backup system-related data before snapshot'
|
||||
Readjson: 'Read the Json file in the snapshot'
|
||||
ReadjsonPath: 'Get the Json file path in the snapshot'
|
||||
ReadjsonContent: 'Read Json file'
|
||||
ReadjsonMarshal: 'Json escape processing'
|
||||
BackupBeforeRecover: 'Backup system data before recovery'
|
||||
ReadJson: 'Read snapshot JSON file'
|
||||
ReadJsonPath: 'Get snapshot JSON path'
|
||||
ReadJsonContent: 'Read JSON content'
|
||||
ReadJsonMarshal: 'Json escape processing'
|
||||
RecoverApp: 'Restore installed apps'
|
||||
RecoverWebsite: 'Recover website directory'
|
||||
RecoverWebsite: 'Restore website directory'
|
||||
RecoverAppImage: 'Restore snapshot image backup'
|
||||
RecoverCompose: 'Restore other composer content'
|
||||
RecoverComposeList: 'Get all composers to be restored'
|
||||
RecoverCompose: 'Restore other compose projects'
|
||||
RecoverComposeList: 'Get all compose projects to restore'
|
||||
RecoverComposeItem: 'Recover compose {{ .name }}'
|
||||
RecoverAppEmpty: 'No application image backup was found in the snapshot file'
|
||||
RecoverBaseData: 'Recover base data and files'
|
||||
RecoverDaemonJsonEmpty: 'Both the snapshot file and the current machine do not have the container configuration daemon.json file'
|
||||
RecoverDaemonJson: 'Restore container configuration daemon.json file'
|
||||
RecoverAppEmpty: 'No app image backup found in snapshot'
|
||||
RecoverBaseData: 'Restore base data and files'
|
||||
RecoverDaemonJsonEmpty: 'daemon.json not found in snapshot or current host'
|
||||
RecoverDaemonJson: 'Restore daemon.json'
|
||||
RecoverDBData: 'Recover database data'
|
||||
RecoverBackups: 'Restore local backup directory'
|
||||
RecoverPanelData: 'Recovery data directory'
|
||||
RecoverPanelData: 'Restore data directory'
|
||||
|
||||
# task - container
|
||||
ContainerNewCliet: 'Initialize Docker Client'
|
||||
ContainerImagePull: 'Pull container image {{ .name }}'
|
||||
ContainerRemoveOld: 'Remove the original container {{ .name }}'
|
||||
ContainerImageCheck: 'Check if the image is pulled normally'
|
||||
ContainerLoadInfo: 'Get basic container information'
|
||||
ContainerRecreate: 'Container update failed, now starting to restore the original container'
|
||||
ContainerImageCheck: 'Check image pull result'
|
||||
ContainerLoadInfo: 'Load container info'
|
||||
ContainerRecreate: 'Container update failed; restoring original container'
|
||||
ContainerCreate: 'Create a new container {{ .name }}'
|
||||
ContainerCreateFailed: 'Container creation failed, delete the failed container'
|
||||
ContainerStartCheck: 'Check if the container has been started'
|
||||
ContainerCreateFailed: 'Container create failed; deleting failed container'
|
||||
ContainerStartCheck: 'Check container started'
|
||||
|
||||
# task - image
|
||||
ImageBuild: 'Image Build'
|
||||
ImageBuildStdoutCheck: 'Parse image output content'
|
||||
ImageBuildRes: 'Image build output: {{ .name }}'
|
||||
ImageBuild: 'Build image'
|
||||
ImagePull: 'Pull image'
|
||||
ImageRepoAuthFromDB: 'Get repository authentication information from the database'
|
||||
ImaegPullRes: 'Image pull output: {{ .name }}'
|
||||
ImageRepoAuthFromDB: 'Load registry auth from DB'
|
||||
ImagePush: 'Push image'
|
||||
ImageRenameTag: 'Modify image tag'
|
||||
ImageRenameTag: 'Update image tag'
|
||||
ImageNewTag: 'New image tag {{ .name }}'
|
||||
ImaegPushRes: 'Image push output: {{ .name }}'
|
||||
ComposeCreate: 'Create a composition'
|
||||
ComposeCreateRes: 'Compose create output: {{ .name }}'
|
||||
ComposeCreate: 'Create compose project'
|
||||
|
||||
# task - website
|
||||
BackupNginxConfig: 'Backup website OpenResty configuration file'
|
||||
CompressFileSuccess: 'Compress directory successfully, compressed to {{ .name }}'
|
||||
CompressDir: 'Compression directory'
|
||||
BackupNginxConfig: 'Backup website OpenResty config'
|
||||
CompressFileSuccess: 'Directory compressed to {{ .name }}'
|
||||
CompressDir: 'Compress directory'
|
||||
DeCompressFile: 'Decompress file {{ .name }}'
|
||||
ErrCheckValid: 'Backup file verification failed, {{ .name }}'
|
||||
ErrCheckValid: 'Backup file validation failed: {{ .name }}'
|
||||
Rollback: 'Rollback'
|
||||
websiteDir: 'Website Directory'
|
||||
RecoverFailedStartRollBack: 'Recovery failed, start rollback'
|
||||
AppBackupFileIncomplete: 'The backup file is incomplete and lacks app.json or app.tar.gz files'
|
||||
websiteDir: 'Website directory'
|
||||
RecoverFailedStartRollBack: 'Recovery failed; starting rollback'
|
||||
AppBackupFileIncomplete: 'Backup is incomplete: missing app.json or app.tar.gz'
|
||||
AppAttributesNotMatch: 'Application type or name does not match'
|
||||
|
||||
#alert
|
||||
ErrAlert: 'The format of the warning message is incorrect, please check and try again!'
|
||||
ErrAlertPush: 'Error in pushing alert information, please check and try again!'
|
||||
ErrAlertSave: 'Error saving the alarm information, please check and try again!'
|
||||
ErrAlertSync: 'Alarm information synchronization error, please check and try again!'
|
||||
ErrAlertRemote: 'Alarm message remote error, please check and try again!'
|
||||
ErrAlertSync: 'Failed to sync alert message'
|
||||
|
||||
#task - runtime
|
||||
ErrInstallExtension: "An installation task is already in progress, please wait for the task to finish"
|
||||
ErrInstallExtension: 'Another extension install task is running'
|
||||
|
||||
# alert mail template
|
||||
PanelAlertTitle: "Panel Alert Notification"
|
||||
TestAlertTitle: "Test Email - Verify Email Connectivity"
|
||||
TestAlert: "This is a test email to verify that your email sending configuration is correct."
|
||||
LicenseExpirationAlert: "Your 1Panel license will expire in {{ .day }} days. Please log in to the panel for details."
|
||||
CronJobFailedAlert: "Your 1Panel scheduled task '{{ .name }}' has failed. Please log in to the panel for details."
|
||||
ClamAlert: "Your 1Panel virus scan task has detected {{ .num }} infected files. Please log in to the panel for details."
|
||||
WebSiteAlert: "There are {{ .num }} websites on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
|
||||
SSLAlert: "There are {{ .num }} SSL certificates on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
|
||||
DiskUsedAlert: "Your 1Panel disk '{{ .name }}' has used {{ .used }}. Please log in to the panel for details."
|
||||
ResourceAlert: "The average {{ .name }} usage over {{ .time }} minutes on your 1Panel is {{ .used }}. Please log in to the panel for details."
|
||||
PanelVersionAlert: "A new version of 1Panel is available. Please log in to the panel to upgrade."
|
||||
PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. Please log in to the panel for details."
|
||||
CommonAlert: "Your 1Panel, {{ .msg }}, please log in to the panel for details."
|
||||
NodeExceptionAlert: "Your 1Panel, {{ .num }} nodes are abnormal, please log in to the panel for details."
|
||||
LicenseExceptionAlert: "Your 1Panel, {{ .num }} licenses are abnormal, please log in to the panel for details."
|
||||
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
|
||||
LicenseExpirationAlert: "Panel {{ .node }}{{ .ip }}: license expires in {{ .day }} days. Log in to view details."
|
||||
CronJobFailedAlert: "Panel {{ .node }}{{ .ip }}: scheduled task {{ .name }} failed. Log in to view details."
|
||||
ClamAlert: "Panel {{ .node }}{{ .ip }}: virus scan found {{ .num }} infected files. Log in to view details."
|
||||
WebSiteAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} websites expire in {{ .day }} days. Log in to view details."
|
||||
SSLAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} SSL certificates expire in {{ .day }} days. Log in to view details."
|
||||
DiskUsedAlert: "Panel {{ .node }}{{ .ip }}: disk {{ .name }} usage reached {{ .used }}. Log in to view details."
|
||||
ResourceAlert: "Panel {{ .node }}{{ .ip }}: average {{ .name }} usage in {{ .time }} minutes is {{ .used }}. Log in to view details."
|
||||
PanelVersionAlert: "Panel {{ .node }}{{ .ip }}: a new panel version is available. Log in to view details."
|
||||
PanelPwdExpirationAlert: "Panel {{ .node }}{{ .ip }}: panel password expires in {{ .day }} days. Log in to view details."
|
||||
CommonAlert: "Panel {{ .node }}{{ .ip }}: {{ .msg }}. Log in to view details."
|
||||
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal. Log in to view details."
|
||||
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details."
|
||||
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Device {{ .name }} not found"
|
||||
DeviceIsMounted: "Device {{ .name }} is mounted, please unmount first"
|
||||
PartitionDiskErr: "Failed to partition, {{ .err }}"
|
||||
FormatDiskErr: "Failed to format disk, {{ .err }}"
|
||||
MountDiskErr: "Failed to mount disk, {{ .err }}"
|
||||
UnMountDiskErr: "Failed to unmount disk, {{ .err }}"
|
||||
XfsNotFound: "xfs filesystem not detected, please install xfsprogs first"
|
||||
DeviceIsMounted: 'Device {{ .name }} is mounted; unmount first'
|
||||
PartitionDiskErr: 'Partition failed: {{ .err }}'
|
||||
FormatDiskErr: 'Format failed: {{ .err }}'
|
||||
MountDiskErr: 'Mount failed: {{ .err }}'
|
||||
XfsNotFound: 'xfs not found; install xfsprogs first'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user