mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-13 08:00:35 +00:00
Compare commits
100
Commits
aliyun
...
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 |
@@ -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
|
||||
+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:
|
||||
version_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,6 +46,10 @@ 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 大语言模型的开源知识库问答系统
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
@@ -56,7 +57,7 @@ func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
|
||||
// @Tags Dashboard
|
||||
// @Summary Load dashboard current info
|
||||
// @Description 获取首页实时数据
|
||||
// @Accept json、
|
||||
// @Accept json
|
||||
// @Param request body dto.DashboardReq true "request"
|
||||
// @Success 200 {object} dto.DashboardCurrent
|
||||
// @Security ApiKeyAuth
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -218,7 +218,7 @@ type ComposeCreate struct {
|
||||
}
|
||||
type ComposeOperation struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Path string `json:"path"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
|
||||
WithFile bool `json:"withFile"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
@@ -147,7 +149,7 @@ 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"`
|
||||
|
||||
InterruptStep string `json:"interruptStep"`
|
||||
|
||||
@@ -924,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1342,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 {
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/1Panel-dev/1Panel/backend/xpack/utils/alert"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
@@ -634,9 +633,8 @@ 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:") {
|
||||
var infectedFiles = 0
|
||||
infectedFiles, _ = strconv.Atoi(strings.TrimPrefix(line, "Infected files:"))
|
||||
if strings.HasPrefix(line, "Infected files: ") {
|
||||
infectedFiles, _ := strconv.Atoi(strings.TrimPrefix(line, "Infected files: "))
|
||||
if infectedFiles > 0 {
|
||||
pushAlert := dto.PushAlert{
|
||||
TaskName: clamName,
|
||||
@@ -644,7 +642,7 @@ func handleAlert(stdout, clamName string, clamId uint) {
|
||||
EntryID: clamId,
|
||||
Param: strconv.Itoa(infectedFiles),
|
||||
}
|
||||
err := alert.PushAlert(pushAlert)
|
||||
err := xpack.PushAlert(pushAlert)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("clamdscan push failed, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -252,6 +252,10 @@ 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)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -30,6 +31,9 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(appItems) == 0 {
|
||||
return errors.New("no such app in database!")
|
||||
}
|
||||
apps = appItems
|
||||
}
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
@@ -65,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
|
||||
@@ -98,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
|
||||
|
||||
@@ -25,8 +25,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var alertTypes = map[string]bool{"app": true, "website": true, "database": true, "directory": true, "log": true, "snapshot": true}
|
||||
|
||||
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
var (
|
||||
message []byte
|
||||
@@ -398,17 +396,15 @@ func hasBackup(cronjobType string) bool {
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
if alertTypes[cronjob.Type] {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,12 +198,28 @@ 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)
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -218,9 +234,8 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
|
||||
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
|
||||
}
|
||||
@@ -268,9 +283,8 @@ 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 := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -312,9 +326,8 @@ 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 := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -322,9 +335,8 @@ 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)
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -345,9 +357,8 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||
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
|
||||
}
|
||||
@@ -355,6 +366,10 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
service := "docker"
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
dockerCmd, err := getDockerRestartCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operation == "stop" {
|
||||
isSocketActive, _ := systemctl.IsActive("docker.socket")
|
||||
if isSocketActive {
|
||||
@@ -371,9 +386,9 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
|
||||
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
return errors.New(stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -428,9 +443,39 @@ 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
|
||||
}
|
||||
|
||||
@@ -211,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" {
|
||||
|
||||
@@ -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
|
||||
@@ -684,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
|
||||
}
|
||||
|
||||
@@ -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,15 +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")
|
||||
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
|
||||
}
|
||||
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
@@ -116,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 {
|
||||
@@ -158,19 +154,37 @@ 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)
|
||||
}
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
if req.Auth {
|
||||
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
@@ -179,15 +193,8 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -441,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])
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ var (
|
||||
ErrCmdIllegal = "ErrCmdIllegal"
|
||||
ErrXpackNotFound = "ErrXpackNotFound"
|
||||
ErrXpackNotActive = "ErrXpackNotActive"
|
||||
ErrXpackLost = "ErrXpackLost"
|
||||
ErrXpackTimeout = "ErrXpackTimeout"
|
||||
ErrXpackOutOfDate = "ErrXpackOutOfDate"
|
||||
)
|
||||
|
||||
@@ -100,6 +102,7 @@ var (
|
||||
ErrFileDownloadDir = "ErrFileDownloadDir"
|
||||
ErrCmdNotFound = "ErrCmdNotFound"
|
||||
ErrFavoriteExist = "ErrFavoriteExist"
|
||||
ErrPathNotDelete = "ErrPathNotDelete"
|
||||
)
|
||||
|
||||
// mysql
|
||||
@@ -143,7 +146,9 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFirewall = "ErrFirewall"
|
||||
ErrFirewallNone = "ErrFirewallNone"
|
||||
ErrFirewallBoth = "ErrFirewallBoth"
|
||||
ErrNFTables = "ErrNFTables"
|
||||
)
|
||||
|
||||
// cronjob
|
||||
|
||||
@@ -10,6 +10,8 @@ const (
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
StatusNone = "None"
|
||||
StatusOnRetry = "OnRetry"
|
||||
StatusLost = "Lost"
|
||||
|
||||
OrderDesc = "descending"
|
||||
OrderAsc = "ascending"
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -22,7 +23,7 @@ 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()
|
||||
@@ -51,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
|
||||
@@ -69,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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -165,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."
|
||||
@@ -197,6 +201,8 @@ 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
|
||||
|
||||
@@ -81,6 +81,7 @@ ErrFileDownloadDir: "不支持下載文件夾"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
||||
ErrSourcePathNotFound: "源目錄不存在"
|
||||
ErrFavoriteExist: "已收藏此路徑"
|
||||
ErrPathNotDelete: "所選目錄不可删除"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
@@ -166,7 +167,9 @@ ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用戶 {{ .name }} 查找失敗 {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "當前未檢測到系統 firewalld 或 ufw 服務,請檢查後重試!"
|
||||
ErrFirewallNone: "未檢測到系統 firewalld 或 ufw 服務,請檢查後重試!"
|
||||
ErrFirewallBoth: "檢測到系統同時存在 firewalld 或 ufw 服務,為避免衝突,請卸載後重試!"
|
||||
ErrNFTables: "端口轉發功能依賴於 iptables 服務,暫不兼容 nftables 操作!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "腳本執行錯誤,請在任務輸出文本域中查看具體信息。"
|
||||
@@ -199,6 +202,8 @@ ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證
|
||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
|
||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
|
||||
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
|
||||
ErrXpackLost: "許可證已達到最大重試次數,請進入【面板設定】【許可證】頁面手動點擊同步按鈕,以確保專業版功能正常使用"
|
||||
ErrXpackTimeout: "請求超時,網絡連接可能不穩定,請稍後再試!"
|
||||
|
||||
#license
|
||||
ErrAlert: "告警資訊格式錯誤,請檢查後重試!"
|
||||
|
||||
@@ -81,6 +81,8 @@ ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||
ErrSourcePathNotFound: "源目录不存在"
|
||||
ErrFavoriteExist: "已收藏此路径"
|
||||
ErrInvalidChar: "禁止使用非法字符"
|
||||
ErrPathNotDelete: "所选目录不可删除"
|
||||
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
@@ -168,7 +170,9 @@ ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "当前未检测到系统 firewalld 或 ufw 服务,请检查后重试!"
|
||||
ErrFirewallNone: "未检测到系统 firewalld 或 ufw 服务,请检查后重试!"
|
||||
ErrFirewallBoth: "检测到系统同时存在 firewalld 或 ufw 服务,为避免冲突,请卸载后重试!"
|
||||
ErrNFTables: "端口转发功能依赖于 iptables 服务,暂不兼容 nftables 操作!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "脚本执行错误,请在任务输出文本域中查看具体信息。"
|
||||
@@ -200,6 +204,8 @@ ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证
|
||||
ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可证 界面导入许可证"
|
||||
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
|
||||
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
|
||||
ErrXpackLost: "许可证已达到最大重试次数,请进入【面板设置】【许可证】页面手动点击同步按钮,以确保专业版功能正常使用"
|
||||
ErrXpackTimeout: "请求超时,网络连接可能不稳定,请稍后再试!"
|
||||
|
||||
#license
|
||||
ErrAlert: "告警信息格式错误,请检查后重试!"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,8 @@ func Init() {
|
||||
migrations.AddClamStatus,
|
||||
migrations.AddAlertMenu,
|
||||
migrations.AddComposeColumn,
|
||||
|
||||
migrations.AddAutoRestart,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -324,3 +324,13 @@ var AddComposeColumn = &gormigrate.Migration{
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,18 +28,18 @@ type FirewallClient interface {
|
||||
}
|
||||
|
||||
func NewFirewallClient() (FirewallClient, error) {
|
||||
_, firewalldErr := os.Stat("/usr/sbin/firewalld")
|
||||
_, ufwErr := os.Stat("/usr/sbin/ufw")
|
||||
firewalld := cmd.Which("firewalld")
|
||||
ufw := cmd.Which("ufw")
|
||||
|
||||
if firewalldErr == nil && ufwErr == nil {
|
||||
return nil, buserr.New("firewalld and ufw both found, only one firewall should be active")
|
||||
if firewalld && ufw {
|
||||
return nil, buserr.New(constant.ErrFirewallBoth)
|
||||
}
|
||||
|
||||
if firewalldErr == nil {
|
||||
if firewalld {
|
||||
return client.NewFirewalld()
|
||||
}
|
||||
if ufwErr == nil {
|
||||
if ufw {
|
||||
return client.NewUfw()
|
||||
}
|
||||
return nil, buserr.New(constant.ErrFirewall)
|
||||
return nil, buserr.New(constant.ErrFirewallNone)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ var repeatKeys = map[string]struct {
|
||||
"include": {},
|
||||
"sub_filter": {},
|
||||
"add_header": {},
|
||||
"error_page": {},
|
||||
}
|
||||
|
||||
func IsRepeatKey(key string) bool {
|
||||
|
||||
@@ -3,7 +3,6 @@ package ssl
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ssl/huaweicloud"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -16,13 +15,16 @@ import (
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/clouddns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dnspod"
|
||||
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namecheap"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/volcengine"
|
||||
"github.com/go-acme/lego/v4/providers/http/webroot"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
@@ -67,7 +69,9 @@ type DnsType string
|
||||
const (
|
||||
DnsPod DnsType = "DnsPod"
|
||||
AliYun DnsType = "AliYun"
|
||||
Volcengine DnsType = "Volcengine"
|
||||
CloudFlare DnsType = "CloudFlare"
|
||||
CloudDns DnsType = "CloudDns"
|
||||
NameSilo DnsType = "NameSilo"
|
||||
NameCheap DnsType = "NameCheap"
|
||||
NameCom DnsType = "NameCom"
|
||||
@@ -87,6 +91,8 @@ type DNSParam struct {
|
||||
APISecret string `json:"apiSecret"`
|
||||
SecretID string `json:"secretID"`
|
||||
Region string `json:"region"`
|
||||
ClientID string `json:"clientID"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -122,6 +128,14 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
|
||||
alidnsConfig.PollingInterval = pollingInterval
|
||||
alidnsConfig.TTL = ttl
|
||||
p, err = alidns.NewDNSProviderConfig(alidnsConfig)
|
||||
case Volcengine:
|
||||
volcConfig := volcengine.NewDefaultConfig()
|
||||
volcConfig.SecretKey = param.SecretKey
|
||||
volcConfig.AccessKey = param.AccessKey
|
||||
volcConfig.PropagationTimeout = propagationTimeout
|
||||
volcConfig.PollingInterval = pollingInterval
|
||||
volcConfig.TTL = ttl
|
||||
p, err = volcengine.NewDNSProviderConfig(volcConfig)
|
||||
case CloudFlare:
|
||||
cloudflareConfig := cloudflare.NewDefaultConfig()
|
||||
cloudflareConfig.AuthEmail = param.Email
|
||||
@@ -130,6 +144,15 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
|
||||
cloudflareConfig.PollingInterval = pollingInterval
|
||||
cloudflareConfig.TTL = 3600
|
||||
p, err = cloudflare.NewDNSProviderConfig(cloudflareConfig)
|
||||
case CloudDns:
|
||||
clouddnsConfig := clouddns.NewDefaultConfig()
|
||||
clouddnsConfig.ClientID = param.ClientID
|
||||
clouddnsConfig.Email = param.Email
|
||||
clouddnsConfig.Password = param.Password
|
||||
clouddnsConfig.PropagationTimeout = propagationTimeout
|
||||
clouddnsConfig.PollingInterval = pollingInterval
|
||||
clouddnsConfig.TTL = ttl
|
||||
p, err = clouddns.NewDNSProviderConfig(clouddnsConfig)
|
||||
case NameCheap:
|
||||
namecheapConfig := namecheap.NewDefaultConfig()
|
||||
namecheapConfig.APIKey = param.APIkey
|
||||
@@ -199,7 +222,7 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
|
||||
dns01.CondOption(len(nameservers) > 0,
|
||||
dns01.AddRecursiveNameservers(nameservers)),
|
||||
dns01.CondOption(websiteSSL.SkipDNS,
|
||||
dns01.DisableCompletePropagationRequirement()),
|
||||
dns01.DisableAuthoritativeNssPropagationRequirement()),
|
||||
dns01.AddDNSTimeout(10*time.Minute),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
// Package huaweicloud implements a DNS provider for solving the DNS-01 challenge using Huawei Cloud.
|
||||
package huaweicloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
"github.com/go-acme/lego/v4/platform/wait"
|
||||
hwauthbasic "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
|
||||
hwconfig "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/config"
|
||||
hwdns "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/dns/v2"
|
||||
hwmodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/dns/v2/model"
|
||||
hwregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/dns/v2/region"
|
||||
)
|
||||
|
||||
// Environment variables names.
|
||||
const (
|
||||
envNamespace = "HUAWEICLOUD_"
|
||||
|
||||
EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
|
||||
EnvSecretAccessKey = envNamespace + "SECRET_ACCESS_KEY"
|
||||
EnvRegion = envNamespace + "REGION"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider.
|
||||
type Config struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
Region string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int32
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: int32(env.GetOrDefaultInt(EnvTTL, 300)),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the challenge.Provider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *hwdns.DnsClient
|
||||
|
||||
recordIDs map[string]string
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Huawei Cloud.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// HUAWEICLOUD_ACCESS_KEY_ID, HUAWEICLOUD_SECRET_ACCESS_KEY, and HUAWEICLOUD_REGION.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey, EnvRegion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("huaweicloud: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AccessKeyID = values[EnvAccessKeyID]
|
||||
config.SecretAccessKey = values[EnvSecretAccessKey]
|
||||
config.Region = values[EnvRegion]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Huawei Cloud.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("huaweicloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.AccessKeyID == "" || config.SecretAccessKey == "" || config.Region == "" {
|
||||
return nil, errors.New("huaweicloud: credentials missing")
|
||||
}
|
||||
|
||||
auth, err := hwauthbasic.NewCredentialsBuilder().
|
||||
WithAk(config.AccessKeyID).
|
||||
WithSk(config.SecretAccessKey).
|
||||
SafeBuild()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("huaweicloud: crendential build: %w", err)
|
||||
}
|
||||
|
||||
region, err := hwregion.SafeValueOf(config.Region)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("huaweicloud: safe region: %w", err)
|
||||
}
|
||||
|
||||
client, err := hwdns.DnsClientBuilder().
|
||||
WithHttpConfig(hwconfig.DefaultHttpConfig().WithTimeout(config.HTTPTimeout)).
|
||||
WithRegion(region).
|
||||
WithCredential(auth).
|
||||
SafeBuild()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("huaweicloud: client build: %w", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: hwdns.NewDnsClient(client),
|
||||
recordIDs: map[string]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("huaweicloud: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("huaweicloud: %w", err)
|
||||
}
|
||||
|
||||
recordSetID, err := d.getOrCreateRecordSetID(domain, zoneID, info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("huaweicloud: %w", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[token] = recordSetID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
err = wait.For("record set sync on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) {
|
||||
rs, errShow := d.client.ShowRecordSet(&hwmodel.ShowRecordSetRequest{
|
||||
ZoneId: zoneID,
|
||||
RecordsetId: recordSetID,
|
||||
})
|
||||
if errShow != nil {
|
||||
return false, fmt.Errorf("show record set: %w", errShow)
|
||||
}
|
||||
|
||||
return !strings.HasSuffix(deref(rs.Status), "PENDING_"), nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("huaweicloud: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
// gets the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[token]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("huaweicloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token)
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("huaweicloud: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("huaweicloud: %w", err)
|
||||
}
|
||||
|
||||
request := &hwmodel.DeleteRecordSetRequest{
|
||||
ZoneId: zoneID,
|
||||
RecordsetId: recordID,
|
||||
}
|
||||
|
||||
_, err = d.client.DeleteRecordSet(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("huaweicloud: delete record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getOrCreateRecordSetID(domain, zoneID string, info dns01.ChallengeInfo) (string, error) {
|
||||
records, err := d.client.ListRecordSetsByZone(&hwmodel.ListRecordSetsByZoneRequest{
|
||||
ZoneId: zoneID,
|
||||
Name: pointer(info.EffectiveFQDN),
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("record list: unable to get record %s for zone %s: %w", info.EffectiveFQDN, domain, err)
|
||||
}
|
||||
|
||||
var existingRecordSet *hwmodel.ListRecordSets
|
||||
|
||||
for _, record := range deref(records.Recordsets) {
|
||||
if deref(record.Type) == "TXT" && deref(record.Name) == info.EffectiveFQDN {
|
||||
existingRecordSet = &record
|
||||
}
|
||||
}
|
||||
|
||||
value := strconv.Quote(info.Value)
|
||||
|
||||
if existingRecordSet == nil {
|
||||
request := &hwmodel.CreateRecordSetRequest{
|
||||
ZoneId: zoneID,
|
||||
Body: &hwmodel.CreateRecordSetRequestBody{
|
||||
Name: info.EffectiveFQDN,
|
||||
Description: pointer("Added TXT record for ACME dns-01 challenge using lego client"),
|
||||
Type: "TXT",
|
||||
Ttl: pointer(d.config.TTL),
|
||||
Records: []string{value},
|
||||
},
|
||||
}
|
||||
|
||||
resp, errCreate := d.client.CreateRecordSet(request)
|
||||
if errCreate != nil {
|
||||
return "", fmt.Errorf("create record set: %w", errCreate)
|
||||
}
|
||||
|
||||
return deref(resp.Id), nil
|
||||
}
|
||||
|
||||
updateRequest := &hwmodel.UpdateRecordSetRequest{
|
||||
ZoneId: zoneID,
|
||||
RecordsetId: deref(existingRecordSet.Id),
|
||||
Body: &hwmodel.UpdateRecordSetReq{
|
||||
Name: existingRecordSet.Name,
|
||||
Description: existingRecordSet.Description,
|
||||
Type: existingRecordSet.Type,
|
||||
Ttl: existingRecordSet.Ttl,
|
||||
Records: pointer(append(deref(existingRecordSet.Records), value)),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := d.client.UpdateRecordSet(updateRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("update record set: %w", err)
|
||||
}
|
||||
|
||||
return deref(resp.Id), nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneID(authZone string) (string, error) {
|
||||
zones, err := d.client.ListPublicZones(&hwmodel.ListPublicZonesRequest{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to get zone: %w", err)
|
||||
}
|
||||
|
||||
for _, zone := range deref(zones.Zones) {
|
||||
if deref(zone.Name) == authZone {
|
||||
return deref(zone.Id), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("zone %q not found", authZone)
|
||||
}
|
||||
|
||||
func pointer[T any](v T) *T { return &v }
|
||||
|
||||
func deref[T any](v *T) T {
|
||||
if v == nil {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
return *v
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
Name = "Huawei Cloud"
|
||||
Description = ''''''
|
||||
URL = "https://huaweicloud.com"
|
||||
Code = "huaweicloud"
|
||||
Since = "v4.19"
|
||||
|
||||
Example = '''
|
||||
HUAWEICLOUD_ACCESS_KEY_ID=your-access-key-id \
|
||||
HUAWEICLOUD_SECRET_ACCESS_KEY=your-secret-access-key \
|
||||
HUAWEICLOUD_REGION=cn-south-1 \
|
||||
lego --email you@example.com --dns huaweicloud --domains my.example.org run
|
||||
'''
|
||||
|
||||
[Configuration]
|
||||
[Configuration.Credentials]
|
||||
HUAWEICLOUD_ACCESS_KEY_ID = "Access key ID"
|
||||
HUAWEICLOUD_SECRET_ACCESS_KEY = "Access Key secret"
|
||||
HUAWEICLOUD_REGION = "Region"
|
||||
|
||||
[Configuration.Additional]
|
||||
HUAWEICLOUD_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||
HUAWEICLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||
HUAWEICLOUD_TTL = "The TTL of the TXT record used for the DNS challenge"
|
||||
HUAWEICLOUD_HTTP_TIMEOUT = "API request timeout"
|
||||
|
||||
[Links]
|
||||
API = "https://console-intl.huaweicloud.com/apiexplorer/#/openapi/DNS/doc?locale=en-us"
|
||||
CN_API = "https://support.huaweicloud.com/api-dns/zh-cn_topic_0132421999.html"
|
||||
GoClient = "https://github.com/huaweicloud/huaweicloud-sdk-go-v3"
|
||||
@@ -1,183 +0,0 @@
|
||||
package huaweicloud
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/platform/tester"
|
||||
hwregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/dns/v2/region"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const envDomain = envNamespace + "DOMAIN"
|
||||
|
||||
var envTest = tester.NewEnvTest(EnvAccessKeyID, EnvSecretAccessKey, EnvRegion).
|
||||
WithDomain(envDomain)
|
||||
|
||||
func TestNewDNSProvider(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
envVars map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "success",
|
||||
envVars: map[string]string{
|
||||
EnvAccessKeyID: "123",
|
||||
EnvSecretAccessKey: "456",
|
||||
EnvRegion: hwregion.CN_EAST_2.Id,
|
||||
},
|
||||
// The "success" cannot be tested because there is an API call that require a valid authentication.
|
||||
// Also, there is a bug during the error message creation:
|
||||
// https://github.com/huaweicloud/huaweicloud-sdk-go-v3/pull/81
|
||||
expected: "huaweicloud: client build: runtime error: invalid memory address or nil pointer dereference",
|
||||
},
|
||||
{
|
||||
desc: "missing credentials",
|
||||
envVars: map[string]string{
|
||||
EnvAccessKeyID: "",
|
||||
EnvSecretAccessKey: "",
|
||||
EnvRegion: "",
|
||||
},
|
||||
expected: "huaweicloud: some credentials information are missing: HUAWEICLOUD_ACCESS_KEY_ID,HUAWEICLOUD_SECRET_ACCESS_KEY,HUAWEICLOUD_REGION",
|
||||
},
|
||||
{
|
||||
desc: "missing access id",
|
||||
envVars: map[string]string{
|
||||
EnvAccessKeyID: "",
|
||||
EnvSecretAccessKey: "456",
|
||||
EnvRegion: hwregion.CN_EAST_2.Id,
|
||||
},
|
||||
expected: "huaweicloud: some credentials information are missing: HUAWEICLOUD_ACCESS_KEY_ID",
|
||||
},
|
||||
{
|
||||
desc: "missing secret key",
|
||||
envVars: map[string]string{
|
||||
EnvAccessKeyID: "123",
|
||||
EnvSecretAccessKey: "",
|
||||
EnvRegion: hwregion.CN_EAST_2.Id,
|
||||
},
|
||||
expected: "huaweicloud: some credentials information are missing: HUAWEICLOUD_SECRET_ACCESS_KEY",
|
||||
},
|
||||
{
|
||||
desc: "missing secret key",
|
||||
envVars: map[string]string{
|
||||
EnvAccessKeyID: "123",
|
||||
EnvSecretAccessKey: "456",
|
||||
EnvRegion: "",
|
||||
},
|
||||
expected: "huaweicloud: some credentials information are missing: HUAWEICLOUD_REGION",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
defer envTest.RestoreEnv()
|
||||
envTest.ClearEnv()
|
||||
|
||||
envTest.Apply(test.envVars)
|
||||
|
||||
p, err := NewDNSProvider()
|
||||
|
||||
if test.expected == "" {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, p)
|
||||
require.NotNil(t, p.config)
|
||||
require.NotNil(t, p.client)
|
||||
} else {
|
||||
require.EqualError(t, err, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDNSProviderConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
accessKeyID string
|
||||
secretAccessKey string
|
||||
region string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "success",
|
||||
accessKeyID: "123",
|
||||
secretAccessKey: "456",
|
||||
region: hwregion.CN_EAST_2.Id,
|
||||
// The "success" cannot be tested because there is an API call that require a valid authentication.
|
||||
// Also, there is a bug during the error message creation:
|
||||
// https://github.com/huaweicloud/huaweicloud-sdk-go-v3/pull/81
|
||||
expected: "huaweicloud: client build: runtime error: invalid memory address or nil pointer dereference",
|
||||
},
|
||||
{
|
||||
desc: "missing credentials",
|
||||
expected: "huaweicloud: credentials missing",
|
||||
},
|
||||
{
|
||||
desc: "missing secret id",
|
||||
secretAccessKey: "456",
|
||||
region: hwregion.CN_EAST_2.Id,
|
||||
expected: "huaweicloud: credentials missing",
|
||||
},
|
||||
{
|
||||
desc: "missing secret key",
|
||||
accessKeyID: "123",
|
||||
region: hwregion.CN_EAST_2.Id,
|
||||
expected: "huaweicloud: credentials missing",
|
||||
},
|
||||
{
|
||||
desc: "missing region",
|
||||
accessKeyID: "123",
|
||||
secretAccessKey: "456",
|
||||
expected: "huaweicloud: credentials missing",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
config := NewDefaultConfig()
|
||||
config.AccessKeyID = test.accessKeyID
|
||||
config.SecretAccessKey = test.secretAccessKey
|
||||
config.Region = test.region
|
||||
|
||||
p, err := NewDNSProviderConfig(config)
|
||||
|
||||
if test.expected == "" {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, p)
|
||||
require.NotNil(t, p.config)
|
||||
require.NotNil(t, p.client)
|
||||
} else {
|
||||
require.EqualError(t, err, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLivePresent(t *testing.T) {
|
||||
if !envTest.IsLiveTest() {
|
||||
t.Skip("skipping live test")
|
||||
}
|
||||
|
||||
envTest.RestoreEnv()
|
||||
provider, err := NewDNSProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = provider.Present(envTest.GetDomain(), "", "123d==")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestLiveCleanUp(t *testing.T) {
|
||||
if !envTest.IsLiveTest() {
|
||||
t.Skip("skipping live test")
|
||||
}
|
||||
|
||||
envTest.RestoreEnv()
|
||||
provider, err := NewDNSProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
@@ -21,6 +23,21 @@ type Ftp struct {
|
||||
DefaultGroup string
|
||||
}
|
||||
|
||||
type FtpList struct {
|
||||
User string
|
||||
Path string
|
||||
Status string
|
||||
}
|
||||
|
||||
type FtpLog struct {
|
||||
IP string `json:"ip"`
|
||||
User string `json:"user"`
|
||||
Time string `json:"time"`
|
||||
Operation string `json:"operation"`
|
||||
Status string `json:"status"`
|
||||
Size string `json:"size"`
|
||||
}
|
||||
|
||||
type FtpClient interface {
|
||||
Status() (bool, bool)
|
||||
Operate(operate string) error
|
||||
@@ -88,9 +105,19 @@ func (f *Ftp) Operate(operate string) error {
|
||||
}
|
||||
|
||||
func (f *Ftp) UserAdd(username, passwd, path string) error {
|
||||
std, err := cmd.Execf("pure-pw useradd %s -u %s -d %s <<EOF \n%s\n%s\nEOF", username, f.DefaultUser, path, passwd, passwd)
|
||||
entry, err := generatePureFtpEntrySimple(username, passwd, path)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
return err
|
||||
}
|
||||
pwdFile, err := os.OpenFile("/etc/pure-ftpd/pureftpd.passwd", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pwdFile.Close()
|
||||
|
||||
_, err = pwdFile.WriteString("\n" + entry + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = f.Reload()
|
||||
std2, err := cmd.Execf("chown -R %s:%s %s", f.DefaultUser, f.DefaultGroup, path)
|
||||
@@ -110,10 +137,54 @@ func (f *Ftp) UserDel(username string) error {
|
||||
}
|
||||
|
||||
func (f *Ftp) SetPasswd(username, passwd string) error {
|
||||
std, err := cmd.Execf("pure-pw passwd %s <<EOF \n%s\n%s\nEOF", username, passwd, passwd)
|
||||
hashedPassword, err := hashPassword(passwd)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
return err
|
||||
}
|
||||
// read now
|
||||
pwdFile, err := os.Open("/etc/pure-ftpd/pureftpd.passwd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pwdFile.Close()
|
||||
|
||||
var entrys []string
|
||||
scanner := bufio.NewScanner(pwdFile)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
userEntry := strings.Split(line, ":")
|
||||
if len(userEntry) < 2 {
|
||||
continue
|
||||
}
|
||||
if userEntry[0] == username {
|
||||
userEntry[1] = string(hashedPassword)
|
||||
line = strings.Join(userEntry, ":")
|
||||
}
|
||||
entrys = append(entrys, line)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
pwdFile.Close()
|
||||
|
||||
// write new
|
||||
pwdFile, err = os.Create("/etc/pure-ftpd/pureftpd.passwd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pwdFile.Close()
|
||||
|
||||
for _, entry := range entrys {
|
||||
_, err := pwdFile.WriteString(entry + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -168,12 +239,6 @@ func (f *Ftp) LoadList() ([]FtpList, error) {
|
||||
return lists, nil
|
||||
}
|
||||
|
||||
type FtpList struct {
|
||||
User string
|
||||
Path string
|
||||
Status string
|
||||
}
|
||||
|
||||
func (f *Ftp) Reload() error {
|
||||
std, err := cmd.Exec("pure-pw mkdb")
|
||||
if err != nil {
|
||||
@@ -262,11 +327,51 @@ func loadLogsByFiles(fileList []string, user, operation string) []FtpLog {
|
||||
return logs
|
||||
}
|
||||
|
||||
type FtpLog struct {
|
||||
IP string `json:"ip"`
|
||||
User string `json:"user"`
|
||||
Time string `json:"time"`
|
||||
Operation string `json:"operation"`
|
||||
Status string `json:"status"`
|
||||
Size string `json:"size"`
|
||||
func hashPassword(password string) ([]byte, error) {
|
||||
// Hash the password using bcrypt with a cost of 10
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hashedPassword, nil
|
||||
}
|
||||
|
||||
func generatePureFtpEntrySimple(username, password, path string) (string, error) {
|
||||
return generatePureFtpEntry(username, password, 1000, 1000, "", path+"/./",
|
||||
"", "", "", "", "",
|
||||
"", "", "", "", "", "", "")
|
||||
}
|
||||
|
||||
func generatePureFtpEntry(username, password string, uid, gid int, gecos, homedir,
|
||||
uploadBandwidth, downloadBandwidth, uploadRatio, downloadRatio, maxConnections, filesQuota, sizeQuota,
|
||||
authorizedLocalIPs, refusedLocalIPs, authorizedClientIPs, refusedClientIPs, timeRestrictions string) (string, error) {
|
||||
|
||||
hashedPassword, err := hashPassword(password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Format the entry
|
||||
entry := fmt.Sprintf("%s:%s:%d:%d:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s",
|
||||
username,
|
||||
hashedPassword,
|
||||
uid,
|
||||
gid,
|
||||
gecos,
|
||||
homedir,
|
||||
uploadBandwidth,
|
||||
downloadBandwidth,
|
||||
uploadRatio,
|
||||
downloadRatio,
|
||||
maxConnections,
|
||||
filesQuota,
|
||||
sizeQuota,
|
||||
authorizedLocalIPs,
|
||||
refusedLocalIPs,
|
||||
authorizedClientIPs,
|
||||
refusedClientIPs,
|
||||
timeRestrictions,
|
||||
)
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
+192
-20
@@ -3872,8 +3872,8 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dashboard/current/:ioOption/:netOption": {
|
||||
"get": {
|
||||
"/dashboard/current": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@@ -3889,18 +3889,13 @@ const docTemplate = `{
|
||||
"summary": "Load dashboard current info",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "request",
|
||||
"name": "ioOption",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "request",
|
||||
"name": "netOption",
|
||||
"in": "path",
|
||||
"required": true
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.DashboardReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -9130,8 +9125,8 @@ const docTemplate = `{
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Delete website [name]",
|
||||
"formatZH": "删除网站 [name]",
|
||||
"formatEN": "Delete runtime [name]",
|
||||
"formatZH": "删除运行环境 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
@@ -10808,7 +10803,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SearchWithPage"
|
||||
"$ref": "#/definitions/dto.PageSnapshot"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -10822,6 +10817,39 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/snapshot/size": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取系统快照大小",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"System Setting"
|
||||
],
|
||||
"summary": "Load system snapshot size",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageSnapshot"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/snapshot/status": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -15591,6 +15619,12 @@ const docTemplate = `{
|
||||
"dto.ClamCreate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15689,6 +15723,12 @@ const docTemplate = `{
|
||||
"dto.ClamUpdate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15805,6 +15845,9 @@ const docTemplate = `{
|
||||
"detailName": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15876,6 +15919,12 @@ const docTemplate = `{
|
||||
"from"
|
||||
],
|
||||
"properties": {
|
||||
"env": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15902,8 +15951,7 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"operation",
|
||||
"path"
|
||||
"operation"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -15912,9 +15960,11 @@ const docTemplate = `{
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"start",
|
||||
"stop",
|
||||
"down"
|
||||
"down",
|
||||
"delete"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
@@ -15987,6 +16037,12 @@ const docTemplate = `{
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"env": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16162,6 +16218,7 @@ const docTemplate = `{
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"start",
|
||||
"stop",
|
||||
"restart",
|
||||
@@ -16309,6 +16366,12 @@ const docTemplate = `{
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16383,9 +16446,16 @@ const docTemplate = `{
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"spec"
|
||||
"spec",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16432,6 +16502,9 @@ const docTemplate = `{
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16557,6 +16630,9 @@ const docTemplate = `{
|
||||
"dto.DashboardBase": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"SystemProxy": {
|
||||
"type": "string"
|
||||
},
|
||||
"appInstalledNumber": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -16581,6 +16657,9 @@ const docTemplate = `{
|
||||
"hostname": {
|
||||
"type": "string"
|
||||
},
|
||||
"ipv4Addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"kernelArch": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16705,6 +16784,26 @@ const docTemplate = `{
|
||||
},
|
||||
"uptime": {
|
||||
"type": "integer"
|
||||
},
|
||||
"xpuData": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.XPUInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.DashboardReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ioOption": {
|
||||
"type": "string"
|
||||
},
|
||||
"netOption": {
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18608,6 +18707,41 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PageSnapshot": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"order",
|
||||
"orderBy",
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"null",
|
||||
"ascending",
|
||||
"descending"
|
||||
]
|
||||
},
|
||||
"orderBy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"created_at"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PasswordUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -19273,6 +19407,9 @@ const docTemplate = `{
|
||||
"sslType"
|
||||
],
|
||||
"properties": {
|
||||
"autoRestart": {
|
||||
"type": "string"
|
||||
},
|
||||
"cert": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -19501,6 +19638,9 @@ const docTemplate = `{
|
||||
"appStoreVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoRestart": {
|
||||
"type": "string"
|
||||
},
|
||||
"bindAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -19925,6 +20065,32 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.XPUInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"deviceID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"deviceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"memory": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryUsed": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryUtil": {
|
||||
"type": "string"
|
||||
},
|
||||
"power": {
|
||||
"type": "string"
|
||||
},
|
||||
"temperature": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files.FileInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -22434,6 +22600,9 @@ const docTemplate = `{
|
||||
"proxyPass": {
|
||||
"type": "string"
|
||||
},
|
||||
"proxySSLName": {
|
||||
"type": "string"
|
||||
},
|
||||
"replaces": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -22476,6 +22645,9 @@ const docTemplate = `{
|
||||
"ID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"disableLog": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nameservers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
+192
-20
@@ -3866,8 +3866,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dashboard/current/:ioOption/:netOption": {
|
||||
"get": {
|
||||
"/dashboard/current": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@@ -3883,18 +3883,13 @@
|
||||
"summary": "Load dashboard current info",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "request",
|
||||
"name": "ioOption",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "request",
|
||||
"name": "netOption",
|
||||
"in": "path",
|
||||
"required": true
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.DashboardReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -9124,8 +9119,8 @@
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Delete website [name]",
|
||||
"formatZH": "删除网站 [name]",
|
||||
"formatEN": "Delete runtime [name]",
|
||||
"formatZH": "删除运行环境 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
@@ -10802,7 +10797,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SearchWithPage"
|
||||
"$ref": "#/definitions/dto.PageSnapshot"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -10816,6 +10811,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/snapshot/size": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取系统快照大小",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"System Setting"
|
||||
],
|
||||
"summary": "Load system snapshot size",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageSnapshot"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/snapshot/status": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -15585,6 +15613,12 @@
|
||||
"dto.ClamCreate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15683,6 +15717,12 @@
|
||||
"dto.ClamUpdate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15799,6 +15839,9 @@
|
||||
"detailName": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15870,6 +15913,12 @@
|
||||
"from"
|
||||
],
|
||||
"properties": {
|
||||
"env": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15896,8 +15945,7 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"operation",
|
||||
"path"
|
||||
"operation"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -15906,9 +15954,11 @@
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"start",
|
||||
"stop",
|
||||
"down"
|
||||
"down",
|
||||
"delete"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
@@ -15981,6 +16031,12 @@
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"env": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16156,6 +16212,7 @@
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"start",
|
||||
"stop",
|
||||
"restart",
|
||||
@@ -16303,6 +16360,12 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16377,9 +16440,16 @@
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"spec"
|
||||
"spec",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"alertCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"alertTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16426,6 +16496,9 @@
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16551,6 +16624,9 @@
|
||||
"dto.DashboardBase": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"SystemProxy": {
|
||||
"type": "string"
|
||||
},
|
||||
"appInstalledNumber": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -16575,6 +16651,9 @@
|
||||
"hostname": {
|
||||
"type": "string"
|
||||
},
|
||||
"ipv4Addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"kernelArch": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16699,6 +16778,26 @@
|
||||
},
|
||||
"uptime": {
|
||||
"type": "integer"
|
||||
},
|
||||
"xpuData": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.XPUInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.DashboardReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ioOption": {
|
||||
"type": "string"
|
||||
},
|
||||
"netOption": {
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18602,6 +18701,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PageSnapshot": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"order",
|
||||
"orderBy",
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"null",
|
||||
"ascending",
|
||||
"descending"
|
||||
]
|
||||
},
|
||||
"orderBy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"created_at"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PasswordUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -19267,6 +19401,9 @@
|
||||
"sslType"
|
||||
],
|
||||
"properties": {
|
||||
"autoRestart": {
|
||||
"type": "string"
|
||||
},
|
||||
"cert": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -19495,6 +19632,9 @@
|
||||
"appStoreVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoRestart": {
|
||||
"type": "string"
|
||||
},
|
||||
"bindAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -19919,6 +20059,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.XPUInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"deviceID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"deviceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"memory": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryUsed": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryUtil": {
|
||||
"type": "string"
|
||||
},
|
||||
"power": {
|
||||
"type": "string"
|
||||
},
|
||||
"temperature": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files.FileInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -22428,6 +22594,9 @@
|
||||
"proxyPass": {
|
||||
"type": "string"
|
||||
},
|
||||
"proxySSLName": {
|
||||
"type": "string"
|
||||
},
|
||||
"replaces": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -22470,6 +22639,9 @@
|
||||
"ID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"disableLog": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nameservers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
+128
-14
@@ -233,6 +233,10 @@ definitions:
|
||||
type: object
|
||||
dto.ClamCreate:
|
||||
properties:
|
||||
alertCount:
|
||||
type: integer
|
||||
alertTitle:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
infectedDir:
|
||||
@@ -297,6 +301,10 @@ definitions:
|
||||
type: object
|
||||
dto.ClamUpdate:
|
||||
properties:
|
||||
alertCount:
|
||||
type: integer
|
||||
alertTitle:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
@@ -371,6 +379,8 @@ definitions:
|
||||
properties:
|
||||
detailName:
|
||||
type: string
|
||||
fileName:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
secret:
|
||||
@@ -424,6 +434,10 @@ definitions:
|
||||
type: object
|
||||
dto.ComposeCreate:
|
||||
properties:
|
||||
env:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
file:
|
||||
type: string
|
||||
from:
|
||||
@@ -447,9 +461,11 @@ definitions:
|
||||
type: string
|
||||
operation:
|
||||
enum:
|
||||
- up
|
||||
- start
|
||||
- stop
|
||||
- down
|
||||
- delete
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
@@ -458,7 +474,6 @@ definitions:
|
||||
required:
|
||||
- name
|
||||
- operation
|
||||
- path
|
||||
type: object
|
||||
dto.ComposeTemplateCreate:
|
||||
properties:
|
||||
@@ -497,6 +512,10 @@ definitions:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
env:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
@@ -614,6 +633,7 @@ definitions:
|
||||
type: array
|
||||
operation:
|
||||
enum:
|
||||
- up
|
||||
- start
|
||||
- stop
|
||||
- restart
|
||||
@@ -713,6 +733,10 @@ definitions:
|
||||
type: object
|
||||
dto.CronjobCreate:
|
||||
properties:
|
||||
alertCount:
|
||||
type: integer
|
||||
alertTitle:
|
||||
type: string
|
||||
appID:
|
||||
type: string
|
||||
backupAccounts:
|
||||
@@ -765,6 +789,10 @@ definitions:
|
||||
type: object
|
||||
dto.CronjobUpdate:
|
||||
properties:
|
||||
alertCount:
|
||||
type: integer
|
||||
alertTitle:
|
||||
type: string
|
||||
appID:
|
||||
type: string
|
||||
backupAccounts:
|
||||
@@ -796,6 +824,8 @@ definitions:
|
||||
type: string
|
||||
spec:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
website:
|
||||
@@ -804,6 +834,7 @@ definitions:
|
||||
- id
|
||||
- name
|
||||
- spec
|
||||
- type
|
||||
type: object
|
||||
dto.CronjobUpdateStatus:
|
||||
properties:
|
||||
@@ -883,6 +914,8 @@ definitions:
|
||||
type: object
|
||||
dto.DashboardBase:
|
||||
properties:
|
||||
SystemProxy:
|
||||
type: string
|
||||
appInstalledNumber:
|
||||
type: integer
|
||||
cpuCores:
|
||||
@@ -899,6 +932,8 @@ definitions:
|
||||
type: integer
|
||||
hostname:
|
||||
type: string
|
||||
ipv4Addr:
|
||||
type: string
|
||||
kernelArch:
|
||||
type: string
|
||||
kernelVersion:
|
||||
@@ -982,6 +1017,19 @@ definitions:
|
||||
type: string
|
||||
uptime:
|
||||
type: integer
|
||||
xpuData:
|
||||
items:
|
||||
$ref: '#/definitions/dto.XPUInfo'
|
||||
type: array
|
||||
type: object
|
||||
dto.DashboardReq:
|
||||
properties:
|
||||
ioOption:
|
||||
type: string
|
||||
netOption:
|
||||
type: string
|
||||
scope:
|
||||
type: string
|
||||
type: object
|
||||
dto.DatabaseCreate:
|
||||
properties:
|
||||
@@ -2278,6 +2326,31 @@ definitions:
|
||||
total:
|
||||
type: integer
|
||||
type: object
|
||||
dto.PageSnapshot:
|
||||
properties:
|
||||
info:
|
||||
type: string
|
||||
order:
|
||||
enum:
|
||||
- "null"
|
||||
- ascending
|
||||
- descending
|
||||
type: string
|
||||
orderBy:
|
||||
enum:
|
||||
- name
|
||||
- created_at
|
||||
type: string
|
||||
page:
|
||||
type: integer
|
||||
pageSize:
|
||||
type: integer
|
||||
required:
|
||||
- order
|
||||
- orderBy
|
||||
- page
|
||||
- pageSize
|
||||
type: object
|
||||
dto.PasswordUpdate:
|
||||
properties:
|
||||
newPassword:
|
||||
@@ -2723,6 +2796,8 @@ definitions:
|
||||
type: object
|
||||
dto.SSLUpdate:
|
||||
properties:
|
||||
autoRestart:
|
||||
type: string
|
||||
cert:
|
||||
type: string
|
||||
domain:
|
||||
@@ -2882,6 +2957,8 @@ definitions:
|
||||
type: string
|
||||
appStoreVersion:
|
||||
type: string
|
||||
autoRestart:
|
||||
type: string
|
||||
bindAddress:
|
||||
type: string
|
||||
bindDomain:
|
||||
@@ -3161,6 +3238,23 @@ definitions:
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
dto.XPUInfo:
|
||||
properties:
|
||||
deviceID:
|
||||
type: integer
|
||||
deviceName:
|
||||
type: string
|
||||
memory:
|
||||
type: string
|
||||
memoryUsed:
|
||||
type: string
|
||||
memoryUtil:
|
||||
type: string
|
||||
power:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
type: object
|
||||
files.FileInfo:
|
||||
properties:
|
||||
content:
|
||||
@@ -4836,6 +4930,8 @@ definitions:
|
||||
type: string
|
||||
proxyPass:
|
||||
type: string
|
||||
proxySSLName:
|
||||
type: string
|
||||
replaces:
|
||||
additionalProperties:
|
||||
type: string
|
||||
@@ -4868,6 +4964,8 @@ definitions:
|
||||
properties:
|
||||
ID:
|
||||
type: integer
|
||||
disableLog:
|
||||
type: boolean
|
||||
nameservers:
|
||||
items:
|
||||
type: string
|
||||
@@ -7981,22 +8079,18 @@ paths:
|
||||
summary: Load os info
|
||||
tags:
|
||||
- Dashboard
|
||||
/dashboard/current/:ioOption/:netOption:
|
||||
get:
|
||||
/dashboard/current:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取首页实时数据
|
||||
parameters:
|
||||
- description: request
|
||||
in: path
|
||||
name: ioOption
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
type: string
|
||||
- description: request
|
||||
in: path
|
||||
name: netOption
|
||||
required: true
|
||||
type: string
|
||||
schema:
|
||||
$ref: '#/definitions/dto.DashboardReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@@ -11310,8 +11404,8 @@ paths:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- id
|
||||
formatEN: Delete website [name]
|
||||
formatZH: 删除网站 [name]
|
||||
formatEN: Delete runtime [name]
|
||||
formatZH: 删除运行环境 [name]
|
||||
paramKeys: []
|
||||
/runtimes/node/modules:
|
||||
post:
|
||||
@@ -12368,7 +12462,7 @@ paths:
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SearchWithPage'
|
||||
$ref: '#/definitions/dto.PageSnapshot'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@@ -12379,6 +12473,26 @@ paths:
|
||||
summary: Page system snapshot
|
||||
tags:
|
||||
- System Setting
|
||||
/settings/snapshot/size:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取系统快照大小
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.PageSnapshot'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Load system snapshot size
|
||||
tags:
|
||||
- System Setting
|
||||
/settings/snapshot/status:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
+13
-37
@@ -1,5 +1,5 @@
|
||||
<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>Modern and Open-Source Linux Server Operation and Management Panel</b></p>
|
||||
<p align="center"><b>#1 Rated Web-based Linux Server Management Tool</b></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" alt="License: GPL v3"></a>
|
||||
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
|
||||
@@ -10,64 +10,40 @@
|
||||
|
||||
------------------------------
|
||||
|
||||
1Panel is an open-source, modern web-based server management control panel.
|
||||
1Panel is an open-source, modern web-based Linux server management control panel.
|
||||
|
||||
- **Efficient Management**: Users can easily manage Linux servers through a web graphical interface, achieving functions such as host monitoring, file management, database management, and container management.
|
||||
- **Rapid Website Building**: Deep integration of open-source website building software WordPress and [Halo](https://github.com/halo-dev/halo/), with operations such as domain binding and SSL certificate configuration completed with one click.
|
||||
- **Application Store**: Curates a variety of high-quality open-source tools and application software, assisting users in easy installation and upgrades.
|
||||
- **Security and Reliability**: Based on container management and application deployment, minimizing vulnerability exposure, while providing features such as firewall and log auditing.
|
||||
- **One-Click Backup**: Supports one-click backup and restoration, allowing users to backup data to various cloud storage media, ensuring data is never lost.
|
||||
|
||||
## UI Display
|
||||
|
||||

|
||||
- **One-Click Backup & Restore**: Supports one-click backup and restoration, allowing users to backup data to various cloud storage media, ensuring data is never lost.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Online Demo**
|
||||
|
||||
- Address: <https://demo.1panel.cn/>
|
||||
- Username: demo
|
||||
- Password: 1panel
|
||||
|
||||
**One-Click Installation**
|
||||
|
||||
Execute the following command to install 1Panel with one click:
|
||||
|
||||
```sh
|
||||
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
|
||||
```
|
||||
|
||||
**Learning Materials**
|
||||
If you have any questions or suggestions, please submit a GitHub Issue.
|
||||
|
||||
- [Online Documentation](https://1panel.cn/docs/)
|
||||
- [Teaching Videos](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760)
|
||||
## Screenshot
|
||||
|
||||
## Community
|
||||
|
||||
If you have any questions or suggestions, please submit a GitHub Issue or join our WeChat group for communication.
|
||||
|
||||
## Security Information
|
||||
|
||||
If you discover any security issues, please contact us through:
|
||||
|
||||
- email: wanghe@fit2cloud.com
|
||||
|
||||
## Our Other Star Projects
|
||||
|
||||
- [MaxKB](https://github.com/1Panel-dev/MaxKB/) - Open-Source Knowledge Base Question-Answering System Based on LLM Large Language Model
|
||||
- [JumpServer](https://github.com/jumpserver/jumpserver/) - A popular open-source PAM system
|
||||
- [DataEase](https://github.com/dataease/dataease/) - An open-source data visualization and analysis tool for everyone
|
||||
- [MeterSphere](https://github.com/metersphere/metersphere/) - An all-in-one open-source automated testing platform
|
||||
- [Halo](https://github.com/halo-dev/halo/) - A powerful and user-friendly open-source website building tool
|
||||

|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#1Panel-dev/1Panel&Date)
|
||||
|
||||
## License
|
||||
## Security Information
|
||||
|
||||
Copyright (c) 2014-2024 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved.
|
||||
If you discover any security issues, please contact us through:
|
||||
- email: wanghe@fit2cloud.com
|
||||
|
||||
## Copyright & License
|
||||
|
||||
Copyright (c) 2014-2024 [FIT2CLOUD](https://fit2cloud.com/), All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { reactive, computed, ref, nextTick, provide } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
import zhTw from 'element-plus/es/locale/lang/zh-tw';
|
||||
import en from 'element-plus/es/locale/lang/en';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
useTheme();
|
||||
@@ -19,6 +20,7 @@ const config = reactive({
|
||||
|
||||
const i18nLocale = computed(() => {
|
||||
if (globalStore.language === 'zh') return zhCn;
|
||||
if (globalStore.language === 'tw') return zhTw;
|
||||
if (globalStore.language === 'en') return en;
|
||||
return zhCn;
|
||||
});
|
||||
|
||||
@@ -132,12 +132,13 @@ class RequestHttp {
|
||||
);
|
||||
return;
|
||||
default:
|
||||
globalStore.isLogin = false;
|
||||
globalStore.errStatus = 'code-' + response.status;
|
||||
router.push({
|
||||
name: 'entrance',
|
||||
params: { code: globalStore.entrance },
|
||||
});
|
||||
return;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
if (!window.navigator.onLine) router.replace({ path: '/500' });
|
||||
|
||||
@@ -29,6 +29,7 @@ export namespace Setting {
|
||||
bindAddress: string;
|
||||
ssl: string;
|
||||
sslType: string;
|
||||
autoRestart: string;
|
||||
allowIPs: string;
|
||||
bindDomain: string;
|
||||
securityEntrance: string;
|
||||
@@ -134,7 +135,7 @@ export namespace Setting {
|
||||
description: string;
|
||||
status: string;
|
||||
message: string;
|
||||
createdAt: DateTimeFormats;
|
||||
created_at: DateTimeFormats;
|
||||
version: string;
|
||||
interruptStep: string;
|
||||
recoverStatus: string;
|
||||
@@ -177,6 +178,8 @@ export namespace Setting {
|
||||
offline: boolean;
|
||||
status: string;
|
||||
message: string;
|
||||
smsUsed: number;
|
||||
smsTotal: number;
|
||||
}
|
||||
export interface LicenseStatus {
|
||||
productPro: string;
|
||||
|
||||
@@ -43,7 +43,7 @@ export const containerRename = (params: Container.ContainerRename) => {
|
||||
return http.post(`/containers/rename`, params);
|
||||
};
|
||||
export const containerOperator = (params: Container.ContainerOperate) => {
|
||||
return http.post(`/containers/operate`, params);
|
||||
return http.post(`/containers/operate`, params, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const containerPrune = (params: Container.ContainerPrune) => {
|
||||
return http.post<Container.ContainerPruneReport>(`/containers/prune`, params);
|
||||
|
||||
@@ -74,7 +74,7 @@ export const WgetFile = (params: File.FileWget) => {
|
||||
};
|
||||
|
||||
export const MoveFile = (params: File.FileMove) => {
|
||||
return http.post<File.File>('files/move', params);
|
||||
return http.post<File.File>('files/move', params, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const DownloadFile = (params: File.FileDownload) => {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='180' height='100' fill="currentColor" viewBox='0 0 180 100'><defs><clipPath id='clipPath'><rect x='0' y='0' width='180' height='100' rx='0' /></clipPath><linearGradient id='gradient1' x1='0.74' y1='1.14' x2='0.31' y2='0.11'><stop offset='0%' stop-color='var(--gradient-start-color, #FFFFFF)' stop-opacity='0' /><stop offset='100%' stop-color='var(--panel-gradient-end-color)' stop-opacity='1' /></linearGradient><linearGradient id='gradient2' x1='0.73' y1='-0.17' x2='0.43' y2='0.98'><stop offset='0%' stop-color='var(--gradient-start-color, #FFFFFF)' stop-opacity='0' /><stop offset='100%' stop-color='var(--panel-gradient-end-color)' stop-opacity='1' /></linearGradient></defs><g clip-path='url(#clipPath)'><rect x='0' y='0' width='180' height='100' rx='0' fill='#FFFFFF' fill-opacity='0' /><g><path d='M83,20.38C83,41.88,72.48,65.8,57.5,80.14C42.52,94.47,23.07,99.22,0,100.04V0H80.15C82.05,6.65,83,13.5,83,20.38Z' fill='url(#gradient1)' /></g><g><path d='M180.08,0.08V23.7C178.84,23.89,177.59,23.98,176.33,23.98C163.22,23.98,152.58,13.95,152.58,1.57Q152.58,0.82,152.64,0.08H180.08Z' fill='url(#gradient2)' /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -75,9 +75,10 @@
|
||||
<el-alert
|
||||
:title="$t('app.checkTitle')"
|
||||
:closable="false"
|
||||
center
|
||||
type="warning"
|
||||
show-icon
|
||||
class="h-8"
|
||||
class="h-6 check-title"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@@ -88,12 +89,12 @@
|
||||
<LayoutContent :title="getTitle(key)" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-4 items-cetner justify-center w-full sm:flex-row">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<div>{{ $t('app.checkInstalledWarn', [data.app]) }}</div>
|
||||
<div @click="goRouter(key)" class="flex items-cetner justify-center text-[#74a4f3]">
|
||||
<span @click="goRouter(key)" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon class="flex items-center justify-center"><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
@@ -107,7 +108,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { CheckAppInstalled, InstalledOp } from '@/api/modules/app';
|
||||
import router from '@/routers';
|
||||
import { onMounted, reactive, ref, watch } from 'vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import Status from '@/components/status/index.vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
@@ -125,21 +126,6 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.appKey,
|
||||
(val) => {
|
||||
key.value = val;
|
||||
onCheck();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.appName,
|
||||
(val) => {
|
||||
name.value = val;
|
||||
onCheck();
|
||||
},
|
||||
);
|
||||
|
||||
let key = ref('');
|
||||
let name = ref('');
|
||||
|
||||
@@ -173,8 +159,8 @@ const isDB = () => {
|
||||
return key.value === 'mysql' || key.value === 'mariadb' || key.value === 'postgresql';
|
||||
};
|
||||
|
||||
const onCheck = async () => {
|
||||
await CheckAppInstalled(key.value, name.value)
|
||||
const onCheck = async (key: any, name: any) => {
|
||||
await CheckAppInstalled(key, name)
|
||||
.then((res) => {
|
||||
data.value = res.data;
|
||||
em('isExist', res.data);
|
||||
@@ -220,7 +206,7 @@ const onOperate = async (operation: string) => {
|
||||
.then(() => {
|
||||
em('update:loading', false);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
onCheck();
|
||||
onCheck(key.value, name.value);
|
||||
em('after');
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -248,6 +234,19 @@ const getTitle = (key: string) => {
|
||||
onMounted(() => {
|
||||
key.value = props.appKey;
|
||||
name.value = props.appName;
|
||||
onCheck();
|
||||
onCheck(key.value, name.value);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
onCheck,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.check-title {
|
||||
color: var(--el-color-warning);
|
||||
border: 1px solid var(--el-color-warning);
|
||||
background-color: transparent;
|
||||
padding: 8px 8px;
|
||||
width: 70px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,9 +33,3 @@ const loadErrInfo = () => {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
margin-left: 40%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -188,7 +188,7 @@ const closePage = () => {
|
||||
|
||||
const openPage = () => {
|
||||
popoverVisible.value = true;
|
||||
selectRow.value.path = props.path || '/';
|
||||
selectRow.value.path = props.dir ? props.path || '/' : '';
|
||||
rowName.value = '';
|
||||
};
|
||||
|
||||
@@ -216,7 +216,7 @@ const open = async (row: File.File) => {
|
||||
}
|
||||
await search(req);
|
||||
}
|
||||
selectRow.value.path = req.path;
|
||||
selectRow.value.path = props.dir ? req.path : '';
|
||||
rowName.value = '';
|
||||
};
|
||||
|
||||
@@ -230,7 +230,7 @@ const jump = async (index: number) => {
|
||||
}
|
||||
path = path || '/';
|
||||
req.path = path;
|
||||
selectRow.value.path = req.path;
|
||||
selectRow.value.path = props.dir ? req.path : '';
|
||||
rowName.value = '';
|
||||
await search(req);
|
||||
popoverVisible.value = true;
|
||||
@@ -286,7 +286,7 @@ const cancelFolder = (row: any) => {
|
||||
data.value.shift();
|
||||
row.isCreate = false;
|
||||
disBtn.value = false;
|
||||
selectRow.value.path = req.path;
|
||||
selectRow.value.path = props.dir ? req.path : '';
|
||||
rowName.value = '';
|
||||
newFolder.value = '';
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ import { UploadFileData } from '@/api/modules/setting';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { getXpackSetting } from '@/utils/xpack';
|
||||
import { getXpackSetting, initFavicon } from '@/utils/xpack';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const { switchTheme } = useTheme();
|
||||
@@ -90,10 +90,12 @@ const submit = async () => {
|
||||
globalStore.isProductPro = true;
|
||||
const xpackRes = await getXpackSetting();
|
||||
if (xpackRes) {
|
||||
globalStore.themeConfig.isGold = xpackRes.data.theme === 'dark-gold';
|
||||
globalStore.themeConfig.theme = xpackRes.data.theme;
|
||||
globalStore.themeConfig.themeColor = xpackRes.data.themeColor;
|
||||
}
|
||||
loading.value = false;
|
||||
switchTheme();
|
||||
initFavicon();
|
||||
uploadRef.value!.clearFiles();
|
||||
uploaderFiles.value = [];
|
||||
open.value = false;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:before-close="handleClose"
|
||||
:size="globalStore.isFullScreen ? '100%' : '50%'"
|
||||
>
|
||||
<template #header>
|
||||
@@ -21,10 +22,10 @@
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import LogFile from '@/components/log-file/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import screenfull from 'screenfull';
|
||||
import { GlobalStore } from '@/store';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
@@ -45,19 +46,22 @@ const open = ref(false);
|
||||
const config = ref();
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const handleClose = (search: boolean) => {
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', search);
|
||||
em('close', false);
|
||||
globalStore.isFullScreen = false;
|
||||
};
|
||||
|
||||
watch(open, (val) => {
|
||||
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
|
||||
});
|
||||
|
||||
function toggleFullscreen() {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.toggle();
|
||||
}
|
||||
globalStore.isFullScreen = !globalStore.isFullScreen;
|
||||
}
|
||||
|
||||
const loadTooltip = () => {
|
||||
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen'));
|
||||
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
||||
};
|
||||
|
||||
const acceptParams = (props: LogProps) => {
|
||||
|
||||
@@ -290,7 +290,7 @@ defineExpose({ changeTail, onDownload, clearLog });
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
background-color: #121212;
|
||||
background-color: var(--panel-logs-bg-color);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -307,4 +307,10 @@ defineExpose({ changeTail, onDownload, clearLog });
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-item span {
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
color: #cccccc;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-card class="router_card">
|
||||
<div class="flex w-full flex-col md:justify-between md:flex-row">
|
||||
<div class="flex w-full flex-col items-center md:justify-between md:flex-row">
|
||||
<el-radio-group v-model="activeName" @change="handleChange">
|
||||
<el-radio-button
|
||||
class="router_card_button"
|
||||
@@ -94,8 +94,9 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.el-radio-button__original-radio:checked + .el-radio-button__inner {
|
||||
color: $primary-color;
|
||||
border-color: $primary-color !important;
|
||||
color: var(--panel-button-text-color) !important;
|
||||
background-color: var(--panel-button-bg-color) !important;
|
||||
border-color: var(--panel-button-active) !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div class="flex flex-wrap" v-if="props.footer">
|
||||
<el-button type="primary" link @click="toForum">
|
||||
<div class="flex w-full flex-col gap-2 md:flex-row items-center">
|
||||
<div class="flex flex-wrap items-center" v-if="props.footer">
|
||||
<el-link type="primary" :underline="false" @click="toForum">
|
||||
<span class="font-normal">{{ $t('setting.forum') }}</span>
|
||||
</el-button>
|
||||
</el-link>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="toDoc">
|
||||
<el-link type="primary" :underline="false" @click="toDoc">
|
||||
<span class="font-normal">{{ $t('setting.doc2') }}</span>
|
||||
</el-button>
|
||||
</el-link>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="toGithub">
|
||||
<el-link type="primary" :underline="false" @click="toGithub">
|
||||
<span class="font-normal">{{ $t('setting.project') }}</span>
|
||||
</el-button>
|
||||
</el-link>
|
||||
<el-divider v-if="!mobile" direction="vertical" />
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<el-button type="primary" link @click="toHalo">
|
||||
<span class="font-normal">
|
||||
{{ isProductPro ? $t('license.pro') : $t('license.community') }}
|
||||
</span>
|
||||
</el-button>
|
||||
<span class="version" @click="copyText(version)">{{ version }}</span>
|
||||
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
|
||||
<el-button type="primary" link @click="onLoadUpgradeInfo">
|
||||
<span class="font-normal">({{ $t('setting.hasNewVersion') }})</span>
|
||||
</el-button>
|
||||
<div class="flex flex-wrap items-center">
|
||||
<el-link :underline="false" type="primary" @click="toHalo">
|
||||
{{ isProductPro ? $t('license.pro') : $t('license.community') }}
|
||||
</el-link>
|
||||
<el-link :underline="false" class="version" type="primary" @click="copyText(version)">
|
||||
{{ version }}
|
||||
</el-link>
|
||||
<el-badge is-dot class="-mt-0.5" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
|
||||
<el-link :underline="false" type="primary" @click="onLoadUpgradeInfo">
|
||||
({{ $t('setting.hasNewVersion') }})
|
||||
</el-link>
|
||||
</el-badge>
|
||||
<el-button
|
||||
<el-link
|
||||
v-if="version !== 'Waiting' && !globalStore.hasNewVersion"
|
||||
type="primary"
|
||||
link
|
||||
:underline="false"
|
||||
@click="onLoadUpgradeInfo"
|
||||
>
|
||||
<span>({{ $t('setting.upgradeCheck') }})</span>
|
||||
</el-button>
|
||||
({{ $t('setting.upgradeCheck') }})
|
||||
</el-link>
|
||||
<el-tag v-if="version === 'Waiting'" round style="margin-left: 10px">
|
||||
{{ $t('setting.upgrading') }}
|
||||
</el-tag>
|
||||
@@ -54,11 +54,6 @@
|
||||
<DrawerHeader :header="$t('commons.button.upgrade')" :back="handleClose" />
|
||||
</template>
|
||||
<div class="panel-MdEditor">
|
||||
<el-alert :closable="false">
|
||||
<span class="line-height">{{ $t('setting.versionHelper') }}</span>
|
||||
<li class="line-height">{{ $t('setting.versionHelper1') }}</li>
|
||||
<li class="line-height">{{ $t('setting.versionHelper2') }}</li>
|
||||
</el-alert>
|
||||
<div class="default-theme" style="margin-left: 20px">
|
||||
<h2 class="inline-block">{{ $t('app.version') }}</h2>
|
||||
</div>
|
||||
@@ -201,11 +196,10 @@ onMounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
.version {
|
||||
font-size: 14px;
|
||||
color: var(--dark-gold-base-color);
|
||||
color: var(--panel-color-primary-light-4);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.5px;
|
||||
cursor: pointer;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.line-height {
|
||||
line-height: 25px;
|
||||
@@ -221,10 +215,16 @@ onMounted(() => {
|
||||
font-size: 14px;
|
||||
}
|
||||
:deep(.default-theme h2) {
|
||||
color: var(--dark-gold-base-color);
|
||||
margin: 13px, 0;
|
||||
color: var(--el-color-primary);
|
||||
margin: 13px 0;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
:deep(.el-link__inner) {
|
||||
font-weight: 400;
|
||||
}
|
||||
:deep(.md-editor-dark) {
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -47,12 +47,13 @@ const acceptParams = (props: WsProps) => {
|
||||
};
|
||||
|
||||
const newTerm = () => {
|
||||
const background = getComputedStyle(document.documentElement).getPropertyValue('--panel-terminal-bg-color').trim();
|
||||
term.value = new Terminal({
|
||||
lineHeight: 1.2,
|
||||
fontSize: 12,
|
||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||
theme: {
|
||||
background: '#000000',
|
||||
background: background,
|
||||
},
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'underline',
|
||||
@@ -243,4 +244,7 @@ onBeforeUnmount(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.xterm) {
|
||||
padding: 5px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,7 +17,15 @@
|
||||
<div class="mb-4" v-if="type === 'website'">
|
||||
<el-alert :closable="false" type="warning" :title="$t('website.websiteBackupWarn')"></el-alert>
|
||||
</div>
|
||||
<el-upload ref="uploadRef" drag :on-change="fileOnChange" class="upload-demo" :auto-upload="false">
|
||||
<el-upload
|
||||
:limit="1"
|
||||
ref="uploadRef"
|
||||
drag
|
||||
:on-exceed="handleExceed"
|
||||
:on-change="fileOnChange"
|
||||
class="upload-demo"
|
||||
:auto-upload="false"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
{{ $t('database.dropHelper') }}
|
||||
@@ -48,7 +56,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-button :disabled="isUpload" v-if="uploaderFiles.length === 1" icon="Upload" @click="onSubmit">
|
||||
<el-button :disabled="isUpload || uploaderFiles.length !== 1" icon="Upload" @click="onSubmit">
|
||||
{{ $t('commons.button.upload') }}
|
||||
</el-button>
|
||||
|
||||
@@ -128,7 +136,7 @@
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
|
||||
import { UploadFile, UploadFiles, UploadInstance, genFileId } from 'element-plus';
|
||||
import { File } from '@/api/interface/file';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { BatchDeleteFile, CheckFile, ChunkUploadFileData, GetUploadList } from '@/api/modules/files';
|
||||
@@ -186,11 +194,11 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
break;
|
||||
case 'website':
|
||||
title.value = name.value;
|
||||
baseDir.value = `${pathRes.data}/uploads/database/${type.value}/${detailName.value}/`;
|
||||
baseDir.value = `${pathRes.data}/uploads/website/${type.value}/${detailName.value}/`;
|
||||
break;
|
||||
case 'app':
|
||||
title.value = name.value;
|
||||
baseDir.value = `${pathRes.data}/uploads/database/${type.value}/${name.value}/`;
|
||||
baseDir.value = `${pathRes.data}/uploads/app/${type.value}/${name.value}/`;
|
||||
}
|
||||
upVisible.value = true;
|
||||
search();
|
||||
@@ -280,6 +288,13 @@ const handleClose = () => {
|
||||
upVisible.value = false;
|
||||
};
|
||||
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
uploadRef.value!.clearFiles();
|
||||
const file = files[0] as UploadRawFile;
|
||||
file.uid = genFileId();
|
||||
uploadRef.value!.handleStart(file);
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (uploaderFiles.value.length !== 1) {
|
||||
return;
|
||||
|
||||
@@ -30,10 +30,24 @@ const props = defineProps({
|
||||
option: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}, // option: { title , xData, yData, formatStr, yAxis, grid, tooltip}
|
||||
},
|
||||
});
|
||||
|
||||
const seriesStyle = [
|
||||
{
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--panel-color-primary').trim(),
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--panel-color-primary-light-9')
|
||||
.trim(),
|
||||
},
|
||||
]),
|
||||
},
|
||||
{
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
@@ -98,6 +112,7 @@ function initChart() {
|
||||
series.push({
|
||||
name: item?.name,
|
||||
type: 'line',
|
||||
itemStyle: seriesStyle[index + 2],
|
||||
areaStyle: seriesStyle[index],
|
||||
data: item?.data,
|
||||
showSymbol: false,
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as echarts from 'echarts';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkGoldTheme, isDarkTheme } = storeToRefs(globalStore);
|
||||
const { isDarkTheme } = storeToRefs(globalStore);
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -25,7 +25,7 @@ const props = defineProps({
|
||||
option: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}, // option: { title , data }
|
||||
},
|
||||
});
|
||||
|
||||
function initChart() {
|
||||
@@ -34,6 +34,12 @@ function initChart() {
|
||||
myChart = echarts.init(document.getElementById(props.id) as HTMLElement);
|
||||
}
|
||||
let percentText = String(props.option.data).split('.');
|
||||
const primaryLight2 = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--panel-color-primary-light-3')
|
||||
.trim();
|
||||
const primaryLight1 = getComputedStyle(document.documentElement).getPropertyValue('--panel-color-primary').trim();
|
||||
const pieBgColor = getComputedStyle(document.documentElement).getPropertyValue('--panel-pie-bg-color').trim();
|
||||
|
||||
const option = {
|
||||
title: [
|
||||
{
|
||||
@@ -99,11 +105,11 @@ function initChart() {
|
||||
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: isDarkGoldTheme.value ? '#836c4c' : 'rgba(81, 192, 255, .1)',
|
||||
color: primaryLight2,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: isDarkGoldTheme.value ? '#eaba63' : '#4261F6',
|
||||
color: primaryLight1,
|
||||
},
|
||||
]),
|
||||
],
|
||||
@@ -119,7 +125,7 @@ function initChart() {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
color: isDarkTheme.value ? '#16191D' : '#fff',
|
||||
color: pieBgColor,
|
||||
data: [
|
||||
{
|
||||
value: 0,
|
||||
|
||||
@@ -168,6 +168,10 @@ export const DNSTypes = [
|
||||
label: i18n.global.t('website.huaweicloud'),
|
||||
value: 'HuaweiCloud',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.volcengine'),
|
||||
value: 'Volcengine',
|
||||
},
|
||||
{
|
||||
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
|
||||
value: 'DnsPod',
|
||||
@@ -176,6 +180,10 @@ export const DNSTypes = [
|
||||
label: 'Cloudflare',
|
||||
value: 'CloudFlare',
|
||||
},
|
||||
{
|
||||
label: 'CloudDns',
|
||||
value: 'CloudDns',
|
||||
},
|
||||
{
|
||||
label: 'NameSilo',
|
||||
value: 'NameSilo',
|
||||
|
||||
@@ -15,6 +15,6 @@ export const useLogo = async () => {
|
||||
const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement;
|
||||
link.type = 'image/x-icon';
|
||||
link.rel = 'shortcut icon';
|
||||
link.href = globalStore.themeConfig.favicon ? '/api/v1/images/favicon' : '/public/favicon.png';
|
||||
link.href = globalStore.themeConfig.favicon ? `/api/v1/images/favicon?t=${Date.now()}` : '/public/favicon.png';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
};
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { GlobalStore } from '@/store';
|
||||
import { setPrimaryColor } from '@/utils/theme';
|
||||
|
||||
export const useTheme = () => {
|
||||
const globalStore = GlobalStore();
|
||||
const switchTheme = () => {
|
||||
if (globalStore.themeConfig.isGold && globalStore.isProductPro) {
|
||||
const body = document.documentElement as HTMLElement;
|
||||
body.setAttribute('class', 'dark-gold');
|
||||
return;
|
||||
const globalStore = GlobalStore();
|
||||
const themeConfig = globalStore.themeConfig;
|
||||
let itemTheme = themeConfig.theme;
|
||||
if (itemTheme === 'auto') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
itemTheme = prefersDark ? 'dark' : 'light';
|
||||
}
|
||||
document.documentElement.className = itemTheme === 'dark' ? 'dark' : 'light';
|
||||
if (globalStore.isProductPro && themeConfig.themeColor) {
|
||||
try {
|
||||
const themeColor = JSON.parse(themeConfig.themeColor);
|
||||
const color = itemTheme === 'dark' ? themeColor.dark : themeColor.light;
|
||||
|
||||
let itemTheme = globalStore.themeConfig.theme;
|
||||
if (globalStore.themeConfig.theme === 'auto') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
itemTheme = prefersDark.matches ? 'dark' : 'light';
|
||||
if (color) {
|
||||
themeConfig.primary = color;
|
||||
setPrimaryColor(color);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse themeColor', e);
|
||||
}
|
||||
}
|
||||
const body = document.documentElement as HTMLElement;
|
||||
if (itemTheme === 'dark') body.setAttribute('class', 'dark');
|
||||
else body.setAttribute('class', '');
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -11,6 +11,7 @@ const message = {
|
||||
true: 'true',
|
||||
false: 'false',
|
||||
example: 'e.g.:',
|
||||
fit2cloud: 'FIT2CLOUD',
|
||||
button: {
|
||||
create: 'Create ',
|
||||
add: 'Add ',
|
||||
@@ -575,8 +576,13 @@ const message = {
|
||||
container: {
|
||||
create: 'Create',
|
||||
edit: 'Edit container',
|
||||
updateContainerHelper:
|
||||
'Container editing requires rebuilding the container. Any data that has not been persisted will be lost. Do you want to continue?',
|
||||
updateHelper1: 'Detected that this container comes from the app store. Please note the following two points:',
|
||||
updateHelper2:
|
||||
'1. The current modifications will not be synchronized to the installed applications in the app store.',
|
||||
updateHelper3:
|
||||
'2. If you modify the application on the installed page, the currently edited content will become invalid.',
|
||||
updateHelper4:
|
||||
'Editing the container requires rebuilding, and any non-persistent data will be lost. Do you want to continue?',
|
||||
containerList: 'Container list',
|
||||
operatorHelper: '{0} will be performed on the following container, Do you want to continue?',
|
||||
operatorAppHelper:
|
||||
@@ -643,7 +649,7 @@ const message = {
|
||||
containerFromAppHelper:
|
||||
'Detected that this container originates from the app store. App operations may cause current edits to be invalidated.',
|
||||
containerFromAppHelper1:
|
||||
'Click the `Settings` button in the installed applications list to enter the editing page and modify the container name.',
|
||||
'Click the [Param] button in the installed applications list to enter the editing page and modify the container name.',
|
||||
command: 'Command',
|
||||
console: 'Console Interaction',
|
||||
tty: 'TTY (-t)',
|
||||
@@ -731,6 +737,8 @@ const message = {
|
||||
urlWarning: 'The URL prefix does not need to include http:// or https://. Please modify.',
|
||||
|
||||
network: 'Network',
|
||||
networkHelper:
|
||||
'Deleting the 1panel-network container network will affect the normal use of some applications and runtime environments. Do you want to continue?',
|
||||
createNetwork: 'Create',
|
||||
networkName: 'Name',
|
||||
driver: 'Driver',
|
||||
@@ -1133,8 +1141,8 @@ const message = {
|
||||
clamLog: 'Scan Logs',
|
||||
freshClam: 'Update Virus Definitions',
|
||||
freshClamLog: 'Update Virus Definitions Logs',
|
||||
alertHelper: 'Professional version supports SMS alert',
|
||||
alertTitle: 'Virus scanning 「{0}」 task failed alert',
|
||||
alertHelper: 'Professional version supports scheduled scan and SMS alert',
|
||||
alertTitle: 'Virus scan task 「{0}」 detected infected file alert',
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
@@ -1290,6 +1298,7 @@ const message = {
|
||||
noNameFile: 'Untitled File',
|
||||
minimap: 'Code Mini Map',
|
||||
fileCanNotRead: 'File can not read',
|
||||
panelInstallDir: '1Panel installation directory cannot be deleted',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: 'Auto Start',
|
||||
@@ -1385,11 +1394,18 @@ const message = {
|
||||
proxyHelper: 'After setting up the proxy server, it will be effective in the following scenarios:',
|
||||
proxyHelper1: 'Downloading and synchronizing installation packages from the app store (Professional)',
|
||||
proxyHelper2: 'System version upgrades and retrieving update information (Professional)',
|
||||
proxyHelper4: 'Docker network access will be done through a proxy server (Professional)',
|
||||
proxyHelper3: 'Verification and synchronization of system licenses',
|
||||
proxyType: 'Proxy Type',
|
||||
proxyUrl: 'Proxy Address',
|
||||
proxyPort: 'Proxy Port',
|
||||
proxyPasswdKeep: 'Remember Password',
|
||||
proxyDocker: 'Docker Proxy',
|
||||
ProxyDockerHelper:
|
||||
'Synchronize proxy server configuration to Docker, support offline server image pulling and other operations',
|
||||
ConfDockerProxy: 'Configure Docker Proxy',
|
||||
RestartNowHelper: 'Configuring Docker proxy requires restarting the Docker service.',
|
||||
RestartNow: 'Restart immediately',
|
||||
systemIPWarning: 'The server address is not currently set. Please set it in the control panel first!',
|
||||
systemIPWarning1: 'The current server address is set to {0}, and quick redirection is not possible!',
|
||||
defaultNetwork: 'Network Card',
|
||||
@@ -1518,8 +1534,11 @@ const message = {
|
||||
bindDomain: 'Bind Domain',
|
||||
unBindDomain: 'Unbind domain',
|
||||
panelSSL: 'Panel SSL',
|
||||
panelSSLHelper:
|
||||
'After the automatic renewal of the panel SSL, you need to manually restart the 1Panel service for the changes to take effect.',
|
||||
sslAutoRestart: 'Restart 1Panel service after certificate auto-renewal',
|
||||
sslChangeHelper1:
|
||||
'Currently, automatic restart of 1Panel service is not selected. The certificate auto-renewal will not take effect immediately and will still require a manual restart of 1Panel.',
|
||||
sslChangeHelper2:
|
||||
'The 1Panel service will automatically restart after setting the panel SSL. Do you want to continue?',
|
||||
unBindDomainHelper:
|
||||
'The action of unbinding a domain name may cause system insecurity. Do you want to continue?',
|
||||
bindDomainHelper:
|
||||
@@ -1667,13 +1686,13 @@ const message = {
|
||||
expiresAt: 'Expiry Date',
|
||||
productName: 'Product Name',
|
||||
productStatus: 'Product Status',
|
||||
Lost01: 'Lost * 1',
|
||||
Lost02: 'Lost * 2',
|
||||
Lost03: 'Lost',
|
||||
Lost: 'Lost',
|
||||
Enable: 'Enabled',
|
||||
Disable: 'Disabled',
|
||||
lostHelper:
|
||||
'The License needs to be periodically synchronized for availability. Please ensure normal external network access. After three losses of connection, the License binding will be released.',
|
||||
'The license has reached the maximum number of retry attempts. Please manually click the sync button to ensure the professional version features are functioning properly.',
|
||||
disableHelper:
|
||||
'License synchronization verification failed. Please manually click the sync button to ensure that the professional version features are functioning properly.',
|
||||
quickUpdate: 'Quick Update',
|
||||
import: 'Import',
|
||||
power: 'Authorize',
|
||||
@@ -1918,7 +1937,8 @@ const message = {
|
||||
applySSL: 'Certificate Application',
|
||||
SSLList: 'Certificate List',
|
||||
createDnsAccount: 'DNS Account',
|
||||
aliyun: 'Aliyun DNS',
|
||||
aliyun: 'Aliyun',
|
||||
volcengine: 'Volcengine',
|
||||
manual: 'Manual parsing',
|
||||
key: 'Key',
|
||||
check: 'View',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudTwLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-cn';
|
||||
import fit2cloudTwLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-tw';
|
||||
let xpackTwLocale = {};
|
||||
const xpackModules = import.meta.glob('../../xpack/lang/tw.ts', { eager: true });
|
||||
if (xpackModules['../../xpack/lang/tw.ts']) {
|
||||
@@ -10,6 +10,7 @@ const message = {
|
||||
true: '是',
|
||||
false: '否',
|
||||
example: '例:',
|
||||
fit2cloud: '飛致雲',
|
||||
button: {
|
||||
create: '創建',
|
||||
add: '添加',
|
||||
@@ -557,7 +558,10 @@ const message = {
|
||||
container: {
|
||||
create: '創建容器',
|
||||
edit: '編輯容器',
|
||||
updateContainerHelper: '容器編輯需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
|
||||
updateHelper1: '檢測到該容器來源於應用商店,請注意以下兩點:',
|
||||
updateHelper2: '1. 當前修改內容不會同步到應用商店的已安裝應用。',
|
||||
updateHelper3: '2. 如果在已安裝頁面修改應用,當前編輯的部分內容將失效。',
|
||||
updateHelper4: '編輯容器需要重建,任何未持久化的數據將丟失,是否繼續?',
|
||||
containerList: '容器列表',
|
||||
operatorHelper: '將對以下容器進行 {0} 操作,是否繼續?',
|
||||
operatorAppHelper:
|
||||
@@ -619,7 +623,7 @@ const message = {
|
||||
inputIpv6: '請輸入 IPv6 地址',
|
||||
|
||||
containerFromAppHelper: '檢測到該容器來源於應用商店,應用操作可能會導致當前編輯失效',
|
||||
containerFromAppHelper1: '在已安裝應用程式列表點擊 `參數` 按鈕,進入編輯頁面即可修改容器名稱。',
|
||||
containerFromAppHelper1: '在已安裝應用程式列表點擊 [參數] 按鈕,進入編輯頁面即可修改容器名稱。',
|
||||
command: '命令',
|
||||
console: '控製臺交互',
|
||||
tty: '偽終端 ( -t )',
|
||||
@@ -707,6 +711,7 @@ const message = {
|
||||
urlWarning: '路徑前綴不需要添加 http:// 或 https://,請修改',
|
||||
|
||||
network: '網絡',
|
||||
networkHelper: '刪除 1panel-network 容器網絡將影響部分應用和運行環境的正常使用,是否繼續?',
|
||||
createNetwork: '創建網絡',
|
||||
networkName: '網絡名',
|
||||
driver: '模式',
|
||||
@@ -1070,8 +1075,8 @@ const message = {
|
||||
clamLog: '掃描日誌',
|
||||
freshClam: '病毒庫刷新配置',
|
||||
freshClamLog: '病毒庫刷新日誌',
|
||||
alertHelper: '專業版支持短信告警功能',
|
||||
alertTitle: '病毒掃描「{0}」任務失敗告警',
|
||||
alertHelper: '專業版支持定時掃描和短信告警功能',
|
||||
alertTitle: '病毒掃描「{0}」任務检测到感染文件告警',
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
@@ -1223,6 +1228,7 @@ const message = {
|
||||
noNameFile: '未命名檔案',
|
||||
minimap: '縮略圖',
|
||||
fileCanNotRead: '此文件不支持預覽',
|
||||
panelInstallDir: '1Panel 安裝目錄不能删除',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '開機自啟',
|
||||
@@ -1310,11 +1316,17 @@ const message = {
|
||||
proxyHelper: '設置代理伺服器後,將在以下場景中生效:',
|
||||
proxyHelper1: '應用商店的安裝包下載和同步(專業版功能)',
|
||||
proxyHelper2: '系統版本升級及獲取更新說明(專業版功能)',
|
||||
proxyHelper4: 'Docker 的網絡訪問將通過代理伺服器進行(專業版功能)',
|
||||
proxyHelper3: '系統許可證的驗證和同步',
|
||||
proxyType: '代理類型',
|
||||
proxyUrl: '代理地址',
|
||||
proxyPort: '代理端口',
|
||||
proxyPasswdKeep: '記住密碼',
|
||||
proxyDocker: 'Docker 代理',
|
||||
proxyDockerHelper: '將代理伺服器配寘同步至Docker,支持離線服務器拉取鏡像等操作',
|
||||
confDockerProxy: '配寘Docker代理',
|
||||
restartNowHelper: '配寘Docker代理需要重啓Docker服務。',
|
||||
restartNow: '立即重啓',
|
||||
systemIPWarning: '當前未設置服務器地址,請先在面板設置中設置!',
|
||||
systemIPWarning1: '當前服務器地址設置為 {0},無法快速跳轉!',
|
||||
defaultNetwork: '默認網卡',
|
||||
@@ -1468,7 +1480,9 @@ const message = {
|
||||
bindDomain: '域名綁定',
|
||||
unBindDomain: '域名解綁',
|
||||
panelSSL: '面板 SSL',
|
||||
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
|
||||
sslAutoRestart: '證書自動續期後重啟 1Panel 服務',
|
||||
sslChangeHelper1: '當前未勾選自動重啟 1Panel 服務,證書自動續期後不會立即生效,仍需手動重啟 1Panel。',
|
||||
sslChangeHelper2: '設置面板 SSL 後將自動重啟 1Panel 服務,是否繼續?',
|
||||
unBindDomainHelper: '解除域名綁定可能造成系統不安全,是否繼續?',
|
||||
bindDomainHelper: '設置域名綁定後,僅能通過設置中域名訪問 1Panel 服務',
|
||||
bindDomainHelper1: '綁定域名為空時,則取消域名綁定',
|
||||
@@ -1550,12 +1564,11 @@ const message = {
|
||||
expiresAt: '到期時間',
|
||||
productName: '產品名稱',
|
||||
productStatus: '產品狀態',
|
||||
Lost01: '失聯 * 1',
|
||||
Lost02: '失聯 * 2',
|
||||
Lost03: '已失聯',
|
||||
Lost: '已失聯',
|
||||
Enable: '已啟用',
|
||||
Disable: '未啟用',
|
||||
lostHelper: '許可證需要定時同步是否可用,請保證正常外網訪問,失聯三次後將解除許可證綁定',
|
||||
lostHelper: '許可證已達到最大重試次數,請手動點擊同步按鈕,以確保專業版功能正常使用。',
|
||||
disableHelper: '許可證同步驗證失敗,請手動點擊同步按鈕,以確保專業版功能正常使用。',
|
||||
quickUpdate: '快速更新',
|
||||
import: '導入',
|
||||
power: '授 權',
|
||||
@@ -1781,7 +1794,8 @@ const message = {
|
||||
applySSL: '證書申請',
|
||||
SSLList: '證書列表',
|
||||
createDnsAccount: 'DNS賬戶',
|
||||
aliyun: '阿裏雲DNS',
|
||||
aliyun: '阿裏雲',
|
||||
volcengine: '火山引擎',
|
||||
manual: '手動解析',
|
||||
key: '密鑰',
|
||||
check: '查看',
|
||||
@@ -2292,7 +2306,7 @@ const message = {
|
||||
isAlert: '是否告警',
|
||||
alertCount: '告警次數',
|
||||
clamHelper: '掃描到感染檔案時觸發簡訊告警',
|
||||
cronJobHelper: '定時執行計畫任務失敗時觸發簡訊告警',
|
||||
cronJobHelper: '定時任務執行失敗時將觸發簡訊告警',
|
||||
licenseHelper: '專業版支持簡訊告警功能',
|
||||
alertCountHelper: '每日最大告警次數',
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ const message = {
|
||||
true: '是',
|
||||
false: '否',
|
||||
example: '例:',
|
||||
fit2cloud: '飞致云',
|
||||
button: {
|
||||
create: '创建',
|
||||
add: '添加',
|
||||
@@ -559,7 +560,10 @@ const message = {
|
||||
container: {
|
||||
create: '创建容器',
|
||||
edit: '编辑容器',
|
||||
updateContainerHelper: '容器编辑需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
||||
updateHelper1: '检测到该容器来源于应用商店,请注意以下两点:',
|
||||
updateHelper2: '1. 当前修改内容不会同步到应用商店的已安装应用。',
|
||||
updateHelper3: '2. 如果在已安装页面修改应用,当前编辑的部分内容将失效。',
|
||||
updateHelper4: '编辑容器需要重建,任何未持久化的数据将丢失,是否继续?',
|
||||
containerList: '容器列表',
|
||||
operatorHelper: '将对以下容器进行 {0} 操作,是否继续?',
|
||||
operatorAppHelper:
|
||||
@@ -622,7 +626,7 @@ const message = {
|
||||
inputIpv6: '请输入 ipv6 地址',
|
||||
|
||||
containerFromAppHelper: '检测到该容器来源于应用商店,应用操作可能会导致当前编辑失效',
|
||||
containerFromAppHelper1: '在已安装应用列表点击 `参数` 按钮,进入编辑页面即可修改容器名称。',
|
||||
containerFromAppHelper1: '在应用商店的已安装页面,点击 [参数] 按钮,进入编辑页面修改容器名称。',
|
||||
command: '命令',
|
||||
console: '控制台交互',
|
||||
tty: '伪终端 ( -t )',
|
||||
@@ -709,6 +713,7 @@ const message = {
|
||||
urlWarning: '路径前缀不需要添加 http:// 或 https://, 请修改',
|
||||
|
||||
network: '网络',
|
||||
networkHelper: '删除 1panel-network 容器网络将影响部分应用和运行环境的正常使用,是否继续?',
|
||||
createNetwork: '创建网络',
|
||||
networkName: '网络名',
|
||||
driver: '模式',
|
||||
@@ -1072,8 +1077,8 @@ const message = {
|
||||
clamLog: '扫描日志',
|
||||
freshClam: '病毒库刷新配置',
|
||||
freshClamLog: '病毒库刷新日志',
|
||||
alertHelper: '专业版支持短信告警功能',
|
||||
alertTitle: '病毒扫描「 {0} 」任务失败告警',
|
||||
alertHelper: '专业版支持定时扫描和短信告警功能',
|
||||
alertTitle: '病毒扫描「 {0} 」任务检测到感染文件告警',
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
@@ -1225,6 +1230,7 @@ const message = {
|
||||
noNameFile: '未命名文件',
|
||||
minimap: '缩略图',
|
||||
fileCanNotRead: '此文件不支持预览',
|
||||
panelInstallDir: '1Panel 安装目录不能删除',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '开机自启',
|
||||
@@ -1313,10 +1319,16 @@ const message = {
|
||||
proxyHelper1: '应用商店的安装包下载和同步(专业版功能)',
|
||||
proxyHelper2: '系统版本升级及获取更新说明(专业版功能)',
|
||||
proxyHelper3: '系统许可证的验证和同步',
|
||||
proxyHelper4: 'Docker 的网络访问将通过代理服务器进行(专业版功能)',
|
||||
proxyType: '代理类型',
|
||||
proxyUrl: '代理地址',
|
||||
proxyPort: '代理端口',
|
||||
proxyPasswdKeep: '记住密码',
|
||||
proxyDocker: 'Docker 代理',
|
||||
proxyDockerHelper: '将代理服务器配置同步至 Docker,支持离线服务器拉取镜像等操作',
|
||||
confDockerProxy: '配置 Docker 代理',
|
||||
restartNowHelper: '配置 Docker 代理需要重启 Docker 服务。',
|
||||
restartNow: '立即重启',
|
||||
systemIPWarning: '当前未设置服务器地址,请先在面板设置中设置!',
|
||||
systemIPWarning1: '当前服务器地址设置为 {0},无法快速跳转!',
|
||||
defaultNetwork: '默认网卡',
|
||||
@@ -1470,7 +1482,9 @@ const message = {
|
||||
bindDomain: '域名绑定',
|
||||
unBindDomain: '域名解绑',
|
||||
panelSSL: '面板 SSL',
|
||||
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
|
||||
sslAutoRestart: '证书自动续期后重启 1Panel 服务',
|
||||
sslChangeHelper1: '当前未勾选自动重启 1Panel 服务,证书自动续期后不会立即生效,仍需手动重启 1Panel。',
|
||||
sslChangeHelper2: '设置面板 SSL 后将自动重启 1Panel 服务,是否继续?',
|
||||
unBindDomainHelper: '解除域名绑定可能造成系统不安全,是否继续?',
|
||||
bindDomainHelper: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务',
|
||||
bindDomainHelper1: '绑定域名为空时,则取消域名绑定',
|
||||
@@ -1494,7 +1508,6 @@ const message = {
|
||||
mfaInterval: '刷新时间(秒)',
|
||||
mfaTitleHelper: '用于区分不同 1Panel 主机,修改后请重新扫描或手动添加密钥信息!',
|
||||
mfaIntervalHelper: '修改刷新时间后,请重新扫描或手动添加密钥信息!',
|
||||
sslChangeHelper: 'https 设置修改需要重启服务,是否继续?',
|
||||
sslDisable: '禁用',
|
||||
sslDisableHelper: '禁用 https 服务,需要重启面板才能生效,是否继续?',
|
||||
noAuthSetting: '未认证设置',
|
||||
@@ -1552,12 +1565,11 @@ const message = {
|
||||
expiresAt: '到期时间',
|
||||
productName: '产品名称',
|
||||
productStatus: '产品状态',
|
||||
Lost01: '失联 * 1',
|
||||
Lost02: '失联 * 2',
|
||||
Lost03: '已失联',
|
||||
Lost: '已失联',
|
||||
Enable: '已激活',
|
||||
Disable: '未激活',
|
||||
lostHelper: '许可证需要定时同步是否可用,请保证正常外网访问,失联三次后将解除许可证绑定',
|
||||
lostHelper: '许可证已达到最大重试次数,请手动点击同步按钮,以确保专业版功能正常使用。',
|
||||
disableHelper: '许可证同步验证失败,请手动点击同步按钮,以确保专业版功能正常使用。',
|
||||
quickUpdate: '快速更新',
|
||||
import: '导入',
|
||||
power: '授 权',
|
||||
@@ -1783,7 +1795,8 @@ const message = {
|
||||
applySSL: '证书申请',
|
||||
SSLList: '证书列表',
|
||||
createDnsAccount: 'DNS账户',
|
||||
aliyun: '阿里云DNS',
|
||||
aliyun: '阿里云',
|
||||
volcengine: '火山引擎',
|
||||
manual: '手动解析',
|
||||
key: '密钥',
|
||||
check: '查看',
|
||||
@@ -2295,7 +2308,7 @@ const message = {
|
||||
isAlert: '是否告警',
|
||||
alertCount: '告警次数',
|
||||
clamHelper: '扫描到感染文件时触发短信告警',
|
||||
cronJobHelper: '定时执行计划任务失败时触发短信告警',
|
||||
cronJobHelper: '定时任务执行失败时将触发短信告警',
|
||||
licenseHelper: '专业版支持短信告警功能',
|
||||
alertCountHelper: '每日最大告警次数',
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="footer" :style="{ height: mobile ? '108px' : '48px' }">
|
||||
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<a href="https://fit2cloud.com/" target="_blank">Copyright © 2014-2024 FIT2CLOUD 飞致云</a>
|
||||
<a href="https://fit2cloud.com/" target="_blank">Copyright © 2014-2024 {{ $t('commons.fit2cloud') }}</a>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 md:flex-col lg:flex-row">
|
||||
<SystemUpgrade :footer="true" />
|
||||
@@ -28,8 +28,8 @@ const mobile = computed(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 48px;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
background: var(--panel-footer-bg);
|
||||
border-top: 1px solid var(--panel-footer-border);
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px;
|
||||
a {
|
||||
|
||||
@@ -18,11 +18,12 @@ const isCollapse = computed(() => menuStore.isCollapse);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
border-top: 1px solid var(--panel-footer-border);
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
color: var(--panel-main-bg-color-1);
|
||||
margin-left: 25px;
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<template>
|
||||
<div class="logo" style="cursor: pointer" @click="goHome">
|
||||
<template v-if="isCollapse">
|
||||
<img v-if="globalStore.themeConfig.logo" :src="'/api/v1/images/logo'" style="cursor: pointer" alt="logo" />
|
||||
<img
|
||||
v-if="globalStore.themeConfig.logo"
|
||||
:src="`/api/v1/images/logo?t=${Date.now()}`"
|
||||
style="cursor: pointer"
|
||||
alt="logo"
|
||||
/>
|
||||
<MenuLogo v-else />
|
||||
</template>
|
||||
<template v-else>
|
||||
<img
|
||||
v-if="globalStore.themeConfig.logoWithText"
|
||||
:src="'/api/v1/images/logoWithText'"
|
||||
:src="`/api/v1/images/logoWithText?t=${Date.now()}`"
|
||||
style="cursor: pointer"
|
||||
alt="logo"
|
||||
/>
|
||||
@@ -37,6 +42,7 @@ const goHome = () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 55px;
|
||||
z-index: 1;
|
||||
img {
|
||||
object-fit: contain;
|
||||
width: 95%;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
element-loading-svg-view-box="-10, -10, 50, 50"
|
||||
element-loading-background="rgba(122, 122, 122, 0.01)"
|
||||
>
|
||||
<div class="fixed">
|
||||
<PrimaryMenu />
|
||||
</div>
|
||||
<Logo :isCollapse="isCollapse" />
|
||||
<el-scrollbar>
|
||||
<el-menu
|
||||
@@ -46,6 +49,7 @@ import { GlobalStore, MenuStore } from '@/store';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { isString } from '@vueuse/core';
|
||||
import { getSettingInfo } from '@/api/modules/setting';
|
||||
import PrimaryMenu from '@/assets/images/menu-bg.svg?component';
|
||||
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
@@ -183,7 +187,7 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: url(@/assets/images/menu-bg.png) var(--el-menu-bg-color) no-repeat top;
|
||||
background: var(--panel-menu-bg-color) no-repeat top;
|
||||
|
||||
.el-scrollbar {
|
||||
flex: 1;
|
||||
|
||||
@@ -100,7 +100,7 @@ onMounted(() => {
|
||||
|
||||
loadStatus();
|
||||
loadProductProFromDB();
|
||||
|
||||
globalStore.isFullScreen = false;
|
||||
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (mqList.addEventListener) {
|
||||
mqList.addEventListener('change', () => {
|
||||
@@ -137,7 +137,7 @@ onMounted(() => {
|
||||
height: 100vh;
|
||||
transition: margin-left 0.3s;
|
||||
margin-left: var(--panel-menu-width);
|
||||
background-color: #f4f4f4;
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.app-main {
|
||||
|
||||
@@ -64,7 +64,7 @@ const webSiteRouter = {
|
||||
hidden: true,
|
||||
component: () => import('@/views/website/runtime/java/index.vue'),
|
||||
meta: {
|
||||
activeMenu: '/websites/runtimes/java',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
@@ -74,7 +74,7 @@ const webSiteRouter = {
|
||||
hidden: true,
|
||||
component: () => import('@/views/website/runtime/go/index.vue'),
|
||||
meta: {
|
||||
activeMenu: '/websites/runtimes/go',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
@@ -84,7 +84,7 @@ const webSiteRouter = {
|
||||
hidden: true,
|
||||
component: () => import('@/views/website/runtime/python/index.vue'),
|
||||
meta: {
|
||||
activeMenu: '/websites/runtimes/python',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,13 +4,13 @@ export interface ThemeConfigProp {
|
||||
panelName: string;
|
||||
primary: string;
|
||||
theme: string; // dark | bright | auto
|
||||
isGold: boolean;
|
||||
footer: boolean;
|
||||
|
||||
title: string;
|
||||
logo: string;
|
||||
logoWithText: string;
|
||||
favicon: string;
|
||||
themeColor: string;
|
||||
}
|
||||
|
||||
export interface GlobalState {
|
||||
|
||||
@@ -14,11 +14,10 @@ const GlobalStore = defineStore({
|
||||
language: '',
|
||||
themeConfig: {
|
||||
panelName: '',
|
||||
primary: '#005EEB',
|
||||
primary: '#005eeb',
|
||||
theme: 'auto',
|
||||
isGold: false,
|
||||
footer: true,
|
||||
|
||||
themeColor: '',
|
||||
title: '',
|
||||
logo: '',
|
||||
logoWithText: '',
|
||||
@@ -46,9 +45,8 @@ const GlobalStore = defineStore({
|
||||
getters: {
|
||||
isDarkTheme: (state) =>
|
||||
state.themeConfig.theme === 'dark' ||
|
||||
state.themeConfig.isGold ||
|
||||
(state.themeConfig.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches),
|
||||
isDarkGoldTheme: (state) => state.themeConfig.isGold && state.isProductPro,
|
||||
isDarkGoldTheme: (state) => state.themeConfig.primary === '#F0BE96' && state.isProductPro,
|
||||
},
|
||||
actions: {
|
||||
setOpenMenuTabs(openMenuTabs: boolean) {
|
||||
|
||||
@@ -124,7 +124,7 @@ html {
|
||||
.input-help {
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
color: #8f959e;
|
||||
color: #ADB0BC;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -219,7 +219,6 @@ html {
|
||||
background: var(--el-button-bg-color, var(--el-fill-color-blank));
|
||||
border: 0;
|
||||
font-weight: 350;
|
||||
border-left: 0;
|
||||
color: var(--el-button-text-color, var(--el-text-color-regular));
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
@@ -361,7 +360,7 @@ html {
|
||||
|
||||
.el-input-group__append {
|
||||
border-left: 0;
|
||||
background-color: #ffffff !important;
|
||||
background-color: var(--el-fill-color-light) !important;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
|
||||
@@ -465,3 +464,24 @@ html {
|
||||
.monaco-editor-tree-dark .el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: #111417;
|
||||
}
|
||||
.check-label{
|
||||
background: var(--panel-main-bg-color-10) !important;
|
||||
.check-label-a {
|
||||
color: var(--panel-color-primary);
|
||||
}
|
||||
}
|
||||
.check-content {
|
||||
background: var(--panel-main-bg-color-10);
|
||||
pre {
|
||||
margin: 0;
|
||||
width: 350px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-descriptions {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -1,274 +1,341 @@
|
||||
html.dark {
|
||||
--el-box-shadow-light: 0px 0px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
--dark-gold-base-color: #5a5a5a;
|
||||
--el-border-color-lighter: #1d2023;
|
||||
--el-fill-color-blank: #111417;
|
||||
--el-bg-color: rgba(0, 11, 21, 1);
|
||||
// --el-text-color-primary: #999999;
|
||||
--el-text-color-regular: #bbbfc4 !important;
|
||||
--el-fill-color-light: #111417;
|
||||
--el-border-color: #303438;
|
||||
--el-bg-color-overlay: rgba(0, 11, 21, 1);
|
||||
--el-border-color-light: #1d2023;
|
||||
// * menu
|
||||
--el-menu-bg-color: #111417 !important;
|
||||
--el-menu-item-bg-color: #111417;
|
||||
--el-menu-text-color: #ffffff;
|
||||
--el-menu-item-bg-color-active: rgb(44, 45, 46);
|
||||
|
||||
// * panel-admin
|
||||
--panel-text-color: rgb(174, 166, 153);
|
||||
--panel-border: 1px solid #1d2023;
|
||||
--panel-border-color: #394c5e;
|
||||
--panel-main-bg-color: rgba(12, 12, 12, 1);
|
||||
--panel-button-active: var(--el-color-primary);
|
||||
--panel-color-primary: #3D8EFF;
|
||||
--panel-color-primary-light-8: #3674CC;
|
||||
--panel-color-primary-light-1: #6EAAFF;
|
||||
--panel-color-primary-light-2: #366FC2;
|
||||
--panel-color-primary-light-3: #3364AD;
|
||||
--panel-color-primary-light-4: #2F558F;
|
||||
--panel-color-primary-light-5: #372E46;
|
||||
--panel-color-primary-light-6: #2A4066;
|
||||
--panel-color-primary-light-7: #2D4A7A;
|
||||
--panel-color-primary-light-9: #2D4A7A;
|
||||
|
||||
--panel-login-shadow-light: 5px 5px 15px rgb(255 255 255 / 20%);
|
||||
--panel-box-shadow-light: 0 0 10px rgb(255 255 255 / 10%);
|
||||
--panel-popup-color: #060708;
|
||||
--panel-alert-bg: #2f3030;
|
||||
--panel-path-bg: #2f3030;
|
||||
--panel-button-disabled: #5a5a5a;
|
||||
--panel-main-bg-color-1: #E3E6F3;
|
||||
--panel-main-bg-color-2: #C0C2CF;
|
||||
--panel-main-bg-color-3: #ADB0BC;
|
||||
--panel-main-bg-color-4: #9597A4;
|
||||
--panel-main-bg-color-5: #90929F;
|
||||
--panel-main-bg-color-6: #787B88;
|
||||
--panel-main-bg-color-7: #5B5E6A;
|
||||
--panel-main-bg-color-8: #434552;
|
||||
--panel-main-bg-color-9: #2E313D;
|
||||
--panel-main-bg-color-10: #242633;
|
||||
--panel-main-bg-color-11: #60626F;
|
||||
--panel-main-bg-color-12: #000000;
|
||||
|
||||
.el-tag.el-tag--info {
|
||||
--el-tag-bg-color: rgb(49, 51, 51);
|
||||
--el-tag-border-color: rgb(64, 67, 67);
|
||||
--panel-alert-error-bg-color: #fef0f0;
|
||||
--panel-alert-error-text-color: #f56c6c;
|
||||
--panel-alert-error-hover-bg-color: #E9657B;
|
||||
|
||||
--panel-alert-success-bg-color: #e1f3d8;
|
||||
--panel-alert-success-text-color: #67c23a;
|
||||
--panel-alert-success-hover-bg-color: #4DC894;
|
||||
|
||||
--panel-alert-warning-bg-color: #59472A;
|
||||
--panel-alert-warning-text-color: #EDAC2C;
|
||||
--panel-alert-warning-hover-bg-color: #F1C161;
|
||||
|
||||
--panel-alert-info-bg-color: var(--panel-main-bg-color-7);
|
||||
--panel-alert-info-text-color: var(--panel-text-color-white);
|
||||
--panel-alert-info-hover-bg-color: var(--panel-main-bg-color-4);
|
||||
|
||||
--el-color-success: #3fb950;
|
||||
--el-color-success-light-5: #4DC894;
|
||||
--el-color-success-light-8: #3fb950;
|
||||
--el-color-success-light-9: var(--panel-main-bg-color-9);
|
||||
|
||||
--el-color-warning: #EDAC2C;
|
||||
--el-color-warning-light-5: #F1C161;
|
||||
--el-color-warning-light-8: #EDAC2C;
|
||||
--el-color-warning-light-9: var(--panel-main-bg-color-9);
|
||||
|
||||
--el-color-danger: #E2324F;
|
||||
--el-color-danger-light-5: #E9657B;
|
||||
--el-color-danger-light-8: #E2324F;
|
||||
--el-color-danger-light-9: var(--panel-main-bg-color-9);
|
||||
|
||||
--el-color-error: #E2324F;
|
||||
--el-color-error-light-5: #E9657B;
|
||||
--el-color-error-light-8: #E2324F;
|
||||
--el-color-error-light-9: var(--panel-main-bg-color-9);
|
||||
|
||||
--el-color-info: var(--panel-main-bg-color-3);
|
||||
--el-color-info-light-5: var(--panel-main-bg-color-3);
|
||||
--el-color-info-light-8: var(--panel-main-bg-color-3);
|
||||
--el-color-info-light-9: var(--panel-main-bg-color-9);
|
||||
|
||||
|
||||
--panel-pie-bg-color: #434552;
|
||||
--panel-text-color-white: #ffffff;
|
||||
|
||||
|
||||
--el-color-primary: var(--panel-color-primary);
|
||||
--el-color-primary-light-1: var(--panel-color-primary-light-1);
|
||||
--el-color-primary-light-2: var(--panel-color-primary-light-2);
|
||||
--el-color-primary-light-3: var(--panel-color-primary-light-3);
|
||||
--el-color-primary-light-4: var(--panel-color-primary-light-4);
|
||||
--el-color-primary-light-5: var(--panel-color-primary-light-5);
|
||||
--el-color-primary-light-6: var(--panel-color-primary-light-6);
|
||||
--el-color-primary-light-7: var(--panel-color-primary-light-7);
|
||||
--el-color-primary-light-8: var(--panel-color-primary-light-8);
|
||||
--el-color-primary-light-9: var(--panel-color-primary-light-9);
|
||||
--el-color-primary-dark-2: var(--panel-color-primary);
|
||||
--el-scrollbar-bg-color: var(--panel-main-bg-color-8);
|
||||
--el-border-color-darker: var(--panel-main-bg-color-6);
|
||||
|
||||
--panel-border: 1px solid var(--panel-main-bg-color-8);
|
||||
--panel-border-color: var(--panel-main-bg-color-8);
|
||||
--panel-button-active: var(--panel-main-bg-color-10);
|
||||
--panel-button-text-color: var(--panel-main-bg-color-10);
|
||||
--panel-button-bg-color: var(--panel-color-primary);
|
||||
--panel-footer-bg: var(--panel-main-bg-color-9);
|
||||
--panel-footer-border: var(--panel-main-bg-color-7);
|
||||
--panel-text-color: var(--panel-main-bg-color-1);
|
||||
--panel-menu-bg-color: var(--panel-main-bg-color-10);
|
||||
--panel-terminal-tag-bg-color: var(--panel-main-bg-color-10);
|
||||
--panel-terminal-tag-active-bg-color: var(--panel-main-bg-color-10);
|
||||
--panel-terminal-bg-color: var(--panel-main-bg-color-10);
|
||||
--panel-logs-bg-color: var(--panel-main-bg-color-9);
|
||||
|
||||
--el-menu-item-bg-color: var(--panel-main-bg-color-10);
|
||||
--el-menu-item-bg-color-active: var(--panel-main-bg-color-8);
|
||||
--el-menu-hover-bg-color: var(--panel-main-bg-color-8);
|
||||
--el-menu-text-color: var(--panel-main-bg-color-2);
|
||||
--el-fill-color-blank: var(--panel-main-bg-color-10);
|
||||
--el-fill-color-light: var(--panel-main-bg-color-10);
|
||||
--el-border-color: var(--panel-main-bg-color-8);
|
||||
--el-border-color-light: var(--panel-main-bg-color-8);
|
||||
--el-border-color-lighter: var(--panel-main-bg-color-8);
|
||||
|
||||
--el-text-color-primary: var(--panel-main-bg-color-2);
|
||||
--el-text-color-regular: var(--panel-main-bg-color-2);
|
||||
|
||||
--el-box-shadow: 0px 12px 32px 4px rgba(36, 38, 51, .36), 0px 8px 20px rgba(36, 38, 51, .72);
|
||||
--el-box-shadow-light: 0px 0px 12px rgba(36, 38, 51, .72);
|
||||
--el-box-shadow-lighter: 0px 0px 6px rgba(36, 38, 51, .72);
|
||||
--el-box-shadow-dark: 0px 16px 48px 16px rgba(36, 38, 51, .72), 0px 12px 32px #242633, 0px 8px 16px -8px #242633;
|
||||
--el-bg-color: var(--panel-main-bg-color-9);
|
||||
--el-bg-color-overlay: var(--panel-main-bg-color-9);
|
||||
|
||||
--el-text-color-placeholder: var(--panel-main-bg-color-4);
|
||||
|
||||
.el-radio-button {
|
||||
--el-radio-button-checked-text-color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
.el-tag.el-tag--light {
|
||||
--el-tag-bg-color: #111417;
|
||||
--el-tag-border-color: var(--el-color-primary);
|
||||
}
|
||||
.el-tag.el-tag--success {
|
||||
--el-tag-border-color: var(--el-color-success);
|
||||
}
|
||||
.el-tag.el-tag--danger {
|
||||
--el-tag-border-color: var(--el-color-danger);
|
||||
}
|
||||
.el-card {
|
||||
--el-card-bg-color: rgb(35, 35, 35);
|
||||
color: #ffffff;
|
||||
border: 1px solid var(--el-card-border-color) !important;
|
||||
.el-descriptions__content:not(.is-bordered-label) {
|
||||
color: var(--panel-main-bg-color-3);
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--el-table-bg-color: rgba(0, 11, 21, 1);
|
||||
--el-table-tr-bg-color: rgba(0, 11, 21, 1);
|
||||
--el-table-header-bg-color: rgba(0, 11, 21, 1);
|
||||
--el-table-border: var(--panel-border);
|
||||
--el-table-border-color: rgb(64, 67, 67);
|
||||
}
|
||||
.el-message-box {
|
||||
--el-messagebox-title-color: var(--el-menu-text-color);
|
||||
border: 1px solid var(--panel-border-color);
|
||||
.el-menu-item:hover, .el-sub-menu__title:hover{
|
||||
background: var(--panel-main-bg-color-8) !important;
|
||||
}
|
||||
|
||||
.el-alert--info {
|
||||
--el-alert-bg-color: rgb(56, 59, 59);
|
||||
.el-menu .el-menu-item {
|
||||
box-shadow: 0 0 4px rgba(36, 38, 51, .72);
|
||||
}
|
||||
|
||||
.el-menu .el-sub-menu__title {
|
||||
box-shadow: 0 0 4px rgba(36, 38, 51, .72);
|
||||
}
|
||||
|
||||
.el-overlay {
|
||||
background-color: rgb(46 49 61 / 80%);
|
||||
}
|
||||
|
||||
.el-tag.el-tag--primary {
|
||||
--el-tag-bg-color: var(--panel-main-bg-color-9);
|
||||
--el-tag-border-color: var(--panel-main-bg-color-11);
|
||||
--el-tag-hover-color: var(--panel-color-primary);
|
||||
}
|
||||
|
||||
.el-tabs--card > .el-tabs__header .el-tabs__nav {
|
||||
border: 1px solid var(--panel-main-bg-color-8);
|
||||
}
|
||||
|
||||
.el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
|
||||
border-bottom-color: var(--panel-color-primary);
|
||||
--el-text-color-regular: var(--panel-color-primary);
|
||||
}
|
||||
|
||||
.el-loading-mask {
|
||||
background-color: #24263375;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
--el-input-bg-color: rgb(47 48 48);
|
||||
--el-input-border-color: #303438;
|
||||
--el-input-border-color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
|
||||
.el-pagination {
|
||||
--el-pagination-button-color: #999999;
|
||||
input:-webkit-autofill {
|
||||
box-shadow: 0 0 0 1000px var(--el-box-shadow) inset;
|
||||
background-color: var(--panel-main-bg-color-1);
|
||||
transition: background-color 1000s ease-out 0.5s;
|
||||
}
|
||||
|
||||
.el-popover {
|
||||
--el-popover-title-text-color: #999999;
|
||||
border: 1px solid var(--panel-border-color);
|
||||
.el-input.is-disabled .el-input__wrapper {
|
||||
--el-disabled-bg-color: var(--panel-main-bg-color-9);
|
||||
--el-disabled-border-color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
|
||||
.md-editor-dark {
|
||||
--md-bk-color: #111417;
|
||||
.el-input > .el-input-group__append:hover {
|
||||
background-color: var(--panel-main-bg-color-9) !important;
|
||||
}
|
||||
|
||||
// * 以下为自定义暗黑模式内容
|
||||
// login
|
||||
.login-container {
|
||||
.login-form {
|
||||
input:-webkit-autofill {
|
||||
box-shadow: 0 0 0 1000px #f1f4f9 inset;
|
||||
-webkit-text-fill-color: #333333;
|
||||
-webkit-transition: background-color 1000s ease-out 0.5s;
|
||||
transition: background-color 1000s ease-out 0.5s;
|
||||
}
|
||||
.el-input__wrapper {
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
||||
}
|
||||
.el-input__inner {
|
||||
color: #606266;
|
||||
}
|
||||
.el-form-item__label {
|
||||
color: var(--panel-main-bg-color-3);
|
||||
}
|
||||
|
||||
.el-card {
|
||||
--el-card-bg-color: var(--panel-main-bg-color-10)
|
||||
}
|
||||
|
||||
.el-button:hover {
|
||||
--el-button-hover-border-color:var(--panel-main-bg-color-11);
|
||||
--el-button-hover-bg-color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
--el-button-text-color: var(--panel-main-bg-color-10);
|
||||
--el-button-hover-link-text-color: var(--panel-color-primary-light-1);
|
||||
&.tag-button,
|
||||
&.brief-button {
|
||||
--el-button-text-color: var(--panel-main-bg-color-10);
|
||||
--el-button-hover-text-color: var(--el-color-white);
|
||||
--el-button-hover-border-color:var(--el-color-primary);
|
||||
--el-button-hover-bg-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.app-button {
|
||||
--el-button-text-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.h-app-button {
|
||||
--el-button-text-color: var(--panel-main-bg-color-10);
|
||||
--el-button-hover-text-color: var(--el-color-white);
|
||||
--el-button-hover-border-color: var(--el-color-primary);
|
||||
--el-button-hover-bg-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// scroll-bar
|
||||
::-webkit-scrollbar {
|
||||
background-color: var(--el-scrollbar-bg-color) !important;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--el-border-color-darker);
|
||||
}
|
||||
// sidebar
|
||||
.sidebar-container-popper {
|
||||
border: 1px solid #66686c;
|
||||
.el-menu--popup-container {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.sidebar-container {
|
||||
border-right: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
.el-menu {
|
||||
.el-menu-item {
|
||||
&:hover {
|
||||
background: rgba(37, 39, 44, 1);
|
||||
}
|
||||
&.is-active {
|
||||
background: var(--el-color-primary);
|
||||
color: #ffffff;
|
||||
&:hover {
|
||||
.el-icon {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
&::before {
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-sub-menu {
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
background: rgba(37, 39, 44, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.menu-collapse {
|
||||
color: var(--el-menu-text-color);
|
||||
border: var(--panel-border);
|
||||
.el-button--primary.is-plain, .el-button--primary.is-text, .el-button--primary.is-link {
|
||||
--el-button-text-color: var(--panel-main-bg-color-2);
|
||||
--el-button-bg-color: var(--panel-main-bg-color-9);
|
||||
--el-button-border-color: var(--panel-main-bg-color-8);
|
||||
--el-button-hover-bg-color: var(--panel-main-bg-color-9);
|
||||
--el-button-hover-border-color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
|
||||
// layout
|
||||
.app-wrapper {
|
||||
.main-container {
|
||||
background-color: var(--panel-main-bg-color) !important;
|
||||
}
|
||||
.app-footer {
|
||||
background-color: var(--panel-main-bg-color) !important;
|
||||
border-top: var(--panel-border);
|
||||
}
|
||||
.mobile-header {
|
||||
background-color: var(--panel-main-bg-color) !important;
|
||||
border-bottom: var(--panel-border);
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.system-label {
|
||||
color: var(--el-menu-text-color);
|
||||
.el-button--primary.is-text,.el-button--primary.is-link {
|
||||
--el-button-text-color: var(--panel-color-primary);
|
||||
}
|
||||
|
||||
.router_card_button {
|
||||
.el-radio-button__inner {
|
||||
background: none !important;
|
||||
}
|
||||
.el-radio-button__original-radio:checked + .el-radio-button__inner {
|
||||
color: #ffffff;
|
||||
background-color: var(--panel-button-active) !important;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.el-button--primary:hover {
|
||||
--el-button-hover-text-color: var(--panel-main-bg-color-7);
|
||||
--el-button-border-color: var(--el-color-primary);
|
||||
--el-button-hover-bg-color: var(--panel-color-primary-light-2);
|
||||
--el-button-hover-border-color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
// content-box
|
||||
.content-box {
|
||||
.text {
|
||||
color: var(--el-text-color-regular) !important;
|
||||
}
|
||||
|
||||
.el-button--primary.is-plain:hover {
|
||||
--el-button-hover-text-color: var(--panel-main-bg-color-10);
|
||||
--el-button-border-color: var(--el-color-primary);
|
||||
--el-button-hover-bg-color: var(--el-color-primary);
|
||||
--el-button-hover-border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-button--primary:active {
|
||||
--el-button-hover-text-color: var(--panel-main-bg-color-7);
|
||||
--el-button-active-bg-color: var(--el-color-primary-light-3);
|
||||
--el-button-active-border-color: var(--el-color-primary-light-3);
|
||||
}
|
||||
.el-button--primary.is-plain:active {
|
||||
color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
|
||||
.el-button:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.el-button.is-disabled {
|
||||
color: var(--panel-main-bg-color-7);
|
||||
border-color: var(--panel-main-bg-color-8);
|
||||
background: var(--panel-main-bg-color-9);
|
||||
}
|
||||
|
||||
.el-button.is-disabled:hover {
|
||||
border-color: var(--panel-main-bg-color-8);
|
||||
background: var(--panel-main-bg-color-9);
|
||||
}
|
||||
|
||||
.el-button--primary.is-link.is-disabled {
|
||||
color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:hover {
|
||||
background-color: var(--panel-main-bg-color-7);
|
||||
}
|
||||
|
||||
.el-drawer .el-drawer__header span {
|
||||
color: var(--el-menu-text-color);
|
||||
}
|
||||
.el-drawer {
|
||||
border-left: 0.5px solid var(--panel-border-color);
|
||||
color: var(--panel-text-color);
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--el-disabled-bg-color);
|
||||
}
|
||||
.el-input.is-disabled .el-input__wrapper {
|
||||
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
|
||||
}
|
||||
.el-radio-button__inner {
|
||||
background: #1d2023;
|
||||
}
|
||||
.el-button--primary.is-plain.is-disabled {
|
||||
background: #1d2023;
|
||||
border-color: #303438;
|
||||
}
|
||||
.el-button--primary.is-plain {
|
||||
background: #1d2023;
|
||||
border-color: #303438;
|
||||
}
|
||||
.el-button.is-link.is-disabled {
|
||||
color: var(--panel-button-disabled) !important;
|
||||
}
|
||||
.el-button.is-disabled {
|
||||
border-color: #303438;
|
||||
color: var(--panel-button-disabled);
|
||||
}
|
||||
.el-popper.is-dark {
|
||||
color: rgb(171 173 173);
|
||||
}
|
||||
.path {
|
||||
border: var(--panel-border);
|
||||
.split {
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
input:-webkit-autofill {
|
||||
box-shadow: 0 0 0 1000px #323232 inset;
|
||||
-webkit-text-fill-color: #cfd3dc;
|
||||
transition: background-color 1000s ease-out 0.5s;
|
||||
}
|
||||
.el-avatar {
|
||||
--el-avatar-bg-color: #111417 !important;
|
||||
box-shadow: 0px 0px 4px rgba(0, 94, 235, 0.1);
|
||||
border: 0.5px solid #1f2022;
|
||||
}
|
||||
.el-page-header__content {
|
||||
color: rgb(174, 166, 153);
|
||||
}
|
||||
.el-dialog {
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
border: 1px solid var(--panel-border-color);
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: var(--panel-border);
|
||||
color: #999999;
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
.el-dialog__title {
|
||||
color: var(--el-menu-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-tabs__item {
|
||||
color: #999999;
|
||||
|
||||
.el-alert--error {
|
||||
--el-alert-bg-color: var(--panel-alert-error-bg-color);
|
||||
--el-color-error: var(--panel-alert-error-text-color);
|
||||
}
|
||||
.el-tabs__item.is-active {
|
||||
color: var(--el-color-primary) !important;
|
||||
|
||||
.el-alert--success {
|
||||
--el-alert-bg-color: var(--panel-alert-success-bg-color);
|
||||
--el-color-success: var(--panel-alert-success-text-color);
|
||||
}
|
||||
|
||||
.el-alert--warning {
|
||||
--el-alert-bg-color: var(--panel-alert-warning-bg-color);
|
||||
--el-color-warning: var(--panel-alert-warning-text-color);
|
||||
}
|
||||
|
||||
.el-alert--info {
|
||||
--el-alert-bg-color: var(--panel-alert-info-bg-color);
|
||||
--el-color-info: var(--panel-alert-info-text-color);
|
||||
}
|
||||
|
||||
.md-editor-dark {
|
||||
--md-bk-color: var(--panel-main-bg-color-9);
|
||||
}
|
||||
|
||||
.md-editor-dark .md-editor-preview {
|
||||
--md-theme-color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.md-editor-dark .default-theme a {
|
||||
--md-theme-link-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.md-editor-dark .default-theme pre code {
|
||||
background-color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
.md-editor-dark .default-theme pre:before {
|
||||
background-color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
|
||||
.el-descriptions__title {
|
||||
color: #999999;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.el-descriptions__content.el-descriptions__cell.is-bordered-content {
|
||||
color: #999999;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.el-descriptions--large .el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell {
|
||||
padding: 12px 15px;
|
||||
@@ -282,94 +349,109 @@ html.dark {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.terminal-tabs {
|
||||
background: none !important;
|
||||
.el-tabs__header {
|
||||
background: #000000;
|
||||
}
|
||||
.el-avatar {
|
||||
--el-avatar-bg-color: var(--panel-text-color-white) !important;
|
||||
box-shadow: 0 0 4px rgba(0, 94, 235, 0.1);
|
||||
border: 0.5px solid var(--panel-main-bg-color-7);
|
||||
}
|
||||
.el-pager {
|
||||
li {
|
||||
color: #999999;
|
||||
&.is-active {
|
||||
color: var(--el-pagination-hover-color);
|
||||
}
|
||||
.el-drawer {
|
||||
.cm-editor {
|
||||
background-color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
.cm-gutters {
|
||||
background-color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
}
|
||||
.el-loading-mask {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.h-app-card {
|
||||
.h-app-content {
|
||||
.h-app-title {
|
||||
color: #f2f8ff;
|
||||
.cm-editor {
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
}
|
||||
.cm-gutters {
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
}
|
||||
|
||||
// scroll-bar
|
||||
::-webkit-scrollbar {
|
||||
background-color: var(--el-scrollbar-bg-color) !important;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--el-border-color-darker);
|
||||
}
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: var(--el-scrollbar-bg-color);
|
||||
}
|
||||
|
||||
.app-warn {
|
||||
span {
|
||||
&:nth-child(2) {
|
||||
color: var(--panel-color-primary);
|
||||
|
||||
&:hover {
|
||||
color: var(--panel-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.infinite-list .infinite-list-item {
|
||||
background: #212426;
|
||||
&:hover {
|
||||
background: #212426;
|
||||
.el-table {
|
||||
--el-table-bg-color: var(--el-bg-color);
|
||||
--el-table-tr-bg-color: var(--el-bg-color);
|
||||
--el-table-header-bg-color: var(--el-bg-color);
|
||||
--el-table-border: 1px solid var(--panel-main-bg-color-8);
|
||||
--el-table-border-color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
--el-messagebox-title-color: var(--el-menu-text-color);
|
||||
border: 1px solid var(--panel-border-color);
|
||||
}
|
||||
|
||||
.el-popover {
|
||||
--el-popover-title-text-color: var(--panel-main-bg-color-2);
|
||||
border: 1px solid var(--panel-border-color);
|
||||
}
|
||||
|
||||
.app-wrapper {
|
||||
.main-container {
|
||||
background-color: var(--panel-main-bg-color-9) !important;
|
||||
}
|
||||
.app-footer {
|
||||
background-color: var(--panel-main-bg-color-9) !important;
|
||||
border-top: var(--panel-border);
|
||||
}
|
||||
.mobile-header {
|
||||
background-color: var(--panel-main-bg-color-9) !important;
|
||||
border-bottom: var(--panel-border);
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-alert--warning.is-light {
|
||||
background-color: rgb(56, 59, 59);
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
.el-dropdown-menu__item.is-disabled {
|
||||
color: var(--panel-button-disabled);
|
||||
}
|
||||
.el-date-editor .el-range-separator {
|
||||
color: var(--panel-button-disabled);
|
||||
}
|
||||
|
||||
.el-input-group__append {
|
||||
border-left: 0;
|
||||
background-color: #212426 !important;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
|
||||
-1px 0 0 0 var(--el-input-border-color) inset;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
.router_card_button {
|
||||
.el-radio-button__inner {
|
||||
background: none !important;
|
||||
}
|
||||
.el-radio-button__original-radio:checked + .el-radio-button__inner {
|
||||
color: var(--panel-main-bg-color-10);
|
||||
background-color: var(--panel-color-primary) !important;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-date-table td.in-range .el-date-table-cell {
|
||||
background-color: var(--panel-main-bg-color-8);
|
||||
}
|
||||
|
||||
.el-collapse-item__header {
|
||||
color: #ffffff;
|
||||
background-color: var(--panel-main-bg-color-10) !important;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background-color: #4f4f4f;
|
||||
.el-checkbox__input.is-checked .el-checkbox__inner::after {
|
||||
border-color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
|
||||
.level-up-pro {
|
||||
--dark-gold-base-color: #eaba63;
|
||||
--dark-gold-text-color: #1f2329;
|
||||
--el-color-primary: var(--dark-gold-base-color);
|
||||
|
||||
.title {
|
||||
color: var(--dark-gold-base-color);
|
||||
}
|
||||
.el-button--primary {
|
||||
&.is-plain {
|
||||
background: var(--dark-gold-base-color);
|
||||
--el-button-text-color: var(--dark-gold-text-color);
|
||||
|
||||
&.is-disabled {
|
||||
background: #1d2023;
|
||||
border-color: #303438;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-text {
|
||||
--el-button-text-color: var(--dark-gold-base-color);
|
||||
}
|
||||
}
|
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner::before {
|
||||
background-color: var(--panel-main-bg-color-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,59 @@
|
||||
:root {
|
||||
--el-color-primary: #005eeb;
|
||||
--el-color-primary-dark-2: #0054d3;
|
||||
--panel-gradient-end-color: var(--el-color-primary-light-7);
|
||||
|
||||
--el-color-primary-light-1: #196eed;
|
||||
--el-color-primary-light-2: #337eef;
|
||||
--el-color-primary-light-3: #4c8ef1;
|
||||
--el-color-primary-light-4: #669ef3;
|
||||
--el-color-primary-light-5: #7faef5;
|
||||
--el-color-primary-light-6: #99bef7;
|
||||
--el-color-primary-light-7: #b2cef9;
|
||||
--el-color-primary-light-8: #ccdefb;
|
||||
--el-color-primary-light-9: #e5eefd;
|
||||
|
||||
--panel-color-primary: #005eeb;
|
||||
--panel-color-primary-light-8: #196eed;
|
||||
--panel-color-primary-light-1: #196eed;
|
||||
--panel-color-primary-light-2: #337eef;
|
||||
--panel-color-primary-light-3: #4c8ef1;
|
||||
--panel-color-primary-light-4: #669ef3;
|
||||
--panel-color-primary-light-5: #7faef5;
|
||||
--panel-color-primary-light-6: #99bef7;
|
||||
--panel-color-primary-light-7: #b2cef9;
|
||||
--panel-color-primary-light-9: #e5eefd;
|
||||
|
||||
--el-color-primary: var(--panel-color-primary);
|
||||
--el-color-primary-light-3: var(--panel-color-primary-light-1);
|
||||
--el-color-primary-light-5: var(--panel-color-primary-light-5);
|
||||
--el-color-primary-light-7: var(--panel-color-primary-light-7);
|
||||
--el-color-primary-light-8: var(--panel-color-primary-light-8);
|
||||
--el-color-primary-light-9: var(--panel-color-primary-light-9);
|
||||
|
||||
--el-text-color-regular: #646a73;
|
||||
}
|
||||
|
||||
html {
|
||||
--el-box-shadow-light: 0px 0px 4px rgba(0, 94, 235, 0.1) !important;
|
||||
|
||||
--el-text-color-regular: #646a73 !important;
|
||||
// * menu
|
||||
--el-menu-bg-color: rgba(0, 94, 235, 0.1) !important;
|
||||
--el-menu-item-bg-color: rgba(255, 255, 255, 0.3);
|
||||
--el-menu-item-bg-color-active: #ffffff;
|
||||
--panel-main-bg-color-9: #f4f4f4;
|
||||
--panel-menu-bg-color: rgba(0, 94, 235, 0.1);
|
||||
--panel-menu-width: 180px;
|
||||
--panel-menu-hide-width: 75px;
|
||||
--panel-text-color: #1f2329;
|
||||
--panel-border: 1px solid #f2f2f2;
|
||||
--panel-button-active: #ffffff;
|
||||
--panel-button-text-color: var(--panel-color-primary);
|
||||
--panel-button-bg-color: #ffffff;
|
||||
--panel-footer-border: #e4e7ed;
|
||||
--panel-terminal-tag-bg-color: #efefef;
|
||||
--panel-terminal-tag-active-bg-color: #575758;
|
||||
--panel-terminal-bg-color: #1e1e1e;
|
||||
--panel-logs-bg-color: #1e1e1e;
|
||||
|
||||
--panel-alert-bg: #e2e4ec;
|
||||
--panel-path-bg: #ffffff;
|
||||
--panel-footer-bg: #ffffff;
|
||||
--panel-pie-bg-color: #ffffff;
|
||||
|
||||
--el-fill-color-light: #ffffff;
|
||||
}
|
||||
|
||||
.el-notification {
|
||||
|
||||
@@ -20,11 +20,37 @@ body,
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.el-switch--small,.is-checked .el-switch__core::after {
|
||||
margin-left: 12px !important;
|
||||
.el-switch--small.is-checked .el-switch__core::after {
|
||||
margin-left: -13px;
|
||||
}
|
||||
|
||||
.el-alert__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
--el-color-primary: #409eff;
|
||||
--el-color-primary-light-5: #a0cfff;
|
||||
--el-color-primary-light-8: #d9ecff;
|
||||
--el-color-primary-light-9: #ecf5ff;
|
||||
--el-color-success: #67c23a;
|
||||
--el-color-success-light-5: #b3e19d;
|
||||
--el-color-success-light-8: #e1f3d8;
|
||||
--el-color-success-light-9: #f0f9eb;
|
||||
--el-color-warning: #e6a23c;
|
||||
--el-color-warning-light-5: #f3d19e;
|
||||
--el-color-warning-light-8: #faecd8;
|
||||
--el-color-warning-light-9: #fdf6ec;
|
||||
--el-color-danger: #f56c6c;
|
||||
--el-color-danger-light-5: #fab6b6;
|
||||
--el-color-danger-light-8: #fde2e2;
|
||||
--el-color-error: #f56c6c;
|
||||
--el-color-error-light-5: #fab6b6;
|
||||
--el-color-error-light-8: #fde2e2;
|
||||
--el-color-error-light-9: #fef0f0;
|
||||
--el-color-info: #909399;
|
||||
--el-color-info-light-5: #c8c9cc;
|
||||
--el-color-info-light-8: #e9e9eb;
|
||||
--el-color-info-light-9: #f4f4f5;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export function setPrimaryColor(color: string) {
|
||||
let setPrimaryColor: (arg0: string) => any;
|
||||
const xpackModules = import.meta.glob('../xpack/utils/theme/tool.ts', { eager: true });
|
||||
if (xpackModules['../xpack/utils/theme/tool.ts']) {
|
||||
setPrimaryColor = xpackModules['../xpack/utils/theme/tool.ts']['setPrimaryColor'] || {};
|
||||
return setPrimaryColor(color);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { MsgWarning } from '../message';
|
||||
|
||||
/**
|
||||
* hex颜色转rgb颜色
|
||||
* @param str 颜色值字符串
|
||||
* @returns 返回处理后的颜色值
|
||||
*/
|
||||
export function hexToRgb(str: any) {
|
||||
let hexs: any = '';
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(str)) return MsgWarning('输入错误的hex');
|
||||
str = str.replace('#', '');
|
||||
hexs = str.match(/../g);
|
||||
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
|
||||
return hexs;
|
||||
}
|
||||
|
||||
/**
|
||||
* rgb颜色转Hex颜色
|
||||
* @param r 代表红色
|
||||
* @param g 代表绿色
|
||||
* @param b 代表蓝色
|
||||
* @returns 返回处理后的颜色值
|
||||
*/
|
||||
export function rgbToHex(r: any, g: any, b: any) {
|
||||
let reg = /^\d{1,3}$/;
|
||||
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return MsgWarning('输入错误的rgb颜色值');
|
||||
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
|
||||
for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
|
||||
return `#${hexs.join('')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加深颜色值
|
||||
* @param color 颜色值字符串
|
||||
* @param level 加深的程度,限0-1之间
|
||||
* @returns 返回处理后的颜色值
|
||||
*/
|
||||
export function getDarkColor(color: string, level: number) {
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(color)) return MsgWarning('输入错误的hex颜色值');
|
||||
let rgb = hexToRgb(color);
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level));
|
||||
return rgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 变浅颜色值
|
||||
* @param color 颜色值字符串
|
||||
* @param level 加深的程度,限0-1之间
|
||||
* @returns 返回处理后的颜色值
|
||||
*/
|
||||
export function getLightColor(color: string, level: number) {
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(color)) return MsgWarning('输入错误的hex颜色值');
|
||||
let rgb = hexToRgb(color);
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
|
||||
return rgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
@@ -589,3 +589,29 @@ export const getFileType = (extension: string) => {
|
||||
});
|
||||
return type;
|
||||
};
|
||||
|
||||
export const escapeProxyURL = (url: string): string => {
|
||||
const encodeMap: { [key: string]: string } = {
|
||||
':': '%%3A',
|
||||
'/': '%%2F',
|
||||
'?': '%%3F',
|
||||
'#': '%%23',
|
||||
'[': '%%5B',
|
||||
']': '%%5D',
|
||||
'@': '%%40',
|
||||
'!': '%%21',
|
||||
$: '%%24',
|
||||
'&': '%%26',
|
||||
"'": '%%27',
|
||||
'(': '%%28',
|
||||
')': '%%29',
|
||||
'*': '%%2A',
|
||||
'+': '%%2B',
|
||||
',': '%%2C',
|
||||
';': '%%3B',
|
||||
'=': '%%3D',
|
||||
'%': '%%25',
|
||||
};
|
||||
|
||||
return url.replace(/[\/:?#[\]@!$&'()*+,;=%~]/g, (match) => encodeMap[match] || match);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ export function resetXSetting() {
|
||||
globalStore.themeConfig.logo = '';
|
||||
globalStore.themeConfig.logoWithText = '';
|
||||
globalStore.themeConfig.favicon = '';
|
||||
globalStore.themeConfig.isGold = false;
|
||||
}
|
||||
|
||||
export function initFavicon() {
|
||||
@@ -18,13 +17,26 @@ export function initFavicon() {
|
||||
const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement;
|
||||
link.type = 'image/x-icon';
|
||||
link.rel = 'shortcut icon';
|
||||
if (globalStore.isDarkGoldTheme) {
|
||||
let goldLink = new URL(`../assets/images/favicon-gold.png`, import.meta.url).href;
|
||||
link.href = favicon ? '/api/v1/images/favicon' : goldLink;
|
||||
let goldLink = new URL(`../assets/images/favicon.svg`, import.meta.url).href;
|
||||
if (globalStore.isProductPro) {
|
||||
const themeColor = globalStore.themeConfig.primary;
|
||||
const svg = `
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="${themeColor}" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.1451 18.8875L5.66228 15.7224V8.40336L3.5376 7.1759V16.9488L9.02038 20.114L11.1451 18.8875Z" />
|
||||
<path d="M18.3397 15.7224L12.0005 19.3819L9.87683 20.6083L12.0005 21.8348L20.4644 16.9488L18.3397 15.7224Z" />
|
||||
<path d="M12.0015 4.74388L14.1252 3.5174L12.0005 2.28995L3.5376 7.17591L5.66228 8.40337L12.0005 4.74388H12.0015Z" />
|
||||
<path d="M14.9816 4.01077L12.8569 5.23723L18.3397 8.40336V15.7224L20.4634 16.9488V7.1759L14.9816 4.01077Z" />
|
||||
<path d="M11.9995 1.02569L21.5576 6.54428V17.5795L11.9995 23.0971L2.44343 17.5795V6.54428L11.9995 1.02569ZM11.9995 0.72728L2.18182 6.39707V17.7366L11.9995 23.4064L21.8182 17.7366V6.39707L11.9995 0.72728Z" />
|
||||
<path d="M12.3079 6.78001L12.9564 7.16695V17.105L12.3079 17.48V6.78001Z" />
|
||||
<path d="M12.3078 6.78001L9.10889 8.6222V9.86954H10.2359V16.2854L12.3059 17.481L12.3078 6.78001Z" />
|
||||
</svg>
|
||||
`;
|
||||
goldLink = `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
||||
link.href = favicon ? `/api/v1/images/favicon?t=${Date.now()}` : goldLink;
|
||||
} else {
|
||||
link.href = favicon ? '/api/v1/images/favicon' : '/public/favicon.png';
|
||||
link.href = favicon ? `/api/v1/images/favicon?t=${Date.now()}` : '/public/favicon.png';
|
||||
}
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
export async function getXpackSetting() {
|
||||
@@ -58,7 +70,7 @@ export async function loadProductProFromDB() {
|
||||
globalStore.isProductPro = false;
|
||||
} else {
|
||||
globalStore.isProductPro =
|
||||
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
|
||||
res.data.status === 'Enable' || res.data.status === 'OnRetry' || res.data.status === 'Lost';
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
globalStore.isTrial = res.data.trial;
|
||||
@@ -79,7 +91,7 @@ export async function getXpackSettingForTheme() {
|
||||
return;
|
||||
}
|
||||
globalStore.isProductPro =
|
||||
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
|
||||
res.data.status === 'Enable' || res.data.status === 'OnRetry' || res.data.status === 'Lost';
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
globalStore.isTrial = res.data.trial;
|
||||
@@ -102,7 +114,8 @@ export async function getXpackSettingForTheme() {
|
||||
globalStore.themeConfig.logo = res2.data?.logo;
|
||||
globalStore.themeConfig.logoWithText = res2.data?.logoWithText;
|
||||
globalStore.themeConfig.favicon = res2.data?.favicon;
|
||||
globalStore.themeConfig.isGold = res2.data?.theme === 'dark-gold';
|
||||
globalStore.themeConfig.themeColor = res2.data?.themeColor;
|
||||
globalStore.themeConfig.theme = res2.data?.theme || 'auto';
|
||||
} else {
|
||||
resetXSetting();
|
||||
}
|
||||
|
||||
@@ -100,10 +100,19 @@
|
||||
<el-col :xs="16" :sm="18" :md="18" :lg="18" :xl="19">
|
||||
<div class="app-content">
|
||||
<div class="app-header">
|
||||
<span class="app-title">{{ app.name }}</span>
|
||||
<el-text type="success" class="!ml-2" v-if="app.installed">
|
||||
{{ $t('app.allReadyInstalled') }}
|
||||
</el-text>
|
||||
<el-space wrap :size="1">
|
||||
<span class="app-title">{{ app.name }}</span>
|
||||
<el-tag
|
||||
type="success"
|
||||
v-if="app.installed"
|
||||
round
|
||||
size="small"
|
||||
class="!ml-2"
|
||||
>
|
||||
{{ $t('app.allReadyInstalled') }}
|
||||
</el-tag>
|
||||
</el-space>
|
||||
|
||||
<el-button
|
||||
class="app-button"
|
||||
type="primary"
|
||||
@@ -282,7 +291,7 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style scoped lang="scss">
|
||||
.header {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.brief {
|
||||
.name {
|
||||
span {
|
||||
@@ -183,6 +183,7 @@ defineExpose({
|
||||
.icon {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.version {
|
||||
@@ -196,4 +197,8 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.md-editor-dark) {
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user