mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
256
Commits
v2.0.1
...
v1.10.21-lts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaae8a5d3b | ||
|
|
5ca91157ca | ||
|
|
bad1b0b105 | ||
|
|
d14a149c57 | ||
|
|
9c6c77615e | ||
|
|
913e64bb75 | ||
|
|
fd8ef545e8 | ||
|
|
d068cc40f0 | ||
|
|
ee0e17a496 | ||
|
|
f1afc0f4d9 | ||
|
|
ea1afe7610 | ||
|
|
4ade175a3d | ||
|
|
c6ea06153a | ||
|
|
eb36a0f8cb | ||
|
|
3087c1a6e8 | ||
|
|
e5b0a1c591 | ||
|
|
af41e8e429 | ||
|
|
7e677ee9fa | ||
|
|
eea4cf68ad | ||
|
|
3a400dd15a | ||
|
|
fbbbd52d72 | ||
|
|
27664c7dce | ||
|
|
e1bdd215b1 | ||
|
|
d04a35565e | ||
|
|
7aad2cf4af | ||
|
|
bfa667584a | ||
|
|
1858cb4df8 | ||
|
|
dc30a3d1c3 | ||
|
|
6e690cc6f2 | ||
|
|
911b4076f5 | ||
|
|
4c01b1e542 | ||
|
|
ec372dbb98 | ||
|
|
0b87025c52 | ||
|
|
7d4230ecfb | ||
|
|
d06583773a | ||
|
|
67c1fbd07a | ||
|
|
5c7fab8f01 | ||
|
|
06554375cb | ||
|
|
e65329e7b3 | ||
|
|
f5435c581b | ||
|
|
5e8b23db6a | ||
|
|
f2849aa6ec | ||
|
|
ee947b6468 | ||
|
|
49c072b40d | ||
|
|
204f9f1fbc | ||
|
|
cccbaafd1b | ||
|
|
7bc06bb6b1 | ||
|
|
c16d5190c6 | ||
|
|
ffdd9b9d79 | ||
|
|
8d35c54672 | ||
|
|
eb0164907c | ||
|
|
478d44885c | ||
|
|
541bc456be | ||
|
|
5ce75992a3 | ||
|
|
c8b3ba7f6c | ||
|
|
d92ce86bb8 | ||
|
|
f7b4c8cd28 | ||
|
|
24c063b106 | ||
|
|
e478d2e624 | ||
|
|
dc0f726029 | ||
|
|
784b8da7af | ||
|
|
79e9dc9914 | ||
|
|
4849c6b42d | ||
|
|
024d142c8d | ||
|
|
126aea931c | ||
|
|
2372281d5e | ||
|
|
376d6609f2 | ||
|
|
96ab4c53af | ||
|
|
96cf46d834 | ||
|
|
208414704d | ||
|
|
7f453aa145 | ||
|
|
123997fa13 | ||
|
|
7faef2f8a7 | ||
|
|
f93cf3b996 | ||
|
|
df5b69756c | ||
|
|
745bb98abe | ||
|
|
cf1d02771f | ||
|
|
52377b0042 | ||
|
|
270501a237 | ||
|
|
2e5283b8d3 | ||
|
|
8cad47b29e | ||
|
|
2458d60d80 | ||
|
|
9a79dc4cdd | ||
|
|
e531b509d6 | ||
|
|
d287fc1186 | ||
|
|
f47f7c7361 | ||
|
|
7a6c99d57d | ||
|
|
5105114321 | ||
|
|
60beebcb4c | ||
|
|
fa03b4226a | ||
|
|
b047a053c5 | ||
|
|
42907cd228 | ||
|
|
425892cfc4 | ||
|
|
16fd804e93 | ||
|
|
2f147dde13 | ||
|
|
0d04f07f2a | ||
|
|
96485218b7 | ||
|
|
227cc57426 | ||
|
|
b99061777a | ||
|
|
8749dc649a | ||
|
|
65da51a478 | ||
|
|
0c31ba1c5f | ||
|
|
acf94b052b | ||
|
|
556441d811 | ||
|
|
efcf17e47c | ||
|
|
02b7744995 | ||
|
|
c86947b69c | ||
|
|
839b8c2844 | ||
|
|
e4a634e6e8 | ||
|
|
eb99c10ad0 | ||
|
|
175b7b89e4 | ||
|
|
5374afb226 | ||
|
|
1a44f48fad | ||
|
|
50dbe7cfc0 | ||
|
|
c7e576c879 | ||
|
|
a3e4847744 | ||
|
|
903f8ed5a7 | ||
|
|
b091c7f364 | ||
|
|
e069db3c45 | ||
|
|
047264dbbc | ||
|
|
0c216ba87a | ||
|
|
31cdc28143 | ||
|
|
e03b728240 | ||
|
|
4c275ba52f | ||
|
|
2ac9a888a8 | ||
|
|
f9bdcf8938 | ||
|
|
c3bef26ae6 | ||
|
|
634849c347 | ||
|
|
c557b737f6 | ||
|
|
1d326afa93 | ||
|
|
cb6c58cb5d | ||
|
|
ec4d708569 | ||
|
|
05b19cefa5 | ||
|
|
fdd57d0801 | ||
|
|
c16d6d1644 | ||
|
|
656b39c880 | ||
|
|
753c996291 | ||
|
|
dcdd0cc2bb | ||
|
|
2f32df16d9 | ||
|
|
9caa9c47b4 | ||
|
|
55bec8a911 | ||
|
|
43f95fd40e | ||
|
|
641812e7c1 | ||
|
|
95d3e75425 | ||
|
|
7f06c7e92e | ||
|
|
0e6fdffeec | ||
|
|
f77681b9c9 | ||
|
|
535d4bbe1b | ||
|
|
1dfbca3d5b | ||
|
|
d2ca265cc2 | ||
|
|
fc70345fcd | ||
|
|
6ae9b982a9 | ||
|
|
0122b4c6ce | ||
|
|
08c0faa20e | ||
|
|
ab7efea8c5 | ||
|
|
f4623664d5 | ||
|
|
df147435c5 | ||
|
|
71c1c5ae83 | ||
|
|
0ea142d6db | ||
|
|
f88a3059be | ||
|
|
48281ddf86 | ||
|
|
7cdcf9b757 | ||
|
|
ef0bb12fbd | ||
|
|
a0857f86b2 | ||
|
|
2219b3f47b | ||
|
|
b6c873f22d | ||
|
|
64bc92a7fd | ||
|
|
6c4c0f7595 | ||
|
|
2d9c977805 | ||
|
|
dff0375848 | ||
|
|
ab825a1a70 | ||
|
|
466617847f | ||
|
|
a53fff2416 | ||
|
|
dd1430dbbf | ||
|
|
5aad16968b | ||
|
|
f2d3acb650 | ||
|
|
666f8c813d | ||
|
|
e87fb7b50e | ||
|
|
75612879c7 | ||
|
|
94e0c80d47 | ||
|
|
feb6f4cbe0 | ||
|
|
1173bbf628 | ||
|
|
1cdc2e93ad | ||
|
|
3356bd8825 | ||
|
|
decc7a4a1e | ||
|
|
a397cab61c | ||
|
|
228e3d9ae3 | ||
|
|
051f670453 | ||
|
|
cf8aa86a67 | ||
|
|
5b00286e65 | ||
|
|
6597caaf05 | ||
|
|
5851124dee | ||
|
|
b18689a0d6 | ||
|
|
a5e7f78a1f | ||
|
|
a1d394fbc2 | ||
|
|
5e82bda666 | ||
|
|
3d5580ad9d | ||
|
|
c83cfbe8b0 | ||
|
|
00cc1b62ef | ||
|
|
e782f75863 | ||
|
|
f5f379465f | ||
|
|
70a8fda449 | ||
|
|
ca14302191 | ||
|
|
7297c2626f | ||
|
|
92a3982f21 | ||
|
|
626f5efdcd | ||
|
|
24e780b984 | ||
|
|
596d4aceb6 | ||
|
|
e770254b65 | ||
|
|
7901c26d32 | ||
|
|
fc65f8f689 | ||
|
|
e0a5e14978 | ||
|
|
07562c203a | ||
|
|
8187a19e6e | ||
|
|
32cd684307 | ||
|
|
3a72fc95d9 | ||
|
|
64405fba3d | ||
|
|
cc7981229c | ||
|
|
3ec208dc85 | ||
|
|
1b92b6cbfa | ||
|
|
779addfcb4 | ||
|
|
7da5f7a884 | ||
|
|
34e14636d4 | ||
|
|
aa222aac31 | ||
|
|
5134caa103 | ||
|
|
dca3c92d72 | ||
|
|
a85d5bad46 | ||
|
|
716613a4db | ||
|
|
12eaa66bc5 | ||
|
|
a6af97e3c1 | ||
|
|
d561c86c68 | ||
|
|
f243d1c9b4 | ||
|
|
1cc2cc6400 | ||
|
|
15f4c05772 | ||
|
|
c9a204b7a7 | ||
|
|
3d0018c0bd | ||
|
|
d639e46bc7 | ||
|
|
fa633bf419 | ||
|
|
40f7ef90b3 | ||
|
|
50efe607d4 | ||
|
|
3b057f2c27 | ||
|
|
d3d018bd44 | ||
|
|
1378609aa2 | ||
|
|
001c896eed | ||
|
|
1cf3a0143c | ||
|
|
f04be521e5 | ||
|
|
2873632a40 | ||
|
|
075879b5b5 | ||
|
|
9b12930376 | ||
|
|
3339ba9bad | ||
|
|
68922676fc | ||
|
|
dcf2afb4a6 | ||
|
|
16a35141dd | ||
|
|
f6c334bdad | ||
|
|
cbb119117b | ||
|
|
cae0a6118f |
@@ -0,0 +1,50 @@
|
||||
name: 'Bug Report'
|
||||
description: 'Report an Bug'
|
||||
title: '[Bug] '
|
||||
assignees: wanghe-fit2cloud
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Contact Information"
|
||||
- type: input
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "Contact Information"
|
||||
description: "The ways to quickly contact you: WeChat group number and nickname, email, etc."
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Environment Information"
|
||||
- type: input
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "1Panel Version"
|
||||
description: "Log in to the 1Panel Web console and check the current version at the bottom right of the page."
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Detailed information"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Problem Description"
|
||||
description: "Briefly describe the issue you’ve encountered."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Steps to Reproduce"
|
||||
description: "How can this issue be reproduced."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "The expected correct result"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Related log output"
|
||||
description: "Please paste any relevant log output here. It will automatically be formatted as code, so no backticks are necessary."
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Additional Information"
|
||||
description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)."
|
||||
@@ -1,61 +1,50 @@
|
||||
name: BUG 提交
|
||||
description: 提交产品缺陷帮助我们更好的改进
|
||||
title: "[BUG]"
|
||||
labels: "类型: 缺陷"
|
||||
name: '反馈缺陷'
|
||||
description: '反馈一个缺陷'
|
||||
title: '[Bug] '
|
||||
assignees: wanghe-fit2cloud
|
||||
body:
|
||||
- type: markdown
|
||||
id: contacts_title
|
||||
attributes:
|
||||
value: "## 联系方式"
|
||||
- type: input
|
||||
id: contacts
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "联系方式"
|
||||
description: "可以快速联系到您的方式:交流群号及昵称、邮箱等"
|
||||
- type: markdown
|
||||
id: environment
|
||||
attributes:
|
||||
value: "## 环境信息"
|
||||
- type: input
|
||||
id: version
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "1Panel 版本"
|
||||
description: "登录 1Panel Web 控制台,在页面右下角查看当前版本。"
|
||||
- type: markdown
|
||||
id: details
|
||||
attributes:
|
||||
value: "## 详细信息"
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: "问题描述"
|
||||
description: "简要描述您碰到的问题"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: how-happened
|
||||
attributes:
|
||||
label: "重现步骤"
|
||||
description: "如果操作可以重现该问题"
|
||||
description: "如何操作可以重现该问题"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expect
|
||||
attributes:
|
||||
label: "期待的正确结果"
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: "相关日志输出"
|
||||
description: "请复制并粘贴任何相关的日志输出。 这将自动格式化为代码,因此无需反引号。"
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: "附加信息"
|
||||
description: "如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。"
|
||||
@@ -0,0 +1,29 @@
|
||||
name: 'Feature Request'
|
||||
description: 'Suggest an idea'
|
||||
title: '[Feature] '
|
||||
assignees: wanghe-fit2cloud
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Environment Information"
|
||||
- type: input
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "1Panel Version"
|
||||
description: "Log in to the 1Panel Web console and check the current version at the bottom right of the page."
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Detailed information"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Please describe your needs or suggestions for improvements"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Please describe the solution you suggest"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Additional Information"
|
||||
description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)."
|
||||
+3
-10
@@ -1,36 +1,29 @@
|
||||
name: 需求建议
|
||||
description: 提出针对本项目的想法和建议
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
name: '功能需求'
|
||||
description: '提出需求或建议'
|
||||
title: '[Feature] '
|
||||
assignees: wanghe-fit2cloud
|
||||
body:
|
||||
- type: markdown
|
||||
id: environment
|
||||
attributes:
|
||||
value: "## 环境信息"
|
||||
- type: input
|
||||
id: version
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "1Panel 版本"
|
||||
description: "登录 1Panel Web 控制台,在页面右下角查看当前版本。"
|
||||
- type: markdown
|
||||
id: details
|
||||
attributes:
|
||||
value: "## 详细信息"
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "请描述您的需求或者改进建议"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: "请描述你建议的实现方案"
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: "附加信息"
|
||||
description: "如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。"
|
||||
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 问题与讨论 / Questions & Discussions
|
||||
- name: Questions & Discussions
|
||||
url: https://github.com/1Panel-dev/1Panel/discussions
|
||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题 / Raise questions about the installation, deployment, use and other aspects of the project.
|
||||
- name: 对 1Panel 项目有其他问题
|
||||
about: Raise questions about the installation, deployment, use and other aspects of the project.
|
||||
- name: 问题与讨论
|
||||
url: https://bbs.fit2cloud.com/c/1p/7
|
||||
about: 如果你对 1Panel 有其他想要提问的,我们欢迎到我们的官方社区进行提问。
|
||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题。
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
name: LLM Code Review
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
llm-code-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fit2cloud/LLM-CodeReview-Action@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
|
||||
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
||||
LANGUAGE: Chinese
|
||||
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
MODEL: qwen2-1.5b-instruct
|
||||
PROMPT: "请检查下面的代码差异是否有不规范、潜在的问题或者优化建议,用中文回答。"
|
||||
top_p: 1
|
||||
temperature: 1
|
||||
# max_tokens: 10000
|
||||
MAX_PATCH_LENGTH: 10000
|
||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||
@@ -24,12 +24,12 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
version: '~> v2'
|
||||
args: release --skip=publish --clean
|
||||
- name: Upload Assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -56,11 +56,11 @@ jobs:
|
||||
dist/*.tar.gz
|
||||
dist/checksums.txt
|
||||
- name: Setup OSSUTIL
|
||||
uses: yizhoumo/setup-ossutil@v1
|
||||
uses: yizhoumo/setup-ossutil@v2
|
||||
with:
|
||||
endpoint: ${{ secrets.OSS_ENDPOINT }}
|
||||
access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }}
|
||||
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
|
||||
ossutil-version: '1.7.14'
|
||||
ossutil-version: '1.7.18'
|
||||
- name: Upload Assets to OSS
|
||||
run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force
|
||||
+2
-3
@@ -29,6 +29,7 @@ build/1panel
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
|
||||
# Dependency directories
|
||||
/pkg/
|
||||
backend/__debug_bin
|
||||
@@ -40,9 +41,7 @@ frontend/auto-imports.d.ts
|
||||
frontend/components.d.ts
|
||||
frontend/src/xpack
|
||||
backend/xpack
|
||||
backend/router/entry_xpack.go
|
||||
backend/server/init_xpack.go
|
||||
backend/utils/xpack/xpack_xpack.go
|
||||
*_xpack.go
|
||||
|
||||
.history/
|
||||
dist/
|
||||
|
||||
+11
-16
@@ -1,5 +1,7 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=jcroql
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# - export NODE_OPTIONS="--max-old-space-size=8192"
|
||||
@@ -40,20 +42,13 @@ archives:
|
||||
- install.sh
|
||||
- README.md
|
||||
- LICENSE
|
||||
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
release:
|
||||
draft: true
|
||||
mode: append
|
||||
extra_files:
|
||||
- glob: dist/*.tar.gz
|
||||
- glob: dist/checksums.txt
|
||||
name_template: "Release {{.Tag}}"
|
||||
|
||||
# The lines beneath this are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3"
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
reviewers:
|
||||
- wanghe-fit2cloud
|
||||
- zhengkunwang223
|
||||
- ssongliu
|
||||
- wanghe-fit2cloud
|
||||
- wangdan-fit2cloud
|
||||
|
||||
approvers:
|
||||
- wanghe-fit2cloud
|
||||
- zhengkunwang223
|
||||
- ssongliu
|
||||
- wanghe-fit2cloud
|
||||
- wangdan-fit2cloud
|
||||
@@ -1,9 +1,10 @@
|
||||
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
||||
<p align="center"><b>Web-based linux server management control panel</b></p>
|
||||
<p align="center"><b>#1 Rated Web-based Linux Server Management Tool</b></p>
|
||||
<p align="center"><b>现代化、开源的 Linux 服务器运维管理面板</b></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: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
<a href="https://hellogithub.com/repository/71791baf930149ac9b84e1acf186573f" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=71791baf930149ac9b84e1acf186573f&claim_uid=p8vB3kP5CMrthiL&theme=dark&theme=neutral" alt="Featured|HelloGitHub" style="width: 250px; height: 55px;" width="250" height="55" /></a>
|
||||
<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: 180px; height: auto;" /></a>
|
||||
<a href="https://hellogithub.com/repository/71791baf930149ac9b84e1acf186573f" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=71791baf930149ac9b84e1acf186573f&claim_uid=p8vB3kP5CMrthiL&theme=dark&theme=neutral" alt="Featured|HelloGitHub" style="width: 180px; height: auto;" /></a>
|
||||
<a href="https://market.aliyun.com/products/53690006/cmjj00062740.html?userCode=kmemb8jp" target="_blank"><img src="https://img.alicdn.com/imgextra/i2/O1CN01H5JIwY1rZ0OobDjnJ_!!6000000005644-2-tps-1000-216.png" alt="1Panel-dev%2F1Panel | Aliyun" style="width: 180px; height: auto;" /></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel?color=%231890FF" alt="License: GPL v3"></a>
|
||||
@@ -45,13 +46,18 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
|
||||
- [社区论坛](https://bbs.fit2cloud.com/c/1p/7)
|
||||
- [如何加入微信交流群?](https://bbs.fit2cloud.com/t/topic/2147)
|
||||
|
||||
## 专业版
|
||||
|
||||
相比于社区版,1Panel 专业版为用户提供了大量增强功能及技术支持服务,增强功能包括 WAF 增强、网站防篡改、网站监控、GPU 监控、黑金主题及自定义界面等。 [点击查看专业版详细介绍](https://www.lxware.cn/1panel)。
|
||||
|
||||
## 飞致云的其他明星项目
|
||||
|
||||
- [MaxKB](https://github.com/1Panel-dev/MaxKB/) - 基于 LLM 大语言模型的开源知识库问答系统
|
||||
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
|
||||
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
|
||||
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
|
||||
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源自动化测试平台
|
||||
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源持续测试工具
|
||||
- [KubePi](https://github.com/1Panel-dev/KubePi/) - 现代化、开源的 K8s 面板
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
|
||||
// @Success 200 {object} model.AppInstall
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/install [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[{"input_column":"name","input_value":"name","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"}
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"安装应用 [name]","formatEN":"Install app [name]"}
|
||||
func (b *BaseApi) InstallApp(c *gin.Context) {
|
||||
var req request.AppInstallCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -120,12 +121,13 @@ func (b *BaseApi) CheckIsSafety(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if status == "disable" && len(code) != 0 {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrNotFound, constant.ErrTypeInternalServer, err)
|
||||
helper.ErrResponse(c, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if status == "unpass" {
|
||||
if middleware.LoadErrCode("err-entrance") != 200 {
|
||||
helper.ErrResponse(c, middleware.LoadErrCode("err-entrance"))
|
||||
code := middleware.LoadErrCode()
|
||||
if code != 200 {
|
||||
helper.ErrResponse(c, code)
|
||||
return
|
||||
}
|
||||
helper.ErrorWithDetail(c, constant.CodeErrEntrance, constant.ErrTypeInternalServer, nil)
|
||||
|
||||
@@ -72,7 +72,7 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
req.StartTime = req.StartTime.In(loc)
|
||||
req.EndTime = req.EndTime.In(loc)
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package v1
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -56,24 +58,16 @@ func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
|
||||
// @Summary Load dashboard current info
|
||||
// @Description 获取首页实时数据
|
||||
// @Accept json
|
||||
// @Param ioOption path string true "request"
|
||||
// @Param netOption path string true "request"
|
||||
// @Param request body dto.DashboardReq true "request"
|
||||
// @Success 200 {object} dto.DashboardCurrent
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /dashboard/current/:ioOption/:netOption [get]
|
||||
// @Router /dashboard/current [post]
|
||||
func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
|
||||
ioOption, ok := c.Params.Get("ioOption")
|
||||
if !ok {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error ioOption in path"))
|
||||
var req dto.DashboardReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
netOption, ok := c.Params.Get("netOption")
|
||||
if !ok {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error netOption in path"))
|
||||
return
|
||||
}
|
||||
|
||||
data := dashboardService.LoadCurrentInfo(ioOption, netOption)
|
||||
data := dashboardService.LoadCurrentInfo(req)
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -336,8 +337,12 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
mode := info.Mode()
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
uid, gid := -1, -1
|
||||
if ok {
|
||||
uid, gid = int(stat.Uid), int(stat.Gid)
|
||||
}
|
||||
|
||||
success := 0
|
||||
failures := make(buserr.MultiErr)
|
||||
@@ -351,6 +356,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
global.LOG.Error(e)
|
||||
continue
|
||||
}
|
||||
_ = os.Chown(dstDir, uid, gid)
|
||||
}
|
||||
tmpFilename := dstFilename + ".tmp"
|
||||
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
|
||||
@@ -378,6 +384,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
} else {
|
||||
_ = os.Chmod(dstFilename, mode)
|
||||
}
|
||||
if uid != -1 && gid != -1 {
|
||||
_ = os.Chown(dstFilename, uid, gid)
|
||||
}
|
||||
success++
|
||||
}
|
||||
if success == 0 {
|
||||
@@ -603,9 +612,17 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
|
||||
if mode == 0 {
|
||||
mode = 0755
|
||||
}
|
||||
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
|
||||
if err = op.CreateDir(dstDir, mode); err != nil {
|
||||
return err
|
||||
uid, gid := -1, -1
|
||||
if info, err := os.Stat(dstDir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = op.CreateDir(dstDir, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
if ok {
|
||||
uid, gid = int(stat.Uid), int(stat.Gid)
|
||||
}
|
||||
}
|
||||
dstFileName := filepath.Join(dstDir, fileName)
|
||||
@@ -615,6 +632,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
|
||||
} else {
|
||||
mode = 0644
|
||||
}
|
||||
|
||||
if overwrite {
|
||||
_ = os.Remove(dstFileName)
|
||||
}
|
||||
@@ -635,6 +653,9 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
|
||||
}
|
||||
_ = os.Remove(chunkPath)
|
||||
}
|
||||
if uid != -1 && gid != -1 {
|
||||
_ = os.Chown(dstFileName, uid, gid)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
req.StartTime = req.StartTime.In(loc)
|
||||
req.EndTime = req.EndTime.In(loc)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ func (b *BaseApi) CreateRuntime(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除网站 [name]","formatEN":"Delete website [name]"}
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除运行环境 [name]","formatEN":"Delete runtime [name]"}
|
||||
func (b *BaseApi) DeleteRuntime(c *gin.Context) {
|
||||
var req request.RuntimeDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -99,12 +99,12 @@ func (b *BaseApi) UpdateSnapDescription(c *gin.Context) {
|
||||
// @Summary Page system snapshot
|
||||
// @Description 获取系统快照列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Param request body dto.PageSnapshot true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/snapshot/search [post]
|
||||
func (b *BaseApi) SearchSnapshot(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
var req dto.PageSnapshot
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -120,6 +120,28 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system snapshot size
|
||||
// @Description 获取系统快照大小
|
||||
// @Accept json
|
||||
// @Param request body dto.PageSnapshot true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/snapshot/size [post]
|
||||
func (b *BaseApi) LoadSnapshotSize(c *gin.Context) {
|
||||
var req dto.PageSnapshot
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := snapshotService.LoadSize(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Recover system backup
|
||||
// @Description 从系统快照恢复
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package dto
|
||||
|
||||
type CreateOrUpdateAlert struct {
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertType string `json:"alertType"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
EntryID uint `json:"entryID"`
|
||||
}
|
||||
|
||||
type AlertBase struct {
|
||||
AlertType string `json:"alertType"`
|
||||
EntryID uint `json:"entryID"`
|
||||
}
|
||||
|
||||
type PushAlert struct {
|
||||
TaskName string `json:"taskName"`
|
||||
AlertType string `json:"alertType"`
|
||||
EntryID uint `json:"entryID"`
|
||||
Param string `json:"param"`
|
||||
}
|
||||
@@ -36,6 +36,7 @@ type CommonBackup struct {
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
Secret string `json:"secret"`
|
||||
FileName string `json:"fileName"`
|
||||
}
|
||||
type CommonRecover struct {
|
||||
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
|
||||
|
||||
@@ -33,6 +33,7 @@ type ClamInfo struct {
|
||||
LastHandleDate string `json:"lastHandleDate"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
}
|
||||
|
||||
type ClamLogSearch struct {
|
||||
@@ -71,6 +72,8 @@ type ClamCreate struct {
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
}
|
||||
|
||||
type ClamUpdate struct {
|
||||
@@ -82,6 +85,8 @@ type ClamUpdate struct {
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
}
|
||||
|
||||
type ClamUpdateStatus struct {
|
||||
|
||||
@@ -116,7 +116,7 @@ type PortHelper struct {
|
||||
|
||||
type ContainerOperation struct {
|
||||
Names []string `json:"names" validate:"required"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause remove"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start stop restart kill pause unpause remove"`
|
||||
}
|
||||
|
||||
type ContainerRename struct {
|
||||
@@ -200,6 +200,7 @@ type ComposeInfo struct {
|
||||
Workdir string `json:"workdir"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
type ComposeContainer struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
@@ -208,22 +209,24 @@ type ComposeContainer struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
type ComposeCreate struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
type ComposeOperation struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop down"`
|
||||
Path string `json:"path"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
|
||||
WithFile bool `json:"withFile"`
|
||||
}
|
||||
type ComposeUpdate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
|
||||
type ContainerLog struct {
|
||||
|
||||
@@ -31,10 +31,13 @@ type CronjobCreate struct {
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
|
||||
Secret string `json:"secret"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
}
|
||||
|
||||
type CronjobUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Spec string `json:"spec" validate:"required"`
|
||||
|
||||
@@ -53,6 +56,8 @@ type CronjobUpdate struct {
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
|
||||
Secret string `json:"secret"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
}
|
||||
|
||||
type CronjobUpdateStatus struct {
|
||||
@@ -99,6 +104,7 @@ type CronjobInfo struct {
|
||||
LastRecordTime string `json:"lastRecordTime"`
|
||||
Status string `json:"status"`
|
||||
Secret string `json:"secret"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
}
|
||||
|
||||
type SearchRecord struct {
|
||||
|
||||
@@ -16,10 +16,11 @@ type DashboardBase struct {
|
||||
KernelArch string `json:"kernelArch"`
|
||||
KernelVersion string `json:"kernelVersion"`
|
||||
VirtualizationSystem string `json:"virtualizationSystem"`
|
||||
|
||||
CPUCores int `json:"cpuCores"`
|
||||
CPULogicalCores int `json:"cpuLogicalCores"`
|
||||
CPUModelName string `json:"cpuModelName"`
|
||||
IpV4Addr string `json:"ipv4Addr"`
|
||||
SystemProxy string `json:"SystemProxy"`
|
||||
CPUCores int `json:"cpuCores"`
|
||||
CPULogicalCores int `json:"cpuLogicalCores"`
|
||||
CPUModelName string `json:"cpuModelName"`
|
||||
|
||||
CurrentInfo DashboardCurrent `json:"currentInfo"`
|
||||
}
|
||||
@@ -34,6 +35,12 @@ type OsInfo struct {
|
||||
DiskSize int64 `json:"diskSize"`
|
||||
}
|
||||
|
||||
type DashboardReq struct {
|
||||
Scope string `json:"scope"`
|
||||
IoOption string `json:"ioOption"`
|
||||
NetOption string `json:"netOption"`
|
||||
}
|
||||
|
||||
type DashboardCurrent struct {
|
||||
Uptime uint64 `json:"uptime"`
|
||||
TimeSinceUptime string `json:"timeSinceUptime"`
|
||||
@@ -72,6 +79,7 @@ type DashboardCurrent struct {
|
||||
NetBytesRecv uint64 `json:"netBytesRecv"`
|
||||
|
||||
GPUData []GPUInfo `json:"gpuData"`
|
||||
XPUData []XPUInfo `json:"xpuData"`
|
||||
|
||||
ShotTime time.Time `json:"shotTime"`
|
||||
}
|
||||
@@ -105,3 +113,13 @@ type GPUInfo struct {
|
||||
MemTotal string `json:"memTotal"`
|
||||
FanSpeed string `json:"fanSpeed"`
|
||||
}
|
||||
|
||||
type XPUInfo struct {
|
||||
DeviceID int `json:"deviceID"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
Memory string `json:"memory"`
|
||||
Temperature string `json:"temperature"`
|
||||
MemoryUsed string `json:"memoryUsed"`
|
||||
Power string `json:"power"`
|
||||
MemoryUtil string `json:"memoryUtil"`
|
||||
}
|
||||
|
||||
@@ -175,21 +175,22 @@ type WebsiteUpdateDirPermission struct {
|
||||
}
|
||||
|
||||
type WebsiteProxyConfig struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
Enable bool `json:"enable" `
|
||||
Cache bool `json:"cache" `
|
||||
CacheTime int `json:"cacheTime" `
|
||||
CacheUnit string `json:"cacheUnit"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Modifier string `json:"modifier"`
|
||||
Match string `json:"match" validate:"required"`
|
||||
ProxyPass string `json:"proxyPass" validate:"required"`
|
||||
ProxyHost string `json:"proxyHost" validate:"required"`
|
||||
Content string `json:"content"`
|
||||
FilePath string `json:"filePath"`
|
||||
Replaces map[string]string `json:"replaces"`
|
||||
SNI bool `json:"sni"`
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
Enable bool `json:"enable" `
|
||||
Cache bool `json:"cache" `
|
||||
CacheTime int `json:"cacheTime" `
|
||||
CacheUnit string `json:"cacheUnit"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Modifier string `json:"modifier"`
|
||||
Match string `json:"match" validate:"required"`
|
||||
ProxyPass string `json:"proxyPass" validate:"required"`
|
||||
ProxyHost string `json:"proxyHost" validate:"required"`
|
||||
Content string `json:"content"`
|
||||
FilePath string `json:"filePath"`
|
||||
Replaces map[string]string `json:"replaces"`
|
||||
SNI bool `json:"sni"`
|
||||
ProxySSLName string `json:"proxySSLName"`
|
||||
}
|
||||
|
||||
type WebsiteProxyReq struct {
|
||||
|
||||
@@ -41,6 +41,7 @@ type WebsiteSSLApply struct {
|
||||
ID uint `json:"ID" validate:"required"`
|
||||
SkipDNSCheck bool `json:"skipDNSCheck"`
|
||||
Nameservers []string `json:"nameservers"`
|
||||
DisableLog bool `json:"disableLog"`
|
||||
}
|
||||
|
||||
type WebsiteAcmeAccountCreate struct {
|
||||
|
||||
@@ -36,10 +36,11 @@ type FileWgetRes struct {
|
||||
}
|
||||
|
||||
type FileLineContent struct {
|
||||
Content string `json:"content"`
|
||||
End bool `json:"end"`
|
||||
Path string `json:"path"`
|
||||
Total int `json:"total"`
|
||||
Content string `json:"content"`
|
||||
End bool `json:"end"`
|
||||
Path string `json:"path"`
|
||||
Total int `json:"total"`
|
||||
Lines []string `json:"lines"`
|
||||
}
|
||||
|
||||
type FileExist struct {
|
||||
|
||||
@@ -30,6 +30,8 @@ type WebsiteRes struct {
|
||||
RuntimeName string `json:"runtimeName"`
|
||||
SSLExpireDate time.Time `json:"sslExpireDate"`
|
||||
SSLStatus string `json:"sslStatus"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
RuntimeType string `json:"runtimeType"`
|
||||
}
|
||||
|
||||
type WebsiteOption struct {
|
||||
|
||||
@@ -30,6 +30,7 @@ type SettingInfo struct {
|
||||
ServerPort string `json:"serverPort"`
|
||||
SSL string `json:"ssl"`
|
||||
SSLType string `json:"sslType"`
|
||||
AutoRestart string `json:"autoRestart"`
|
||||
BindDomain string `json:"bindDomain"`
|
||||
AllowIPs string `json:"allowIPs"`
|
||||
SecurityEntrance string `json:"securityEntrance"`
|
||||
@@ -73,12 +74,13 @@ type SettingUpdate struct {
|
||||
}
|
||||
|
||||
type SSLUpdate struct {
|
||||
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
|
||||
Domain string `json:"domain"`
|
||||
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
SSLID uint `json:"sslID"`
|
||||
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
|
||||
Domain string `json:"domain"`
|
||||
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
SSLID uint `json:"sslID"`
|
||||
AutoRestart string `json:"autoRestart"`
|
||||
}
|
||||
type SSLInfo struct {
|
||||
Domain string `json:"domain"`
|
||||
@@ -98,6 +100,12 @@ type PortUpdate struct {
|
||||
ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"`
|
||||
}
|
||||
|
||||
type PageSnapshot struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
}
|
||||
type SnapshotStatus struct {
|
||||
Panel string `json:"panel"`
|
||||
PanelInfo string `json:"panelInfo"`
|
||||
@@ -141,9 +149,8 @@ type SnapshotInfo struct {
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Version string `json:"version"`
|
||||
Size int64 `json:"size"`
|
||||
|
||||
InterruptStep string `json:"interruptStep"`
|
||||
RecoverStatus string `json:"recoverStatus"`
|
||||
@@ -154,6 +161,12 @@ type SnapshotInfo struct {
|
||||
LastRollbackedAt string `json:"lastRollbackedAt"`
|
||||
}
|
||||
|
||||
type SnapshotFile struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type UpgradeInfo struct {
|
||||
TestVersion string `json:"testVersion"`
|
||||
NewVersion string `json:"newVersion"`
|
||||
|
||||
@@ -12,4 +12,5 @@ type Compose struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"type:varchar(256)" json:"name"`
|
||||
Path string `gorm:"type:varchar(256)" json:"path"`
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ type IBackupRepo interface {
|
||||
WithByFileName(fileName string) DBOption
|
||||
WithByType(backupType string) DBOption
|
||||
WithByCronID(cronjobID uint) DBOption
|
||||
WithFileNameStartWith(filePrefix string) DBOption
|
||||
}
|
||||
|
||||
func NewIBackupRepo() IBackupRepo {
|
||||
@@ -81,6 +82,12 @@ func (u *BackupRepo) WithByFileName(fileName string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *BackupRepo) WithFileNameStartWith(filePrefix string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("file_name LIKE ?", filePrefix+"%")
|
||||
}
|
||||
}
|
||||
|
||||
func (u *BackupRepo) WithByType(backupType string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(backupType) == 0 {
|
||||
|
||||
@@ -19,6 +19,7 @@ type IComposeTemplateRepo interface {
|
||||
CreateRecord(compose *model.Compose) error
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
ListRecord() ([]model.Compose, error)
|
||||
UpdateRecord(name string, vars map[string]interface{}) error
|
||||
}
|
||||
|
||||
func NewIComposeTemplateRepo() IComposeTemplateRepo {
|
||||
@@ -102,3 +103,7 @@ func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error {
|
||||
}
|
||||
return db.Delete(&model.Compose{}).Error
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateRepo) UpdateRecord(name string, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.Compose{}).Where("name = ?", name).Updates(vars).Error
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption {
|
||||
return db.Where("ca_id = ?", caID)
|
||||
}
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
|
||||
var sslList []model.WebsiteSSL
|
||||
db := getDb(opts...).Model(&model.WebsiteSSL{})
|
||||
|
||||
@@ -91,10 +91,10 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
|
||||
}
|
||||
var appDTOs []*response.AppDto
|
||||
for _, ap := range apps {
|
||||
ap.ReadMe = ""
|
||||
ap.Website = ""
|
||||
ap.Document = ""
|
||||
ap.Github = ""
|
||||
// ap.ReadMe = ""
|
||||
// ap.Website = ""
|
||||
// ap.Document = ""
|
||||
// ap.Github = ""
|
||||
appDTO := &response.AppDto{
|
||||
ID: ap.ID,
|
||||
Name: ap.Name,
|
||||
@@ -145,6 +145,9 @@ func (a AppService) GetAppTags() ([]response.TagDTO, error) {
|
||||
|
||||
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
||||
var appDTO response.AppDTO
|
||||
if key == "postgres" {
|
||||
key = "postgresql"
|
||||
}
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -185,7 +188,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
||||
|
||||
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
||||
if !fileOp.Stat(versionPath) || detail.Update {
|
||||
if err = downloadApp(app, detail, nil); err != nil {
|
||||
if err = downloadApp(app, detail, nil); err != nil && !fileOp.Stat(versionPath) {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,6 +535,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
if app.Key == constant.AppMysql {
|
||||
majorVersion := getMajorVersion(install.Version)
|
||||
if !strings.HasPrefix(detail.Version, majorVersion) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
versions = append(versions, dto.AppVersion{
|
||||
Version: detail.Version,
|
||||
DetailId: detail.ID,
|
||||
@@ -703,6 +709,22 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
}
|
||||
}
|
||||
appParam.Values = form.Values
|
||||
} else if form.Type == "apps" {
|
||||
if m, ok := form.Child.(map[string]interface{}); ok {
|
||||
result := make(map[string]string)
|
||||
for key, value := range m {
|
||||
if strVal, ok := value.(string); ok {
|
||||
result[key] = strVal
|
||||
}
|
||||
}
|
||||
if envKey, ok := result["envKey"]; ok {
|
||||
serviceName := envs[envKey]
|
||||
if serviceName != nil {
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(serviceName.(string)))
|
||||
appParam.ShowValue = appInstall.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params = append(params, appParam)
|
||||
} else {
|
||||
|
||||
@@ -186,17 +186,18 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
||||
}
|
||||
case constant.AppRedis:
|
||||
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
authParam := dto.RedisAuthParam{
|
||||
RootPassword: password.(string),
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
authParam := dto.RedisAuthParam{
|
||||
RootPassword: "",
|
||||
}
|
||||
database.Password = password.(string)
|
||||
if password != "" {
|
||||
authParam.RootPassword = password.(string)
|
||||
database.Password = password.(string)
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
}
|
||||
}
|
||||
if err := databaseRepo.Create(ctx, database); err != nil {
|
||||
@@ -516,8 +517,19 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
)
|
||||
global.LOG.Infof(i18n.GetMsgWithName("UpgradeAppStart", install.Name, nil))
|
||||
if req.Backup {
|
||||
backupRecord, err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name})
|
||||
iBackUpService := NewIBackupService()
|
||||
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||
backupRecord, err := iBackUpService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
|
||||
if err == nil {
|
||||
backups, _ := iBackUpService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
|
||||
if len(backups) > 3 {
|
||||
backupsToDelete := backups[:len(backups)-3]
|
||||
var deleteIDs []uint
|
||||
for _, backup := range backupsToDelete {
|
||||
deleteIDs = append(deleteIDs, backup.ID)
|
||||
}
|
||||
_ = iBackUpService.BatchDeleteRecord(deleteIDs)
|
||||
}
|
||||
localDir, err := loadLocalDir()
|
||||
if err == nil {
|
||||
backupFile = path.Join(localDir, backupRecord.FileDir, backupRecord.FileName)
|
||||
@@ -912,18 +924,34 @@ func upApp(appInstall *model.AppInstall, pullImages bool) {
|
||||
errMsg string
|
||||
)
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
projectName := strings.ToLower(appInstall.Name)
|
||||
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
return err
|
||||
}
|
||||
images, err := composeV2.GetDockerComposeImages(projectName, envByte, []byte(appInstall.DockerCompose))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, image := range images {
|
||||
if out, err = cmd.ExecWithTimeOut("docker pull "+image, 60*time.Minute); err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
} else {
|
||||
if err.Error() == buserr.New(constant.ErrCmdTimeout).Error() {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut")
|
||||
} else {
|
||||
errMsg = i18n.GetMsgWithMap("ErrImagePull", map[string]interface{}{"err": err.Error()})
|
||||
}
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1224,6 +1252,31 @@ func synAppInstall(containers map[string]types.Container, appInstall *model.AppI
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
|
||||
func getMajorVersion(version string) string {
|
||||
parts := strings.Split(version, ".")
|
||||
if len(parts) >= 2 {
|
||||
return parts[0] + "." + parts[1]
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func ignoreUpdate(installed model.AppInstall) bool {
|
||||
if installed.App.Type == "php" || installed.Status == constant.Installing {
|
||||
return true
|
||||
}
|
||||
if installed.App.Key == constant.AppMysql {
|
||||
majorVersion := getMajorVersion(installed.Version)
|
||||
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||
for _, appDetail := range appDetails {
|
||||
if strings.HasPrefix(appDetail.Version, majorVersion) && common.CompareVersion(appDetail.Version, installed.Version) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
|
||||
var (
|
||||
res []response.AppInstallDTO
|
||||
@@ -1246,7 +1299,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
}
|
||||
|
||||
for _, installed := range appInstallList {
|
||||
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
|
||||
if updated && ignoreUpdate(installed) {
|
||||
continue
|
||||
}
|
||||
if sync && !doNotNeedSync(installed) {
|
||||
@@ -1305,6 +1358,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
continue
|
||||
}
|
||||
lastVersion := versions[0]
|
||||
if app.Key == constant.AppMysql {
|
||||
for _, version := range versions {
|
||||
majorVersion := getMajorVersion(installed.Version)
|
||||
if !strings.HasPrefix(version, majorVersion) {
|
||||
continue
|
||||
} else {
|
||||
lastVersion = version
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if common.IsCrossVersion(installed.Version, lastVersion) {
|
||||
installDTO.CanUpdate = app.CrossVersionUpdate
|
||||
} else {
|
||||
|
||||
@@ -40,6 +40,7 @@ type IBackupService interface {
|
||||
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
|
||||
BatchDeleteRecord(ids []uint) error
|
||||
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
||||
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
|
||||
|
||||
ListFiles(req dto.BackupSearchFile) []string
|
||||
|
||||
@@ -100,6 +101,20 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (u *BackupService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
|
||||
records, err := backupRepo.ListRecord(
|
||||
commonRepo.WithOrderBy("created_at asc"),
|
||||
commonRepo.WithByName(name),
|
||||
commonRepo.WithByType("app"),
|
||||
backupRepo.WithFileNameStartWith(fileName),
|
||||
backupRepo.WithByDetailName(detailName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, err
|
||||
}
|
||||
|
||||
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
||||
total, records, err := backupRepo.PageRecord(
|
||||
search.Page, search.PageSize,
|
||||
|
||||
@@ -39,7 +39,10 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
||||
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
|
||||
backupDir := path.Join(localDir, itemDir)
|
||||
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
fileName := req.FileName
|
||||
if req.FileName == "" {
|
||||
fileName = fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
}
|
||||
if err := handleAppBackup(&install, backupDir, fileName, "", req.Secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -92,6 +93,8 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_ = StopAllCronJob(false)
|
||||
}
|
||||
if baseInfo.FreshIsActive {
|
||||
version, err := cmd.Exec("freshclam --version")
|
||||
@@ -139,7 +142,7 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
|
||||
item.LastHandleDate = "-"
|
||||
datas = append(datas, item)
|
||||
}
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for i := 0; i < len(datas); i++ {
|
||||
logPaths := loadFileByName(datas[i].Name)
|
||||
sort.Slice(logPaths, func(i, j int) bool {
|
||||
@@ -152,6 +155,16 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
|
||||
}
|
||||
datas[i].LastHandleDate = t1.Format(constant.DateTimeLayout)
|
||||
}
|
||||
alertBase := dto.AlertBase{
|
||||
AlertType: "clams",
|
||||
EntryID: datas[i].ID,
|
||||
}
|
||||
alertCount := xpack.GetAlert(alertBase)
|
||||
if alertCount != 0 {
|
||||
datas[i].AlertCount = alertCount
|
||||
} else {
|
||||
datas[i].AlertCount = 0
|
||||
}
|
||||
}
|
||||
return total, datas, err
|
||||
}
|
||||
@@ -178,6 +191,19 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
if err := clamRepo.Create(&clam); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.AlertCount != 0 {
|
||||
createAlert := dto.CreateOrUpdateAlert{
|
||||
AlertTitle: req.AlertTitle,
|
||||
AlertCount: req.AlertCount,
|
||||
AlertType: "clams",
|
||||
EntryID: clam.ID,
|
||||
}
|
||||
err := xpack.CreateAlert(createAlert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -223,6 +249,16 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
if err := clamRepo.Update(req.ID, upMap); err != nil {
|
||||
return err
|
||||
}
|
||||
updateAlert := dto.CreateOrUpdateAlert{
|
||||
AlertTitle: req.AlertTitle,
|
||||
AlertType: "clams",
|
||||
AlertCount: req.AlertCount,
|
||||
EntryID: clam.ID,
|
||||
}
|
||||
err := xpack.UpdateAlert(updateAlert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -263,12 +299,20 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
|
||||
if err := clamRepo.Delete(commonRepo.WithByID(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
alertBase := dto.AlertBase{
|
||||
AlertType: "clams",
|
||||
EntryID: clam.ID,
|
||||
}
|
||||
err := xpack.DeleteAlert(alertBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
if cmd.Which("clamdscan") == false {
|
||||
if cleaned := StopAllCronJob(true); cleaned {
|
||||
return buserr.New("ErrClamdscanNotFound")
|
||||
}
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
|
||||
@@ -303,6 +347,7 @@ func (c *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
}
|
||||
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
|
||||
stdout, err := cmd.Execf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
|
||||
handleAlert(stdout, clam.Name, clam.ID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
@@ -321,7 +366,7 @@ func (c *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, er
|
||||
}
|
||||
|
||||
var filterFiles []string
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for _, item := range logPaths {
|
||||
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, item, nyc)
|
||||
if err != nil {
|
||||
@@ -473,6 +518,29 @@ func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StopAllCronJob(withCheck bool) bool {
|
||||
if withCheck {
|
||||
isActive := false
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
}
|
||||
if isActive {
|
||||
return false
|
||||
}
|
||||
}
|
||||
clams, _ := clamRepo.List(commonRepo.WithByStatus(constant.StatusEnable))
|
||||
for i := 0; i < len(clams); i++ {
|
||||
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
|
||||
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadFileByName(name string) []string {
|
||||
var logPaths []string
|
||||
pathItem := path.Join(global.CONF.System.DataDir, resultDir, name)
|
||||
@@ -560,3 +628,27 @@ func (c *ClamService) loadLogPath(name string) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleAlert(stdout, clamName string, clamId uint) {
|
||||
if strings.Contains(stdout, "- SCAN SUMMARY -") {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "Infected files: ") {
|
||||
infectedFiles, _ := strconv.Atoi(strings.TrimPrefix(line, "Infected files: "))
|
||||
if infectedFiles > 0 {
|
||||
pushAlert := dto.PushAlert{
|
||||
TaskName: clamName,
|
||||
AlertType: "clams",
|
||||
EntryID: clamId,
|
||||
Param: strconv.Itoa(infectedFiles),
|
||||
}
|
||||
err := xpack.PushAlert(pushAlert)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("clamdscan push failed, err: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
IsFromApp = true
|
||||
}
|
||||
|
||||
ports := loadContainerPort(item.Ports)
|
||||
exposePorts := transPortToStr(records[i].Ports)
|
||||
info := dto.ContainerInfo{
|
||||
ContainerID: item.ID,
|
||||
CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout),
|
||||
@@ -196,7 +196,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
ImageName: item.Image,
|
||||
State: item.State,
|
||||
RunTime: item.Status,
|
||||
Ports: ports,
|
||||
Ports: exposePorts,
|
||||
IsFromApp: IsFromApp,
|
||||
IsFromCompose: IsFromCompose,
|
||||
}
|
||||
@@ -445,6 +445,8 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
}
|
||||
}
|
||||
|
||||
exposePorts, _ := loadPortByInspect(oldContainer.ID, client)
|
||||
data.ExposedPorts = loadContainerPortForInfo(exposePorts)
|
||||
networkSettings := oldContainer.NetworkSettings
|
||||
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
||||
if bridgeNetworkSettings.IPAMConfig != nil {
|
||||
@@ -465,19 +467,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
for key, val := range oldContainer.Config.Labels {
|
||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||
}
|
||||
for key, val := range oldContainer.HostConfig.PortBindings {
|
||||
var itemPort dto.PortHelper
|
||||
if !strings.Contains(string(key), "/") {
|
||||
continue
|
||||
}
|
||||
itemPort.ContainerPort = strings.Split(string(key), "/")[0]
|
||||
itemPort.Protocol = strings.Split(string(key), "/")[1]
|
||||
for _, binds := range val {
|
||||
itemPort.HostIP = binds.HostIP
|
||||
itemPort.HostPort = binds.HostPort
|
||||
data.ExposedPorts = append(data.ExposedPorts, itemPort)
|
||||
}
|
||||
}
|
||||
|
||||
data.AutoRemove = oldContainer.HostConfig.AutoRemove
|
||||
data.Privileged = oldContainer.HostConfig.Privileged
|
||||
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
|
||||
@@ -908,6 +898,10 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
|
||||
filePath = composeItem.Path
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return ""
|
||||
@@ -1201,7 +1195,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
|
||||
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil {
|
||||
global.LOG.Errorf("restart after container update failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Errorf("recreate after container update successful")
|
||||
global.LOG.Info("recreate after container update successful")
|
||||
}
|
||||
|
||||
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
|
||||
@@ -1224,7 +1218,65 @@ func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadContainerPort(ports []types.Port) []string {
|
||||
func loadPortByInspect(id string, client *client.Client) ([]types.Port, error) {
|
||||
container, err := client.ContainerInspect(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var itemPorts []types.Port
|
||||
for key, val := range container.ContainerJSONBase.HostConfig.PortBindings {
|
||||
if !strings.Contains(string(key), "/") {
|
||||
continue
|
||||
}
|
||||
item := strings.Split(string(key), "/")
|
||||
itemPort, _ := strconv.ParseUint(item[0], 10, 16)
|
||||
|
||||
for _, itemVal := range val {
|
||||
publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16)
|
||||
itemPorts = append(itemPorts, types.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP})
|
||||
}
|
||||
}
|
||||
return itemPorts, nil
|
||||
}
|
||||
|
||||
func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper {
|
||||
var exposedPorts []dto.PortHelper
|
||||
samePortMap := make(map[string]dto.PortHelper)
|
||||
ports := transPortToStr(itemPorts)
|
||||
var itemPort dto.PortHelper
|
||||
for _, item := range ports {
|
||||
itemStr := strings.Split(item, "->")
|
||||
if len(itemStr) < 2 {
|
||||
continue
|
||||
}
|
||||
lastIndex := strings.LastIndex(itemStr[0], ":")
|
||||
if lastIndex == -1 {
|
||||
itemPort.HostPort = itemStr[0]
|
||||
} else {
|
||||
itemPort.HostIP = itemStr[0][0:lastIndex]
|
||||
itemPort.HostPort = itemStr[0][lastIndex+1:]
|
||||
}
|
||||
itemContainer := strings.Split(itemStr[1], "/")
|
||||
if len(itemContainer) != 2 {
|
||||
continue
|
||||
}
|
||||
itemPort.ContainerPort = itemContainer[0]
|
||||
itemPort.Protocol = itemContainer[1]
|
||||
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
|
||||
if val, ok := samePortMap[keyItem]; ok {
|
||||
val.HostIP = ""
|
||||
samePortMap[keyItem] = val
|
||||
} else {
|
||||
samePortMap[keyItem] = itemPort
|
||||
}
|
||||
}
|
||||
for _, val := range samePortMap {
|
||||
exposedPorts = append(exposedPorts, val)
|
||||
}
|
||||
return exposedPorts
|
||||
}
|
||||
|
||||
func transPortToStr(ports []types.Port) []string {
|
||||
var (
|
||||
ipv4Ports []types.Port
|
||||
ipv6Ports []types.Port
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -31,6 +32,12 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
|
||||
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||
const composeCreatedBy = "createdBy"
|
||||
|
||||
type DockerCompose struct {
|
||||
Version string `yaml:"version"`
|
||||
Services map[string]map[string]interface{} `yaml:"services"`
|
||||
Networks map[string]interface{} `yaml:"networks"`
|
||||
}
|
||||
|
||||
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
var (
|
||||
records []dto.ComposeInfo
|
||||
@@ -52,6 +59,20 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
|
||||
composeCreatedByLocal, _ := composeRepo.ListRecord()
|
||||
|
||||
composeLocalMap := make(map[string]dto.ComposeInfo)
|
||||
for _, localItem := range composeCreatedByLocal {
|
||||
composeItemLocal := dto.ComposeInfo{
|
||||
ContainerNumber: 0,
|
||||
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
|
||||
ConfigFile: localItem.Path,
|
||||
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
|
||||
}
|
||||
composeItemLocal.CreatedBy = "1Panel"
|
||||
composeItemLocal.Path = localItem.Path
|
||||
composeLocalMap[localItem.Name] = composeItemLocal
|
||||
}
|
||||
|
||||
composeMap := make(map[string]dto.ComposeInfo)
|
||||
for _, container := range list {
|
||||
if name, ok := container.Labels[composeProjectLabel]; ok {
|
||||
@@ -95,12 +116,24 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range composeCreatedByLocal {
|
||||
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
mergedMap := make(map[string]dto.ComposeInfo)
|
||||
for key, localItem := range composeLocalMap {
|
||||
mergedMap[key] = localItem
|
||||
}
|
||||
for key, item := range composeMap {
|
||||
if existingItem, exists := mergedMap[key]; exists {
|
||||
if item.ContainerNumber > 0 {
|
||||
if existingItem.ContainerNumber <= 0 {
|
||||
mergedMap[key] = item
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mergedMap[key] = item
|
||||
}
|
||||
}
|
||||
for key, value := range composeMap {
|
||||
|
||||
for key, value := range mergedMap {
|
||||
value.Name = key
|
||||
records = append(records, value)
|
||||
}
|
||||
@@ -127,7 +160,26 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
BackDatas = records[start:end]
|
||||
}
|
||||
return int64(total), BackDatas, nil
|
||||
listItem := loadEnv(BackDatas)
|
||||
return int64(total), listItem, nil
|
||||
}
|
||||
|
||||
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
|
||||
for i := 0; i < len(list); i++ {
|
||||
envFilePath := filepath.Join(path.Dir(list[i].Path), "1panel.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
@@ -141,6 +193,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
if err := u.loadPath(&req); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return false, err
|
||||
}
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -173,6 +228,9 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return "", err
|
||||
}
|
||||
go func() {
|
||||
defer file.Close()
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
|
||||
@@ -186,7 +244,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("docker-compose up %s successful!", req.Name)
|
||||
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
|
||||
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name, Path: req.Path})
|
||||
_, _ = file.WriteString("docker-compose up successful!")
|
||||
}()
|
||||
|
||||
@@ -194,23 +252,36 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
if len(req.Path) == 0 && req.Operation == "delete" {
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
return nil
|
||||
}
|
||||
if cmd.CheckIllegal(req.Path, req.Operation) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
|
||||
if req.Operation == "down" {
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
if req.Operation == "delete" {
|
||||
if stdout, err := compose.Operate(req.Path, "down"); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
if req.WithFile {
|
||||
_ = os.RemoveAll(path.Dir(req.Path))
|
||||
}
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
if req.Operation == "up" {
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
} else {
|
||||
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -232,6 +303,10 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -280,3 +355,26 @@ func recreateCompose(content, path string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newComposeEnv(pathItem string, env []string) error {
|
||||
if len(env) == 0 {
|
||||
return nil
|
||||
}
|
||||
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
|
||||
|
||||
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
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
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
|
||||
records = list.Volumes[start:end]
|
||||
}
|
||||
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for _, item := range records {
|
||||
tag := make([]string, 0)
|
||||
for _, val := range item.Labels {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/robfig/cron/v3"
|
||||
@@ -53,6 +54,16 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
||||
} else {
|
||||
item.LastRecordTime = "-"
|
||||
}
|
||||
alertBase := dto.AlertBase{
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertCount := xpack.GetAlert(alertBase)
|
||||
if alertCount != 0 {
|
||||
item.AlertCount = alertCount
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
}
|
||||
dtoCronjobs = append(dtoCronjobs, item)
|
||||
}
|
||||
return total, dtoCronjobs, err
|
||||
@@ -190,6 +201,18 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
|
||||
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||
return err
|
||||
}
|
||||
if cronjobDto.AlertCount != 0 {
|
||||
createAlert := dto.CreateOrUpdateAlert{
|
||||
AlertTitle: cronjobDto.AlertTitle,
|
||||
AlertCount: cronjobDto.AlertCount,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
err := xpack.CreateAlert(createAlert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -232,6 +255,14 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
alertBase := dto.AlertBase{
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
err := xpack.DeleteAlert(alertBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -281,7 +312,21 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
upMap["default_download"] = req.DefaultDownload
|
||||
upMap["retain_copies"] = req.RetainCopies
|
||||
upMap["secret"] = req.Secret
|
||||
return cronjobRepo.Update(id, upMap)
|
||||
err = cronjobRepo.Update(id, upMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAlert := dto.CreateOrUpdateAlert{
|
||||
AlertTitle: req.AlertTitle,
|
||||
AlertType: cronModel.Type,
|
||||
AlertCount: req.AlertCount,
|
||||
EntryID: cronModel.ID,
|
||||
}
|
||||
err = xpack.UpdateAlert(updateAlert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
@@ -293,6 +338,7 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
entryIDs string
|
||||
err error
|
||||
)
|
||||
|
||||
if status == constant.StatusEnable {
|
||||
entryIDs, err = u.StartJob(&cronjob, false)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -17,15 +18,23 @@ import (
|
||||
|
||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
|
||||
var apps []model.AppInstall
|
||||
if cronjob.AppID == "all" {
|
||||
if strings.Contains(cronjob.AppID, "all") {
|
||||
apps, _ = appInstallRepo.ListBy()
|
||||
} else {
|
||||
itemID, _ := strconv.Atoi(cronjob.AppID)
|
||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
appIds := strings.Split(cronjob.AppID, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(appIds); i++ {
|
||||
itemID, _ := strconv.Atoi(appIds[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
appItems, err := appInstallRepo.ListBy(commonRepo.WithIdsIn(idItems))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apps = append(apps, app)
|
||||
if len(appItems) == 0 {
|
||||
return errors.New("no such app in database!")
|
||||
}
|
||||
apps = appItems
|
||||
}
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
@@ -60,6 +69,9 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
|
||||
|
||||
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time) error {
|
||||
webs := loadWebsForJob(cronjob)
|
||||
if len(webs) == 0 {
|
||||
return errors.New("no such website in database!")
|
||||
}
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -93,6 +105,9 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
|
||||
|
||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time) error {
|
||||
dbs := loadDbsForJob(cronjob)
|
||||
if len(dbs) == 0 {
|
||||
return errors.New("no such db in database!")
|
||||
}
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -232,7 +247,7 @@ type databaseHelper struct {
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||
var dbs []databaseHelper
|
||||
if cronjob.DBName == "all" {
|
||||
if strings.Contains(cronjob.DBName, "all") {
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItems, _ := mysqlRepo.List()
|
||||
for _, mysql := range mysqlItems {
|
||||
@@ -254,36 +269,42 @@ func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
itemID, _ := strconv.Atoi(cronjob.DBName)
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
|
||||
dbNames := strings.Split(cronjob.DBName, ",")
|
||||
for _, name := range dbNames {
|
||||
itemID, _ := strconv.Atoi(name)
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
|
||||
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||
var weblist []model.Website
|
||||
if cronjob.Website == "all" {
|
||||
if strings.Contains(cronjob.Website, "all") {
|
||||
weblist, _ = websiteRepo.List()
|
||||
return weblist
|
||||
}
|
||||
itemID, _ := strconv.Atoi(cronjob.Website)
|
||||
webItem, _ := websiteRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
if webItem.ID != 0 {
|
||||
weblist = append(weblist, webItem)
|
||||
websites := strings.Split(cronjob.Website, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(websites); i++ {
|
||||
itemID, _ := strconv.Atoi(websites[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
weblist, _ = websiteRepo.List(commonRepo.WithIdsIn(idItems))
|
||||
return weblist
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
pathUtils "path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -19,6 +21,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -84,12 +87,12 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
|
||||
err = u.handleSnapshot(*cronjob, record.StartTime, record.Records)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if len(message) != 0 {
|
||||
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
return
|
||||
}
|
||||
if len(message) != 0 {
|
||||
@@ -139,7 +142,6 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string, secret string)
|
||||
|
||||
excludes := strings.Split(exclusionRules, ",")
|
||||
excludeRules := ""
|
||||
excludes = append(excludes, "*.sock")
|
||||
for _, exclude := range excludes {
|
||||
if len(exclude) == 0 {
|
||||
continue
|
||||
@@ -162,10 +164,14 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string, secret string)
|
||||
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s %s", " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
|
||||
itemPrefix := pathUtils.Base(sourceDir)
|
||||
if itemPrefix == "/" {
|
||||
itemPrefix = ""
|
||||
}
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|%s/|') -zcf %s %s %s", sourceDir, "%P\n", itemPrefix, targetDir+"/"+name, excludeRules, path)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
||||
@@ -239,7 +245,6 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
|
||||
}
|
||||
msg := i18n.GetMsgWithMap("CutWebsiteLogSuccess", map[string]interface{}{"name": website.PrimaryDomain, "path": dstFilePath})
|
||||
global.LOG.Infof(msg)
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
u.removeExpiredLog(*cronjob)
|
||||
@@ -326,7 +331,6 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
|
||||
}
|
||||
|
||||
func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper, record model.BackupRecord) {
|
||||
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
|
||||
var opts []repo.DBOption
|
||||
opts = append(opts, commonRepo.WithByFrom("cronjob"))
|
||||
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
|
||||
@@ -361,7 +365,6 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
|
||||
}
|
||||
|
||||
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
|
||||
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
|
||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
|
||||
if len(records) <= int(cronjob.RetainCopies) {
|
||||
return
|
||||
@@ -391,3 +394,17 @@ func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.
|
||||
func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log"
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
pushAlert := dto.PushAlert{
|
||||
TaskName: cronjob.Name,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
Param: cronjob.Type,
|
||||
}
|
||||
err := xpack.PushAlert(pushAlert)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,19 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
network "net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
@@ -17,9 +23,6 @@ import (
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
type DashboardService struct{}
|
||||
@@ -27,7 +30,7 @@ type DashboardService struct{}
|
||||
type IDashboardService interface {
|
||||
LoadOsInfo() (*dto.OsInfo, error)
|
||||
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
|
||||
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
|
||||
LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent
|
||||
|
||||
Restart(operation string) error
|
||||
}
|
||||
@@ -94,11 +97,20 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
|
||||
baseInfo.KernelVersion = hostInfo.KernelVersion
|
||||
ss, _ := json.Marshal(hostInfo)
|
||||
baseInfo.VirtualizationSystem = string(ss)
|
||||
|
||||
baseInfo.IpV4Addr = GetOutboundIP()
|
||||
httpProxy := os.Getenv("http_proxy")
|
||||
if httpProxy == "" {
|
||||
httpProxy = os.Getenv("HTTP_PROXY")
|
||||
}
|
||||
if httpProxy != "" {
|
||||
baseInfo.SystemProxy = httpProxy
|
||||
}
|
||||
baseInfo.SystemProxy = "noProxy"
|
||||
appInstall, err := appInstallRepo.ListBy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseInfo.AppInstalledNumber = len(appInstall)
|
||||
postgresqlDbs, err := postgresqlRepo.List()
|
||||
if err != nil {
|
||||
@@ -128,78 +140,88 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
|
||||
baseInfo.CPUCores, _ = cpu.Counts(false)
|
||||
baseInfo.CPULogicalCores, _ = cpu.Counts(true)
|
||||
|
||||
baseInfo.CurrentInfo = *u.LoadCurrentInfo(ioOption, netOption)
|
||||
baseInfo.CurrentInfo = *u.LoadCurrentInfo(dto.DashboardReq{
|
||||
Scope: "ioNet",
|
||||
IoOption: ioOption,
|
||||
NetOption: netOption,
|
||||
})
|
||||
return &baseInfo, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent {
|
||||
func (u *DashboardService) LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent {
|
||||
var currentInfo dto.DashboardCurrent
|
||||
if req.Scope == "gpu" {
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
currentInfo.XPUData = loadXpuInfo()
|
||||
}
|
||||
|
||||
hostInfo, _ := host.Info()
|
||||
currentInfo.Uptime = hostInfo.Uptime
|
||||
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
|
||||
currentInfo.Procs = hostInfo.Procs
|
||||
|
||||
currentInfo.CPUTotal, _ = cpu.Counts(true)
|
||||
totalPercent, _ := cpu.Percent(0, false)
|
||||
if len(totalPercent) == 1 {
|
||||
currentInfo.CPUUsedPercent = totalPercent[0]
|
||||
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
|
||||
}
|
||||
currentInfo.CPUPercent, _ = cpu.Percent(0, true)
|
||||
|
||||
loadInfo, _ := load.Avg()
|
||||
currentInfo.Load1 = loadInfo.Load1
|
||||
currentInfo.Load5 = loadInfo.Load5
|
||||
currentInfo.Load15 = loadInfo.Load15
|
||||
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
|
||||
|
||||
memoryInfo, _ := mem.VirtualMemory()
|
||||
currentInfo.MemoryTotal = memoryInfo.Total
|
||||
currentInfo.MemoryAvailable = memoryInfo.Available
|
||||
currentInfo.MemoryUsed = memoryInfo.Used
|
||||
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
currentInfo.SwapMemoryTotal = swapInfo.Total
|
||||
currentInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
|
||||
if ioOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
} else {
|
||||
diskInfo, _ := disk.IOCounters(ioOption)
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
if req.Scope == "basic" {
|
||||
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
|
||||
currentInfo.Procs = hostInfo.Procs
|
||||
currentInfo.CPUTotal, _ = cpu.Counts(true)
|
||||
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
|
||||
if len(totalPercent) == 1 {
|
||||
currentInfo.CPUUsedPercent = totalPercent[0]
|
||||
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
|
||||
}
|
||||
currentInfo.CPUPercent, _ = cpu.Percent(100*time.Millisecond, true)
|
||||
|
||||
loadInfo, _ := load.Avg()
|
||||
currentInfo.Load1 = loadInfo.Load1
|
||||
currentInfo.Load5 = loadInfo.Load5
|
||||
currentInfo.Load15 = loadInfo.Load15
|
||||
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
|
||||
|
||||
memoryInfo, _ := mem.VirtualMemory()
|
||||
currentInfo.MemoryTotal = memoryInfo.Total
|
||||
currentInfo.MemoryAvailable = memoryInfo.Available
|
||||
currentInfo.MemoryUsed = memoryInfo.Used
|
||||
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
currentInfo.SwapMemoryTotal = swapInfo.Total
|
||||
currentInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
}
|
||||
|
||||
if netOption == "all" {
|
||||
netInfo, _ := net.IOCounters(false)
|
||||
if len(netInfo) != 0 {
|
||||
currentInfo.NetBytesSent = netInfo[0].BytesSent
|
||||
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
|
||||
if req.Scope == "ioNet" {
|
||||
if req.IoOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
} else {
|
||||
diskInfo, _ := disk.IOCounters(req.IoOption)
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
}
|
||||
} else {
|
||||
netInfo, _ := net.IOCounters(true)
|
||||
for _, state := range netInfo {
|
||||
if state.Name == netOption {
|
||||
currentInfo.NetBytesSent = state.BytesSent
|
||||
currentInfo.NetBytesRecv = state.BytesRecv
|
||||
|
||||
if req.NetOption == "all" {
|
||||
netInfo, _ := net.IOCounters(false)
|
||||
if len(netInfo) != 0 {
|
||||
currentInfo.NetBytesSent = netInfo[0].BytesSent
|
||||
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
|
||||
}
|
||||
} else {
|
||||
netInfo, _ := net.IOCounters(true)
|
||||
for _, state := range netInfo {
|
||||
if state.Name == req.NetOption {
|
||||
currentInfo.NetBytesSent = state.BytesSent
|
||||
currentInfo.NetBytesRecv = state.BytesRecv
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,6 +230,18 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
||||
return ¤tInfo
|
||||
}
|
||||
|
||||
func GetOutboundIP() string {
|
||||
conn, err := network.Dial("udp", "8.8.8.8:80")
|
||||
|
||||
if err != nil {
|
||||
return "IPNotFound"
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*network.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
type diskInfo struct {
|
||||
Type string
|
||||
Mount string
|
||||
@@ -235,13 +269,13 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(fields[1]) == "tmpfs" {
|
||||
if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[2], "K") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[6], "docker") {
|
||||
if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") {
|
||||
continue
|
||||
}
|
||||
isExclude := false
|
||||
@@ -323,3 +357,19 @@ func loadGPUInfo() []dto.GPUInfo {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func loadXpuInfo() []dto.XPUInfo {
|
||||
list := xpack.LoadXpuInfo()
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var data []dto.XPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.XPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
continue
|
||||
}
|
||||
data = append(data, dataItem)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -141,6 +141,10 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
||||
}
|
||||
|
||||
func (u *MysqlService) BindUser(req dto.BindUser) error {
|
||||
if cmd.CheckIllegal(req.Username, req.Password, req.Permission) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), commonRepo.WithByName(req.DB))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -533,7 +537,10 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
|
||||
info.Position = "OFF"
|
||||
rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, "show master status;")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
rows, err = executeSqlForRows(app.ContainerName, app.Key, app.Password, "SHOW BINARY LOG STATUS;")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(rows) > 2 {
|
||||
itemValue := strings.Split(rows[1], "\t")
|
||||
|
||||
+110
-17
@@ -4,14 +4,17 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -195,9 +198,29 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
|
||||
daemonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"}
|
||||
}
|
||||
}
|
||||
case "http-proxy", "https-proxy":
|
||||
delete(daemonMap, "proxies")
|
||||
if len(req.Value) > 0 {
|
||||
proxies := map[string]interface{}{
|
||||
req.Key: req.Value,
|
||||
}
|
||||
daemonMap["proxies"] = proxies
|
||||
}
|
||||
case "socks5-proxy", "close-proxy":
|
||||
delete(daemonMap, "proxies")
|
||||
if len(req.Value) > 0 {
|
||||
proxies := map[string]interface{}{
|
||||
"http-proxy": req.Value,
|
||||
"https-proxy": req.Value,
|
||||
}
|
||||
daemonMap["proxies"] = proxies
|
||||
}
|
||||
}
|
||||
if len(daemonMap) == 0 {
|
||||
_ = os.Remove(constant.DaemonJsonPath)
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
|
||||
@@ -207,10 +230,12 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
|
||||
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -254,9 +279,12 @@ func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
|
||||
return err
|
||||
}
|
||||
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -294,9 +322,12 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
|
||||
return err
|
||||
}
|
||||
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -304,6 +335,9 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
|
||||
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||
if len(req.File) == 0 {
|
||||
_ = os.Remove(constant.DaemonJsonPath)
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err := createIfNotExistDaemonJsonFile()
|
||||
@@ -319,21 +353,42 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||
_, _ = write.WriteString(req.File)
|
||||
write.Flush()
|
||||
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
service := "docker"
|
||||
if req.Operation == "stop" {
|
||||
service = "docker.socket"
|
||||
}
|
||||
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
dockerCmd, err := getDockerRestartCommand()
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
return err
|
||||
}
|
||||
if req.Operation == "stop" {
|
||||
isSocketActive, _ := systemctl.IsActive("docker.socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if req.Operation == "restart" {
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -386,3 +441,41 @@ func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize st
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateDockerConfig() error {
|
||||
if !cmd.Which("dockerd") {
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmd.Exec("dockerd --validate")
|
||||
if strings.Contains(stdout, "unknown flag: --validate") {
|
||||
return nil
|
||||
}
|
||||
if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") {
|
||||
return fmt.Errorf("Docker configuration validation failed, err: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDockerRestartCommand() (string, error) {
|
||||
stdout, err := cmd.Exec("which docker")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find docker: %v", err)
|
||||
}
|
||||
dockerPath := stdout
|
||||
if strings.Contains(dockerPath, "snap") {
|
||||
return "snap", nil
|
||||
}
|
||||
return "systemctl", nil
|
||||
}
|
||||
|
||||
func restartDocker() error {
|
||||
restartCmd, err := getDockerRestartCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := cmd.Execf("%s restart docker", restartCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart Docker: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -62,9 +62,13 @@ func NewIFileService() IFileService {
|
||||
|
||||
func (f *FileService) GetFileList(op request.FileOption) (response.FileInfo, error) {
|
||||
var fileInfo response.FileInfo
|
||||
if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
|
||||
data, err := os.Stat(op.Path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return fileInfo, nil
|
||||
}
|
||||
if !data.IsDir() {
|
||||
op.FileOption.Path = filepath.Dir(op.FileOption.Path)
|
||||
}
|
||||
info, err := files.NewFileInfo(op.FileOption)
|
||||
if err != nil {
|
||||
return fileInfo, err
|
||||
@@ -141,7 +145,7 @@ func shouldFilterPath(path string) bool {
|
||||
func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error {
|
||||
for _, v := range items {
|
||||
if shouldFilterPath(v.Path) {
|
||||
global.LOG.Info("File Tree: Skipping %s due to filter\n", v.Path)
|
||||
global.LOG.Infof("File Tree: Skipping %s due to filter\n", v.Path)
|
||||
continue
|
||||
}
|
||||
childNode := response.FileTree{
|
||||
@@ -167,7 +171,7 @@ func (f *FileService) buildChildNode(childNode *response.FileTree, fileInfo *fil
|
||||
subInfo, err := files.NewFileInfo(op.FileOption)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) || errors.Is(err, unix.EACCES) {
|
||||
global.LOG.Info("File Tree: Skipping %s due to permission denied\n", fileInfo.Path)
|
||||
global.LOG.Infof("File Tree: Skipping %s due to permission denied\n", fileInfo.Path)
|
||||
return nil
|
||||
}
|
||||
global.LOG.Errorf("File Tree: Skipping %s due to error: %s\n", fileInfo.Path, err.Error())
|
||||
@@ -207,6 +211,12 @@ func (f *FileService) Create(op request.FileCreate) error {
|
||||
}
|
||||
|
||||
func (f *FileService) Delete(op request.FileDelete) error {
|
||||
if op.IsDir {
|
||||
excludeDir := global.CONF.System.DataDir
|
||||
if filepath.Base(op.Path) == ".1panel_clash" || op.Path == excludeDir {
|
||||
return buserr.New(constant.ErrPathNotDelete)
|
||||
}
|
||||
}
|
||||
fo := files.NewFileOp()
|
||||
recycleBinStatus, _ := settingRepo.Get(settingRepo.WithByKey("FileRecycleBin"))
|
||||
if recycleBinStatus.Value == "disable" {
|
||||
@@ -474,11 +484,20 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
|
||||
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines = append(preLines, lines...)
|
||||
}
|
||||
|
||||
res := &response.FileLineContent{
|
||||
Content: strings.Join(lines, "\n"),
|
||||
End: isEndOfFile,
|
||||
Path: logFilePath,
|
||||
Total: total,
|
||||
Lines: lines,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -86,6 +87,11 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
|
||||
case "port":
|
||||
rules, err = client.ListPort()
|
||||
case "forward":
|
||||
isSupport, errSup := checkIsSupport()
|
||||
if !isSupport {
|
||||
return 0, nil, errSup
|
||||
}
|
||||
|
||||
rules, err = client.ListForward()
|
||||
case "address":
|
||||
rules, err = client.ListAddress()
|
||||
@@ -179,6 +185,7 @@ func (u *FirewallService) OperateFirewall(operation string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRestartDocker := false
|
||||
switch operation {
|
||||
case "start":
|
||||
if err := client.Start(); err != nil {
|
||||
@@ -188,26 +195,30 @@ func (u *FirewallService) OperateFirewall(operation string) error {
|
||||
_ = client.Stop()
|
||||
return err
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
needRestartDocker = true
|
||||
case "stop":
|
||||
if err := client.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
needRestartDocker = true
|
||||
case "restart":
|
||||
if err := client.Restart(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
needRestartDocker = true
|
||||
case "disablePing":
|
||||
return u.updatePingStatus("0")
|
||||
case "enablePing":
|
||||
return u.updatePingStatus("1")
|
||||
default:
|
||||
return fmt.Errorf("not supported operation: %s", operation)
|
||||
}
|
||||
return fmt.Errorf("not support such operation: %s", operation)
|
||||
if needRestartDocker {
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool) error {
|
||||
@@ -301,27 +312,69 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
||||
}
|
||||
|
||||
func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error {
|
||||
isSupport, errSup := checkIsSupport()
|
||||
if !isSupport {
|
||||
return errSup
|
||||
}
|
||||
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rules, _ := client.ListForward()
|
||||
i := 0
|
||||
for _, rule := range rules {
|
||||
shouldKeep := true
|
||||
for i := range req.Rules {
|
||||
reqRule := &req.Rules[i]
|
||||
if reqRule.TargetIP == "" {
|
||||
reqRule.TargetIP = "127.0.0.1"
|
||||
}
|
||||
|
||||
if reqRule.Operation == "remove" {
|
||||
for _, proto := range strings.Split(reqRule.Protocol, "/") {
|
||||
if reqRule.Port == rule.Port &&
|
||||
reqRule.TargetPort == rule.TargetPort &&
|
||||
reqRule.TargetIP == rule.TargetIP &&
|
||||
proto == rule.Protocol {
|
||||
shouldKeep = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if shouldKeep {
|
||||
rules[i] = rule
|
||||
i++
|
||||
}
|
||||
}
|
||||
rules = rules[:i]
|
||||
|
||||
for _, rule := range rules {
|
||||
for _, reqRule := range req.Rules {
|
||||
if reqRule.Operation == "remove" {
|
||||
continue
|
||||
}
|
||||
if reqRule.TargetIP == "" {
|
||||
reqRule.TargetIP = "127.0.0.1"
|
||||
}
|
||||
if reqRule.Port == rule.Port && reqRule.TargetPort == rule.TargetPort && reqRule.TargetIP == rule.TargetIP {
|
||||
return constant.ErrRecordExist
|
||||
|
||||
for _, proto := range strings.Split(reqRule.Protocol, "/") {
|
||||
if reqRule.Port == rule.Port &&
|
||||
reqRule.TargetPort == rule.TargetPort &&
|
||||
reqRule.TargetIP == rule.TargetIP &&
|
||||
proto == rule.Protocol {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(req.Rules, func(i, j int) bool {
|
||||
if req.Rules[i].Operation == "remove" && req.Rules[j].Operation != "remove" {
|
||||
return true
|
||||
}
|
||||
if req.Rules[i].Operation != "remove" && req.Rules[j].Operation == "remove" {
|
||||
return false
|
||||
}
|
||||
n1, _ := strconv.Atoi(req.Rules[i].Num)
|
||||
n2, _ := strconv.Atoi(req.Rules[j].Num)
|
||||
return n1 > n2
|
||||
@@ -588,14 +641,6 @@ 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
|
||||
}
|
||||
apps := u.loadPortByApp()
|
||||
for _, app := range apps {
|
||||
if len(app.HttpPort) != 0 && app.HttpPort != "0" {
|
||||
if err := client.Port(fireClient.FireInfo{Port: app.HttpPort, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return client.Reload()
|
||||
}
|
||||
@@ -692,3 +737,14 @@ func checkPortUsed(ports, proto string, apps []portOfApp) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkIsSupport() (bool, error) {
|
||||
std, err := cmd.Exec("iptables --version")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("handle iptables --version failed, stdout: %s, err: %v", std, err)
|
||||
}
|
||||
if strings.Contains(std, "nf_tables") {
|
||||
return false, buserr.New(constant.ErrNFTables)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
authStr := base64.StdEncoding.EncodeToString(encodedJSON)
|
||||
options.RegistryAuth = authStr
|
||||
}
|
||||
image := repo.DownloadUrl + "/" + req.ImageName
|
||||
@@ -295,7 +295,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
||||
out, err := client.ImagePull(context.TODO(), image, options)
|
||||
if err != nil {
|
||||
_, _ = file.WriteString("image pull failed!")
|
||||
global.LOG.Errorf("image %s pull failed, err: %v", image, err)
|
||||
_, _ = file.WriteString(fmt.Sprintf("image %s pull failed, err: %v", image, err))
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
@@ -432,7 +432,7 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
|
||||
}
|
||||
defer client.Close()
|
||||
for _, id := range req.Names {
|
||||
if _, err := client.ImageRemove(context.TODO(), id, types.ImageRemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
|
||||
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
|
||||
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
|
||||
if strings.Contains(id, "sha256:") {
|
||||
return buserr.New(constant.ErrObjectInUsed)
|
||||
|
||||
@@ -84,11 +84,16 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
|
||||
if imageRepo.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
|
||||
if req.Protocol == "http" {
|
||||
_ = u.handleRegistries(req.DownloadUrl, "", "create")
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
|
||||
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
|
||||
}
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
@@ -112,23 +117,18 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.Auth {
|
||||
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := copier.Copy(&imageRepo, &req); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
|
||||
imageRepo.Status = constant.StatusSuccess
|
||||
if req.Auth {
|
||||
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
|
||||
imageRepo.Status = constant.StatusFailed
|
||||
imageRepo.Message = err.Error()
|
||||
}
|
||||
}
|
||||
if err := imageRepoRepo.Create(&imageRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return imageRepoRepo.Create(&imageRepo)
|
||||
}
|
||||
|
||||
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
|
||||
@@ -154,32 +154,47 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.DownloadUrl != req.DownloadUrl || (!repo.Auth && req.Auth) {
|
||||
_ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update")
|
||||
if repo.Protocol == "http" && req.Protocol == "https" {
|
||||
if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil {
|
||||
return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err)
|
||||
}
|
||||
}
|
||||
if repo.Protocol == "http" && req.Protocol == "http" {
|
||||
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
|
||||
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
|
||||
}
|
||||
}
|
||||
if repo.Protocol == "https" && req.Protocol == "http" {
|
||||
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
|
||||
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
|
||||
}
|
||||
}
|
||||
if repo.Auth != req.Auth || repo.DownloadUrl != req.DownloadUrl {
|
||||
if repo.Auth {
|
||||
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
|
||||
}
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if req.Auth {
|
||||
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["download_url"] = req.DownloadUrl
|
||||
upMap["protocol"] = req.Protocol
|
||||
upMap["username"] = req.Username
|
||||
upMap["password"] = req.Password
|
||||
upMap["auth"] = req.Auth
|
||||
|
||||
upMap["status"] = constant.StatusSuccess
|
||||
upMap["message"] = ""
|
||||
if req.Auth {
|
||||
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
|
||||
upMap["status"] = constant.StatusFailed
|
||||
upMap["message"] = err.Error()
|
||||
}
|
||||
}
|
||||
return imageRepoRepo.Update(req.ID, upMap)
|
||||
}
|
||||
|
||||
|
||||
@@ -201,16 +201,19 @@ func StartMonitor(removeBefore bool, interval string) error {
|
||||
service := NewIMonitorService()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
monitorCancel = cancel
|
||||
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
nextMinute := now.Truncate(time.Minute).Add(time.Minute)
|
||||
time.AfterFunc(time.Until(nextMinute), func() {
|
||||
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
global.MonitorCronID = monitorID
|
||||
})
|
||||
service.Run()
|
||||
|
||||
go service.saveIODataToDB(ctx, float64(intervalItem))
|
||||
go service.saveNetDataToDB(ctx, float64(intervalItem))
|
||||
|
||||
global.MonitorCronID = monitorID
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
|
||||
opts []repo.DBOption
|
||||
)
|
||||
if create.Name != "" {
|
||||
opts = append(opts, commonRepo.WithLikeName(create.Name))
|
||||
opts = append(opts, commonRepo.WithByName(create.Name))
|
||||
}
|
||||
if create.Type != "" {
|
||||
opts = append(opts, commonRepo.WithByType(create.Type))
|
||||
@@ -83,7 +83,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
|
||||
if exist != nil {
|
||||
return nil, buserr.New(constant.ErrImageExist)
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
if !fileOp.Stat(create.CodeDir) {
|
||||
return nil, buserr.New(constant.ErrPathNotFound)
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
|
||||
if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
runtime.Port = create.Port
|
||||
if err = handleNodeAndJava(create, runtime, fileOp, appVersionDir); err != nil {
|
||||
return nil, err
|
||||
@@ -217,7 +217,7 @@ func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error {
|
||||
global.LOG.Errorf("delete image id [%s] error %v", imageID, err)
|
||||
}
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete {
|
||||
if out != "" {
|
||||
return errors.New(out)
|
||||
@@ -300,7 +300,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
|
||||
}
|
||||
}
|
||||
res.AppParams = appParams
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
res.Params = make(map[string]interface{})
|
||||
envs, err := gotenv.Unmarshal(runtime.Env)
|
||||
if err != nil {
|
||||
@@ -308,7 +308,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
|
||||
}
|
||||
for k, v := range envs {
|
||||
switch k {
|
||||
case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP", "JAVA_APP_PORT", "GO_APP_PORT":
|
||||
case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP", "JAVA_APP_PORT", "GO_APP_PORT", "APP_PORT":
|
||||
port, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -361,7 +361,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
|
||||
if exist != nil {
|
||||
return buserr.New(constant.ErrImageExist)
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
if runtime.Port != req.Port {
|
||||
if err = checkPortExist(req.Port); err != nil {
|
||||
return err
|
||||
@@ -441,7 +441,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
|
||||
return err
|
||||
}
|
||||
go buildRuntime(runtime, imageID, req.Rebuild)
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
runtime.Version = req.Version
|
||||
runtime.CodeDir = req.CodeDir
|
||||
runtime.Port = req.Port
|
||||
@@ -495,6 +495,9 @@ func (r *RuntimeService) OperateRuntime(req request.RuntimeOperate) error {
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
_ = docker.CreateDefaultDockerNetwork()
|
||||
}()
|
||||
switch req.Operate {
|
||||
case constant.RuntimeUp:
|
||||
if err = runComposeCmdWithLog(req.Operate, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -218,7 +219,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
|
||||
_ = logFile.Close()
|
||||
}()
|
||||
|
||||
cmd := exec.Command("docker-compose", "-f", composePath, "build")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "docker-compose", "-f", composePath, "build")
|
||||
multiWriterStdout := io.MultiWriter(os.Stdout, logFile)
|
||||
cmd.Stdout = multiWriterStdout
|
||||
var stderrBuf bytes.Buffer
|
||||
@@ -228,7 +232,11 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
runtime.Status = constant.RuntimeError
|
||||
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + buserr.New("ErrCmdTimeout").Error()
|
||||
} else {
|
||||
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
|
||||
}
|
||||
} else {
|
||||
runtime.Status = constant.RuntimeNormal
|
||||
runtime.Message = ""
|
||||
@@ -338,6 +346,14 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case constant.RuntimePython:
|
||||
create.Params["CODE_DIR"] = create.CodeDir
|
||||
create.Params["PYTHON_VERSION"] = create.Version
|
||||
create.Params["PANEL_APP_PORT_HTTP"] = create.Port
|
||||
composeContent, err = handleCompose(env, composeContent, create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newMap := make(map[string]string)
|
||||
@@ -384,7 +400,8 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
|
||||
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${JAVA_APP_PORT}")
|
||||
case constant.RuntimeGo:
|
||||
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${GO_APP_PORT}")
|
||||
|
||||
case constant.RuntimePython:
|
||||
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${APP_PORT}")
|
||||
}
|
||||
|
||||
for i, port := range create.ExposedPorts {
|
||||
|
||||
@@ -322,6 +322,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
if err := settingRepo.Update("SSL", req.SSL); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("AutoRestart", req.AutoRestart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
@@ -354,7 +357,7 @@ func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "import":
|
||||
case "import-paste", "import-local":
|
||||
data, err = loadInfoFromCert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -380,7 +383,7 @@ func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Domain = ssl.Domains
|
||||
data.Domain = ssl.PrimaryDomain
|
||||
data.SSLID = uint(id)
|
||||
data.Timeout = ssl.ExpireDate.Format(constant.DateTimeLayout)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ type SnapshotService struct {
|
||||
}
|
||||
|
||||
type ISnapshotService interface {
|
||||
SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
|
||||
LoadSize(req dto.PageSnapshot) ([]dto.SnapshotFile, error)
|
||||
SnapshotCreate(req dto.SnapshotCreate) error
|
||||
SnapshotRecover(req dto.SnapshotRecover) error
|
||||
SnapshotRollback(req dto.SnapshotRecover) error
|
||||
@@ -46,13 +47,32 @@ func NewISnapshotService() ISnapshotService {
|
||||
return &SnapshotService{}
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
|
||||
dtoSnap, err := loadSnapSize(systemBackups)
|
||||
func (u *SnapshotService) SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) {
|
||||
total, records, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return total, dtoSnap, err
|
||||
var data []dto.SnapshotInfo
|
||||
for _, record := range records {
|
||||
var item dto.SnapshotInfo
|
||||
if err := copier.Copy(&item, &record); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
return total, data, err
|
||||
}
|
||||
|
||||
func (u *SnapshotService) LoadSize(req dto.PageSnapshot) ([]dto.SnapshotFile, error) {
|
||||
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := loadSnapSize(records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||
@@ -421,16 +441,7 @@ func rebuildAllAppInstall() error {
|
||||
go func(app model.AppInstall) {
|
||||
defer wg.Done()
|
||||
dockerComposePath := app.GetComposePath()
|
||||
out, err := compose.Down(dockerComposePath)
|
||||
if err != nil {
|
||||
_ = handleErr(app, err, out)
|
||||
return
|
||||
}
|
||||
out, err = compose.Up(dockerComposePath)
|
||||
if err != nil {
|
||||
_ = handleErr(app, err, out)
|
||||
return
|
||||
}
|
||||
_, _ = compose.Up(dockerComposePath)
|
||||
app.Status = constant.Running
|
||||
_ = appInstallRepo.Save(context.Background(), &app)
|
||||
}(appInstalls[i])
|
||||
@@ -507,15 +518,12 @@ func loadOs() string {
|
||||
}
|
||||
}
|
||||
|
||||
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotInfo, error) {
|
||||
var datas []dto.SnapshotInfo
|
||||
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotFile, error) {
|
||||
var datas []dto.SnapshotFile
|
||||
clientMap := make(map[string]loadSizeHelper)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(records); i++ {
|
||||
var item dto.SnapshotInfo
|
||||
if err := copier.Copy(&item, &records[i]); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
item := dto.SnapshotFile{Name: records[i].Name, ID: records[i].ID}
|
||||
itemPath := fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)
|
||||
if _, ok := clientMap[records[i].DefaultDownload]; !ok {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].DefaultDownload))
|
||||
|
||||
@@ -231,7 +231,6 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
|
||||
exMap := make(map[string]struct{})
|
||||
exStr := ""
|
||||
excludes := strings.Split(exclusionRules, ";")
|
||||
excludes = append(excludes, "*.sock")
|
||||
for _, exclude := range excludes {
|
||||
if len(exclude) == 0 {
|
||||
continue
|
||||
@@ -257,10 +256,10 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
|
||||
commands := ""
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s %s", " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, "%P\n", targetDir+"/"+name, exStr, sourceDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
||||
|
||||
@@ -47,7 +47,7 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "Backup" {
|
||||
if err := backupBeforeRecover(snap, ""); err != nil {
|
||||
if err := backupBeforeRecover(snap); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
}
|
||||
|
||||
func backupBeforeRecover(snap model.Snapshot, secret string) error {
|
||||
func backupBeforeRecover(snap model.Snapshot) error {
|
||||
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
var wg sync.WaitGroup
|
||||
var status model.SnapshotStatus
|
||||
@@ -231,7 +231,9 @@ func recoverDaemonJson(src string, fileOp files.FileOp) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,16 @@ func (u *SSHService) OperateSSH(operation string) error {
|
||||
if operation == "enable" || operation == "disable" {
|
||||
serviceName += ".service"
|
||||
}
|
||||
if operation == "stop" {
|
||||
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop %s", sudo, serviceName+".socket")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop %s.socket failed, err: %v", serviceName, std)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(stdout, "alias name or linked unit file") {
|
||||
@@ -312,7 +322,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||
|
||||
showCountFrom := (req.Page - 1) * req.PageSize
|
||||
showCountTo := req.Page * req.PageSize
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
qqWry, err := qqwry.NewQQwry()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load qqwry datas failed: %s", err)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
@@ -139,8 +140,10 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
}
|
||||
for _, web := range websites {
|
||||
var (
|
||||
appName string
|
||||
runtimeName string
|
||||
appName string
|
||||
runtimeName string
|
||||
runtimeType string
|
||||
appInstallID uint
|
||||
)
|
||||
switch web.Type {
|
||||
case constant.Deployment:
|
||||
@@ -149,12 +152,15 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
return 0, nil, err
|
||||
}
|
||||
appName = appInstall.Name
|
||||
appInstallID = appInstall.ID
|
||||
case constant.Runtime:
|
||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
runtimeName = runtime.Name
|
||||
runtimeType = runtime.Type
|
||||
appInstallID = runtime.ID
|
||||
}
|
||||
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias)
|
||||
|
||||
@@ -173,6 +179,8 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
SSLStatus: checkSSLStatus(web.WebsiteSSL.ExpireDate),
|
||||
RuntimeName: runtimeName,
|
||||
SitePath: sitePath,
|
||||
AppInstallID: appInstallID,
|
||||
RuntimeType: runtimeType,
|
||||
})
|
||||
}
|
||||
return total, websiteDTOs, nil
|
||||
@@ -305,6 +313,14 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
switch runtime.Type {
|
||||
case constant.RuntimePHP:
|
||||
if runtime.Resource == constant.ResourceAppstore {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
if !checkImageExist(client, runtime.Image) {
|
||||
return buserr.WithName("ErrImageNotExist", runtime.Name)
|
||||
}
|
||||
var (
|
||||
req request.AppInstallCreate
|
||||
install *model.AppInstall
|
||||
@@ -336,7 +352,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
}
|
||||
website.Proxy = proxy
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
|
||||
}
|
||||
}
|
||||
@@ -471,7 +487,9 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
||||
tx, ctx := helper.GetTxAndContext()
|
||||
defer tx.Rollback()
|
||||
|
||||
go NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
|
||||
go func() {
|
||||
_ = NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
|
||||
}()
|
||||
if err := websiteRepo.DeleteBy(ctx, commonRepo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -623,8 +641,10 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
||||
wafSite := wafWebsite
|
||||
oldDomains := wafSite.Domains
|
||||
var newDomains []string
|
||||
removed := false
|
||||
for _, domain := range oldDomains {
|
||||
if domain == webSiteDomain.Domain {
|
||||
if domain == webSiteDomain.Domain && !removed {
|
||||
removed = true
|
||||
continue
|
||||
}
|
||||
newDomains = append(newDomains, domain)
|
||||
@@ -910,6 +930,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
|
||||
websiteSSL.Provider = constant.Manual
|
||||
websiteSSL.PrivateKey = privateKey
|
||||
websiteSSL.Pem = certificate
|
||||
websiteSSL.Status = constant.SSLReady
|
||||
|
||||
res.SSL = websiteSSL
|
||||
}
|
||||
@@ -1646,6 +1667,9 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
}
|
||||
if req.SNI {
|
||||
location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
|
||||
if req.ProxySSLName != "" {
|
||||
location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
|
||||
}
|
||||
} else {
|
||||
location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
|
||||
}
|
||||
@@ -1728,6 +1752,9 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
if directive.GetName() == "proxy_ssl_server_name" {
|
||||
proxyConfig.SNI = directive.GetParameters()[0] == "on"
|
||||
}
|
||||
if directive.GetName() == "proxy_ssl_name" {
|
||||
proxyConfig.ProxySSLName = directive.GetParameters()[0]
|
||||
}
|
||||
}
|
||||
res = append(res, proxyConfig)
|
||||
}
|
||||
|
||||
@@ -217,15 +217,14 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
if req.Domains != "" {
|
||||
domainArray := strings.Split(req.Domains, "\n")
|
||||
for _, domain := range domainArray {
|
||||
if !common.IsValidDomain(domain) {
|
||||
err = buserr.WithName("ErrDomainFormat", domain)
|
||||
return nil, err
|
||||
} else {
|
||||
if ipAddress := net.ParseIP(domain); ipAddress == nil {
|
||||
domains = append(domains, domain)
|
||||
} else {
|
||||
ips = append(ips, ipAddress)
|
||||
if ipAddress := net.ParseIP(domain); ipAddress == nil {
|
||||
if !common.IsValidDomain(domain) {
|
||||
err = buserr.WithName("ErrDomainFormat", domain)
|
||||
return nil, err
|
||||
}
|
||||
domains = append(domains, domain)
|
||||
} else {
|
||||
ips = append(ips, ipAddress)
|
||||
}
|
||||
}
|
||||
if len(domains) > 0 {
|
||||
@@ -432,14 +431,14 @@ func (w WebsiteCAService) DownloadFile(id uint) (*os.File, error) {
|
||||
if err = fileOp.CreateDir(dir, 0666); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(path.Join(dir, "ca.csr"), strings.NewReader(ca.CSR), 0644); err != nil {
|
||||
if err = fileOp.WriteFile(path.Join(dir, "ca.crt"), strings.NewReader(ca.CSR), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(path.Join(dir, "private.key"), strings.NewReader(ca.PrivateKey), 0644); err != nil {
|
||||
if err = fileOp.WriteFile(path.Join(dir, "ca.key"), strings.NewReader(ca.PrivateKey), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileName := ca.Name + ".zip"
|
||||
if err = fileOp.Compress([]string{path.Join(dir, "ca.csr"), path.Join(dir, "private.key")}, dir, fileName, files.SdkZip, ""); err != nil {
|
||||
if err = fileOp.Compress([]string{path.Join(dir, "ca.crt"), path.Join(dir, "ca.key")}, dir, fileName, files.SdkZip, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Open(path.Join(dir, fileName))
|
||||
|
||||
@@ -181,6 +181,13 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
|
||||
return create, nil
|
||||
}
|
||||
|
||||
func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{}, disableLog bool) {
|
||||
if disableLog {
|
||||
return
|
||||
}
|
||||
logger.Println(i18n.GetMsgWithMap(msgKey, params))
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
||||
var (
|
||||
err error
|
||||
@@ -272,11 +279,13 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
||||
defer logFile.Close()
|
||||
logger := log.New(logFile, "", log.LstdFlags)
|
||||
legoLogger.Logger = logger
|
||||
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
|
||||
if websiteSSL.Provider == constant.DNSAccount {
|
||||
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
|
||||
if !apply.DisableLog {
|
||||
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
|
||||
if websiteSSL.Provider == constant.DNSAccount {
|
||||
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
|
||||
}
|
||||
legoLogger.Logger.Println(startMsg)
|
||||
}
|
||||
legoLogger.Logger.Println(startMsg)
|
||||
resource, err := client.ObtainSSL(domains, privateKey)
|
||||
if err != nil {
|
||||
handleError(websiteSSL, err)
|
||||
@@ -296,7 +305,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
||||
websiteSSL.Type = cert.Issuer.CommonName
|
||||
websiteSSL.Organization = cert.Issuer.Organization[0]
|
||||
websiteSSL.Status = constant.SSLReady
|
||||
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
|
||||
printSSLLog(logger, "ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}, apply.DisableLog)
|
||||
saveCertificateFile(websiteSSL, logger)
|
||||
|
||||
if websiteSSL.ExecShell {
|
||||
@@ -304,11 +313,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
||||
if websiteSSL.PushDir {
|
||||
workDir = websiteSSL.Dir
|
||||
}
|
||||
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellStart"))
|
||||
printSSLLog(logger, "ExecShellStart", nil, apply.DisableLog)
|
||||
if err = cmd.ExecShellWithTimeOut(websiteSSL.Shell, workDir, logger, 30*time.Minute); err != nil {
|
||||
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()}))
|
||||
printSSLLog(logger, "ErrExecShell", map[string]interface{}{"err": err.Error()}, apply.DisableLog)
|
||||
} else {
|
||||
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
|
||||
printSSLLog(logger, "ExecShellSuccess", nil, apply.DisableLog)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,9 +329,9 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
||||
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
|
||||
if len(websites) > 0 {
|
||||
for _, website := range websites {
|
||||
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}))
|
||||
printSSLLog(logger, "ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}, apply.DisableLog)
|
||||
if err := createPemFile(website, *websiteSSL); err != nil {
|
||||
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}))
|
||||
printSSLLog(logger, "ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}, apply.DisableLog)
|
||||
}
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
@@ -330,10 +339,10 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
||||
return
|
||||
}
|
||||
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
legoLogger.Logger.Println(i18n.GetMsgByKey(constant.ErrSSLApply))
|
||||
printSSLLog(logger, constant.ErrSSLApply, nil, apply.DisableLog)
|
||||
return
|
||||
}
|
||||
legoLogger.Logger.Println(i18n.GetMsgByKey("ApplyWebSiteSSLSuccess"))
|
||||
printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -13,6 +12,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
@@ -276,7 +277,7 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
server.UpdateRoot(rootIndex)
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, "")
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
}
|
||||
@@ -1001,20 +1002,23 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
|
||||
}
|
||||
}
|
||||
|
||||
func GetSystemSSL() (bool, uint) {
|
||||
func GetSystemSSL() (bool, bool, uint) {
|
||||
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
|
||||
return false, 0
|
||||
return false, false, 0
|
||||
}
|
||||
if sslSetting.Value == "enable" {
|
||||
sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID"))
|
||||
idValue, _ := strconv.Atoi(sslID.Value)
|
||||
if idValue > 0 {
|
||||
return true, uint(idValue)
|
||||
if idValue <= 0 {
|
||||
return false, false, 0
|
||||
}
|
||||
|
||||
auto, _ := settingRepo.Get(settingRepo.WithByKey("AutoRestart"))
|
||||
return true, auto.Value == "enable", uint(idValue)
|
||||
}
|
||||
return false, 0
|
||||
return false, false, 0
|
||||
}
|
||||
|
||||
func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
|
||||
@@ -1033,7 +1037,7 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
|
||||
return buserr.WithErr(constant.ErrSSLApply, err)
|
||||
}
|
||||
}
|
||||
enable, sslID := GetSystemSSL()
|
||||
enable, auto, sslID := GetSystemSSL()
|
||||
if enable && sslID == websiteSSL.ID {
|
||||
fileOp := files.NewFileOp()
|
||||
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
|
||||
@@ -1045,6 +1049,9 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
|
||||
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
|
||||
return err
|
||||
}
|
||||
if auto {
|
||||
_, _ = cmd.Exec("systemctl restart 1panel.service")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
AlertEnable = "Enable"
|
||||
AlertDisable = "Disable"
|
||||
AlertSuccess = "Success"
|
||||
AlertError = "Error"
|
||||
AlertSyncError = "SyncError"
|
||||
AlertPushError = "PushError"
|
||||
AlertPushSuccess = "PushSuccess"
|
||||
)
|
||||
@@ -46,6 +46,8 @@ var (
|
||||
ErrCmdIllegal = "ErrCmdIllegal"
|
||||
ErrXpackNotFound = "ErrXpackNotFound"
|
||||
ErrXpackNotActive = "ErrXpackNotActive"
|
||||
ErrXpackLost = "ErrXpackLost"
|
||||
ErrXpackTimeout = "ErrXpackTimeout"
|
||||
ErrXpackOutOfDate = "ErrXpackOutOfDate"
|
||||
)
|
||||
|
||||
@@ -95,11 +97,12 @@ var (
|
||||
ErrPathNotFound = "ErrPathNotFound"
|
||||
ErrMovePathFailed = "ErrMovePathFailed"
|
||||
ErrLinkPathNotFound = "ErrLinkPathNotFound"
|
||||
ErrFileIsExist = "ErrFileIsExist"
|
||||
ErrFileIsExist = "ErrFileIsExist"
|
||||
ErrFileUpload = "ErrFileUpload"
|
||||
ErrFileDownloadDir = "ErrFileDownloadDir"
|
||||
ErrCmdNotFound = "ErrCmdNotFound"
|
||||
ErrFavoriteExist = "ErrFavoriteExist"
|
||||
ErrPathNotDelete = "ErrPathNotDelete"
|
||||
)
|
||||
|
||||
// mysql
|
||||
@@ -143,7 +146,9 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFirewall = "ErrFirewall"
|
||||
ErrFirewallNone = "ErrFirewallNone"
|
||||
ErrFirewallBoth = "ErrFirewallBoth"
|
||||
ErrNFTables = "ErrNFTables"
|
||||
)
|
||||
|
||||
// cronjob
|
||||
@@ -162,3 +167,12 @@ var (
|
||||
ErrLicenseSave = "ErrLicenseSave"
|
||||
ErrLicenseSync = "ErrLicenseSync"
|
||||
)
|
||||
|
||||
// alert
|
||||
var (
|
||||
ErrAlert = "ErrAlert"
|
||||
ErrAlertPush = "ErrAlertPush"
|
||||
ErrAlertSave = "ErrAlertSave"
|
||||
ErrAlertSync = "ErrAlertSync"
|
||||
ErrAlertRemote = "ErrAlertRemote"
|
||||
)
|
||||
|
||||
@@ -14,10 +14,11 @@ const (
|
||||
RuntimeUnhealthy = "unhealthy"
|
||||
RuntimeCreating = "creating"
|
||||
|
||||
RuntimePHP = "php"
|
||||
RuntimeNode = "node"
|
||||
RuntimeJava = "java"
|
||||
RuntimeGo = "go"
|
||||
RuntimePHP = "php"
|
||||
RuntimeNode = "node"
|
||||
RuntimeJava = "java"
|
||||
RuntimeGo = "go"
|
||||
RuntimePython = "python"
|
||||
|
||||
RuntimeProxyUnix = "unix"
|
||||
RuntimeProxyTcp = "tcp"
|
||||
|
||||
@@ -10,6 +10,8 @@ const (
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
StatusNone = "None"
|
||||
StatusOnRetry = "OnRetry"
|
||||
StatusLost = "Lost"
|
||||
|
||||
OrderDesc = "descending"
|
||||
OrderAsc = "ascending"
|
||||
|
||||
@@ -17,14 +17,14 @@ import (
|
||||
)
|
||||
|
||||
func Run() {
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
|
||||
|
||||
var (
|
||||
interval model.Setting
|
||||
status model.Setting
|
||||
)
|
||||
syncBeforeStart()
|
||||
go syncBeforeStart()
|
||||
if err := global.DB.Where("key = ?", "MonitorStatus").Find(&status).Error; err != nil {
|
||||
global.LOG.Errorf("load monitor status from db failed, err: %v", err)
|
||||
}
|
||||
|
||||
+12
-6
@@ -1,16 +1,18 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ssl struct {
|
||||
@@ -21,11 +23,11 @@ func NewSSLJob() *ssl {
|
||||
}
|
||||
|
||||
func (ssl *ssl) Run() {
|
||||
systemSSLEnable, sslID := service.GetSystemSSL()
|
||||
systemSSLEnable, auto, sslID := service.GetSystemSSL()
|
||||
sslRepo := repo.NewISSLRepo()
|
||||
sslService := service.NewIWebsiteSSLService()
|
||||
sslList, _ := sslRepo.List()
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
global.LOG.Info("The scheduled certificate update task is currently in progress ...")
|
||||
now := time.Now().Add(10 * time.Second)
|
||||
for _, s := range sslList {
|
||||
@@ -50,7 +52,8 @@ func (ssl *ssl) Run() {
|
||||
}
|
||||
} else {
|
||||
if err := sslService.ObtainSSL(request.WebsiteSSLApply{
|
||||
ID: s.ID,
|
||||
ID: s.ID,
|
||||
DisableLog: true,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
|
||||
continue
|
||||
@@ -68,6 +71,9 @@ func (ssl *ssl) Run() {
|
||||
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
|
||||
continue
|
||||
}
|
||||
if auto {
|
||||
_, _ = cmd.Exec("systemctl restart 1panel.service")
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
@@ -8,8 +11,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type website struct{}
|
||||
@@ -19,7 +20,7 @@ func NewWebsiteJob() *website {
|
||||
}
|
||||
|
||||
func (w *website) Run() {
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
websites, _ := repo.NewIWebsiteRepo().List()
|
||||
global.LOG.Info("Website scheduled task in progress ...")
|
||||
now := time.Now().Add(10 * time.Minute)
|
||||
|
||||
@@ -81,6 +81,8 @@ ErrCmdNotFound: "{{ .name}} command does not exist, please install this command
|
||||
ErrSourcePathNotFound: "Source directory does not exist"
|
||||
ErrFavoriteExist: "This path has been collected"
|
||||
ErrInvalidChar: "Illegal characters are prohibited"
|
||||
ErrPathNotDelete: "The selected directory cannot be deleted"
|
||||
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "Domain is already exist"
|
||||
@@ -94,6 +96,7 @@ ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the
|
||||
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
|
||||
ErrDomainFormat: "{{ .name }} domain format error"
|
||||
ErrDefaultAlias: "default is a reserved code name, please use another code name"
|
||||
ErrImageNotExist: "Running environment {{.name}} image does not exist, please re-edit the running environment"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
|
||||
@@ -164,7 +167,9 @@ ErrConfigAlreadyExist: "A configuration file with the same name already exists"
|
||||
ErrUserFindErr: "Failed to find user {{ .name }} {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "No firewalld or ufw service is detected. Please check and try again!"
|
||||
ErrFirewallNone: "No firewalld or ufw service detected on the system. Please check and try again!"
|
||||
ErrFirewallBoth: "Both firewalld and ufw services are detected on the system. To avoid conflicts, please uninstall one and try again!"
|
||||
ErrNFTables: "Port forwarding functionality relies on the iptables service and is currently not compatible with nftables operations!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "Script execution error, please check the specific information in the task output text area."
|
||||
@@ -196,3 +201,13 @@ ErrLicenseSync: "Failed to sync license information, no license information dete
|
||||
ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface"
|
||||
ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface"
|
||||
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
|
||||
ErrXpackLost: "The license has reached the maximum number of retry attempts. Please go to the [Settings] [License] page and manually click the sync button to ensure that the professional version features are functioning properly."
|
||||
ErrXpackTimeout: "Request timed out, the network connection may be unstable, please try again later!"
|
||||
|
||||
|
||||
#license
|
||||
ErrAlert: "Alert information format error, please check and try again!"
|
||||
ErrAlertPush: "Alert push error, please check and try again!"
|
||||
ErrAlertSave: "Alert save error, please check and try again!"
|
||||
ErrAlertSync: "Alert sync error, please check and try again!"
|
||||
ErrAlertRemote: "Remote alert error, please check and try again!"
|
||||
@@ -81,6 +81,7 @@ ErrFileDownloadDir: "不支持下載文件夾"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
||||
ErrSourcePathNotFound: "源目錄不存在"
|
||||
ErrFavoriteExist: "已收藏此路徑"
|
||||
ErrPathNotDelete: "所選目錄不可删除"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
@@ -94,6 +95,7 @@ ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可
|
||||
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
|
||||
ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
||||
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
||||
ErrImageNotExist: "執行環境 {{.name}} 鏡像不存在,請重新編輯執行環境"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
||||
@@ -165,7 +167,9 @@ ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用戶 {{ .name }} 查找失敗 {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "當前未檢測到系統 firewalld 或 ufw 服務,請檢查後重試!"
|
||||
ErrFirewallNone: "未檢測到系統 firewalld 或 ufw 服務,請檢查後重試!"
|
||||
ErrFirewallBoth: "檢測到系統同時存在 firewalld 或 ufw 服務,為避免衝突,請卸載後重試!"
|
||||
ErrNFTables: "端口轉發功能依賴於 iptables 服務,暫不兼容 nftables 操作!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "腳本執行錯誤,請在任務輸出文本域中查看具體信息。"
|
||||
@@ -198,3 +202,12 @@ ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證
|
||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
|
||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
|
||||
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
|
||||
ErrXpackLost: "許可證已達到最大重試次數,請進入【面板設定】【許可證】頁面手動點擊同步按鈕,以確保專業版功能正常使用"
|
||||
ErrXpackTimeout: "請求超時,網絡連接可能不穩定,請稍後再試!"
|
||||
|
||||
#license
|
||||
ErrAlert: "告警資訊格式錯誤,請檢查後重試!"
|
||||
ErrAlertPush: "告警資訊推送錯誤,請檢查後重試!"
|
||||
ErrAlertSave: "告警資訊保存錯誤,請檢查後重試!"
|
||||
ErrAlertSync: "告警資訊同步錯誤,請檢查後重試!"
|
||||
ErrAlertRemote: "告警資訊遠端錯誤,請檢查後重試!"
|
||||
|
||||
@@ -81,6 +81,8 @@ ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||
ErrSourcePathNotFound: "源目录不存在"
|
||||
ErrFavoriteExist: "已收藏此路径"
|
||||
ErrInvalidChar: "禁止使用非法字符"
|
||||
ErrPathNotDelete: "所选目录不可删除"
|
||||
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
@@ -94,6 +96,7 @@ ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可
|
||||
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
|
||||
ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
||||
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
||||
ErrImageNotExist: "运行环境 {{.name}} 镜像不存在,请重新编辑运行环境"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
||||
@@ -167,7 +170,9 @@ ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "当前未检测到系统 firewalld 或 ufw 服务,请检查后重试!"
|
||||
ErrFirewallNone: "未检测到系统 firewalld 或 ufw 服务,请检查后重试!"
|
||||
ErrFirewallBoth: "检测到系统同时存在 firewalld 或 ufw 服务,为避免冲突,请卸载后重试!"
|
||||
ErrNFTables: "端口转发功能依赖于 iptables 服务,暂不兼容 nftables 操作!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "脚本执行错误,请在任务输出文本域中查看具体信息。"
|
||||
@@ -199,4 +204,13 @@ ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证
|
||||
ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可证 界面导入许可证"
|
||||
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
|
||||
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
|
||||
ErrXpackLost: "许可证已达到最大重试次数,请进入【面板设置】【许可证】页面手动点击同步按钮,以确保专业版功能正常使用"
|
||||
ErrXpackTimeout: "请求超时,网络连接可能不稳定,请稍后再试!"
|
||||
|
||||
#license
|
||||
ErrAlert: "告警信息格式错误,请检查后重试!"
|
||||
ErrAlertPush: "告警信息推送错误,请检查后重试!"
|
||||
ErrAlertSave: "告警信息保存错误,请检查后重试!"
|
||||
ErrAlertSync: "告警信息同步错误,请检查后重试!"
|
||||
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
|
||||
|
||||
|
||||
Vendored
+3
-1
@@ -1,6 +1,7 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
|
||||
func Init() {
|
||||
c := global.CONF.System.Cache
|
||||
|
||||
_ = os.RemoveAll(c)
|
||||
_ = os.Mkdir(c, 0755)
|
||||
options := badger.Options{
|
||||
Dir: c,
|
||||
ValueDir: c,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
@@ -126,11 +128,24 @@ func handleSnapStatus() {
|
||||
}
|
||||
|
||||
func handleCronjobStatus() {
|
||||
_ = global.DB.Model(&model.JobRecords{}).Where("status = ?", constant.StatusWaiting).
|
||||
Updates(map[string]interface{}{
|
||||
"status": constant.StatusFailed,
|
||||
"message": "the task was interrupted due to the restart of the 1panel service",
|
||||
}).Error
|
||||
var jobRecords []model.JobRecords
|
||||
_ = global.DB.Where("status = ?", constant.StatusWaiting).Find(&jobRecords).Error
|
||||
for _, record := range jobRecords {
|
||||
err := global.DB.Model(&model.JobRecords{}).Where("status = ?", constant.StatusWaiting).
|
||||
Updates(map[string]interface{}{
|
||||
"status": constant.StatusFailed,
|
||||
"message": "the task was interrupted due to the restart of the 1panel service",
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to update job ID: %v, Error:%v", record.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var cronjob *model.Cronjob
|
||||
_ = global.DB.Where("id = ?", record.CronjobID).First(&cronjob).Error
|
||||
handleCronJobAlert(cronjob)
|
||||
}
|
||||
}
|
||||
|
||||
func loadLocalDir() {
|
||||
@@ -210,3 +225,20 @@ func initDir() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
if cronjob.Type == "snapshot" {
|
||||
return
|
||||
}
|
||||
pushAlert := dto.PushAlert{
|
||||
TaskName: cronjob.Name,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
Param: cronjob.Type,
|
||||
}
|
||||
err := xpack.PushAlert(pushAlert)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,10 @@ func Init() {
|
||||
migrations.AddShellColumn,
|
||||
migrations.AddClam,
|
||||
migrations.AddClamStatus,
|
||||
migrations.AddAlertMenu,
|
||||
migrations.AddComposeColumn,
|
||||
|
||||
migrations.AddAutoRestart,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -288,3 +288,49 @@ var AddClamStatus = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAlertMenu = &gormigrate.Migration{
|
||||
ID: "20240706-update-xpack-hide-menu",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var (
|
||||
setting model.Setting
|
||||
menu dto.XpackHideMenu
|
||||
)
|
||||
tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").First(&setting)
|
||||
if err := json.Unmarshal([]byte(setting.Value), &menu); err != nil {
|
||||
return err
|
||||
}
|
||||
menu.Children = append(menu.Children, dto.XpackHideMenu{
|
||||
ID: "7",
|
||||
Title: "xpack.alert.alert",
|
||||
Path: "/xpack/alert/dashboard",
|
||||
Label: "XAlertDashboard",
|
||||
IsCheck: true,
|
||||
})
|
||||
data, err := json.Marshal(menu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
|
||||
},
|
||||
}
|
||||
|
||||
var AddComposeColumn = &gormigrate.Migration{
|
||||
ID: "20240906-add-compose-command",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Compose{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAutoRestart = &gormigrate.Migration{
|
||||
ID: "20241021-add-auto-restart",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "AutoRestart", Value: "enable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -127,6 +127,9 @@ var AddTablePHPExtensions = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "WordPress", Extensions: "exif,igbinary,imagick,intl,zip,apcu,memcached,opcache,redis,bc,image,shmop,mysqli,pdo_mysql,gd"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "BookStack", Extensions: "gd,dom,iconv,mbstring,mysqlnd,openssl,pdo,pdo_mysql,tokenizer,xml"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "Flarum", Extensions: "curl,gd,pdo_mysql,mysqli,bz2,exif,yaf,imap"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -29,8 +29,9 @@ func BindDomain() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
if domains != status.Value {
|
||||
if LoadErrCode("err-domain") != 200 {
|
||||
helper.ErrResponse(c, LoadErrCode("err-domain"))
|
||||
code := LoadErrCode()
|
||||
if code != 200 {
|
||||
helper.ErrResponse(c, code)
|
||||
return
|
||||
}
|
||||
helper.ErrorWithDetail(c, constant.CodeErrDomain, constant.ErrTypeInternalServer, errors.New("domain not allowed"))
|
||||
|
||||
@@ -19,6 +19,7 @@ var whiteUrlList = map[string]struct{}{
|
||||
"/api/v1/logs/operation": {},
|
||||
"/api/v1/logs/login": {},
|
||||
"/api/v1/auth/logout": {},
|
||||
"/api/v1/dashboard/current": {},
|
||||
|
||||
"/api/v1/apps/installed/loadport": {},
|
||||
"/api/v1/apps/installed/check": {},
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
)
|
||||
|
||||
func LoadErrCode(errInfo string) int {
|
||||
func LoadErrCode() int {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
codeVal, err := settingRepo.Get(settingRepo.WithByKey("NoAuthSetting"))
|
||||
if err != nil {
|
||||
|
||||
@@ -35,8 +35,9 @@ func WhiteAllow() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
}
|
||||
if LoadErrCode("err-ip") != 200 {
|
||||
helper.ErrResponse(c, LoadErrCode("err-ip"))
|
||||
code := LoadErrCode()
|
||||
if code != 200 {
|
||||
helper.ErrResponse(c, code)
|
||||
return
|
||||
}
|
||||
helper.ErrorWithDetail(c, constant.CodeErrIP, constant.ErrTypeInternalServer, errors.New("IP address not allowed"))
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -96,10 +97,11 @@ func OperationLog() gin.HandlerFunc {
|
||||
if funcs.InputValue == key {
|
||||
var names []string
|
||||
if funcs.IsList {
|
||||
sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
||||
_ = global.DB.Raw(sql, value).Scan(&names)
|
||||
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s in (?)", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
||||
_ = global.DB.Raw(query, value).Scan(&names)
|
||||
} else {
|
||||
_ = global.DB.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names)
|
||||
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s = ?", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
||||
_ = global.DB.Raw(query, value).Scan(&names)
|
||||
}
|
||||
formatMap[funcs.OutputValue] = strings.Join(names, ",")
|
||||
break
|
||||
@@ -109,12 +111,21 @@ func OperationLog() gin.HandlerFunc {
|
||||
}
|
||||
for key, value := range formatMap {
|
||||
if strings.Contains(operationDic.FormatEN, "["+key+"]") {
|
||||
if arrays, ok := value.([]string); ok {
|
||||
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
|
||||
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
|
||||
} else {
|
||||
t := reflect.TypeOf(value)
|
||||
if t.Kind() != reflect.Array && t.Kind() != reflect.Slice {
|
||||
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", value))
|
||||
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", value))
|
||||
} else {
|
||||
val := reflect.ValueOf(value)
|
||||
length := val.Len()
|
||||
|
||||
var elements []string
|
||||
for i := 0; i < length; i++ {
|
||||
element := val.Index(i).Interface().(string)
|
||||
elements = append(elements, element)
|
||||
}
|
||||
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ",")))
|
||||
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ",")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func PasswordExpired() gin.HandlerFunc {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
|
||||
return
|
||||
}
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, extime.Value, loc)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
|
||||
|
||||
@@ -18,7 +18,7 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
{
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
|
||||
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
|
||||
cmdRouter.POST("/current", baseApi.LoadDashboardCurrentInfo)
|
||||
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
|
||||
settingRouter.POST("/snapshot/status", baseApi.LoadSnapShotStatus)
|
||||
settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot)
|
||||
settingRouter.POST("/snapshot/size", baseApi.LoadSnapshotSize)
|
||||
settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot)
|
||||
settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot)
|
||||
settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot)
|
||||
|
||||
@@ -27,12 +27,16 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
|
||||
if len(scType) == 0 {
|
||||
scType = "Standard"
|
||||
}
|
||||
mode := loadParamFromVars("mode", vars)
|
||||
if len(mode) == 0 {
|
||||
mode = "virtual hosted"
|
||||
}
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Region: aws.String(region),
|
||||
DisableSSL: aws.Bool(true),
|
||||
S3ForcePathStyle: aws.Bool(false),
|
||||
S3ForcePathStyle: aws.Bool(mode == "path"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -204,7 +204,7 @@ func SudoHandleCmd() string {
|
||||
|
||||
func Which(name string) bool {
|
||||
stdout, err := Execf("which %s", name)
|
||||
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0 && strings.HasPrefix(stdout, "/")) {
|
||||
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -256,13 +256,6 @@ func LoadSizeUnit2F(value float64) string {
|
||||
return fmt.Sprintf("%.2f", value)
|
||||
}
|
||||
|
||||
func LoadTimeZone() string {
|
||||
loc := time.Now().Location()
|
||||
if _, err := time.LoadLocation(loc.String()); err != nil {
|
||||
return "Asia/Shanghai"
|
||||
}
|
||||
return loc.String()
|
||||
}
|
||||
func LoadTimeZoneByCmd() string {
|
||||
loc := time.Now().Location().String()
|
||||
if _, err := time.LoadLocation(loc); err != nil {
|
||||
@@ -325,3 +318,27 @@ func SplitStr(str string, spi ...string) []string {
|
||||
func IsValidIP(ip string) bool {
|
||||
return net.ParseIP(ip) != nil
|
||||
}
|
||||
|
||||
const (
|
||||
b = uint64(1)
|
||||
kb = 1024 * b
|
||||
mb = 1024 * kb
|
||||
gb = 1024 * mb
|
||||
)
|
||||
|
||||
func FormatBytes(bytes uint64) string {
|
||||
switch {
|
||||
case bytes < kb:
|
||||
return fmt.Sprintf("%dB", bytes)
|
||||
case bytes < mb:
|
||||
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
|
||||
case bytes < gb:
|
||||
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
|
||||
default:
|
||||
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
|
||||
}
|
||||
}
|
||||
|
||||
func FormatPercent(percent float64) string {
|
||||
return fmt.Sprintf("%.2f%%", percent)
|
||||
}
|
||||
|
||||
@@ -613,6 +613,9 @@ func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType
|
||||
func (f FileOp) Decompress(srcFile string, dst string, cType CompressType, secret string) error {
|
||||
if cType == Tar || cType == Zip || cType == TarGz {
|
||||
shellArchiver, err := NewShellArchiver(cType)
|
||||
if !f.Stat(dst) {
|
||||
_ = f.CreateDir(dst, 0755)
|
||||
}
|
||||
if err == nil {
|
||||
if err = shellArchiver.Extract(srcFile, dst, secret); err == nil {
|
||||
return nil
|
||||
|
||||
@@ -72,6 +72,9 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
|
||||
info, err := appFs.Stat(op.Path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, buserr.New(constant.ErrLinkPathNotFound)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -101,7 +104,26 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
}
|
||||
|
||||
if file.IsSymlink {
|
||||
file.LinkPath = GetSymlink(op.Path)
|
||||
linkPath := GetSymlink(op.Path)
|
||||
if !filepath.IsAbs(linkPath) {
|
||||
dir := filepath.Dir(op.Path)
|
||||
var err error
|
||||
linkPath, err = filepath.Abs(filepath.Join(dir, linkPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
file.LinkPath = linkPath
|
||||
targetInfo, err := appFs.Stat(linkPath)
|
||||
if err != nil {
|
||||
file.IsDir = false
|
||||
file.Mode = "-"
|
||||
file.User = "-"
|
||||
file.Group = "-"
|
||||
} else {
|
||||
file.IsDir = targetInfo.IsDir()
|
||||
}
|
||||
file.Extension = filepath.Ext(file.LinkPath)
|
||||
}
|
||||
if op.Expand {
|
||||
if err := handleExpansion(file, op); err != nil {
|
||||
@@ -308,7 +330,26 @@ func (f *FileInfo) processFiles(files []FileSearchInfo, option FileOption) ([]*F
|
||||
file.FavoriteID = favorite.ID
|
||||
}
|
||||
if isSymlink {
|
||||
file.LinkPath = GetSymlink(fPath)
|
||||
linkPath := GetSymlink(fPath)
|
||||
if !filepath.IsAbs(linkPath) {
|
||||
dir := filepath.Dir(fPath)
|
||||
var err error
|
||||
linkPath, err = filepath.Abs(filepath.Join(dir, linkPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
file.LinkPath = linkPath
|
||||
targetInfo, err := file.Fs.Stat(linkPath)
|
||||
if err != nil {
|
||||
file.IsDir = false
|
||||
file.Mode = "-"
|
||||
file.User = "-"
|
||||
file.Group = "-"
|
||||
} else {
|
||||
file.IsDir = targetInfo.IsDir()
|
||||
}
|
||||
file.Extension = filepath.Ext(file.LinkPath)
|
||||
}
|
||||
if df.Size() > 0 {
|
||||
file.MimeType = GetMimeType(fPath)
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewTarArchiver(compressType CompressType) ShellArchiver {
|
||||
}
|
||||
|
||||
func (t TarArchiver) Extract(FilePath string, dstDir string, secret string) error {
|
||||
return cmd.ExecCmd(fmt.Sprintf("%s %s %s -C %s", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir))
|
||||
return cmd.ExecCmd(fmt.Sprintf("%s %s \"%s\" -C \"%s\"", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir))
|
||||
}
|
||||
|
||||
func (t TarArchiver) Compress(sourcePaths []string, dstFile string, secret string) error {
|
||||
|
||||
@@ -20,11 +20,11 @@ func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
var err error
|
||||
commands := ""
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + filePath + " | "
|
||||
commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, dstDir+" > /dev/null 2>&1")
|
||||
extraCmd := fmt.Sprintf("openssl enc -d -aes-256-cbc -k '%s' -in '%s' | ", secret, filePath)
|
||||
commands = fmt.Sprintf("%s tar -zxvf - -C '%s' > /dev/null 2>&1", extraCmd, dstDir)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar -zxvf %s %s", filePath+" -C ", dstDir+" > /dev/null 2>&1")
|
||||
commands = fmt.Sprintf("tar -zxvf '%s' -C '%s' > /dev/null 2>&1", filePath, dstDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
if err = cmd.ExecCmd(commands); err != nil {
|
||||
@@ -34,27 +34,25 @@ func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
}
|
||||
|
||||
func (t TarGzArchiver) Compress(sourcePaths []string, dstFile string, secret string) error {
|
||||
var err error
|
||||
path := ""
|
||||
itemDir := ""
|
||||
var itemDirs []string
|
||||
for _, item := range sourcePaths {
|
||||
itemDir += filepath.Base(item) + " "
|
||||
itemDirs = append(itemDirs, fmt.Sprintf("\"%s\"", filepath.Base(item)))
|
||||
}
|
||||
aheadDir := dstFile[:strings.LastIndex(dstFile, "/")]
|
||||
itemDir := strings.Join(itemDirs, " ")
|
||||
aheadDir := filepath.Dir(sourcePaths[0])
|
||||
if len(aheadDir) == 0 {
|
||||
aheadDir = "/"
|
||||
}
|
||||
path += fmt.Sprintf("- -C %s %s", aheadDir, itemDir)
|
||||
commands := ""
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
||||
commands = fmt.Sprintf("tar -zcf %s %s %s", path, extraCmd, dstFile)
|
||||
extraCmd := fmt.Sprintf("| openssl enc -aes-256-cbc -salt -k '%s' -out '%s'", secret, dstFile)
|
||||
commands = fmt.Sprintf("tar -zcf - -C \"%s\" %s %s", aheadDir, itemDir, extraCmd)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar -zcf %s -C %s %s", dstFile, aheadDir, itemDir)
|
||||
commands = fmt.Sprintf("tar -zcf \"%s\" -C \"%s\" %s", dstFile, aheadDir, itemDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
if err = cmd.ExecCmd(commands); err != nil {
|
||||
if err := cmd.ExecCmd(commands); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -72,16 +72,21 @@ func countLines(path string) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
lineCount := 0
|
||||
for scanner.Scan() {
|
||||
lineCount++
|
||||
reader := bufio.NewReader(file)
|
||||
count := 0
|
||||
for {
|
||||
_, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if count > 0 {
|
||||
count++
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
count++
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return lineCount, nil
|
||||
}
|
||||
|
||||
func ReadFileByLine(filename string, page, pageSize int, latest bool) (lines []string, isEndOfFile bool, total int, err error) {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/firewall/client"
|
||||
)
|
||||
|
||||
@@ -29,11 +28,18 @@ type FirewallClient interface {
|
||||
}
|
||||
|
||||
func NewFirewallClient() (FirewallClient, error) {
|
||||
if _, err := os.Stat("/usr/sbin/firewalld"); err == nil {
|
||||
firewalld := cmd.Which("firewalld")
|
||||
ufw := cmd.Which("ufw")
|
||||
|
||||
if firewalld && ufw {
|
||||
return nil, buserr.New(constant.ErrFirewallBoth)
|
||||
}
|
||||
|
||||
if firewalld {
|
||||
return client.NewFirewalld()
|
||||
}
|
||||
if _, err := os.Stat("/usr/sbin/ufw"); err == nil {
|
||||
if ufw {
|
||||
return client.NewUfw()
|
||||
}
|
||||
return nil, buserr.New(constant.ErrFirewall)
|
||||
return nil, buserr.New(constant.ErrFirewallNone)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -35,6 +36,9 @@ func HandleGetWithTransport(url, method string, transport *http.Transport, timeo
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return 0, nil, errors.New(resp.Status)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
|
||||
@@ -40,7 +40,6 @@ func ValidCode(code, intervalStr, secret string) bool {
|
||||
}
|
||||
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
||||
now := time.Now().Unix()
|
||||
strInt64 := strconv.FormatInt(now, 10)
|
||||
id16, _ := strconv.Atoi(strInt64)
|
||||
return totp.Verify(code, int64(id16))
|
||||
prevTime := now - int64(interval)
|
||||
return totp.Verify(code, now) || totp.Verify(code, prevTime)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user