mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
289
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
2c92226f88 | ||
|
|
cd9cb9b9c9 | ||
|
|
b2777055bc | ||
|
|
c8673b60bb | ||
|
|
21975dff2d | ||
|
|
6ab14185e0 | ||
|
|
1f70646952 | ||
|
|
c90ed9e873 | ||
|
|
e4ee07d36b | ||
|
|
8fb3e78554 | ||
|
|
01ab004876 | ||
|
|
6205eac51c | ||
|
|
d9a5418029 | ||
|
|
74a8e61f3e | ||
|
|
e18472f1a3 | ||
|
|
e3dea80cb7 | ||
|
|
50947f4ddc | ||
|
|
d302bc07b3 | ||
|
|
c6df20e565 | ||
|
|
6f9a1321d1 | ||
|
|
eb4eb31578 | ||
|
|
f8f5fcfca2 | ||
|
|
4e7b654090 | ||
|
|
e8059d3676 | ||
|
|
ffadc6721b | ||
|
|
e2e774d215 | ||
|
|
f23d3678fb | ||
|
|
dfade50bd8 | ||
|
|
d25b5f4f2a | ||
|
|
9a17bc74b0 | ||
|
|
015605526d | ||
|
|
099896dc96 | ||
|
|
aa1bc6fa8e | ||
|
|
f27a429fc5 | ||
|
|
f4587d0ae5 | ||
|
|
41bae1e636 | ||
|
|
5295fd08d4 | ||
|
|
36fdf05d2b | ||
|
|
5794dbc0a6 | ||
|
|
c10474db69 | ||
|
|
d68309fd44 | ||
|
|
1c634756a0 | ||
|
|
ae536550c3 | ||
|
|
63d5d70314 | ||
|
|
0be73600e5 | ||
|
|
c9cc064f51 | ||
|
|
dc47eb5ae4 | ||
|
|
955d9db699 | ||
|
|
3c1549e288 | ||
|
|
9fd030b6c3 | ||
|
|
63a10691b3 | ||
|
|
5674673c5d | ||
|
|
74f29e3dc3 | ||
|
|
1dac8593ae | ||
|
|
71fd123be1 | ||
|
|
d4d66cad2e | ||
|
|
c78a6ec047 | ||
|
|
1ae3198b10 | ||
|
|
a6c172b1ab | ||
|
|
9e9d68f841 | ||
|
|
123e6b90a3 | ||
|
|
00b4a6caf5 | ||
|
|
8642e20d19 | ||
|
|
2b81f7ef7a | ||
|
|
c08e83e5d4 | ||
|
|
863ee5c172 | ||
|
|
a91f372fbd | ||
|
|
825acd5b46 | ||
|
|
70e64e648f | ||
|
|
7855e89ca9 | ||
|
|
75273abda7 | ||
|
|
02b278ecff | ||
|
|
e43d7b7a59 | ||
|
|
61f321a04d | ||
|
|
7885b00213 | ||
|
|
2ad882b7c2 | ||
|
|
2546809013 | ||
|
|
4fcf3e11cd | ||
|
|
1f6cd6f4e9 | ||
|
|
5c1ce232db | ||
|
|
172e9e212b | ||
|
|
279a16c256 | ||
|
|
e772cdff3c | ||
|
|
8881827f9d | ||
|
|
d9579901fe | ||
|
|
682d78698f | ||
|
|
2cc8567c2f | ||
|
|
184a214bff | ||
|
|
c6376541ed | ||
|
|
6aca579854 | ||
|
|
15bc6cf884 | ||
|
|
895dde74dd | ||
|
|
c477d20a05 | ||
|
|
2fdb1700e5 | ||
|
|
11764e1ba5 | ||
|
|
3212e23f43 | ||
|
|
84a58f2bb1 | ||
|
|
191396c78a | ||
|
|
956a64c692 | ||
|
|
6f9a8d5afe | ||
|
|
9424d061fa | ||
|
|
3db8e47679 | ||
|
|
5602741550 | ||
|
|
1ef3f540c2 | ||
|
|
51846895d5 | ||
|
|
551542c99c | ||
|
|
ab13ddec48 | ||
|
|
2d1792c6de | ||
|
|
3aedb7e82d | ||
|
|
a9c9f0272a | ||
|
|
8606427e8c | ||
|
|
3d3d22dc06 | ||
|
|
23a3a5cf74 | ||
|
|
0f5410d225 | ||
|
|
1b7be836ef | ||
|
|
66e7223db9 | ||
|
|
d801b811d4 | ||
|
|
307029272e | ||
|
|
9aa3caab16 | ||
|
|
b47a4525dc | ||
|
|
38ab0d9ca0 | ||
|
|
38985671c6 | ||
|
|
d1c2a69820 | ||
|
|
3a1b9fedd1 | ||
|
|
8119ba75fc | ||
|
|
be4c22012e | ||
|
|
60fef48cf0 | ||
|
|
bb72ea0fc0 | ||
|
|
af48fed3c2 | ||
|
|
ce3fa94708 | ||
|
|
7f9f4ae740 | ||
|
|
8bec6f2b28 | ||
|
|
b397363c9d | ||
|
|
acb800b505 | ||
|
|
ec37af2f18 | ||
|
|
d2b030e698 | ||
|
|
c6f496f2a5 | ||
|
|
722e1929f1 | ||
|
|
f9a20ec443 | ||
|
|
f5568d4874 | ||
|
|
87bbe4ee12 | ||
|
|
a034b03c1d | ||
|
|
470f4e4db7 | ||
|
|
9428efd8c0 | ||
|
|
bf4d91b68b | ||
|
|
781155b029 | ||
|
|
3caf0ac529 | ||
|
|
7d25228e55 | ||
|
|
4fb099912e |
+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,4 @@
|
||||
<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>
|
||||
<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 +29,38 @@
|
||||
</p>
|
||||
|
||||
------------------------------
|
||||
## What is 1Panel?
|
||||
|
||||
1Panel is an open-source, modern web-based control panel for Linux server management.
|
||||
1Panel is a modern, open-source Linux server management 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
|
||||
|
||||
🔹 **AI Integration**
|
||||
Seamlessly deploy OpenClaw agents, host local LLMs via Ollama, and monitor GPU utilization—all within a unified workspace engineered for efficient AI development and inference.
|
||||
|
||||
🔹 **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 (WordPress, MySQL, Redis, 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 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 leading 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.fit2cloud.com/1panel/package/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 +83,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,292 @@
|
||||
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 {object} []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 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)
|
||||
}
|
||||
+42
-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,20 +203,53 @@ 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
|
||||
// @Summary Search app detail by appkey and version
|
||||
// @Accept json
|
||||
// @Param appId path integer true "app key"
|
||||
// @Param version path string true "app version"
|
||||
// @Success 200 {object} response.AppDetailSimpleDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/detail/node/:appKey/:version [get]
|
||||
func (b *BaseApi) GetAppDetailForNode(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
version := c.Param("version")
|
||||
appDetailDTO, err := appService.GetAppDetailByKey(appKey, version)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, appDetailDTO)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -25,6 +25,23 @@ func (b *BaseApi) CheckBackupUsed(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Check backup account
|
||||
// @Accept json
|
||||
// @Param request body dto.BackupOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /backups/check [post]
|
||||
func (b *BaseApi) CheckBackup(c *gin.Context) {
|
||||
var req dto.BackupOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, backupService.CheckConn(req))
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Create backup account
|
||||
// @Accept json
|
||||
|
||||
@@ -393,6 +393,28 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Clean compose log
|
||||
// @Accept json
|
||||
// @Param request body dto.ComposeLogClean true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/compose/clean/log [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理容器编排 [name] 日志","formatEN":"clean compose [name] logs"}
|
||||
func (b *BaseApi) CleanComposeLog(c *gin.Context) {
|
||||
var req dto.ComposeLogClean
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := containerService.ComposeLogClean(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Rename Container
|
||||
// @Accept json
|
||||
@@ -502,13 +524,12 @@ func (b *BaseApi) Inspect(c *gin.Context) {
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Router /containers/download/log [post]
|
||||
func (b *BaseApi) DownloadContainerLogs(c *gin.Context) {
|
||||
var req dto.ContainerLog
|
||||
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)
|
||||
}
|
||||
@@ -710,12 +731,34 @@ 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
|
||||
// @Router /containers/search/log [get]
|
||||
@@ -728,6 +771,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")
|
||||
@@ -737,6 +781,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
||||
Since: since,
|
||||
Follow: follow,
|
||||
Tail: tail,
|
||||
Timestamp: timestamp,
|
||||
Type: "container",
|
||||
}
|
||||
if compose != "" {
|
||||
|
||||
@@ -27,7 +27,7 @@ func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
|
||||
// @Tags Dashboard
|
||||
// @Summary Load app launcher
|
||||
// @Accept json
|
||||
// @Success 200 {Array} dto.AppLauncher
|
||||
// @Success 200 {array} dto.AppLauncher
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/app/launcher [get]
|
||||
@@ -44,7 +44,7 @@ func (b *BaseApi) LoadAppLauncher(c *gin.Context) {
|
||||
// @Summary Load app launcher options
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchByFilter true "request"
|
||||
// @Success 200 {Array} dto.LauncherOption
|
||||
// @Success 200 {array} dto.LauncherOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/app/launcher/option [post]
|
||||
@@ -85,7 +85,7 @@ func (b *BaseApi) UpdateAppLauncher(c *gin.Context) {
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load quick jump options
|
||||
// @Success 200 {Array} dto.QuickJump
|
||||
// @Success 200 {array} dto.QuickJump
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/option [get]
|
||||
@@ -181,7 +181,7 @@ func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load top cpu processes
|
||||
// @Success 200 {Array} dto.Process
|
||||
// @Success 200 {array} dto.Process
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/current/top/cpu [get]
|
||||
@@ -192,7 +192,7 @@ func (b *BaseApi) LoadDashboardTopCPU(c *gin.Context) {
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load top memory processes
|
||||
// @Success 200 {Array} dto.Process
|
||||
// @Success 200 {array} dto.Process
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/current/top/mem [get]
|
||||
|
||||
@@ -214,6 +214,7 @@ func (b *BaseApi) ListDBFormatCollationOptions(c *gin.Context) {
|
||||
// @Summary Load mysql database from remote
|
||||
// @Accept json
|
||||
// @Param request body dto.MysqlLoadDB true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/load [post]
|
||||
|
||||
@@ -167,6 +167,7 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
||||
// @Summary Load postgresql database from remote
|
||||
// @Accept json
|
||||
// @Param request body dto.PostgresqlLoadDB true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/pg/:database/load [post]
|
||||
|
||||
@@ -27,7 +27,7 @@ func (b *BaseApi) LoadDeviceBaseInfo(c *gin.Context) {
|
||||
// @Tags Device
|
||||
// @Summary list time zone options
|
||||
// @Accept json
|
||||
// @Success 200 {Array} string
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/device/zone/options [get]
|
||||
|
||||
@@ -20,6 +20,7 @@ var (
|
||||
aiToolService = service.NewIAIToolService()
|
||||
mcpServerService = service.NewIMcpServerService()
|
||||
tensorrtLLMService = service.NewITensorRTLLMService()
|
||||
agentService = service.NewIAgentService()
|
||||
|
||||
containerService = service.NewIContainerService()
|
||||
composeTemplateService = service.NewIComposeTemplateService()
|
||||
|
||||
@@ -28,7 +28,7 @@ func (b *BaseApi) LoadFail2BanBaseInfo(c *gin.Context) {
|
||||
// @Summary Page fail2ban ip list
|
||||
// @Accept json
|
||||
// @Param request body dto.Fail2BanSearch true "request"
|
||||
// @Success 200 {Array} string
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/fail2ban/search [post]
|
||||
@@ -51,6 +51,7 @@ func (b *BaseApi) SearchFail2Ban(c *gin.Context) {
|
||||
// @Summary Operate fail2ban
|
||||
// @Accept json
|
||||
// @Param request body dto.Operate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/fail2ban/operate [post]
|
||||
@@ -72,7 +73,8 @@ func (b *BaseApi) OperateFail2Ban(c *gin.Context) {
|
||||
// @Tags Fail2ban
|
||||
// @Summary Operate sshd of fail2ban
|
||||
// @Accept json
|
||||
// @Param request body dto.Operate true "request"
|
||||
// @Param request body dto.Fail2BanSet true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/fail2ban/operate/sshd [post]
|
||||
|
||||
@@ -272,6 +272,29 @@ func (b *BaseApi) GetContent(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Preview file content
|
||||
// @Accept json
|
||||
// @Param request body request.FileContentReq true "request"
|
||||
// @Success 200 {object} response.FileInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/preview [post]
|
||||
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"预览文件内容 [path]","formatEN":"Preview file content [path]"}
|
||||
func (b *BaseApi) PreviewContent(c *gin.Context) {
|
||||
var req request.FileContentReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
info, err := fileService.GetPreviewContent(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Update file content
|
||||
// @Accept json
|
||||
@@ -955,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)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ func (b *BaseApi) LoadFtpLogInfo(c *gin.Context) {
|
||||
// @Summary Operate FTP
|
||||
// @Accept json
|
||||
// @Param request body dto.Operate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/ftp/operate [post]
|
||||
|
||||
@@ -34,7 +34,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
// @Tags Monitor
|
||||
// @Summary Load monitor data
|
||||
// @Param request body dto.MonitorGPUSearch true "request"
|
||||
// @Success 200 {object} dto.dto.MonitorGPUData
|
||||
// @Success 200 {object} dto.MonitorGPUData
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/gpu/search [post]
|
||||
|
||||
@@ -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,13 +80,38 @@ 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)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update local is conn
|
||||
// @Accept json
|
||||
// @Param request body dto.SSHDefaultConn true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/conn/default [post]
|
||||
// @x-panel-log {"bodyKeys":["defaultConn"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"本地终端默认连接 [defaultConn]","formatEN":"update system default conn [defaultConn]"}
|
||||
func (b *BaseApi) SetDefaultIsConn(c *gin.Context) {
|
||||
var req dto.SSHDefaultConn
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.SetDefaultIsConn(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Check local conn info
|
||||
// @Success 200 {bool} isOk
|
||||
// @Success 200 {boolean} isOk
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/check/info [post]
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// @Tags Host
|
||||
// @Summary Check if a system component exists
|
||||
// @Accept json
|
||||
// @Param name query string true "Component name to check (e.g., rsync, docker)"
|
||||
// @Param name path string true "Component name to check (e.g., rsync, docker)"
|
||||
// @Success 200 {object} response.ComponentInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
|
||||
@@ -73,7 +73,7 @@ func (b *BaseApi) GetWebsiteOptions(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites [post]
|
||||
// @x-panel-log {"bodyKeys":["primaryDomain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 [primaryDomain]","formatEN":"Create website [primaryDomain]"}
|
||||
// @x-panel-log {"bodyKeys":["alias"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 [alias]","formatEN":"Create website [alias]"}
|
||||
func (b *BaseApi) CreateWebsite(c *gin.Context) {
|
||||
var req request.WebsiteCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -1223,3 +1223,43 @@ func (b *BaseApi) UpdateCORSConfig(c *gin.Context) {
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Update Stream Config
|
||||
// @Accept json
|
||||
// @Param request body request.StreamUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/stream/update [post]
|
||||
func (b *BaseApi) UpdateStreamConfig(c *gin.Context) {
|
||||
var req request.StreamUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.UpdateStream(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Batch set HTTPS for websites
|
||||
// @Accept json
|
||||
// @Param request body request.BatchWebsiteHttps true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/batch/https [post]
|
||||
func (b *BaseApi) BatchSetHttps(c *gin.Context) {
|
||||
var req request.BatchWebsiteHttps
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.BatchSetHttps(c, req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
@@ -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,151 @@
|
||||
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" validate:"required"`
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
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"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Model string `json:"model"`
|
||||
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"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
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"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
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"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
+20
-19
@@ -84,25 +84,26 @@ type ExtraProperties struct {
|
||||
}
|
||||
|
||||
type AppProperty struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
|
||||
Description Locale `json:"description"`
|
||||
Key string `json:"key"`
|
||||
Required []string `json:"Required"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate"`
|
||||
Limit int `json:"limit" yaml:"limit"`
|
||||
Recommend int `json:"recommend" yaml:"recommend"`
|
||||
Website string `json:"website"`
|
||||
Github string `json:"github"`
|
||||
Document string `json:"document"`
|
||||
Architectures []string `json:"architectures"`
|
||||
MemoryRequired int `json:"memoryRequired" yaml:"memoryRequired"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
Version float64 `json:"version"`
|
||||
Deprecated float64 `json:"deprecated"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
|
||||
Description Locale `json:"description"`
|
||||
Key string `json:"key"`
|
||||
Required []string `json:"Required"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
|
||||
Limit int `json:"limit" yaml:"limit"`
|
||||
Recommend int `json:"recommend" yaml:"recommend"`
|
||||
Website string `json:"website"`
|
||||
Github string `json:"github"`
|
||||
Document string `json:"document"`
|
||||
Architectures []string `json:"architectures"`
|
||||
MemoryRequired int `json:"memoryRequired" yaml:"memoryRequired"`
|
||||
GpuSupport bool `json:"gpuSupport" yaml:"gpuSupport"`
|
||||
Version float64 `json:"version"`
|
||||
Deprecated float64 `json:"deprecated"`
|
||||
BatchInstallSupport bool `json:"batchInstallSupport"`
|
||||
}
|
||||
|
||||
type AppConfigVersion struct {
|
||||
|
||||
+14
-6
@@ -33,6 +33,12 @@ type BackupInfo struct {
|
||||
RememberAuth bool `json:"rememberAuth"`
|
||||
}
|
||||
|
||||
type BackupCheckRes struct {
|
||||
IsOk bool `json:"isOk"`
|
||||
Msg string `json:"msg"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type ForBuckets struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
@@ -59,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"`
|
||||
}
|
||||
@@ -77,6 +84,7 @@ type CommonRecover struct {
|
||||
Secret string `json:"secret"`
|
||||
TaskID string `json:"taskID"`
|
||||
BackupRecordID uint `json:"backupRecordID"`
|
||||
Timeout int `json:"timeout"`
|
||||
}
|
||||
|
||||
type RecordSearch struct {
|
||||
|
||||
+32
-19
@@ -15,8 +15,9 @@ type PageContainer struct {
|
||||
}
|
||||
|
||||
type InspectReq struct {
|
||||
ID string `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
ID string `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
type ContainerInfo struct {
|
||||
@@ -261,41 +262,52 @@ type ComposeInfo struct {
|
||||
Workdir string `json:"workdir"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env []string `json:"env"`
|
||||
Env string `json:"env"`
|
||||
}
|
||||
type ComposeContainer struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
Name string `json:"name"`
|
||||
CreateTime string `json:"createTime"`
|
||||
State string `json:"state"`
|
||||
ContainerID string `json:"containerID"`
|
||||
Name string `json:"name"`
|
||||
CreateTime string `json:"createTime"`
|
||||
State string `json:"state"`
|
||||
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"`
|
||||
Path string `json:"path"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"`
|
||||
WithFile bool `json:"withFile"`
|
||||
Force bool `josn:"force"`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
type ComposeUpdate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Env []string `json:"env"`
|
||||
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"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
DetailPath string `json:"detailPath"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -305,5 +317,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"`
|
||||
|
||||
@@ -258,6 +258,7 @@ type DatabaseInfo struct {
|
||||
Version string `json:"version"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
InitialDB string `json:"initialDB"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
|
||||
@@ -309,13 +310,14 @@ type DatabaseCreate struct {
|
||||
}
|
||||
|
||||
type DatabaseUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
InitialDB string `json:"initialDB"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
|
||||
SSL bool `json:"ssl"`
|
||||
RootCert string `json:"rootCert"`
|
||||
|
||||
@@ -19,7 +19,7 @@ type RuleSearch struct {
|
||||
}
|
||||
|
||||
type FirewallOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -39,8 +39,18 @@ type MonitorSettingUpdate struct {
|
||||
}
|
||||
|
||||
type MonitorGPUOptions struct {
|
||||
GPUType string `json:"gpuType"`
|
||||
Options []string `json:"options"`
|
||||
GPUType string `json:"gpuType"`
|
||||
ChartHide []GPUChartHide `json:"chartHide"`
|
||||
Options []string `json:"options"`
|
||||
}
|
||||
type GPUChartHide struct {
|
||||
ProductName string `json:"productName"`
|
||||
Process bool `json:"process"`
|
||||
GPU bool `json:"gpu"`
|
||||
Memory bool `json:"memory"`
|
||||
Power bool `json:"power"`
|
||||
Temperature bool `json:"temperature"`
|
||||
Speed bool `json:"speed"`
|
||||
}
|
||||
type MonitorGPUSearch struct {
|
||||
ProductName string `json:"productName"`
|
||||
@@ -59,6 +69,7 @@ type MonitorGPUData struct {
|
||||
MemoryPercent []float64 `json:"memoryPercent"`
|
||||
SpeedValue []int `json:"speedValue"`
|
||||
|
||||
ProcessCount []int `json:"processCount"`
|
||||
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,16 @@ type AppInstallCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Services map[string]string `json:"services"`
|
||||
TaskID string `json:"taskID"`
|
||||
|
||||
AppContainerConfig
|
||||
NodePushConfig
|
||||
}
|
||||
|
||||
type NodePushConfig struct {
|
||||
Nodes []string `json:"nodes"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
AppKey string `json:"appKey"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type AppContainerConfig struct {
|
||||
@@ -45,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 {
|
||||
|
||||
@@ -13,6 +13,7 @@ type DiskMountRequest struct {
|
||||
MountPoint string `json:"mountPoint" validate:"required"`
|
||||
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
|
||||
AutoMount bool `json:"autoMount"`
|
||||
NoFail bool `json:"noFail"`
|
||||
}
|
||||
|
||||
type DiskUnmountRequest struct {
|
||||
|
||||
@@ -13,7 +13,6 @@ type FileContentReq struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
IsDetail bool `json:"isDetail"`
|
||||
}
|
||||
|
||||
type SearchUploadWithPage struct {
|
||||
dto.PageInfo
|
||||
Path string `json:"path" validate:"required"`
|
||||
@@ -164,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"`
|
||||
}
|
||||
|
||||
@@ -38,6 +38,22 @@ type WebsiteCreate struct {
|
||||
FtpConfig
|
||||
DataBaseConfig
|
||||
SSLConfig
|
||||
StreamConfig
|
||||
}
|
||||
|
||||
type StreamConfig struct {
|
||||
StreamPorts string `json:"streamPorts"`
|
||||
Name string `json:"name"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
UDP bool `json:"udp"`
|
||||
|
||||
Servers []dto.NginxUpstreamServer `json:"servers"`
|
||||
}
|
||||
|
||||
type StreamUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
|
||||
StreamConfig
|
||||
}
|
||||
|
||||
type WebsiteOptionReq struct {
|
||||
@@ -114,6 +130,25 @@ type BatchWebsiteGroup struct {
|
||||
GroupID uint `json:"groupID" validate:"required"`
|
||||
}
|
||||
|
||||
type BatchWebsiteHttps struct {
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
WebsiteSSLID uint `json:"websiteSSLId"`
|
||||
Type string `json:"type" validate:"oneof=existed auto manual"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
Certificate string `json:"certificate"`
|
||||
PrivateKeyPath string `json:"privateKeyPath"`
|
||||
CertificatePath string `json:"certificatePath"`
|
||||
ImportType string `json:"importType"`
|
||||
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
|
||||
SSLProtocol []string `json:"SSLProtocol"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
Hsts bool `json:"hsts"`
|
||||
HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"`
|
||||
HttpsPorts []int `json:"httpsPorts"`
|
||||
Http3 bool `json:"http3"`
|
||||
}
|
||||
|
||||
type WebsiteRedirectUpdate struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
@@ -276,6 +311,7 @@ type WebsiteHtmlReq struct {
|
||||
type WebsiteHtmlUpdate struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Sync bool `json:"sync"`
|
||||
}
|
||||
|
||||
type WebsiteLBCreate struct {
|
||||
|
||||
@@ -10,6 +10,10 @@ type WebsiteSSLSearch struct {
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
}
|
||||
|
||||
type WebsiteSSLListReq struct {
|
||||
AcmeAccountID string `json:"acmeAccountID"`
|
||||
}
|
||||
|
||||
type WebsiteSSLCreate struct {
|
||||
PrimaryDomain string `json:"primaryDomain" validate:"required"`
|
||||
OtherDomains string `json:"otherDomains"`
|
||||
@@ -31,6 +35,7 @@ type WebsiteSSLCreate struct {
|
||||
Shell string `json:"shell"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
IsIp bool `json:"isIp"`
|
||||
}
|
||||
|
||||
type WebsiteDNSReq struct {
|
||||
@@ -62,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 {
|
||||
|
||||
@@ -29,16 +29,18 @@ type AppDTO struct {
|
||||
}
|
||||
|
||||
type AppItem struct {
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
ID uint `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Installed bool `json:"installed"`
|
||||
Limit int `json:"limit"`
|
||||
Tags []string `json:"tags"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
Recommend int `json:"recommend"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
ID uint `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Installed bool `json:"installed"`
|
||||
Limit int `json:"limit"`
|
||||
Tags []string `json:"tags"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
Recommend int `json:"recommend"`
|
||||
Type string `json:"type"`
|
||||
BatchInstallSupport bool `json:"batchInstallSupport"`
|
||||
}
|
||||
|
||||
type TagDTO struct {
|
||||
@@ -74,6 +76,10 @@ type AppDetailDTO struct {
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
}
|
||||
|
||||
type AppDetailSimpleDTO struct {
|
||||
ID uint `json:"id"`
|
||||
}
|
||||
|
||||
type IgnoredApp struct {
|
||||
Icon string `json:"icon"`
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -81,3 +81,7 @@ type FileConvertLog struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type FileRemarksRes struct {
|
||||
Remarks map[string]string `json:"remarks"`
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package response
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
@@ -16,6 +17,10 @@ type WebsiteDTO struct {
|
||||
RuntimeType string `json:"runtimeType"`
|
||||
SiteDir string `json:"siteDir"`
|
||||
OpenBaseDir bool `json:"openBaseDir"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
UDP bool `json:"udp"`
|
||||
|
||||
Servers []dto.NginxUpstreamServer `json:"servers"`
|
||||
}
|
||||
|
||||
type WebsiteRes struct {
|
||||
@@ -35,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"`
|
||||
|
||||
@@ -37,6 +37,7 @@ type SyncTime struct {
|
||||
|
||||
type CleanData struct {
|
||||
SystemClean []CleanTree `json:"systemClean"`
|
||||
BackupClean []CleanTree `json:"backupClean"`
|
||||
UploadClean []CleanTree `json:"uploadClean"`
|
||||
DownloadClean []CleanTree `json:"downloadClean"`
|
||||
SystemLogClean []CleanTree `json:"systemLogClean"`
|
||||
@@ -62,6 +63,10 @@ type Clean struct {
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
type SSHDefaultConn struct {
|
||||
WithReset bool `json:"withReset"`
|
||||
DefaultConn string `json:"defaultConn"`
|
||||
}
|
||||
type SSHConnData struct {
|
||||
Addr string `json:"addr" validate:"required"`
|
||||
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
|
||||
|
||||
@@ -52,6 +52,7 @@ type DataTree struct {
|
||||
Label string `json:"label"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
IsLocal bool `json:"isLocal"`
|
||||
Size uint64 `json:"size"`
|
||||
IsCheck bool `json:"isCheck"`
|
||||
IsDisable bool `json:"isDisable"`
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
type Agent struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;unique"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
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,15 @@
|
||||
package model
|
||||
|
||||
type AgentAccount struct {
|
||||
BaseModel
|
||||
Provider string `json:"provider"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
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 {
|
||||
|
||||
@@ -34,6 +34,7 @@ type App struct {
|
||||
MemoryRequired int `json:"memoryRequired"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
RequiredPanelVersion float64 `json:"requiredPanelVersion"`
|
||||
BatchInstallSupport bool `json:"batchInstallSupport" yaml:"batchInstallSupport"`
|
||||
|
||||
Details []AppDetail `json:"-" gorm:"-:migration"`
|
||||
TagsKey []string `json:"tags" yaml:"tags" gorm:"-"`
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -37,6 +37,8 @@ type Website struct {
|
||||
|
||||
Favorite bool `json:"favorite"`
|
||||
|
||||
StreamPorts string `json:"streamPorts"`
|
||||
|
||||
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
|
||||
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -41,6 +41,7 @@ type WebsiteSSL struct {
|
||||
PushNode bool `json:"pushNode"`
|
||||
PrivateKeyPath string `json:"privateKeyPath"`
|
||||
CertPath string `json:"certPath"`
|
||||
IsIp bool `json:"isIP"`
|
||||
|
||||
AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"`
|
||||
DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"`
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
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: 1,
|
||||
Enabled: true,
|
||||
},
|
||||
"deepseek": {
|
||||
Key: "deepseek",
|
||||
DisplayName: "DeepSeek",
|
||||
Sort: 2,
|
||||
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"},
|
||||
},
|
||||
},
|
||||
"openai": {
|
||||
Key: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
Sort: 3,
|
||||
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: 4,
|
||||
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: 5,
|
||||
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: 6,
|
||||
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: 7,
|
||||
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: 8,
|
||||
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"},
|
||||
},
|
||||
},
|
||||
"kimi-coding": {
|
||||
Key: "kimi-coding",
|
||||
DisplayName: "Kimi Coding",
|
||||
Sort: 9,
|
||||
DefaultBaseURL: "https://api.moonshot.cn/anthropic/v1",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{
|
||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5"},
|
||||
},
|
||||
},
|
||||
"qwen": {
|
||||
Key: "qwen",
|
||||
DisplayName: "Qwen",
|
||||
Sort: 10,
|
||||
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
|
||||
|
||||
@@ -33,7 +34,7 @@ type IAppRepo interface {
|
||||
DeleteByIDs(ctx context.Context, ids []uint) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
|
||||
GetTopRecomment() ([]string, error)
|
||||
GetTopRecommend() ([]string, error)
|
||||
}
|
||||
|
||||
func NewIAppRepo() IAppRepo {
|
||||
@@ -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)
|
||||
@@ -124,7 +131,7 @@ func (a AppRepo) GetBy(opts ...DBOption) ([]model.App, error) {
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (a AppRepo) GetTopRecomment() ([]string, error) {
|
||||
func (a AppRepo) GetTopRecommend() ([]string, error) {
|
||||
var (
|
||||
apps []model.App
|
||||
names []string
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+132
-263
@@ -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,13 @@ 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)
|
||||
}
|
||||
|
||||
func NewIAppService() IAppService {
|
||||
@@ -118,13 +126,15 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (*response.
|
||||
}
|
||||
}
|
||||
appDTO := &response.AppItem{
|
||||
ID: ap.ID,
|
||||
Name: ap.Name,
|
||||
Key: ap.Key,
|
||||
Limit: ap.Limit,
|
||||
GpuSupport: ap.GpuSupport,
|
||||
Recommend: ap.Recommend,
|
||||
Description: ap.GetDescription(ctx),
|
||||
ID: ap.ID,
|
||||
Name: ap.Name,
|
||||
Key: ap.Key,
|
||||
Limit: ap.Limit,
|
||||
GpuSupport: ap.GpuSupport,
|
||||
Recommend: ap.Recommend,
|
||||
Description: ap.GetDescription(ctx),
|
||||
Type: ap.Type,
|
||||
BatchInstallSupport: ap.BatchInstallSupport,
|
||||
}
|
||||
appDTOs = append(appDTOs, appDTO)
|
||||
tags, err := getAppTags(ap.ID, lang)
|
||||
@@ -202,6 +212,20 @@ func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, erro
|
||||
return &appDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error) {
|
||||
var appDetailDTO response.AppDetailSimpleDTO
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(appKey))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.ID = appDetail.ID
|
||||
return appDetailDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppDetail(appID uint, version, appType string) (response.AppDetailDTO, error) {
|
||||
var (
|
||||
appDetailDTO response.AppDetailDTO
|
||||
@@ -315,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
|
||||
@@ -385,11 +409,21 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
|
||||
App: app,
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
var composeRes []byte
|
||||
if req.EditCompose {
|
||||
if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil {
|
||||
return
|
||||
}
|
||||
} 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)
|
||||
_, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
appDetail.DockerCompose = string(composeRes)
|
||||
_ = appDetailRepo.Update(context.Background(), appDetail)
|
||||
}
|
||||
if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -499,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 {
|
||||
@@ -775,6 +811,11 @@ 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)
|
||||
_, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s)
|
||||
@@ -807,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()
|
||||
@@ -864,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
|
||||
}
|
||||
|
||||
@@ -121,7 +121,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 +238,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 {
|
||||
@@ -515,6 +515,7 @@ func (a *AppInstallService) GetServices(key string) ([]response.AppService, erro
|
||||
} else {
|
||||
service.From = constant.AppResourceRemote
|
||||
service.Status = constant.StatusRunning
|
||||
service.Config = map[string]string{"PANEL_DB_ROOT_PASSWORD": db.Password, "PANEL_DB_ROOT_USER": db.Username}
|
||||
}
|
||||
res = append(res, service)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/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.CONF.RemoteURL.AppRepo, 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
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appSyncContext) syncAppIconsAndDetails() error {
|
||||
total := len(c.list.Apps)
|
||||
global.LOG.Infof("[AppStore] sync app detail start, total apps: %d", total)
|
||||
|
||||
var (
|
||||
icon200Count = 0
|
||||
icon304Count = 0
|
||||
iconFailCount = 0
|
||||
)
|
||||
|
||||
for i, l := range c.list.Apps {
|
||||
if (i+1)%10 == 0 {
|
||||
c.task.LogWithProgress(i18n.GetMsgByKey("SyncAppDetail"), i+1, total)
|
||||
}
|
||||
|
||||
app, ok := c.appsMap[l.AppProperty.Key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
iconUrl, hasPending := c.pendingIcons[l.AppProperty.Key]
|
||||
if hasPending {
|
||||
status, iconField := c.downloadAppIcon(iconUrl, l.AppProperty.Key, app.Icon)
|
||||
switch status {
|
||||
case http.StatusOK:
|
||||
app.Icon = iconField
|
||||
icon200Count++
|
||||
case http.StatusNotModified:
|
||||
icon304Count++
|
||||
default:
|
||||
global.LOG.Warnf("[AppStore] download icon failed url=%s, appKey=%s", iconUrl, l.AppProperty.Key)
|
||||
iconFailCount++
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
c.appsMap[l.AppProperty.Key] = app
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"maps"
|
||||
"math"
|
||||
"net/http"
|
||||
@@ -45,6 +44,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -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 {
|
||||
_ = 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")
|
||||
@@ -959,7 +987,7 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
|
||||
}
|
||||
}
|
||||
|
||||
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, logger *log.Logger) (err error) {
|
||||
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, logger *logrus.Logger) (err error) {
|
||||
if app.IsLocalApp() || app.IsCustomApp() {
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
@@ -1312,9 +1341,13 @@ func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, t
|
||||
app.MemoryRequired = config.MemoryRequired
|
||||
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 {
|
||||
@@ -1530,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
|
||||
@@ -1553,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)
|
||||
}
|
||||
@@ -1582,20 +1616,30 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
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"))
|
||||
@@ -1607,11 +1651,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)
|
||||
@@ -1623,15 +1675,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
|
||||
}
|
||||
|
||||
@@ -1960,23 +2020,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
|
||||
}
|
||||
@@ -2091,15 +2154,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]
|
||||
@@ -2107,7 +2176,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2137,7 +2208,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error {
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+52
-39
@@ -34,6 +34,7 @@ type IBackupService interface {
|
||||
LoadBackupOptions() ([]dto.BackupOption, error)
|
||||
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
|
||||
Create(backupDto dto.BackupOperate) error
|
||||
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
|
||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||
Update(req dto.BackupOperate) error
|
||||
Delete(id uint) error
|
||||
@@ -71,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))
|
||||
}
|
||||
@@ -123,6 +124,43 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, inter
|
||||
return count, data, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes {
|
||||
var res dto.BackupCheckRes
|
||||
var backup model.BackupAccount
|
||||
if err := copier.Copy(&backup, &req); err != nil {
|
||||
res.Msg = i18n.GetMsgWithDetail("ErrStructTransform", err.Error())
|
||||
return res
|
||||
}
|
||||
itemAccessKey, err := base64.StdEncoding.DecodeString(backup.AccessKey)
|
||||
if err != nil {
|
||||
res.Msg = err.Error()
|
||||
return res
|
||||
}
|
||||
backup.AccessKey = string(itemAccessKey)
|
||||
itemCredential, err := base64.StdEncoding.DecodeString(backup.Credential)
|
||||
if err != nil {
|
||||
res.Msg = err.Error()
|
||||
return res
|
||||
}
|
||||
backup.Credential = string(itemCredential)
|
||||
|
||||
if req.Type == constant.OneDrive || req.Type == constant.GoogleDrive {
|
||||
refreshToken, err := loadRefreshTokenByCode(&backup)
|
||||
if err != nil {
|
||||
res.Msg = err.Error()
|
||||
return res
|
||||
}
|
||||
res.Token = base64.StdEncoding.EncodeToString([]byte(refreshToken))
|
||||
}
|
||||
isOk, err := u.checkBackupConn(&backup)
|
||||
if err != nil {
|
||||
res.Msg = err.Error()
|
||||
return res
|
||||
}
|
||||
res.IsOk = isOk
|
||||
return res
|
||||
}
|
||||
|
||||
func (u *BackupService) Create(req dto.BackupOperate) error {
|
||||
if req.Type == constant.Local {
|
||||
return buserr.New("ErrBackupLocalCreate")
|
||||
@@ -147,19 +185,6 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
|
||||
return err
|
||||
}
|
||||
backup.Credential = string(itemCredential)
|
||||
|
||||
if req.Type == constant.OneDrive || req.Type == constant.GoogleDrive || req.Type == constant.ALIYUN {
|
||||
if err := loadRefreshTokenByCode(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.Type != constant.Local {
|
||||
isOk, err := u.checkBackupConn(&backup)
|
||||
if err != nil || !isOk {
|
||||
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
}
|
||||
|
||||
backup.AccessKey, err = encrypt.StringEncrypt(backup.AccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -248,16 +273,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
global.Dir.LocalBackupDir = newBackup.BackupPath
|
||||
}
|
||||
|
||||
if newBackup.Type == constant.OneDrive || newBackup.Type == constant.GoogleDrive || newBackup.Type == constant.ALIYUN {
|
||||
if err := loadRefreshTokenByCode(&newBackup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if backup.Type != constant.Local {
|
||||
isOk, err := u.checkBackupConn(&newBackup)
|
||||
if err != nil || !isOk {
|
||||
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -356,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
|
||||
}
|
||||
@@ -514,37 +530,34 @@ func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.Clou
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func loadRefreshTokenByCode(backup *model.BackupAccount) error {
|
||||
func loadRefreshTokenByCode(backup *model.BackupAccount) (string, error) {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||
return "", fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||
}
|
||||
if token, ok := varMap["refresh_token"]; ok && len(token.(string)) != 0 {
|
||||
return "", nil
|
||||
}
|
||||
refreshToken := ""
|
||||
var err error
|
||||
if backup.Type == constant.GoogleDrive {
|
||||
switch backup.Type {
|
||||
case constant.GoogleDrive:
|
||||
refreshToken, err = client.RefreshGoogleToken("authorization_code", "refreshToken", varMap)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if backup.Type == constant.OneDrive {
|
||||
case constant.OneDrive:
|
||||
refreshToken, err = client.RefreshToken("authorization_code", "refreshToken", varMap)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if backup.Type != constant.ALIYUN {
|
||||
delete(varMap, "code")
|
||||
varMap["refresh_token"] = refreshToken
|
||||
}
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||
itemVars, err := json.Marshal(varMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshal var map failed, err: %v", err)
|
||||
}
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
backup.Vars = string(itemVars)
|
||||
return nil
|
||||
return refreshToken, nil
|
||||
}
|
||||
|
||||
func loadBackupNamesByID(accountIDs string, downloadID uint) ([]string, string, error) {
|
||||
|
||||
@@ -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
|
||||
@@ -57,11 +57,11 @@ func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
recoveFile, err := loadSqlFile(req.File)
|
||||
recoverFile, err := loadSqlFile(req.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.File = recoveFile
|
||||
req.File = recoverFile
|
||||
|
||||
if err := handleMysqlRecover(req, nil, false); err != nil {
|
||||
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()
|
||||
}
|
||||
@@ -193,7 +198,16 @@ func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback
|
||||
return recoverDatabase(parentTask)
|
||||
}
|
||||
|
||||
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, 3*time.Hour)
|
||||
var timeout time.Duration
|
||||
switch req.Timeout {
|
||||
case -1:
|
||||
timeout = 0
|
||||
case 0:
|
||||
timeout = 3 * time.Hour
|
||||
default:
|
||||
timeout = time.Duration(req.Timeout) * time.Second
|
||||
}
|
||||
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, timeout)
|
||||
go func() {
|
||||
_ = itemTask.Execute()
|
||||
}()
|
||||
@@ -216,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
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
|
||||
pgclient "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
|
||||
)
|
||||
|
||||
func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
|
||||
@@ -56,11 +55,11 @@ func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
recoveFile, err := loadSqlFile(req.File)
|
||||
recoverFile, err := loadSqlFile(req.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.File = recoveFile
|
||||
req.File = recoverFile
|
||||
if err := handlePostgresqlRecover(req, nil, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -82,7 +81,7 @@ func handlePostgresqlBackup(db DatabaseHelper, parentTask *task.Task, recordID u
|
||||
}
|
||||
}
|
||||
|
||||
itemHandler := func() error { return doPostgresqlgBackup(db, targetDir, fileName, secret, backupTask) }
|
||||
itemHandler := func() error { return doPostgresqlBackup(db, targetDir, fileName, secret, backupTask) }
|
||||
if parentTask != nil {
|
||||
return itemHandler()
|
||||
}
|
||||
@@ -188,20 +187,29 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol
|
||||
return recoverDatabase(parentTask)
|
||||
}
|
||||
|
||||
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, 3*time.Hour)
|
||||
var timeout time.Duration
|
||||
switch req.Timeout {
|
||||
case -1:
|
||||
timeout = 0
|
||||
case 0:
|
||||
timeout = 3 * time.Hour
|
||||
default:
|
||||
timeout = time.Duration(req.Timeout) * time.Second
|
||||
}
|
||||
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, timeout)
|
||||
go func() {
|
||||
_ = itemTask.Execute()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func doPostgresqlgBackup(db DatabaseHelper, targetDir, fileName, secret string, task *task.Task) error {
|
||||
func doPostgresqlBackup(db DatabaseHelper, targetDir, fileName, secret string, task *task.Task) error {
|
||||
cli, err := LoadPostgresqlClientByFrom(db.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
backupInfo := pgclient.BackupInfo{
|
||||
backupInfo := client.BackupInfo{
|
||||
Database: db.Database,
|
||||
Name: db.Name,
|
||||
TargetDir: targetDir,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -131,7 +131,7 @@ func handleWebsiteRecover(website *model.Website, parentTask *task.Task, recover
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetOpenrestyDir(SiteConfDir)); err != nil {
|
||||
if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetConfDir(*website)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ func doWebsiteBackup(website *model.Website, parentTask *task.Task, backupDir, f
|
||||
if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
nginxConfFile := GetSitePath(*website, SiteConf)
|
||||
nginxConfFile := GetWebsiteConfigPath(*website)
|
||||
if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -19,12 +19,8 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
@@ -45,7 +41,9 @@ import (
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/client"
|
||||
"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"
|
||||
)
|
||||
@@ -61,9 +59,15 @@ 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
|
||||
@@ -76,15 +80,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
|
||||
Prune(req dto.ContainerPrune) error
|
||||
|
||||
LoadUsers(req dto.OperationWithName) []string
|
||||
@@ -361,6 +364,9 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
}
|
||||
for _, container := range containers {
|
||||
config := container.Labels[composeConfigLabel]
|
||||
if len(req.Detail) != 0 && strings.Contains(config, req.Detail) {
|
||||
config = req.Detail
|
||||
}
|
||||
workdir := container.Labels[composeWorkdirLabel]
|
||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
||||
filePath = config
|
||||
@@ -433,7 +439,6 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
}
|
||||
}
|
||||
taskItem.Log(i18n.GetMsgByKey("PruneStart"))
|
||||
DeletedNumber := 0
|
||||
SpaceReclaimed := 0
|
||||
switch req.PruneType {
|
||||
case "container":
|
||||
@@ -441,21 +446,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 {
|
||||
@@ -468,7 +470,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{}
|
||||
@@ -477,10 +478,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() {
|
||||
@@ -956,6 +956,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)
|
||||
}
|
||||
@@ -968,12 +971,18 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
|
||||
|
||||
var dockerCmd *exec.Cmd
|
||||
if params.Type == "compose" {
|
||||
dockerComposCmd := common.GetDockerComposeCommand()
|
||||
if dockerComposCmd == "docker-compose" {
|
||||
newCmdArgs := append([]string{"-f", params.Compose}, cmdArgs...)
|
||||
dockerCmd = exec.Command(dockerComposCmd, newCmdArgs...)
|
||||
dockerComposeCmd := common.GetDockerComposeCommand()
|
||||
var yamlFiles []string
|
||||
for _, item := range strings.Split(params.Compose, ",") {
|
||||
if len(item) != 0 {
|
||||
yamlFiles = append(yamlFiles, "-f", item)
|
||||
}
|
||||
}
|
||||
if dockerComposeCmd == "docker-compose" {
|
||||
newCmdArgs := append(yamlFiles, cmdArgs...)
|
||||
dockerCmd = exec.Command(dockerComposeCmd, newCmdArgs...)
|
||||
} else {
|
||||
newCmdArgs := append([]string{"compose", "-f", params.Compose}, cmdArgs...)
|
||||
newCmdArgs := append(append([]string{"compose"}, yamlFiles...), cmdArgs...)
|
||||
dockerCmd = exec.Command("docker", newCmdArgs...)
|
||||
}
|
||||
} else {
|
||||
@@ -1046,17 +1055,23 @@ 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")
|
||||
}
|
||||
commandArg := []string{"logs", container}
|
||||
dockerCommand := global.CONF.DockerConfig.Command
|
||||
if containerType == "compose" {
|
||||
var yamlFiles []string
|
||||
for _, item := range strings.Split(container, ",") {
|
||||
if len(item) != 0 {
|
||||
yamlFiles = append(yamlFiles, "-f", item)
|
||||
}
|
||||
}
|
||||
if dockerCommand == "docker-compose" {
|
||||
commandArg = []string{"-f", container, "logs"}
|
||||
commandArg = append(yamlFiles, "logs")
|
||||
} else {
|
||||
commandArg = []string{"compose", "-f", container, "logs"}
|
||||
commandArg = append(append([]string{"compose"}, yamlFiles...), "logs")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1068,6 +1083,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...)
|
||||
@@ -1143,10 +1161,8 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
res.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
res.Body.Close()
|
||||
var stats *container.StatsResponse
|
||||
if err := json.Unmarshal(body, &stats); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,10 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -23,7 +25,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const composeProjectLabel = "com.docker.compose.project"
|
||||
@@ -73,6 +74,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
Name: container.Names[0][1:],
|
||||
State: container.State,
|
||||
CreateTime: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
|
||||
Ports: transPortToStr(container.Ports),
|
||||
}
|
||||
if compose, has := composeMap[name]; has {
|
||||
compose.ContainerCount++
|
||||
@@ -179,7 +181,7 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
cmd := getComposeCmd(req.Path, "config")
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false, errors.New(string(stdout))
|
||||
return false, fmt.Errorf("docker-compose config failed, std: %s, err: %v", string(stdout), err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -203,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)
|
||||
@@ -231,24 +233,24 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
return err
|
||||
}
|
||||
if req.WithFile {
|
||||
_ = os.RemoveAll(path.Dir(req.Path))
|
||||
for _, item := range strings.Split(req.Path, ",") {
|
||||
if len(item) != 0 {
|
||||
_ = os.RemoveAll(path.Dir(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = composeRepo.DeleteRecord(repo.WithByName(req.Name))
|
||||
return nil
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
if req.Operation == "up" {
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
return fmt.Errorf("docker-compose up failed, std: %s, err: %v", stdout, err)
|
||||
}
|
||||
} else {
|
||||
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
return fmt.Errorf("docker-compose %s failed, std: %s, err: %v", req.Operation, stdout, err)
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -256,34 +258,107 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
||||
if cmd.CheckIllegal(req.Name, req.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
oldFile, err := os.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
file, err := os.OpenFile(req.Path, 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.Path)
|
||||
if err := newComposeEnv(req.Path, 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 {
|
||||
if err := recreateCompose(string(oldFile), req.Path); err != nil {
|
||||
return fmt.Errorf("update failed when handle compose up, err: %s, recreate failed: %v", string(stdout), err)
|
||||
}
|
||||
return fmt.Errorf("update failed when handle compose up, err: %s", string(stdout))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
options := container.ListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", composeProjectLabel)
|
||||
|
||||
list, err := client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
for _, item := range list {
|
||||
if name, ok := item.Labels[composeProjectLabel]; ok {
|
||||
if name != req.Name {
|
||||
continue
|
||||
}
|
||||
containerItem, err := client.ContainerInspect(ctx, item.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, constant.FilePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if err = file.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = file.Seek(0, 0)
|
||||
|
||||
files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath))
|
||||
for _, file := range files {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
return u.ComposeOperation(dto.ComposeOperation{
|
||||
Name: req.Name,
|
||||
Path: req.Path,
|
||||
Operation: "restart",
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -308,11 +383,8 @@ func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
||||
}
|
||||
|
||||
func removeContainerForCompose(composeName, composePath string) error {
|
||||
if _, err := os.Stat(composePath); err == nil {
|
||||
if stdout, err := compose.Operate(composePath, "down"); err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
return nil
|
||||
if stdout, err := compose.Operate(composePath, "down"); err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
var options container.ListOptions
|
||||
options.All = true
|
||||
@@ -352,40 +424,31 @@ func recreateCompose(content, path string) error {
|
||||
|
||||
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
|
||||
for i := 0; i < len(list); i++ {
|
||||
envFilePath := path.Join(path.Dir(list[i].Path), "1panel.env")
|
||||
tmpPath := list[i].Path
|
||||
if strings.Contains(list[i].Path, ",") {
|
||||
tmpPath = strings.Split(list[i].Path, ",")[0]
|
||||
}
|
||||
envFilePath := path.Join(path.Dir(tmpPath), ".env")
|
||||
file, err := os.ReadFile(envFilePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines := strings.Split(string(file), "\n")
|
||||
for _, line := range lines {
|
||||
lineItem := strings.TrimSpace(line)
|
||||
if len(lineItem) != 0 && !strings.HasPrefix(lineItem, "#") {
|
||||
list[i].Env = append(list[i].Env, lineItem)
|
||||
}
|
||||
}
|
||||
list[i].Env = string(file)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func newComposeEnv(pathItem string, env []string) error {
|
||||
if len(env) == 0 {
|
||||
return nil
|
||||
}
|
||||
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
|
||||
func newComposeEnv(pathItem string, env string) error {
|
||||
envFilePath := path.Join(path.Dir(pathItem), ".env")
|
||||
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to create env file: %v", err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
for _, env := range env {
|
||||
envItem := strings.TrimSpace(env)
|
||||
if _, err := file.WriteString(fmt.Sprintf("%s\n", envItem)); err != nil {
|
||||
global.LOG.Errorf("failed to write env to file: %v", err)
|
||||
return err
|
||||
}
|
||||
if _, err := file.WriteString(env); err != nil {
|
||||
global.LOG.Errorf("failed to write env to file: %v", err)
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
list, err := client.VolumeList(context.TODO(), volume.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
|
||||
@@ -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 {
|
||||
@@ -447,7 +449,7 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
|
||||
func (u *CronjobService) LoadNextHandle(specStr string) ([]string, error) {
|
||||
spec := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
||||
now := time.Now()
|
||||
var nexts [5]string
|
||||
var next [5]string
|
||||
if strings.HasPrefix(specStr, "@every ") {
|
||||
duration := time.Minute
|
||||
if strings.HasSuffix(specStr, "s") {
|
||||
@@ -462,10 +464,10 @@ func (u *CronjobService) LoadNextHandle(specStr string) ([]string, error) {
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
nextTime := now.Add(time.Duration(durationItem) * duration)
|
||||
nexts[i] = nextTime.Format(constant.DateTimeLayout)
|
||||
next[i] = nextTime.Format(constant.DateTimeLayout)
|
||||
now = nextTime
|
||||
}
|
||||
return nexts[:], nil
|
||||
return next[:], nil
|
||||
}
|
||||
sched, err := spec.Parse(specStr)
|
||||
if err != nil {
|
||||
@@ -473,10 +475,10 @@ func (u *CronjobService) LoadNextHandle(specStr string) ([]string, error) {
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
nextTime := sched.Next(now)
|
||||
nexts[i] = nextTime.Format(constant.DateTimeLayout)
|
||||
next[i] = nextTime.Format(constant.DateTimeLayout)
|
||||
now = nextTime
|
||||
}
|
||||
return nexts[:], nil
|
||||
return next[:], nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string {
|
||||
@@ -633,8 +635,8 @@ func (u *CronjobService) HandleStop(id uint) error {
|
||||
if len(record.TaskID) == 0 {
|
||||
return nil
|
||||
}
|
||||
if cancle, ok := global.TaskCtxMap[record.TaskID]; ok {
|
||||
cancle()
|
||||
if cancel, ok := global.TaskCtxMap[record.TaskID]; ok {
|
||||
cancel()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := doPostgresqlgBackup(dbInfo, backupDir, record.FileName, cronjob.Secret, taskItem); err != nil {
|
||||
if err := doPostgresqlBackup(dbInfo, backupDir, record.FileName, cronjob.Secret, taskItem); err != nil {
|
||||
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
|
||||
retry++
|
||||
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, ","),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -440,10 +445,10 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
}
|
||||
|
||||
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||
var weblist []model.Website
|
||||
var list []model.Website
|
||||
if cronjob.Website == "all" {
|
||||
weblist, _ = websiteRepo.List()
|
||||
return weblist
|
||||
list, _ = websiteRepo.List()
|
||||
return list
|
||||
}
|
||||
websites := strings.Split(cronjob.Website, ",")
|
||||
var idItems []uint
|
||||
@@ -451,8 +456,8 @@ func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||
itemID, _ := strconv.Atoi(websites[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
weblist, _ = websiteRepo.GetBy(repo.WithByIDs(idItems))
|
||||
return weblist
|
||||
list, _ = websiteRepo.GetBy(repo.WithByIDs(idItems))
|
||||
return list
|
||||
}
|
||||
|
||||
func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret string) error {
|
||||
|
||||
@@ -64,7 +64,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
return
|
||||
}
|
||||
if err = u.loadTask(cronjob, &record, taskItem); err != nil {
|
||||
global.LOG.Debugf("preper to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
|
||||
global.LOG.Debugf("prepare to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
|
||||
item, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(item.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
@@ -275,6 +275,10 @@ func (u *CronjobService) handleSyncIpGroup(cronjob model.Cronjob, taskItem *task
|
||||
}
|
||||
ipGroupDir := pathUtils.Join(appInstall.GetPath(), "1pwaf", "data", "rules", "ip_group")
|
||||
urlDir := pathUtils.Join(ipGroupDir, "ip_group_url")
|
||||
_, err = os.Stat(urlDir)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return buserr.New("WafIpGroupNotFound")
|
||||
}
|
||||
|
||||
urlsFiles, err := os.ReadDir(urlDir)
|
||||
if err != nil {
|
||||
@@ -414,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))
|
||||
@@ -457,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
|
||||
}
|
||||
@@ -285,7 +285,7 @@ func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher,
|
||||
}
|
||||
|
||||
showList, err := launcherRepo.ListName()
|
||||
defaultList, err := appRepo.GetTopRecomment()
|
||||
defaultList, err := appRepo.GetTopRecommend()
|
||||
if err != nil {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -119,9 +119,10 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
if req.Timeout == 0 {
|
||||
req.Timeout = 30
|
||||
}
|
||||
var err error
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
_, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
_, err = postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
@@ -130,17 +131,15 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
})
|
||||
return err == nil
|
||||
case constant.AppRedis:
|
||||
_, err := redisclient.NewRedisClient(redisclient.DBInfo{
|
||||
_, err = redisclient.NewRedisClient(redisclient.DBInfo{
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
})
|
||||
return err == nil
|
||||
case "mysql", "mariadb":
|
||||
_, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
_, err = mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
@@ -154,10 +153,13 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: req.Timeout,
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
if err != nil {
|
||||
global.LOG.Errorf("check database connection failed, err: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
@@ -174,12 +176,13 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
InitialDB: req.InitialDB,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -275,12 +278,13 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
InitialDB: req.InitialDB,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -326,6 +330,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
|
||||
upMap["port"] = req.Port
|
||||
upMap["username"] = req.Username
|
||||
upMap["password"] = pass
|
||||
upMap["initial_db"] = req.InitialDB
|
||||
upMap["timeout"] = req.Timeout
|
||||
upMap["description"] = req.Description
|
||||
upMap["ssl"] = req.SSL
|
||||
|
||||
@@ -115,7 +115,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
||||
}
|
||||
|
||||
if req.From == "local" && req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
return nil, errors.New("cannot set root as user name")
|
||||
}
|
||||
|
||||
cli, version, err := LoadMysqlClientByFrom(req.Database)
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type PostgresqlService struct{}
|
||||
@@ -138,10 +137,6 @@ func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCrea
|
||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
|
||||
if req.From == "local" && req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
}
|
||||
|
||||
cli, err := LoadPostgresqlClientByFrom(req.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -185,6 +180,7 @@ func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, e
|
||||
dbInfo.Port = databaseItem.Port
|
||||
dbInfo.Username = databaseItem.Username
|
||||
dbInfo.Password = databaseItem.Password
|
||||
dbInfo.InitialDB = databaseItem.InitialDB
|
||||
} else {
|
||||
app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database)
|
||||
if err != nil {
|
||||
|
||||
+532
-124
@@ -11,36 +11,27 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/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/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
fileUtils "github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
upgradePath = "1panel/tmp/upgrade"
|
||||
snapshotTmpPath = "1panel/tmp/system"
|
||||
rollbackPath = "1panel/tmp"
|
||||
cachePath = "1panel/cache"
|
||||
oldOriginalPath = "original"
|
||||
oldAppBackupPath = "1panel/resource/apps_bak"
|
||||
oldDownloadPath = "1panel/tmp/download"
|
||||
oldUpgradePath = "1panel/tmp"
|
||||
tmpUploadPath = "1panel/tmp/upload"
|
||||
uploadPath = "1panel/uploads"
|
||||
downloadPath = "1panel/download"
|
||||
rollbackPath = "1panel/tmp"
|
||||
upgradePath = "1panel/tmp/upgrade"
|
||||
uploadPath = "1panel/uploads"
|
||||
downloadPath = "1panel/download"
|
||||
)
|
||||
|
||||
func (u *DeviceService) Scan() dto.CleanData {
|
||||
@@ -61,54 +52,10 @@ func (u *DeviceService) Scan() dto.CleanData {
|
||||
Type: "1panel_original",
|
||||
Children: loadTreeWithDir(true, "1panel_original", originalPath, fileOp),
|
||||
})
|
||||
treeData = append(treeData, loadUpgradeTree(fileOp))
|
||||
treeData = append(treeData, loadAgentPackage(fileOp))
|
||||
|
||||
upgradePath := path.Join(global.Dir.BaseDir, upgradePath)
|
||||
upgradeSize, _ := fileOp.GetDirSize(upgradePath)
|
||||
upgradeTree := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "upgrade",
|
||||
Size: uint64(upgradeSize),
|
||||
IsCheck: false,
|
||||
IsRecommend: true,
|
||||
Type: "upgrade",
|
||||
Children: loadTreeWithDir(true, "upgrade", upgradePath, fileOp),
|
||||
}
|
||||
if len(upgradeTree.Children) != 0 {
|
||||
sort.Slice(upgradeTree.Children, func(i, j int) bool {
|
||||
return common.CompareVersion(upgradeTree.Children[i].Label, upgradeTree.Children[j].Label)
|
||||
})
|
||||
if global.IsMaster {
|
||||
var copiesSeeting model.Setting
|
||||
_ = global.CoreDB.Where("key = ?", "UpgradeBackupCopies").First(&copiesSeeting).Error
|
||||
copies, _ := strconv.Atoi(copiesSeeting.Value)
|
||||
if copies == 0 || copies > len(upgradeTree.Children) {
|
||||
copies = len(upgradeTree.Children)
|
||||
}
|
||||
for i := 0; i < copies; i++ {
|
||||
upgradeTree.Children[i].IsCheck = false
|
||||
upgradeTree.Children[i].IsRecommend = false
|
||||
}
|
||||
} else {
|
||||
upgradeTree.Children[0].IsCheck = false
|
||||
upgradeTree.Children[0].IsRecommend = false
|
||||
}
|
||||
}
|
||||
treeData = append(treeData, upgradeTree)
|
||||
|
||||
tmpBackupTree := loadTmpBackupTree(fileOp)
|
||||
tmpBackupSize := uint64(0)
|
||||
for _, tmp := range tmpBackupTree {
|
||||
tmpBackupSize += tmp.Size
|
||||
}
|
||||
treeData = append(treeData, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "backup",
|
||||
Size: tmpBackupSize,
|
||||
IsCheck: tmpBackupSize > 0,
|
||||
IsRecommend: true,
|
||||
Type: "backup",
|
||||
Children: tmpBackupTree,
|
||||
})
|
||||
SystemClean.BackupClean = loadBackupTree(fileOp)
|
||||
|
||||
rollBackTree := loadRollBackTree(fileOp)
|
||||
rollbackSize := uint64(0)
|
||||
@@ -124,17 +71,6 @@ func (u *DeviceService) Scan() dto.CleanData {
|
||||
Type: "rollback",
|
||||
Children: rollBackTree,
|
||||
})
|
||||
|
||||
cachePath := path.Join(global.Dir.BaseDir, cachePath)
|
||||
cacheSize, _ := fileOp.GetDirSize(cachePath)
|
||||
treeData = append(treeData, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "cache",
|
||||
Size: uint64(cacheSize),
|
||||
IsCheck: false,
|
||||
IsRecommend: false,
|
||||
Type: "cache",
|
||||
})
|
||||
SystemClean.SystemClean = treeData
|
||||
|
||||
uploadTreeData := loadUploadTree(fileOp)
|
||||
@@ -154,7 +90,6 @@ func (u *DeviceService) Scan() dto.CleanData {
|
||||
|
||||
func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
size := uint64(0)
|
||||
restart := false
|
||||
for _, item := range req {
|
||||
size += item.Size
|
||||
switch item.TreeType {
|
||||
@@ -164,13 +99,17 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
case "upgrade":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, upgradePath, item.Name))
|
||||
|
||||
case "backup":
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/app"))
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/database"))
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/website"))
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/directory"))
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/log"))
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/system"))
|
||||
case "agent":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, "1panel/agent/package", item.Name))
|
||||
|
||||
case "tmp_backup":
|
||||
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp"))
|
||||
case "unknown_backup":
|
||||
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"))
|
||||
@@ -183,25 +122,14 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
case "rollback_website":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "website", item.Name))
|
||||
|
||||
case "cache":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, cachePath, item.Name))
|
||||
restart = true
|
||||
|
||||
case "upload":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, item.Name))
|
||||
if len(item.Name) == 0 {
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, tmpUploadPath))
|
||||
}
|
||||
case "upload_tmp":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, tmpUploadPath, item.Name))
|
||||
case "upload_app":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "app", item.Name))
|
||||
case "upload_database":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "database", item.Name))
|
||||
case "upload_website":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "website", item.Name))
|
||||
case "upload_directory":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "directory", item.Name))
|
||||
case "download":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, item.Name))
|
||||
case "download_app":
|
||||
@@ -210,8 +138,6 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "database", item.Name))
|
||||
case "download_website":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "website", item.Name))
|
||||
case "download_directory":
|
||||
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "directory", item.Name))
|
||||
|
||||
case "system_log":
|
||||
if len(item.Name) == 0 {
|
||||
@@ -243,6 +169,8 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
} else {
|
||||
dropTaskLog(path.Join(global.Dir.TaskDir, item.Name))
|
||||
}
|
||||
case "website_log":
|
||||
dropWebsiteLog(item.Name)
|
||||
case "script":
|
||||
dropFileOrDir(path.Join(global.Dir.TmpDir, "script", item.Name))
|
||||
case "images":
|
||||
@@ -253,16 +181,15 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
_, _ = dropVolumes()
|
||||
case "build_cache":
|
||||
_, _ = dropBuildCache()
|
||||
case "app_tmp_download_version":
|
||||
dropFileOrDir(path.Join(global.Dir.RemoteAppResourceDir, item.Name))
|
||||
}
|
||||
}
|
||||
|
||||
_ = 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)))
|
||||
|
||||
if restart {
|
||||
go controller.RestartPanel(false, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
|
||||
@@ -284,25 +211,20 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
|
||||
}
|
||||
}
|
||||
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, snapshotTmpPath), taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(global.Dir.LocalBackupDir, "tmp/system"), taskItem, &size, &fileCount)
|
||||
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "app"), taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "website"), taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "database"), taskItem, &size, &fileCount)
|
||||
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, oldOriginalPath), taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, oldAppBackupPath), taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, oldDownloadPath), taskItem, &size, &fileCount)
|
||||
oldUpgradePath := path.Join(global.Dir.BaseDir, oldUpgradePath)
|
||||
oldUpgradeFiles, _ := os.ReadDir(oldUpgradePath)
|
||||
upgrades := path.Join(global.Dir.BaseDir, upgradePath)
|
||||
oldUpgradeFiles, _ := os.ReadDir(upgrades)
|
||||
if len(oldUpgradeFiles) != 0 {
|
||||
for i := 0; i < len(oldUpgradeFiles); i++ {
|
||||
dropWithTask(path.Join(oldUpgradePath, oldUpgradeFiles[i].Name()), taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(upgrades, oldUpgradeFiles[i].Name()), taskItem, &size, &fileCount)
|
||||
}
|
||||
}
|
||||
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, tmpUploadPath), taskItem, &size, &fileCount)
|
||||
dropWithExclude(path.Join(global.Dir.BaseDir, uploadPath), []string{"theme"}, taskItem, &size, &fileCount)
|
||||
dropWithTask(path.Join(global.Dir.BaseDir, downloadPath), taskItem, &size, &fileCount)
|
||||
|
||||
@@ -338,17 +260,340 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
|
||||
}
|
||||
}
|
||||
|
||||
func loadTmpBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
func loadUpgradeTree(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
upgradePath := path.Join(global.Dir.BaseDir, upgradePath)
|
||||
upgradeSize, _ := fileOp.GetDirSize(upgradePath)
|
||||
upgradeTree := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "upgrade",
|
||||
Size: uint64(upgradeSize),
|
||||
IsCheck: false,
|
||||
IsRecommend: true,
|
||||
Type: "upgrade",
|
||||
Children: loadTreeWithDir(true, "upgrade", upgradePath, fileOp),
|
||||
}
|
||||
if len(upgradeTree.Children) != 0 {
|
||||
sort.Slice(upgradeTree.Children, func(i, j int) bool {
|
||||
return common.CompareVersion(upgradeTree.Children[i].Label, upgradeTree.Children[j].Label)
|
||||
})
|
||||
if global.IsMaster {
|
||||
var copiesSetting model.Setting
|
||||
_ = global.CoreDB.Where("key = ?", "UpgradeBackupCopies").First(&copiesSetting).Error
|
||||
copies, _ := strconv.Atoi(copiesSetting.Value)
|
||||
if copies == 0 || copies > len(upgradeTree.Children) {
|
||||
copies = len(upgradeTree.Children)
|
||||
}
|
||||
for i := 0; i < copies; i++ {
|
||||
upgradeTree.Children[i].IsCheck = false
|
||||
upgradeTree.Children[i].IsRecommend = false
|
||||
}
|
||||
} else {
|
||||
upgradeTree.Children[0].IsCheck = false
|
||||
upgradeTree.Children[0].IsRecommend = false
|
||||
}
|
||||
}
|
||||
return upgradeTree
|
||||
}
|
||||
|
||||
func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
pathItem := path.Join(global.Dir.BaseDir, "1panel/agent/package")
|
||||
itemTree := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "agent_packages",
|
||||
IsCheck: false,
|
||||
IsRecommend: true,
|
||||
Type: "agent",
|
||||
}
|
||||
files, _ := os.ReadDir(pathItem)
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
itemSize, _ := fileOp.GetDirSize(path.Join(pathItem, file.Name()))
|
||||
itemTree.Size += uint64(itemSize)
|
||||
itemTree.Children = append(itemTree.Children, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: file.Name(),
|
||||
Name: file.Name(),
|
||||
Size: uint64(itemSize),
|
||||
IsCheck: true,
|
||||
IsRecommend: true,
|
||||
Type: "agent",
|
||||
})
|
||||
} else {
|
||||
itemSize, _ := file.Info()
|
||||
itemName := file.Name()
|
||||
isCurrentVersion := strings.HasPrefix(itemName, fmt.Sprintf("1panel-agent_%s_", global.CONF.Base.Version))
|
||||
if isCurrentVersion {
|
||||
continue
|
||||
}
|
||||
itemTree.Size += uint64(itemSize.Size())
|
||||
itemTree.Children = append(itemTree.Children, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: itemName,
|
||||
Name: itemName,
|
||||
Size: uint64(itemSize.Size()),
|
||||
IsCheck: !isCurrentVersion,
|
||||
IsRecommend: true,
|
||||
Type: "agent",
|
||||
})
|
||||
}
|
||||
}
|
||||
if itemTree.Size == 0 {
|
||||
itemTree.IsCheck = false
|
||||
}
|
||||
return itemTree
|
||||
}
|
||||
|
||||
func loadBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
var treeData []dto.CleanTree
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/app"), "tmp_backup_app", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/website"), "tmp_backup_website", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/database"), "tmp_backup_database", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/system"), "tmp_backup_snapshot", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/directory"), "tmp_backup_directory", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/log"), "tmp_backup_log", fileOp)
|
||||
|
||||
tmpSize, _ := fileOp.GetDirSize(path.Join(global.Dir.LocalBackupDir, "tmp"))
|
||||
treeData = append(treeData, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "tmp_backup",
|
||||
Size: uint64(tmpSize),
|
||||
IsCheck: tmpSize != 0,
|
||||
IsRecommend: true,
|
||||
Type: "tmp_backup",
|
||||
})
|
||||
backupRecords, _ := backupRepo.ListRecord()
|
||||
var recordMap = make(map[string][]string)
|
||||
for _, record := range backupRecords {
|
||||
if val, ok := recordMap[record.FileDir]; ok {
|
||||
val = append(val, record.FileName)
|
||||
recordMap[record.FileDir] = val
|
||||
} else {
|
||||
recordMap[record.FileDir] = []string{record.FileName}
|
||||
}
|
||||
}
|
||||
|
||||
treeData = append(treeData, loadUnknownApps(fileOp, recordMap))
|
||||
treeData = append(treeData, loadUnknownDbs(fileOp, recordMap))
|
||||
treeData = append(treeData, loadUnknownWebsites(fileOp, recordMap))
|
||||
treeData = append(treeData, loadUnknownSnapshot(fileOp))
|
||||
treeData = append(treeData, loadUnknownWebsiteLog(fileOp))
|
||||
return treeData
|
||||
}
|
||||
|
||||
func loadUnknownApps(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
|
||||
apps, _ := appInstallRepo.ListBy(context.Background())
|
||||
var excludePaths []string
|
||||
for _, app := range apps {
|
||||
itemName := fmt.Sprintf("app/%s/%s", app.App.Key, app.Name)
|
||||
if val, ok := recordMap[itemName]; ok {
|
||||
for _, item := range val {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
|
||||
}
|
||||
}
|
||||
}
|
||||
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, backupPath, &treeData, excludePaths)
|
||||
return treeData
|
||||
}
|
||||
func loadUnknownDbs(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
|
||||
dbs, _ := databaseRepo.GetList()
|
||||
var excludePaths []string
|
||||
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 := 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, 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.Alias)
|
||||
if val, ok := recordMap[itemName]; ok {
|
||||
for _, item := range val {
|
||||
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
|
||||
}
|
||||
}
|
||||
}
|
||||
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, backupPath, &treeData, excludePaths)
|
||||
return treeData
|
||||
}
|
||||
func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
snaps, _ := snapshotRepo.GetList()
|
||||
var excludePaths []string
|
||||
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",
|
||||
}
|
||||
entries, _ := os.ReadDir(backupPath)
|
||||
for _, entry := range entries {
|
||||
childPath := filepath.Join(backupPath, entry.Name())
|
||||
if isExactPathMatch(childPath, excludePaths) {
|
||||
continue
|
||||
}
|
||||
childNode := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: entry.Name(),
|
||||
IsCheck: false,
|
||||
IsRecommend: false,
|
||||
Name: childPath,
|
||||
Type: "unknown_backup",
|
||||
}
|
||||
if entry.IsDir() {
|
||||
itemSize, _ := fileOp.GetDirSize(childPath)
|
||||
childNode.Size = uint64(itemSize)
|
||||
childNode.IsCheck = true
|
||||
childNode.IsRecommend = true
|
||||
treeData.Size += childNode.Size
|
||||
} else {
|
||||
info, _ := entry.Info()
|
||||
childNode.Size = uint64(info.Size())
|
||||
treeData.Size += childNode.Size
|
||||
}
|
||||
|
||||
treeData.Children = append(treeData.Children, childNode)
|
||||
}
|
||||
return treeData
|
||||
}
|
||||
|
||||
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp) dto.CleanTree {
|
||||
treeData := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "unknown_website_log",
|
||||
IsCheck: false,
|
||||
IsRecommend: true,
|
||||
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()
|
||||
if _, ok := websiteMap[dirName]; !ok {
|
||||
dirPath := path.Join(dir, dirName)
|
||||
itemSize, _ := fileOp.GetDirSize(dirPath)
|
||||
childData := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: dirName,
|
||||
IsCheck: true,
|
||||
IsRecommend: true,
|
||||
Name: dirPath,
|
||||
Type: "unknown_backup",
|
||||
Size: uint64(itemSize),
|
||||
}
|
||||
treeData.Size += uint64(itemSize)
|
||||
treeData.Children = append(treeData.Children, childData)
|
||||
}
|
||||
}
|
||||
if treeData.Size > 0 {
|
||||
treeData.IsCheck = true
|
||||
}
|
||||
return treeData
|
||||
}
|
||||
|
||||
func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, rootTree *dto.CleanTree, excludes []string) error {
|
||||
index++
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
childPath := filepath.Join(dir, entry.Name())
|
||||
if isExactPathMatch(childPath, excludes) {
|
||||
continue
|
||||
}
|
||||
childNode := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: entry.Name(),
|
||||
IsCheck: false,
|
||||
IsRecommend: false,
|
||||
Name: childPath,
|
||||
Type: "unknown_backup",
|
||||
}
|
||||
if entry.IsDir() {
|
||||
if index < 4 {
|
||||
if err = loadFileOrDirWithExclude(fileOp, index, childPath, &childNode, excludes); err != nil {
|
||||
return err
|
||||
}
|
||||
childNode.Size = 0
|
||||
for _, child := range childNode.Children {
|
||||
childNode.Size += child.Size
|
||||
}
|
||||
rootTree.Size += childNode.Size
|
||||
} else {
|
||||
itemSize, _ := fileOp.GetDirSize(childPath)
|
||||
childNode.Size = uint64(itemSize)
|
||||
rootTree.Size += childNode.Size
|
||||
}
|
||||
} else {
|
||||
info, _ := entry.Info()
|
||||
childNode.Size = uint64(info.Size())
|
||||
rootTree.Size += childNode.Size
|
||||
}
|
||||
|
||||
rootTree.Children = append(rootTree.Children, childNode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isExactPathMatch(path string, excludePaths []string) bool {
|
||||
cleanPath := filepath.Clean(path)
|
||||
|
||||
for _, excludePath := range excludePaths {
|
||||
cleanExclude := filepath.Clean(excludePath)
|
||||
if cleanPath == cleanExclude {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func loadRollBackTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
var treeData []dto.CleanTree
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "app"), "rollback_app", fileOp)
|
||||
@@ -360,7 +605,6 @@ func loadRollBackTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
|
||||
func loadUploadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
var treeData []dto.CleanTree
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, tmpUploadPath), "upload_tmp", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "app"), "upload_app", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "website"), "upload_website", fileOp)
|
||||
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "database"), "upload_database", fileOp)
|
||||
@@ -382,6 +626,22 @@ func loadDownloadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
uploadTreeData := loadTreeWithAllFile(true, path5, "download", path5, fileOp)
|
||||
treeData = append(treeData, uploadTreeData...)
|
||||
|
||||
appTmpDownloadTree := loadAppTmpDownloadTree(fileOp)
|
||||
if len(appTmpDownloadTree) > 0 {
|
||||
parentTree := dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "app_tmp_download",
|
||||
IsCheck: true,
|
||||
IsRecommend: true,
|
||||
Type: "app_tmp_download",
|
||||
Name: "apps",
|
||||
}
|
||||
for _, child := range appTmpDownloadTree {
|
||||
parentTree.Size += child.Size
|
||||
}
|
||||
parentTree.Children = appTmpDownloadTree
|
||||
treeData = append(treeData, parentTree)
|
||||
}
|
||||
return treeData
|
||||
}
|
||||
|
||||
@@ -393,26 +653,113 @@ func loadLogTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
for _, file := range list1 {
|
||||
size += file.Size
|
||||
}
|
||||
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: uint64(size), Children: list1, Type: "system_log", IsRecommend: true})
|
||||
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: size, Children: list1, Type: "system_log", IsRecommend: true})
|
||||
|
||||
path2 := path.Join(global.Dir.TaskDir)
|
||||
list2 := loadTreeWithDir(false, "task_log", path2, fileOp)
|
||||
size2, _ := fileOp.GetDirSize(path2)
|
||||
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "task_log", Size: uint64(size2), Children: list2, Type: "task_log"})
|
||||
|
||||
path3 := path.Join(global.Dir.TmpDir, "script")
|
||||
list3 := loadTreeWithAllFile(true, path3, "script", path3, fileOp)
|
||||
size3, _ := fileOp.GetDirSize(path3)
|
||||
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "script", Size: uint64(size3), Children: list3, Type: "script", IsRecommend: true})
|
||||
websiteLogList := loadWebsiteLogTree(fileOp)
|
||||
logTotalSize := uint64(0)
|
||||
for _, websiteLog := range websiteLogList {
|
||||
logTotalSize += websiteLog.Size
|
||||
}
|
||||
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "website_log", Size: logTotalSize, Children: websiteLogList, Type: "website_log", IsRecommend: false})
|
||||
|
||||
return treeData
|
||||
}
|
||||
|
||||
func loadWebsiteLogTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
websites, _ := websiteRepo.List()
|
||||
if len(websites) == 0 {
|
||||
return nil
|
||||
}
|
||||
var res []dto.CleanTree
|
||||
for _, website := range websites {
|
||||
size3, _ := fileOp.GetDirSize(path.Join(GetSiteDir(website.Alias), "log"))
|
||||
res = append(res, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: website.PrimaryDomain,
|
||||
Size: uint64(size3),
|
||||
Type: "website_log",
|
||||
Name: website.Alias,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func loadAppTmpDownloadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
|
||||
appDirs, err := os.ReadDir(global.Dir.RemoteAppResourceDir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var res []dto.CleanTree
|
||||
for _, appDir := range appDirs {
|
||||
if !appDir.IsDir() {
|
||||
continue
|
||||
}
|
||||
appKey := appDir.Name()
|
||||
app, _ := appRepo.GetFirst(appRepo.WithKey(appKey))
|
||||
if app.ID == 0 {
|
||||
continue
|
||||
}
|
||||
appPath := filepath.Join(global.Dir.RemoteAppResourceDir, appKey)
|
||||
versionDirs, err := os.ReadDir(appPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
|
||||
existingVersions := make(map[string]bool)
|
||||
for _, appDetail := range appDetails {
|
||||
existingVersions[appDetail.Version] = true
|
||||
}
|
||||
var missingVersions []string
|
||||
for _, versionDir := range versionDirs {
|
||||
if !versionDir.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
version := versionDir.Name()
|
||||
if !existingVersions[version] {
|
||||
missingVersions = append(missingVersions, version)
|
||||
}
|
||||
}
|
||||
if len(missingVersions) > 0 {
|
||||
var appTree dto.CleanTree
|
||||
appTree.ID = uuid.NewString()
|
||||
appTree.Label = app.Name
|
||||
appTree.Type = "app_tmp_download"
|
||||
appTree.Name = appKey
|
||||
appTree.IsRecommend = true
|
||||
appTree.IsCheck = true
|
||||
for _, version := range missingVersions {
|
||||
versionPath := filepath.Join(appPath, version)
|
||||
size, _ := fileOp.GetDirSize(versionPath)
|
||||
appTree.Size += uint64(size)
|
||||
appTree.Children = append(appTree.Children, dto.CleanTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: version,
|
||||
Size: uint64(size),
|
||||
IsCheck: true,
|
||||
IsRecommend: true,
|
||||
Type: "app_tmp_download_version",
|
||||
Name: path.Join(appKey, version),
|
||||
})
|
||||
}
|
||||
res = append(res, appTree)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func loadContainerTree() []dto.CleanTree {
|
||||
var treeData []dto.CleanTree
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return treeData
|
||||
}
|
||||
defer client.Close()
|
||||
diskUsage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
|
||||
if err != nil {
|
||||
return treeData
|
||||
@@ -527,7 +874,7 @@ func loadTreeWithAllFile(isCheck bool, originalPath, treeType, pathItem string,
|
||||
ID: uuid.NewString(),
|
||||
Label: file.Name(),
|
||||
Type: treeType,
|
||||
Size: uint64(size),
|
||||
Size: size,
|
||||
Name: name,
|
||||
IsCheck: isCheck,
|
||||
IsRecommend: isCheck,
|
||||
@@ -545,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()
|
||||
@@ -552,6 +911,7 @@ func dropBuildCache() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
opts := build.CachePruneOptions{}
|
||||
opts.All = true
|
||||
res, err := client.BuildCachePrune(context.Background(), opts)
|
||||
@@ -568,6 +928,7 @@ func dropImages() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
pruneFilters.Add("dangling", "false")
|
||||
res, err := client.ImagesPrune(context.Background(), pruneFilters)
|
||||
@@ -584,6 +945,7 @@ func dropContainers() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
res, err := client.ContainersPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
@@ -599,6 +961,7 @@ func dropVolumes() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
versions, err := client.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
@@ -616,6 +979,18 @@ func dropVolumes() (int, int) {
|
||||
return len(res.VolumesDeleted), int(res.SpaceReclaimed)
|
||||
}
|
||||
|
||||
func dropWebsiteLog(alias string) {
|
||||
accessLogPath := path.Join(GetSiteDir(alias), "log", "access.log")
|
||||
errorLogPath := path.Join(GetSiteDir(alias), "log", "error.log")
|
||||
if err := os.Truncate(accessLogPath, 0); err != nil {
|
||||
global.LOG.Errorf("truncate access log %s failed, err %v", accessLogPath, err)
|
||||
}
|
||||
|
||||
if err := os.Truncate(errorLogPath, 0); err != nil {
|
||||
global.LOG.Errorf("truncate error log %s failed, err %v", errorLogPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func dropTaskLog(logDir string) {
|
||||
files, err := os.ReadDir(logDir)
|
||||
if err != nil {
|
||||
@@ -727,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
|
||||
}
|
||||
|
||||
@@ -50,6 +50,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
|
||||
}
|
||||
@@ -104,6 +107,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)
|
||||
}
|
||||
@@ -131,7 +137,11 @@ func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
if req.AutoMount {
|
||||
if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, ""); err != nil {
|
||||
options := ""
|
||||
if req.NoFail {
|
||||
options = "defaults,nofail"
|
||||
}
|
||||
if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, options); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
}
|
||||
@@ -140,6 +150,9 @@ 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")
|
||||
}
|
||||
|
||||
@@ -463,6 +463,11 @@ func isPointMounted(mountPoint string) bool {
|
||||
}
|
||||
|
||||
func addToFstabWithOptions(device, mountPoint, filesystem, options string) error {
|
||||
uuid, err := getDeviceUUID(device)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get UUID for device %s: %v", device, err)
|
||||
}
|
||||
|
||||
if filesystem == "" {
|
||||
fsType, err := getFilesystemType(device)
|
||||
if err != nil {
|
||||
@@ -476,7 +481,7 @@ func addToFstabWithOptions(device, mountPoint, filesystem, options string) error
|
||||
options = "defaults"
|
||||
}
|
||||
|
||||
entry := fmt.Sprintf("%s %s %s %s 0 2\n", device, mountPoint, filesystem, options)
|
||||
entry := fmt.Sprintf("UUID=%s %s %s %s 0 2\n", uuid, mountPoint, filesystem, options)
|
||||
|
||||
file, err := os.OpenFile("/etc/fstab", os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
@@ -568,3 +573,15 @@ func parseSizeToBytes(sizeStr string) int64 {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
func getDeviceUUID(device string) (string, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -s UUID -o value %s", device))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uuid := strings.TrimSpace(output)
|
||||
if uuid == "" {
|
||||
return "", fmt.Errorf("no UUID found for device %s", device)
|
||||
}
|
||||
return uuid, 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()
|
||||
|
||||
+178
-10
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -56,6 +57,7 @@ type IFileService interface {
|
||||
Compress(c request.FileCompress) error
|
||||
DeCompress(c request.FileDeCompress) error
|
||||
GetContent(op request.FileContentReq) (response.FileInfo, error)
|
||||
GetPreviewContent(op request.FileContentReq) (response.FileInfo, error)
|
||||
SaveContent(edit request.FileEdit) error
|
||||
FileDownload(d request.FileDownload) (string, error)
|
||||
DirSize(req request.DirSizeReq) (response.DirSizeRes, error)
|
||||
@@ -74,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{}
|
||||
}
|
||||
@@ -171,7 +180,6 @@ func shouldFilterPath(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 递归构建文件树(只取当前目录以及当前目录下的第一层子节点)
|
||||
func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error {
|
||||
for _, v := range items {
|
||||
if shouldFilterPath(v.Path) {
|
||||
@@ -271,6 +279,11 @@ func (f *FileService) Delete(op request.FileDelete) error {
|
||||
return fo.DeleteFile(op.Path)
|
||||
}
|
||||
}
|
||||
info, _ := fo.Fs.Stat(op.Path)
|
||||
if info == nil || files.IsSymlink(info.Mode()) {
|
||||
return os.Remove(op.Path)
|
||||
}
|
||||
|
||||
if err := NewIRecycleBinService().Create(request.RecycleBinCreate{SourcePath: op.Path}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,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 {
|
||||
@@ -374,6 +386,82 @@ func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo,
|
||||
return response.FileInfo{FileInfo: *info}, nil
|
||||
}
|
||||
|
||||
func (f *FileService) GetPreviewContent(op request.FileContentReq) (response.FileInfo, error) {
|
||||
info, err := files.NewFileInfo(files.FileOption{
|
||||
Path: op.Path,
|
||||
Expand: false,
|
||||
IsDetail: op.IsDetail,
|
||||
})
|
||||
if err != nil {
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
|
||||
if files.IsBlockDevice(info.FileMode) {
|
||||
return response.FileInfo{FileInfo: *info}, nil
|
||||
}
|
||||
|
||||
file, err := os.Open(op.Path)
|
||||
if err != nil {
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
headBuf := make([]byte, 1024)
|
||||
n, err := file.Read(headBuf)
|
||||
if err != nil && err != io.EOF {
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
headBuf = headBuf[:n]
|
||||
|
||||
if len(headBuf) > 0 && files.DetectBinary(headBuf) {
|
||||
return response.FileInfo{FileInfo: *info}, nil
|
||||
}
|
||||
|
||||
const maxSize = 10 * 1024 * 1024
|
||||
if info.Size <= maxSize {
|
||||
if _, err := file.Seek(0, 0); err != nil {
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
info.Content = string(content)
|
||||
} else {
|
||||
lines, err := files.TailFromEnd(op.Path, 300)
|
||||
if err != nil {
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
info.Content = strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
content := []byte(info.Content)
|
||||
if len(content) > 1024 {
|
||||
content = content[:1024]
|
||||
}
|
||||
if !utf8.Valid(content) {
|
||||
_, decodeName, _ := charset.DetermineEncoding(content, "")
|
||||
decoder := files.GetDecoderByName(decodeName)
|
||||
if decoder != nil {
|
||||
reader := strings.NewReader(info.Content)
|
||||
var dec *encoding.Decoder
|
||||
if decodeName == "windows-1252" {
|
||||
dec = simplifiedchinese.GBK.NewDecoder()
|
||||
} else {
|
||||
dec = decoder.NewDecoder()
|
||||
}
|
||||
decodedReader := transform.NewReader(reader, dec)
|
||||
contents, err := io.ReadAll(decodedReader)
|
||||
if err != nil {
|
||||
return response.FileInfo{}, err
|
||||
}
|
||||
info.Content = string(contents)
|
||||
}
|
||||
}
|
||||
|
||||
return response.FileInfo{FileInfo: *info}, nil
|
||||
}
|
||||
|
||||
func (f *FileService) SaveContent(edit request.FileEdit) error {
|
||||
info, err := files.NewFileInfo(files.FileOption{
|
||||
Path: edit.Path,
|
||||
@@ -577,6 +665,9 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
configPath = pathSet.Value
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
file, err := os.Open(logFilePath)
|
||||
@@ -676,6 +767,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 {
|
||||
@@ -702,6 +839,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 {
|
||||
|
||||
+20
-134
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
@@ -23,9 +21,6 @@ import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
const confPath = "/etc/sysctl.conf"
|
||||
const panelSysctlPath = "/etc/sysctl.d/98-onepanel.conf"
|
||||
|
||||
type FirewallService struct{}
|
||||
|
||||
type IFirewallService interface {
|
||||
@@ -62,7 +57,7 @@ func (u *FirewallService) LoadBaseInfo(tab string) (dto.FirewallBaseInfo, error)
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
baseInfo.PingStatus = u.pingStatus()
|
||||
baseInfo.PingStatus = firewall.LoadPingStatus()
|
||||
baseInfo.Version, _ = client.Version()
|
||||
}()
|
||||
go func() {
|
||||
@@ -117,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
|
||||
@@ -206,10 +187,18 @@ func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
|
||||
return err
|
||||
}
|
||||
needRestartDocker = true
|
||||
case "disablePing":
|
||||
return u.updatePingStatus("0")
|
||||
case "enablePing":
|
||||
return u.updatePingStatus("1")
|
||||
case "disableBanPing":
|
||||
if err := firewall.UpdatePingStatus("0"); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.Update("BanPing", constant.StatusDisable)
|
||||
return nil
|
||||
case "enableBanPing":
|
||||
if err := firewall.UpdatePingStatus("1"); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.Update("BanPing", constant.StatusEnable)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("not supported operation: %s", req.Operation)
|
||||
}
|
||||
@@ -589,105 +578,6 @@ func (u *FirewallService) cleanUnUsedData(client firewall.FirewallClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *FirewallService) pingStatus() string {
|
||||
data, err := os.ReadFile("/proc/sys/net/ipv4/icmp_echo_ignore_all")
|
||||
if err != nil {
|
||||
return constant.StatusNone
|
||||
}
|
||||
v6Data, v6err := os.ReadFile("/proc/sys/net/ipv6/icmp/echo_ignore_all")
|
||||
if v6err != nil {
|
||||
if strings.TrimSpace(string(data)) == "1" {
|
||||
return constant.StatusEnable
|
||||
}
|
||||
return constant.StatusDisable
|
||||
} else {
|
||||
if strings.TrimSpace(string(data)) == "1" && strings.TrimSpace(string(v6Data)) == "1" {
|
||||
return constant.StatusEnable
|
||||
}
|
||||
return constant.StatusDisable
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (u *FirewallService) updatePingStatus(enable string) error {
|
||||
var targetPath string
|
||||
var applyCmd string
|
||||
|
||||
if _, err := os.Stat(confPath); os.IsNotExist(err) {
|
||||
// Debian 13
|
||||
targetPath = panelSysctlPath
|
||||
applyCmd = fmt.Sprintf("%s sysctl --system", cmd.SudoHandleCmd())
|
||||
if err := cmd.RunDefaultBashCf("%s mkdir -p /etc/sysctl.d", cmd.SudoHandleCmd()); err != nil {
|
||||
return fmt.Errorf("failed to create directory /etc/sysctl.d: %v", err)
|
||||
}
|
||||
} else {
|
||||
targetPath = confPath
|
||||
applyCmd = fmt.Sprintf("%s sysctl -p", cmd.SudoHandleCmd())
|
||||
}
|
||||
|
||||
lineBytes, err := os.ReadFile(targetPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to read %s: %v", targetPath, err)
|
||||
}
|
||||
|
||||
if err := cmd.RunDefaultBashCf("echo %s | %s tee /proc/sys/net/ipv4/icmp_echo_ignore_all > /dev/null", enable, cmd.SudoHandleCmd()); err != nil {
|
||||
return fmt.Errorf("failed to apply ipv4 ping status temporarily: %v", err)
|
||||
}
|
||||
|
||||
var hasIpv6 bool
|
||||
if _, err := os.Stat("/proc/sys/net/ipv6/icmp/echo_ignore_all"); err == nil {
|
||||
hasIpv6 = true
|
||||
if err := cmd.RunDefaultBashCf("echo %s | %s tee /proc/sys/net/ipv6/icmp/echo_ignore_all > /dev/null", enable, cmd.SudoHandleCmd()); err != nil {
|
||||
global.LOG.Warnf("failed to apply ipv6 ping status temporarily: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var files []string
|
||||
if err == nil {
|
||||
files = strings.Split(string(lineBytes), "\n")
|
||||
}
|
||||
|
||||
var newFiles []string
|
||||
hasIPv4Line, hasIPv6Line := false, false
|
||||
|
||||
for _, line := range files {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "net.ipv4.icmp_echo_ignore_all") {
|
||||
newFiles = append(newFiles, "net.ipv4.icmp_echo_ignore_all="+enable)
|
||||
hasIPv4Line = true
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "net.ipv6.icmp.echo_ignore_all") {
|
||||
newFiles = append(newFiles, "net.ipv6.icmp.echo_ignore_all="+enable)
|
||||
hasIPv6Line = true
|
||||
continue
|
||||
}
|
||||
newFiles = append(newFiles, line)
|
||||
}
|
||||
|
||||
if !hasIPv4Line {
|
||||
newFiles = append(newFiles, "net.ipv4.icmp_echo_ignore_all="+enable)
|
||||
}
|
||||
if hasIpv6 && !hasIPv6Line {
|
||||
newFiles = append(newFiles, "net.ipv6.icmp.echo_ignore_all="+enable)
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %s: %v", targetPath, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil {
|
||||
return fmt.Errorf("failed to write to %s: %v", targetPath, err)
|
||||
}
|
||||
|
||||
if err := cmd.RunDefaultBashC(applyCmd); err != nil {
|
||||
global.LOG.Warnf("failed to apply persistent config with '%s': %v", applyCmd, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) error {
|
||||
if !global.IsMaster {
|
||||
if err := client.Port(fireClient.FireInfo{Port: global.CONF.Base.Port, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
@@ -711,6 +601,9 @@ func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) er
|
||||
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "udp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Reload()
|
||||
}
|
||||
@@ -813,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))
|
||||
|
||||
+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 {
|
||||
@@ -263,7 +263,15 @@ func stopBeforeUpdateRepo() error {
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := controller.HandleRestart("docker"); err != nil {
|
||||
dockerService, _ := controller.LoadServiceName("docker")
|
||||
if len(dockerService) == 0 {
|
||||
return errors.New("docker service not found")
|
||||
}
|
||||
client, err := controller.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.Operate("restart", dockerService); err != nil {
|
||||
return fmt.Errorf("failed to restart Docker: %v", err)
|
||||
}
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
@@ -276,8 +284,8 @@ func stopBeforeUpdateRepo() error {
|
||||
cancel()
|
||||
return errors.New("the docker service cannot be restarted")
|
||||
default:
|
||||
active, err := controller.CheckActive("docker")
|
||||
if active && err != nil {
|
||||
active, _ := client.IsActive(dockerService)
|
||||
if active {
|
||||
global.LOG.Info("docker restart with new conf successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -363,6 +375,9 @@ func initPreRules() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p udp -m udp --dport 443 -j ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.DropAllTcp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -137,16 +137,45 @@ func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
|
||||
return gpuInfo.GPUs[i].Index < gpuInfo.GPUs[j].Index
|
||||
})
|
||||
for _, item := range gpuInfo.GPUs {
|
||||
var chartHide dto.GPUChartHide
|
||||
chartHide.ProductName = fmt.Sprintf("%d - %s", item.Index, item.ProductName)
|
||||
chartHide.GPU = item.GPUUtil == "" || item.GPUUtil == "N/A"
|
||||
if (item.MemTotal == "" || item.MemTotal == "N/A") && (item.MemUsed == "" || item.MemUsed == "N/A") {
|
||||
chartHide.Memory = true
|
||||
}
|
||||
if (item.MaxPowerLimit == "" || item.MaxPowerLimit == "N/A") && (item.PowerDraw == "" || item.PowerDraw == "N/A") {
|
||||
chartHide.Power = true
|
||||
}
|
||||
chartHide.Temperature = item.Temperature == "" || item.Temperature == "N/A"
|
||||
chartHide.Speed = item.FanSpeed == "" || item.FanSpeed == "N/A"
|
||||
data.ChartHide = append(data.ChartHide, chartHide)
|
||||
data.Options = append(data.Options, fmt.Sprintf("%d - %s", item.Index, item.ProductName))
|
||||
}
|
||||
return data
|
||||
} else {
|
||||
data.GPUType = "xpu"
|
||||
var err error
|
||||
data.Options, err = xpuClient.LoadDeviceList()
|
||||
if err != nil || len(data.Options) == 0 {
|
||||
xpu, err := xpuClient.LoadGpuInfo()
|
||||
if err != nil || len(xpu.Xpu) == 0 {
|
||||
global.LOG.Error("Load XPU info failed or no XPU found, err: ", err)
|
||||
}
|
||||
sort.Slice(xpu.Xpu, func(i, j int) bool {
|
||||
return xpu.Xpu[i].Basic.DeviceID < xpu.Xpu[j].Basic.DeviceID
|
||||
})
|
||||
for _, item := range xpu.Xpu {
|
||||
var chartHide dto.GPUChartHide
|
||||
chartHide.GPU = true
|
||||
chartHide.Speed = true
|
||||
chartHide.ProductName = fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName)
|
||||
if (item.Stats.MemoryUsed == "" || item.Stats.MemoryUsed == "N/A") && (item.Basic.Memory == "" || item.Basic.FreeMemory == "N/A") {
|
||||
chartHide.Memory = true
|
||||
}
|
||||
if item.Stats.Power == "" || item.Stats.Power == "N/A" {
|
||||
chartHide.Power = true
|
||||
}
|
||||
chartHide.Temperature = item.Stats.Temperature == "" || item.Stats.Temperature == "N/A"
|
||||
data.ChartHide = append(data.ChartHide, chartHide)
|
||||
data.Options = append(data.Options, fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName))
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -182,8 +211,10 @@ func (m *MonitorService) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.Monit
|
||||
}
|
||||
var process []dto.GPUProcess
|
||||
if err := json.Unmarshal([]byte(gpu.Processes), &process); err == nil {
|
||||
data.ProcessCount = append(data.ProcessCount, len(process))
|
||||
data.GPUProcesses = append(data.GPUProcesses, process)
|
||||
} else {
|
||||
data.ProcessCount = append(data.ProcessCount, 0)
|
||||
data.GPUProcesses = append(data.GPUProcesses, []dto.GPUProcess{})
|
||||
}
|
||||
data.SpeedValue = append(data.SpeedValue, gpu.FanSpeed)
|
||||
@@ -383,9 +414,7 @@ func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := monitorRepo.BatchCreateMonitorIO(ioList); err != nil {
|
||||
global.LOG.Errorf("Insert io monitoring data failed, err: %v", err)
|
||||
}
|
||||
_ = monitorRepo.BatchCreateMonitorIO(ioList)
|
||||
m.DiskIO <- ioStat2
|
||||
}
|
||||
}
|
||||
@@ -422,9 +451,7 @@ func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64)
|
||||
}
|
||||
}
|
||||
|
||||
if err := monitorRepo.BatchCreateMonitorNet(netList); err != nil {
|
||||
global.LOG.Errorf("Insert network monitoring data failed, err: %v", err)
|
||||
}
|
||||
_ = monitorRepo.BatchCreateMonitorNet(netList)
|
||||
m.NetIO <- netStat2
|
||||
}
|
||||
}
|
||||
@@ -458,15 +485,15 @@ func loadTopCPU() []dto.Process {
|
||||
}
|
||||
name, err := p.Name()
|
||||
if err != nil {
|
||||
name = "undifine"
|
||||
name = "undefined"
|
||||
}
|
||||
cmd, err := p.Cmdline()
|
||||
if err != nil {
|
||||
cmd = "undifine"
|
||||
cmd = "undefined"
|
||||
}
|
||||
user, err := p.Username()
|
||||
if err != nil {
|
||||
user = "undifine"
|
||||
user = "undefined"
|
||||
}
|
||||
if len(top5) == 5 {
|
||||
top5[minIndex] = dto.Process{Percent: percent, Pid: p.Pid, User: user, Name: name, Cmd: cmd}
|
||||
@@ -509,15 +536,15 @@ func loadTopMem() []dto.Process {
|
||||
}
|
||||
name, err := p.Name()
|
||||
if err != nil {
|
||||
name = "undifine"
|
||||
name = "undefined"
|
||||
}
|
||||
cmd, err := p.Cmdline()
|
||||
if err != nil {
|
||||
cmd = "undifine"
|
||||
cmd = "undefined"
|
||||
}
|
||||
user, err := p.Username()
|
||||
if err != nil {
|
||||
user = "undifine"
|
||||
user = "undefined"
|
||||
}
|
||||
percent, _ := p.MemoryPercent()
|
||||
if len(top5) == 5 {
|
||||
|
||||
@@ -51,7 +51,7 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
|
||||
if website != nil {
|
||||
nginxFull.Website = *website
|
||||
var siteNginxConfig dto.NginxConfig
|
||||
siteConfigPath := GetSitePath(*website, SiteConf)
|
||||
siteConfigPath := GetWebsiteConfigPath(*website)
|
||||
siteNginxConfig.FilePath = siteConfigPath
|
||||
siteNginxContent, err := os.ReadFile(siteConfigPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/jinzhu/copier"
|
||||
@@ -22,6 +23,7 @@ type ISettingService interface {
|
||||
|
||||
TestConnByInfo(req dto.SSHConnData) bool
|
||||
SaveConnInfo(req dto.SSHConnData) error
|
||||
SetDefaultIsConn(req dto.SSHDefaultConn) error
|
||||
GetSystemProxy() (*dto.SystemProxy, error)
|
||||
GetLocalConn() dto.SSHConnData
|
||||
GetSettingByKey(key string) string
|
||||
@@ -125,6 +127,15 @@ func (u *SettingService) SaveConnInfo(req dto.SSHConnData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) SetDefaultIsConn(req dto.SSHDefaultConn) error {
|
||||
if req.DefaultConn == constant.StatusDisable && req.WithReset {
|
||||
if err := settingRepo.Update("LocalSSHConn", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return settingRepo.Update("LocalSSHConnShow", req.DefaultConn)
|
||||
}
|
||||
|
||||
func (u *SettingService) GetSystemProxy() (*dto.SystemProxy, error) {
|
||||
systemProxy := dto.SystemProxy{}
|
||||
systemProxy.Type, _ = settingRepo.GetValueByKey("ProxyType")
|
||||
@@ -138,6 +149,7 @@ func (u *SettingService) GetSystemProxy() (*dto.SystemProxy, error) {
|
||||
|
||||
func (u *SettingService) GetLocalConn() dto.SSHConnData {
|
||||
var data dto.SSHConnData
|
||||
data.LocalSSHConnShow, _ = settingRepo.GetValueByKey("LocalSSHConnShow")
|
||||
connItem, _ := settingRepo.GetValueByKey("LocalSSHConn")
|
||||
if len(connItem) == 0 {
|
||||
return data
|
||||
|
||||
@@ -224,12 +224,16 @@ func loadApps(fileOp fileUtils.FileOp) ([]dto.DataTree, error) {
|
||||
appTreeMap := make(map[string]dto.DataTree)
|
||||
for _, app := range apps {
|
||||
itemApp := dto.DataTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: fmt.Sprintf("%s - %s", app.App.Name, app.Name),
|
||||
Key: app.App.Key,
|
||||
Name: app.Name,
|
||||
ID: uuid.NewString(),
|
||||
Label: fmt.Sprintf("%s - %s", app.App.Name, app.Name),
|
||||
Key: app.App.Key,
|
||||
Name: app.Name,
|
||||
IsLocal: app.App.Resource == "local",
|
||||
}
|
||||
appPath := path.Join(global.Dir.DataDir, "apps", app.App.Key, app.Name)
|
||||
if itemApp.IsLocal {
|
||||
appPath = path.Join(global.Dir.AppDir, "local", strings.TrimPrefix(app.App.Key, "local"), app.Name)
|
||||
}
|
||||
itemAppData := dto.DataTree{ID: uuid.NewString(), Label: "appData", Key: app.App.Key, Name: app.Name, IsCheck: true, Path: appPath}
|
||||
if app.App.Key == constant.AppOpenresty && len(websites) != 0 {
|
||||
itemAppData.IsDisable = true
|
||||
@@ -295,7 +299,11 @@ func loadAppImage(list []dto.DataTree) []dto.DataTree {
|
||||
|
||||
for i := 0; i < len(list); i++ {
|
||||
itemAppImage := dto.DataTree{ID: uuid.NewString(), Label: "appImage"}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("cat %s | grep image: ", path.Join(global.Dir.AppDir, list[i].Key, list[i].Name, "docker-compose.yml"))
|
||||
appPath := path.Join(global.Dir.DataDir, "apps", list[i].Key, list[i].Name)
|
||||
if list[i].IsLocal {
|
||||
appPath = path.Join(global.Dir.AppDir, "local", strings.TrimPrefix(list[i].Key, "local"), list[i].Name)
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("cat %s | grep image: ", path.Join(appPath, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
list[i].Children = append(list[i].Children, itemAppImage)
|
||||
continue
|
||||
@@ -328,11 +336,26 @@ func loadPanelFile(fileOp fileUtils.FileOp) ([]dto.DataTree, error) {
|
||||
Path: path.Join(global.Dir.DataDir, fileItem.Name()),
|
||||
}
|
||||
switch itemData.Label {
|
||||
case "agent", "runtime", "docker", "task", "geo", "secret", "uploads":
|
||||
case "runtime", "docker", "task", "geo", "secret", "uploads":
|
||||
itemData.IsDisable = true
|
||||
case "clamav", "download":
|
||||
panelPath := path.Join(global.Dir.DataDir, itemData.Label)
|
||||
itemData.Children, _ = loadFile(panelPath, 3, fileOp)
|
||||
case "agent":
|
||||
panelPath := path.Join(global.Dir.DataDir, itemData.Label)
|
||||
itemData.Children, _ = loadFile(panelPath, 2, fileOp)
|
||||
itemData.IsCheck = false
|
||||
for i := 0; i < len(itemData.Children); i++ {
|
||||
if itemData.Children[i].Label != "package" {
|
||||
itemData.Children[i].IsDisable = true
|
||||
} else {
|
||||
itemData.Size = -itemData.Children[i].Size
|
||||
itemData.Children[i].IsCheck = false
|
||||
for j := 0; j < len(itemData.Children[i].Children); j++ {
|
||||
itemData.Children[i].Children[j].IsCheck = false
|
||||
}
|
||||
}
|
||||
}
|
||||
case "apps", "backup", "log", "db", "tmp":
|
||||
continue
|
||||
}
|
||||
@@ -341,7 +364,7 @@ func loadPanelFile(fileOp fileUtils.FileOp) ([]dto.DataTree, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
itemData.Size = uint64(sizeItem)
|
||||
itemData.Size = uint64(int64(sizeItem) + int64(itemData.Size))
|
||||
} else {
|
||||
fileInfo, err := fileItem.Info()
|
||||
if err != nil {
|
||||
|
||||
@@ -405,7 +405,7 @@ func snapAppImage(snap snapHelper, req dto.SnapshotCreate, targetDir string) err
|
||||
if len(imageList) != 0 {
|
||||
snap.Task.Log(strings.Join(imageList, " "))
|
||||
snap.Task.Logf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz"))
|
||||
if err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunBashCf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz")); err != nil {
|
||||
if err := cmd.RunDefaultBashCf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz")); err != nil {
|
||||
snap.Task.LogFailedWithErr(i18n.GetMsgByKey("SnapDockerSave"), err)
|
||||
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,54 @@ 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(
|
||||
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,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDBData" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"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 +174,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 +193,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,16 +218,16 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
}
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"RecoverDBData",
|
||||
func(t *task.Task) error {
|
||||
return restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"), &itemHelper)
|
||||
},
|
||||
nil,
|
||||
nil, 0, 90*time.Minute,
|
||||
)
|
||||
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
@@ -277,14 +277,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 +293,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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -465,6 +465,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))
|
||||
|
||||
@@ -88,31 +88,25 @@ func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorR
|
||||
}
|
||||
|
||||
composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
serverDTO.Environments, err = getDockerComposeEnvironments(composeByte)
|
||||
if err != nil {
|
||||
continue
|
||||
if err == nil {
|
||||
serverDTO.Environments, _ = getDockerComposeEnvironments(composeByte)
|
||||
}
|
||||
volumes, err := getDockerComposeVolumes(composeByte)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var defaultVolumes = map[string]string{
|
||||
"${MODEL_PATH}": "${MODEL_PATH}",
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
exist := false
|
||||
for key, value := range defaultVolumes {
|
||||
if key == volume.Source && value == volume.Target {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
var defaultVolumes = map[string]string{
|
||||
"${MODEL_PATH}": "${MODEL_PATH}",
|
||||
}
|
||||
if !exist {
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, volume)
|
||||
for _, volume := range volumes {
|
||||
exist := false
|
||||
for key, value := range defaultVolumes {
|
||||
if key == volume.Source && value == volume.Target {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
items = append(items, serverDTO)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user