Compare commits

..
100 Commits
Author SHA1 Message Date
wanghe-fit2cloud aaae8a5d3b fix: Remove the build cache 2024-11-20 12:08:35 +08:00
wanghe-fit2cloud 5ca91157ca chore: modify question templates. 2024-11-19 18:12:11 +08:00
wanghe-fit2cloud bad1b0b105 perf: Update related workflows 2024-11-19 18:08:58 +08:00
wanghe-fit2cloud d14a149c57 Remove .vscode folder from the repository 2024-11-19 18:06:57 +08:00
吴小白 9c6c77615e build(deps): bump goreleaser from v2.4.5 to latest (#7122)
#### What this PR does / why we need it?
- https://github.com/goreleaser/goreleaser/issues/5292
- https://github.com/goreleaser/goreleaser/commit/700889269f740f659c9f3867f2b79fed677b74b0

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-11-19 03:56:05 +00:00
吴小白 913e64bb75 perf: remove go cache (#7114)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-11-18 08:02:03 +00:00
蘭 fd8ef545e8 style: 主题样式优化 (#7113) 2024-11-18 07:42:03 +00:00
ssongliu d068cc40f0 fix: 修改未激活状态下信息内容 (#7112) 2024-11-18 06:58:03 +00:00
ssongliu ee0e17a496 fix: 解决数据库切换版本不正确的问题 (#7111) 2024-11-18 06:16:04 +00:00
吴小白 f1afc0f4d9 build(deps): bump goreleaser from v2 to v2.4.5 (#7110)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-11-18 06:14:03 +00:00
吴小白 ea1afe7610 build(deps): bump golang from 1.22 to 1.23 (#7109) 2024-11-18 11:33:25 +08:00
ssongliu 4ade175a3d fix: 统一 xpack 失联接口返回内容 (#7106) 2024-11-18 02:30:03 +00:00
igophper c6ea06153a fix: 解决编辑端口规则不能保存的问题 (#7100) 2024-11-17 22:52:02 +08:00
ssongliu eb36a0f8cb fix: 优化许可证错误信息 (#7094) 2024-11-16 13:00:02 +00:00
ssongliu 3087c1a6e8 fix: 完善 license 同步失联逻辑 (#7090) 2024-11-15 15:22:01 +00:00
蘭 e5b0a1c591 feat: Echarts 组件适配主题颜色 (#7087) 2024-11-15 20:14:34 +08:00
igophper af41e8e429 feat: 增加DNS服务商CloudDNS (#7081)
Refs #7038
2024-11-15 16:46:45 +08:00
蘭 7e677ee9fa fix: 获取告警短信使用量 (#7077) 2024-11-15 08:06:02 +00:00
蘭 eea4cf68ad fix: 修复许可证失效主题切换错误问题 (#7075) 2024-11-15 07:24:01 +00:00
刘瑞斌 3a400dd15a feat: 增加火山引擎DNS (#7006) 2024-11-15 11:27:41 +08:00
maninhill fbbbd52d72 chore: Update README_EN.md 2024-11-15 06:58:19 +08:00
maninhill 27664c7dce chore: Update README.md 2024-11-14 22:56:36 +08:00
蘭 e1bdd215b1 fix: 编辑计划任务和病毒扫描告警问题处理 (#7058) 2024-11-14 21:52:37 +08:00
蘭 d04a35565e feat: 主题样式优化 (#7053) 2024-11-14 09:32:01 +00:00
蘭 7aad2cf4af fix: 客户端缓存导致界面设置 Logo 不能正常显示 (#7048)
Refs #7020
2024-11-14 08:18:02 +00:00
蘭 bfa667584a fix: 编辑计划任务和病毒扫描告警问题处理 (#7047)
Refs #7039
2024-11-14 16:15:47 +08:00
ssongliu 1858cb4df8 fix: 导入恢复限制文件数量 (#7045) 2024-11-14 06:20:01 +00:00
ssongliu dc30a3d1c3 fix: 解决 vue 组件未兼容繁体的问题 (#7044)
Refs #7033
2024-11-14 06:18:02 +00:00
蘭 6e690cc6f2 style: 主题样式优化 (#7037) 2024-11-13 14:16:01 +00:00
蘭 911b4076f5 feat: 主题色保存/恢复增加确认提示 (#7036) 2024-11-13 14:14:01 +00:00
蘭 4c01b1e542 fix: 删除目录校验 (#7035) 2024-11-13 14:12:01 +00:00
蘭 ec372dbb98 fix: 删除目录校验是否为禁止删除的目录 (#7031) 2024-11-13 07:10:00 +00:00
蘭 0b87025c52 feat: 面板目录禁止删除 (#7029)
Refs #7008
2024-11-13 06:52:02 +00:00
蘭 7d4230ecfb fix: 关闭代理同时更新 Docker 代理 (#7028) 2024-11-13 06:50:00 +00:00
ssongliu d06583773a fix: 优化概览页磁盘显示 (#7023) 2024-11-13 06:46:01 +00:00
ssongliu 67c1fbd07a fix: 优化登陆页面特征信息 (#7027)
Refs #7000
2024-11-13 06:44:00 +00:00
蘭 5c7fab8f01 style: 面板样式优化 (#7026) 2024-11-13 06:30:00 +00:00
ssongliu 06554375cb fix: 修改面板证书显示域名信息 (#7019) 2024-11-12 23:04:00 +08:00
蘭 e65329e7b3 perf: 优化 docker 代理 (#7016) 2024-11-12 11:18:00 +00:00
蘭 f5435c581b perf: 优化主题色及样式 (#7015) 2024-11-12 10:32:00 +00:00
ssongliu 5e8b23db6a fix: 快照恢复忽略应用恢复失败问题 (#7012) 2024-11-12 17:34:23 +08:00
蘭 f2849aa6ec perf: 优化主题色及样式 (#7009) 2024-11-12 13:48:49 +08:00
Xiao TanandBH1XAQ ee947b6468 fix: 修复 pure-ftpd 上传缓慢问题 (#6994)
* fix: 修复 pure-ftpd 上传缓慢问题

* fix: 修复 pure-ftpd 修改密码用户名的判断依据

---------

Co-authored-by: BH1XAQ <tanxiao@16iot.cn>
2024-11-12 09:27:21 +08:00
蘭 49c072b40d perf: 优化主题色及样式 (#7002) 2024-11-11 22:46:27 +08:00
蘭 204f9f1fbc feat: 面板代理服务器提供 docker 代理 (#6986) 2024-11-08 18:19:48 +08:00
zhengkunwang cccbaafd1b feat: 修改应用安装超时时间 (#6983) 2024-11-08 06:55:21 +00:00
BugKing 7bc06bb6b1 docs: Update README.md 2024-11-08 09:25:05 +08:00
BugKing c16d5190c6 docs: Update README.md
增加专业版简要说明。
2024-11-08 09:25:05 +08:00
蘭 ffdd9b9d79 feat: 增加自定义主题颜色,调整默认暗色主题配色 (#6964) 2024-11-06 09:39:20 +00:00
ssongliu 8d35c54672 fix: 优化防火墙端口转发错误信息 (#6967) 2024-11-06 08:31:19 +00:00
ssongliu eb0164907c fix: 解决面板证书信息获取失败的问题 (#6962)
Refs #6863
2024-11-06 06:37:19 +00:00
wanghe 478d44885c Update OWNERS (#6963) 2024-11-06 14:33:34 +08:00
dependabot[bot] 541bc456be build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#6959)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 10:22:52 +08:00
liqiang-fit2cloud 5ce75992a3 Update llm-code-review.yml 2024-11-05 14:57:12 +08:00
蘭 c8b3ba7f6c style: 组件样式调整 (#6943) 2024-11-04 10:50:53 +00:00
liqiang-fit2cloud d92ce86bb8 build: add code review action. 2024-11-04 17:59:28 +08:00
ssongliu f7b4c8cd28 fix: 优化容器创建提示信息 (#6925) 2024-11-01 14:27:10 +00:00
YaKun9 24c063b106 feat: 概览-状态,磁盘数量多过时默认收起 (#6918)
Refs https://github.com/1Panel-dev/1Panel/issues/6917
feat: 概览-状态,磁盘数量多过时默认收起
2024-11-01 14:25:10 +00:00
igophper e478d2e624 fix: 解决清空网站日志时的闪烁问题 (#6905)
Refs #6904
2024-10-31 10:19:36 +08:00
ssongliu dc0f726029 fix: 解决操作日志打印错误的问题 (#6899) 2024-10-30 17:02:19 +08:00
ssongliu 784b8da7af fix: 解决远程数据库无法切换的问题 (#6893)
Refs #6883
2024-10-30 08:14:41 +00:00
ssongliu 79e9dc9914 fix: 解决全屏切换直接关闭后菜单不显示的问题 (#6894)
Refs #6889
2024-10-30 08:12:39 +00:00
wanghe 4849c6b42d Update README.md 2024-10-30 13:51:35 +08:00
wanghe 024d142c8d Update README.md 2024-10-28 15:58:12 +08:00
ssongliu 126aea931c fix: 解决容器编排路径为空时无法删除的问题 (#6862) 2024-10-27 15:18:02 +00:00
ssongliu 2372281d5e fix: 解决容器镜像仓库编辑失败的问题 (#6845)
Refs #6839
2024-10-25 14:11:55 +00:00
ssongliu 376d6609f2 fix: 补全进程守护 watch 引用 (#6836) 2024-10-24 15:00:18 +08:00
ssongliu 96ab4c53af fix: 解决计划任务告警误报的问题 (#6825) 2024-10-23 14:08:19 +00:00
ssongliu 96cf46d834 fix: 解决低版本 dockerd 命令不兼容的问题 (#6824) 2024-10-23 10:34:19 +00:00
ssongliu 208414704d style: 容器样式调整 (#6822)
Refs #6819
2024-10-23 09:32:19 +00:00
zhengkunwang 7f453aa145 feat: 启动运行环境增加默认网络检测 (#6820) 2024-10-23 09:08:20 +00:00
蘭 123997fa13 feat: 面板重启导致计划任务失败时进行短信告警 (#6817) 2024-10-23 03:46:19 +00:00
zhengkunwang 7faef2f8a7 feat: 创建运行环境网站增加 Python 选项 (#6815) 2024-10-23 02:52:16 +00:00
ssongliu f93cf3b996 fix: 解决快照列表排序的问题 (#6816)
Refs #6810
2024-10-23 02:46:17 +00:00
zhengkunwang df5b69756c feat: 创建运行环境网站增加默认网络创建 (#6807) 2024-10-22 11:24:34 +00:00
蘭 745bb98abe fix: 完善短信告警国际化 (#6806) 2024-10-22 11:20:32 +00:00
ssongliu cf1d02771f fix: 解决 Docker 重启失败的问题 (#6805) 2024-10-22 09:42:28 +00:00
zhengkunwang 52377b0042 fix: 解决 Python 运行环境编辑报错的问题 (#6801) 2024-10-22 08:10:28 +00:00
蘭 270501a237 fix: 文件夹选择控件,选择文件时不默认选择当前目录 (#6799)
Refs #6012
2024-10-22 07:40:29 +00:00
zhengkunwang 2e5283b8d3 fix: 解决 Mysql 特定版本忽略升级失效的问题 (#6800)
Refs https://github.com/1Panel-dev/1Panel/issues/6796
2024-10-22 07:34:29 +00:00
ssongliu 8cad47b29e feat: 修改容器操作超时时间 (#6798)
Refs #6747
2024-10-22 07:20:28 +00:00
Ludovic Fernandez 2458d60d80 fix: otel dependencies (#6793) 2024-10-22 07:39:07 +08:00
ssongliu 9a79dc4cdd feat: SSL 配置增加重启选项 (#6792) 2024-10-21 22:47:14 +08:00
Ludovic Fernandez e531b509d6 fix: huawei cloud (#6791) 2024-10-21 22:15:32 +08:00
蘭 d287fc1186 fix: 解析病毒扫描内容格式处理 (#6790) 2024-10-21 14:06:20 +00:00
zhengkunwang f47f7c7361 fix: 解决创建运行环境报名称存在的问题 (#6785) 2024-10-21 08:50:22 +00:00
zhengkunwang 7a6c99d57d fix: 解决证书自动续签日志报错的问题 (#6784) 2024-10-21 07:10:20 +00:00
zhengkunwang 5105114321 fix: 解决网站开启 HTTPS 之后 404 配置丢失的问题 (#6775) 2024-10-18 09:17:38 +00:00
zhengkunwang 60beebcb4c style: 日志样式调整 (#6774) 2024-10-18 08:55:38 +00:00
ssongliu fa03b4226a fix: 解决容器编排跳转路径错误的问题 (#6773) 2024-10-18 08:43:37 +00:00
蘭 b047a053c5 fix: 解决移动端主机文件批量删除错误 (#6765) 2024-10-18 03:29:36 +00:00
ssongliu 42907cd228 feat: postgresql 远程数据库支持 17.x 版本 (#6759) 2024-10-17 13:37:32 +00:00
蘭 425892cfc4 refactor: 调整包引用 (#6758) 2024-10-17 13:35:32 +00:00
ssongliu 16fd804e93 feat: 优化数据库切换效果 (#6757)
Refs #6755
2024-10-17 10:05:32 +00:00
John Bro 2f147dde13 fix: 容器部分操作动态修改执行命令 (#6750)
Refs #3242
2024-10-17 10:03:31 +00:00
蘭 0d04f07f2a fix: Switch 组件 small 尺寸样式修复 (#6752) 2024-10-17 07:31:30 +00:00
蘭 96485218b7 feat: 病毒扫描定时扫描和短信告警合并提示许可证绑定 (#6751) 2024-10-17 07:29:31 +00:00
蘭 227cc57426 feat: 计划任务失败告警支持全类型告警 (#6749) 2024-10-17 07:27:31 +00:00
zhengkunwang b99061777a feat: PHP 镜像 build 增加超时时间 (#6744) 2024-10-17 04:13:30 +00:00
zhengkunwang 8749dc649a style: 优化一些样式 (#6740) 2024-10-16 09:37:27 +00:00
117 changed files with 3177 additions and 985 deletions
+50
View File
@@ -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)."
@@ -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: "如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。"
+4 -4
View File
@@ -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: 提出针对本项目安装部署、使用及其他方面的相关问题。
+28
View File
@@ -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"
+4 -4
View File
@@ -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
View File
@@ -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:"
-3
View File
@@ -1,3 +0,0 @@
{
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3"
}
+2 -4
View File
@@ -1,11 +1,9 @@
reviewers:
- wanghe-fit2cloud
- zhengkunwang223
- ssongliu
- wanghe-fit2cloud
- wangdan-fit2cloud
approvers:
- wanghe-fit2cloud
- zhengkunwang223
- ssongliu
- wanghe-fit2cloud
- wangdan-fit2cloud
+8 -3
View File
@@ -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 大语言模型的开源知识库问答系统
+5 -3
View File
@@ -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)
+20
View File
@@ -0,0 +1,20 @@
package dto
type CreateOrUpdateAlert struct {
AlertTitle string `json:"alertTitle"`
AlertType string `json:"alertType"`
AlertCount uint `json:"alertCount"`
EntryID uint `json:"entryID"`
}
type AlertBase struct {
AlertType string `json:"alertType"`
EntryID uint `json:"entryID"`
}
type PushAlert struct {
TaskName string `json:"taskName"`
AlertType string `json:"alertType"`
EntryID uint `json:"entryID"`
Param string `json:"param"`
}
+3
View File
@@ -33,6 +33,7 @@ type ClamInfo struct {
LastHandleDate string `json:"lastHandleDate"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
}
type ClamLogSearch struct {
@@ -71,6 +72,8 @@ type ClamCreate struct {
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type ClamUpdate struct {
+6
View File
@@ -31,10 +31,13 @@ type CronjobCreate struct {
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdate struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
Name string `json:"name" validate:"required"`
Spec string `json:"spec" validate:"required"`
@@ -53,6 +56,8 @@ type CronjobUpdate struct {
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdateStatus struct {
@@ -99,6 +104,7 @@ type CronjobInfo struct {
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
}
type SearchRecord struct {
+24 -8
View File
@@ -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
}
}
+67
View File
@@ -8,6 +8,7 @@ import (
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@@ -154,6 +155,16 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
}
datas[i].LastHandleDate = t1.Format(constant.DateTimeLayout)
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: datas[i].ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
datas[i].AlertCount = alertCount
} else {
datas[i].AlertCount = 0
}
}
return total, datas, err
}
@@ -180,6 +191,19 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
if err := clamRepo.Create(&clam); err != nil {
return err
}
if req.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertCount: req.AlertCount,
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.CreateAlert(createAlert)
if err != nil {
return err
}
}
return nil
}
@@ -225,6 +249,16 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
if err := clamRepo.Update(req.ID, upMap); err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: "clams",
AlertCount: req.AlertCount,
EntryID: clam.ID,
}
err := xpack.UpdateAlert(updateAlert)
if err != nil {
return err
}
return nil
}
@@ -265,6 +299,14 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
if err := clamRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.DeleteAlert(alertBase)
if err != nil {
return err
}
}
return nil
}
@@ -305,6 +347,7 @@ func (c *ClamService) HandleOnce(req dto.OperateByID) error {
}
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
stdout, err := cmd.Execf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
handleAlert(stdout, clam.Name, clam.ID)
if err != nil {
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
}
@@ -585,3 +628,27 @@ func (c *ClamService) loadLogPath(name string) string {
return ""
}
func handleAlert(stdout, clamName string, clamId uint) {
if strings.Contains(stdout, "- SCAN SUMMARY -") {
lines := strings.Split(stdout, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Infected files: ") {
infectedFiles, _ := strconv.Atoi(strings.TrimPrefix(line, "Infected files: "))
if infectedFiles > 0 {
pushAlert := dto.PushAlert{
TaskName: clamName,
AlertType: "clams",
EntryID: clamId,
Param: strconv.Itoa(infectedFiles),
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("clamdscan push failed, err: %v", err)
}
break
}
}
}
}
}
+47 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
@@ -53,6 +54,16 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
} else {
item.LastRecordTime = "-"
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
} else {
item.AlertCount = 0
}
dtoCronjobs = append(dtoCronjobs, item)
}
return total, dtoCronjobs, err
@@ -190,6 +201,18 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
if err := cronjobRepo.Create(&cronjob); err != nil {
return err
}
if cronjobDto.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: cronjobDto.AlertTitle,
AlertCount: cronjobDto.AlertCount,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.CreateAlert(createAlert)
if err != nil {
return err
}
}
return nil
}
@@ -232,6 +255,14 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.DeleteAlert(alertBase)
if err != nil {
return err
}
}
return nil
@@ -281,7 +312,21 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["default_download"] = req.DefaultDownload
upMap["retain_copies"] = req.RetainCopies
upMap["secret"] = req.Secret
return cronjobRepo.Update(id, upMap)
err = cronjobRepo.Update(id, upMap)
if err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: cronModel.Type,
AlertCount: req.AlertCount,
EntryID: cronModel.ID,
}
err = xpack.UpdateAlert(updateAlert)
if err != nil {
return err
}
return nil
}
func (u *CronjobService) UpdateStatus(id uint, status string) error {
@@ -293,6 +338,7 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
entryIDs string
err error
)
if status == constant.StatusEnable {
entryIDs, err = u.StartJob(&cronjob, false)
if err != nil {
+17
View File
@@ -12,6 +12,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -20,6 +21,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/pkg/errors"
)
@@ -90,6 +92,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
if len(message) != 0 {
@@ -391,3 +394,17 @@ func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.
func hasBackup(cronjobType string) bool {
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log"
}
func handleCronJobAlert(cronjob *model.Cronjob) {
pushAlert := dto.PushAlert{
TaskName: cronjob.Name,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
Param: cronjob.Type,
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
return
}
}
+17
View File
@@ -198,6 +198,23 @@ 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)
+6
View File
@@ -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" {
+64 -5
View File
@@ -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()
@@ -306,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
@@ -689,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
}
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -357,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
@@ -383,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)
}
+1 -10
View File
@@ -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])
-4
View File
@@ -309,10 +309,6 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
if err != nil {
return err
}
if err = docker.CreateDefaultDockerNetwork(); err != nil {
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
return
}
website.RuntimeID = runtime.ID
switch runtime.Type {
case constant.RuntimePHP:
+6 -1
View File
@@ -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
+2
View File
@@ -10,6 +10,8 @@ const (
StatusEnable = "Enable"
StatusDisable = "Disable"
StatusNone = "None"
StatusOnRetry = "OnRetry"
StatusLost = "Lost"
OrderDesc = "descending"
OrderAsc = "ascending"
+7 -1
View File
@@ -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
+6 -1
View File
@@ -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: "告警資訊格式錯誤,請檢查後重試!"
+7 -1
View File
@@ -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: "告警信息格式错误,请检查后重试!"
+37 -7
View File
@@ -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() {
@@ -178,8 +193,6 @@ func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
}
sudo := cmd.SudoHandleCmd()
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", sudo, global.CONF.System.ChangeUserInfo)
return
}
if strings.Contains(global.CONF.System.ChangeUserInfo, "username") {
@@ -212,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
}
}
+3 -2
View File
@@ -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"))
+1 -1
View File
@@ -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 {
+3 -2
View File
@@ -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"))
+8 -9
View File
@@ -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)
}
+23
View File
@@ -15,6 +15,7 @@ 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"
@@ -23,6 +24,7 @@ import (
"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
+122 -17
View File
@@ -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
}
+21
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"time"
"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"
@@ -39,3 +40,23 @@ func LoadXpuInfo() []interface{} {
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
return 0, buserr.New(constant.ErrXpackNotFound)
}
func CreateAlert(createAlert dto.CreateOrUpdateAlert) error {
return nil
}
func UpdateAlert(updateAlert dto.CreateOrUpdateAlert) error {
return nil
}
func DeleteAlert(alertBase dto.AlertBase) error {
return nil
}
func GetAlert(alertBase dto.AlertBase) uint {
return 0
}
func PushAlert(pushAlert dto.PushAlert) error {
return nil
}
+1 -1
View File
@@ -2,7 +2,7 @@ system:
db_file: 1Panel.db
base_dir: /opt
mode: dev
repo_url: https://resource.fit2cloud.com/1panel/package/aliyun
repo_url: https://resource.fit2cloud.com/1panel/package
app_repo: https://apps-assets.fit2cloud.com
is_demo: false
port: 9999
+13 -37
View File
@@ -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
![UI Display](https://resource.fit2cloud.com/1panel/img/overview_en.png)
- **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
![UI Display](https://resource.fit2cloud.com/1panel/img/overview_en.png)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](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
+2
View File
@@ -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;
});
+2 -1
View File
@@ -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' });
+3
View File
@@ -30,6 +30,9 @@ export namespace Cronjob {
retainCopies: number;
status: string;
secret: string;
hasAlert: boolean;
alertCount: number;
alertTitle: string;
}
export interface CronjobCreate {
name: string;
+2
View File
@@ -178,6 +178,8 @@ export namespace Setting {
offline: boolean;
status: string;
message: string;
smsUsed: number;
smsTotal: number;
}
export interface LicenseStatus {
productPro: string;
+3
View File
@@ -139,6 +139,9 @@ export namespace Toolbox {
spec: string;
specObj: Cronjob.SpecObj;
description: string;
hasAlert: boolean;
alertCount: number;
alertTitle: string;
}
export interface ClamCreate {
name: string;
+1 -1
View File
@@ -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) => {
+1
View File
@@ -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

+18 -8
View File
@@ -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" />
@@ -158,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);
@@ -205,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(() => {
@@ -233,10 +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>
@@ -0,0 +1,121 @@
<template>
<div>
<el-dialog class="level-up-pro" v-model="open" :close-on-click-modal="false" @close="handleClose">
<div style="text-align: center" v-loading="loading">
<span class="text-3xl font-medium title">{{ $t('license.levelUpPro') }}</span>
<el-row type="flex" justify="center" class="mt-6">
<el-col :span="22">
<el-upload
action="#"
:auto-upload="false"
ref="uploadRef"
class="upload-demo"
drag
:limit="1"
:on-change="fileOnChange"
:on-exceed="handleExceed"
v-model:file-list="uploaderFiles"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
{{ $t('license.importHelper') }}
</div>
</el-upload>
</el-col>
</el-row>
<el-button
type="primary"
class="mt-3 w-52"
:disabled="loading || uploaderFiles.length == 0"
plain
@click="submit"
>
{{ $t('license.power') }}
</el-button>
<div class="mt-3 mb-5">
<el-button text type="primary" @click="toHalo">{{ $t('license.knowMorePro') }}</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import i18n from '@/lang';
import { ref } from 'vue';
import { MsgSuccess } from '@/utils/message';
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, initFavicon } from '@/utils/xpack';
const globalStore = GlobalStore();
const { switchTheme } = useTheme();
const loading = ref(false);
const open = ref(false);
const uploadRef = ref<UploadInstance>();
const uploaderFiles = ref<UploadFiles>([]);
const handleClose = () => {
open.value = false;
uploadRef.value!.clearFiles();
};
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
uploaderFiles.value = uploadFiles;
};
const handleExceed: UploadProps['onExceed'] = (files) => {
uploadRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadRef.value!.handleStart(file);
};
const toHalo = () => {
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
};
const submit = async () => {
if (uploaderFiles.value.length !== 1) {
return;
}
const file = uploaderFiles.value[0];
const formData = new FormData();
formData.append('file', file.raw);
loading.value = true;
await UploadFileData(formData)
.then(async () => {
globalStore.isProductPro = true;
const xpackRes = await getXpackSetting();
if (xpackRes) {
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;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
window.location.reload();
})
.catch(() => {
loading.value = false;
uploadRef.value!.clearFiles();
uploaderFiles.value = [];
});
};
const acceptParams = () => {
uploaderFiles.value = [];
uploadRef.value?.clearFiles();
open.value = true;
};
defineExpose({
acceptParams,
});
</script>
+1 -1
View File
@@ -290,7 +290,7 @@ defineExpose({ changeTail, onDownload, clearLog });
overflow-y: auto;
overflow-x: auto;
position: relative;
background-color: #1e1e1e;
background-color: var(--panel-logs-bg-color);
margin-top: 10px;
}
@@ -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,35 +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">
<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>
@@ -49,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>
@@ -121,6 +121,10 @@ const handleClose = () => {
drawerVisible.value = false;
};
const toHalo = () => {
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
};
const toDoc = () => {
window.open('https://1panel.cn/docs/', '_blank', 'noopener,noreferrer');
};
@@ -192,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;
@@ -212,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>
+5 -1
View File
@@ -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>
+20 -5
View File
@@ -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,
+8
View File
@@ -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',
+1 -1
View File
@@ -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);
};
+19 -12
View File
@@ -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 {
+56 -2
View File
@@ -11,6 +11,7 @@ const message = {
true: 'true',
false: 'false',
example: 'e.g.:',
fit2cloud: 'FIT2CLOUD',
button: {
create: 'Create ',
add: 'Add ',
@@ -1101,6 +1102,7 @@ const message = {
clam: {
clam: 'Virus Scan',
cron: 'Scheduled scan',
cronHelper: 'Professional version supports scheduled scan feature',
specErr: 'Execution schedule format error, please check and retry!',
disableMsg:
'Stopping scheduled execution will prevent this scan task from running automatically. Do you want to continue?',
@@ -1139,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: {
@@ -1296,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',
@@ -1391,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',
@@ -1664,6 +1674,49 @@ const message = {
compressPassword: 'Compression Password',
backupRecoverMessage: 'Please enter the compression or decompression password (leave blank to not set)',
},
license: {
community: 'Community Edition: ',
community2: 'Community Edition',
pro: 'Professional Edition: ',
trial: 'Trial Edition',
office: 'Official Edition',
trialInfo: 'Version',
authorizationId: 'Subscription Authorization ID',
authorizedUser: 'Authorized User',
expiresAt: 'Expiry Date',
productName: 'Product Name',
productStatus: 'Product Status',
Lost: 'Lost',
Enable: 'Enabled',
Disable: 'Disabled',
lostHelper:
'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',
unbind: 'Unbind License',
unbindHelper: 'All Pro related Settings will be cleared after unbinding. Do you want to continue? ',
importLicense: 'Import License',
importHelper: 'Please click or drag the license file here',
technicalAdvice: 'Technical Advice',
advice: 'Consultation',
indefinitePeriod: 'Indefinite Period',
levelUpPro: 'Upgrade to Professional Edition',
licenseSync: 'License Sync',
knowMorePro: 'Learn More',
closeAlert: 'The current page can be closed in the panel settings',
introduce: 'Feature Introduction',
waf: 'Upgrading to the professional version can provide features such as interception map, logs, block records, geographical location blocking, custom rules, custom interception pages, etc.',
tamper: 'Upgrading to the professional version can protect websites from unauthorized modifications or tampering.',
gpu: 'Upgrading to the professional version can help users visually monitor important parameters of GPU such as workload, temperature, memory usage in real time.',
setting:
'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.',
monitor:
'Upgrade to the professional version to view the real-time status of the website, visitor trends, visitor sources, request logs and other information. ',
alert: 'Upgrade to the professional version to receive alarm information via SMS and view alarm logs, fully control various key events, and ensure worry-free system operation',
},
clean: {
scan: 'Start Scanning',
scanHelper: 'Easily tidy up junk files accumulated during 1Panel runtime',
@@ -1885,6 +1938,7 @@ const message = {
SSLList: 'Certificate List',
createDnsAccount: 'DNS Account',
aliyun: 'Aliyun',
volcengine: 'Volcengine',
manual: 'Manual parsing',
key: 'Key',
check: 'View',
+55 -6
View File
@@ -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: '添加',
@@ -1038,6 +1039,7 @@ const message = {
clam: {
clam: '病毒掃描',
cron: '定時掃描',
cronHelper: '專業版支持定時掃描功能',
specErr: '執行周期格式錯誤,請檢查後重試!',
disableMsg: '停止定時執行會導致該掃描任務不再自動執行。是否繼續?',
enableMsg: '啟用定時執行會讓該掃描任務定期自動執行。是否繼續?',
@@ -1073,8 +1075,8 @@ const message = {
clamLog: '掃描日誌',
freshClam: '病毒庫刷新配置',
freshClamLog: '病毒庫刷新日誌',
alertHelper: '專業版支持短信告警功能',
alertTitle: '病毒掃描「{0}」任務失敗告警',
alertHelper: '專業版支持定時掃描和短信告警功能',
alertTitle: '病毒掃描「{0}」任務检测到感染文件告警',
},
},
logs: {
@@ -1226,6 +1228,7 @@ const message = {
noNameFile: '未命名檔案',
minimap: '縮略圖',
fileCanNotRead: '此文件不支持預覽',
panelInstallDir: '1Panel 安裝目錄不能删除',
},
ssh: {
autoStart: '開機自啟',
@@ -1311,13 +1314,19 @@ const message = {
systemIP: '伺服器地址',
proxy: '代理伺服器',
proxyHelper: '設置代理伺服器後,將在以下場景中生效:',
proxyHelper1: '應用商店的安裝包下載和同步',
proxyHelper2: '系統版本升級及獲取更新說明',
proxyHelper1: '應用商店的安裝包下載和同步(專業版功能)',
proxyHelper2: '系統版本升級及獲取更新說明(專業版功能)',
proxyHelper4: 'Docker 的網絡訪問將通過代理伺服器進行(專業版功能)',
proxyHelper3: '系統許可證的驗證和同步',
proxyType: '代理類型',
proxyUrl: '代理地址',
proxyPort: '代理端口',
proxyPasswdKeep: '記住密碼',
proxyDocker: 'Docker 代理',
proxyDockerHelper: '將代理伺服器配寘同步至Docker,支持離線服務器拉取鏡像等操作',
confDockerProxy: '配寘Docker代理',
restartNowHelper: '配寘Docker代理需要重啓Docker服務。',
restartNow: '立即重啓',
systemIPWarning: '當前未設置服務器地址,請先在面板設置中設置!',
systemIPWarning1: '當前服務器地址設置為 {0},無法快速跳轉!',
defaultNetwork: '默認網卡',
@@ -1543,6 +1552,45 @@ const message = {
compressPassword: '壓縮密碼',
backupRecoverMessage: '請輸入壓縮或解壓縮密碼(留空則不設定)',
},
license: {
community: '社區版:',
community2: '社區版',
pro: '專業版:',
trial: '試用版',
office: '正式版',
trialInfo: '版本',
authorizationId: '訂閱授權 ID',
authorizedUser: '被授權方',
expiresAt: '到期時間',
productName: '產品名稱',
productStatus: '產品狀態',
Lost: '已失聯',
Enable: '已啟用',
Disable: '未啟用',
lostHelper: '許可證已達到最大重試次數,請手動點擊同步按鈕,以確保專業版功能正常使用。',
disableHelper: '許可證同步驗證失敗,請手動點擊同步按鈕,以確保專業版功能正常使用。',
quickUpdate: '快速更新',
import: '導入',
power: '授 權',
unbind: '解除綁定',
unbindHelper: '解除綁定後將清除所有專業版相關設置,是否繼續?',
importLicense: '導入許可證',
importHelper: '請點擊或拖動許可文件到此處',
technicalAdvice: '技術諮詢',
advice: '諮詢',
indefinitePeriod: '無限期',
levelUpPro: '升級專業版',
licenseSync: '許可證同步',
knowMorePro: '了解更多',
closeAlert: '當前頁面可在面板設置中關閉顯示',
introduce: '功能介紹',
waf: '升級專業版可以獲得攔截地圖、日誌、封鎖記錄、地理位置封禁、自定義規則、自定義攔截頁面等功能。',
tamper: '升級專業版可以保護網站免受未經授權的修改或篡改。',
gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載、溫度、顯存等重要參數。',
setting: '升級專業版可以自定義面板 Logo、歡迎簡介等信息。',
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ',
alert: '陞級專業版可通過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統運行無憂。',
},
clean: {
scan: '開始掃描',
scanHelper: '輕鬆梳理 1Panel 運行期間積累的垃圾文件',
@@ -1747,6 +1795,7 @@ const message = {
SSLList: '證書列表',
createDnsAccount: 'DNS賬戶',
aliyun: '阿裏雲',
volcengine: '火山引擎',
manual: '手動解析',
key: '密鑰',
check: '查看',
@@ -2257,7 +2306,7 @@ const message = {
isAlert: '是否告警',
alertCount: '告警次數',
clamHelper: '掃描到感染檔案時觸發簡訊告警',
cronJobHelper: '定時執行計畫任務失敗時觸發簡訊告警',
cronJobHelper: '定時任務執行失敗時將觸發簡訊告警',
licenseHelper: '專業版支持簡訊告警功能',
alertCountHelper: '每日最大告警次數',
},
+54 -5
View File
@@ -10,6 +10,7 @@ const message = {
true: '是',
false: '否',
example: '例:',
fit2cloud: '飞致云',
button: {
create: '创建',
add: '添加',
@@ -1039,6 +1040,7 @@ const message = {
clam: {
clam: '病毒扫描',
cron: '定时扫描',
cronHelper: '专业版支持定时扫描功能 ',
specErr: '执行周期格式错误,请检查后重试!',
disableMsg: '停止定时执行会导致该扫描任务不再自动执行。是否继续?',
enableMsg: '启用定时执行会让该扫描任务定期自动执行。是否继续?',
@@ -1075,8 +1077,8 @@ const message = {
clamLog: '扫描日志',
freshClam: '病毒库刷新配置',
freshClamLog: '病毒库刷新日志',
alertHelper: '专业版支持短信告警功能',
alertTitle: '病毒扫描「 {0} 」任务失败告警',
alertHelper: '专业版支持定时扫描和短信告警功能',
alertTitle: '病毒扫描「 {0} 」任务检测到感染文件告警',
},
},
logs: {
@@ -1228,6 +1230,7 @@ const message = {
noNameFile: '未命名文件',
minimap: '缩略图',
fileCanNotRead: '此文件不支持预览',
panelInstallDir: '1Panel 安装目录不能删除',
},
ssh: {
autoStart: '开机自启',
@@ -1313,13 +1316,19 @@ const message = {
systemIP: '服务器地址',
proxy: '代理服务器',
proxyHelper: '设置代理服务器后,将在以下场景中生效:',
proxyHelper1: '应用商店的安装包下载和同步',
proxyHelper2: '系统版本升级及获取更新说明',
proxyHelper1: '应用商店的安装包下载和同步(专业版功能)',
proxyHelper2: '系统版本升级及获取更新说明(专业版功能)',
proxyHelper3: '系统许可证的验证和同步',
proxyHelper4: 'Docker 的网络访问将通过代理服务器进行(专业版功能)',
proxyType: '代理类型',
proxyUrl: '代理地址',
proxyPort: '代理端口',
proxyPasswdKeep: '记住密码',
proxyDocker: 'Docker 代理',
proxyDockerHelper: '将代理服务器配置同步至 Docker,支持离线服务器拉取镜像等操作',
confDockerProxy: '配置 Docker 代理',
restartNowHelper: '配置 Docker 代理需要重启 Docker 服务。',
restartNow: '立即重启',
systemIPWarning: '当前未设置服务器地址,请先在面板设置中设置!',
systemIPWarning1: '当前服务器地址设置为 {0},无法快速跳转!',
defaultNetwork: '默认网卡',
@@ -1544,6 +1553,45 @@ const message = {
compressPassword: '压缩密码',
backupRecoverMessage: '请输入压缩或解压缩密码(留空则不设置)',
},
license: {
community: '社区版:',
community2: '社区版',
pro: '专业版:',
trial: '试用版',
office: '正式版',
trialInfo: '版本',
authorizationId: '订阅授权 ID',
authorizedUser: '被授权方',
expiresAt: '到期时间',
productName: '产品名称',
productStatus: '产品状态',
Lost: '已失联',
Enable: '已激活',
Disable: '未激活',
lostHelper: '许可证已达到最大重试次数,请手动点击同步按钮,以确保专业版功能正常使用。',
disableHelper: '许可证同步验证失败,请手动点击同步按钮,以确保专业版功能正常使用。',
quickUpdate: '快速更新',
import: '导入',
power: '授 权',
unbind: '解除绑定',
unbindHelper: '解除绑定后将清除所有专业版相关设置,是否继续?',
importLicense: '导入许可证',
importHelper: '请点击或拖动许可文件到此处',
technicalAdvice: '技术咨询',
advice: '咨询',
indefinitePeriod: '无限期',
levelUpPro: '升级专业版',
licenseSync: '许可证同步',
knowMorePro: '了解更多',
closeAlert: '当前页面可在面板设置中关闭显示',
introduce: '功能介绍',
waf: '升级专业版可以获得拦截地图、日志、封锁记录、地理位置封禁、自定义规则、自定义拦截页面等功能。',
tamper: '升级专业版可以保护网站免受未经授权的修改或篡改。',
gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载、温度、显存等重要参数。',
setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。',
monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。',
alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。',
},
clean: {
scan: '开始扫描',
scanHelper: '轻松梳理 1Panel 运行期间积累的垃圾文件',
@@ -1748,6 +1796,7 @@ const message = {
SSLList: '证书列表',
createDnsAccount: 'DNS账户',
aliyun: '阿里云',
volcengine: '火山引擎',
manual: '手动解析',
key: '密钥',
check: '查看',
@@ -2259,7 +2308,7 @@ const message = {
isAlert: '是否告警',
alertCount: '告警次数',
clamHelper: '扫描到感染文件时触发短信告警',
cronJobHelper: '定时执行计划任务失败时触发短信告警',
cronJobHelper: '定时任务执行失败时将触发短信告警',
licenseHelper: '专业版支持短信告警功能',
alertCountHelper: '每日最大告警次数',
},
+3 -3
View File
@@ -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;
+1 -1
View File
@@ -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 {
+10
View File
@@ -37,6 +37,16 @@ const settingRouter = {
activeMenu: 'Setting',
},
},
{
path: 'license',
name: 'License',
component: () => import('@/views/setting/license/index.vue'),
hidden: true,
meta: {
requiresAuth: true,
activeMenu: 'Setting',
},
},
{
path: 'about',
name: 'About',
+1 -1
View File
@@ -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 {
+3 -5
View File
@@ -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) {
+23 -3
View File
@@ -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;
}
+375 -293
View File
@@ -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);
}
}
}
+35 -9
View File
@@ -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 {
+34 -3
View File
@@ -20,6 +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;
}
+8
View File
@@ -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);
}
}
-59
View File
@@ -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]);
}
+26
View File
@@ -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);
};
+22 -9
View File
@@ -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();
}
+14 -5
View File
@@ -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>
+1 -1
View File
@@ -3,7 +3,7 @@
<div v-if="showButton">
<RouterButton :buttons="buttons">
<template #route-button>
<div class="pr-5 mt-1">
<div class="pr-5">
<el-badge is-dot :hidden="!canUpdate">
<el-button @click="sync" type="primary" plain :disabled="syncing">
{{ $t('app.syncAppList') }}
@@ -1,5 +1,5 @@
<template>
<el-dialog v-model="open" :title="$t('app.checkTitle')" width="50%" :close-on-click-modal="false">
<el-dialog v-model="open" :title="$t('app.checkTitle')" width="40%" :close-on-click-modal="false">
<el-row>
<el-col :span="20" :offset="2" v-if="open">
<el-alert
@@ -9,11 +9,18 @@
show-icon
:closable="false"
/>
<br />
<el-descriptions border :column="1">
<el-descriptions-item v-for="(item, key) in map" :key="key">
<el-descriptions :column="1" border class="mt-5">
<el-descriptions-item
v-for="(item, key) in map"
:key="key"
label-class-name="check-label"
class-name="check-content"
min-width="60px"
>
<template #label>
<a href="javascript:void(0);" @click="toPage(item[0])">{{ $t('app.' + item[0]) }}</a>
<a href="javascript:void(0);" class="check-label-a" @click="toPage(item[0])">
{{ $t('app.' + item[0]) }}
</a>
</template>
<span class="resources">
{{ map.get(item[0]).toString() }}
@@ -204,7 +204,7 @@
</div>
</template>
</el-table-column>
<el-table-column :label="$t('container.related')" min-width="200" prop="appName">
<el-table-column :label="$t('container.related')" min-width="210" prop="appName">
<template #default="{ row }">
<div>
<el-tooltip
@@ -483,7 +483,11 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
dialogVisible.value = true;
if (dialogData.value.title === 'create') {
submit();
} else {
dialogVisible.value = true;
}
});
};
@@ -320,6 +320,31 @@
</el-form-item>
</div>
<el-form-item prop="hasAlert">
<el-checkbox v-model="dialogData.rowData!.hasAlert" :label="$t('alert.isAlert')" />
<span class="input-help">{{ $t('alert.cronJobHelper') }}</span>
</el-form-item>
<el-form-item
prop="alertCount"
v-if="dialogData.rowData!.hasAlert && isProductPro"
:label="$t('alert.alertCount')"
>
<el-input-number
style="width: 200px"
:min="1"
step-strictly
:step="1"
v-model.number="dialogData.rowData!.alertCount"
></el-input-number>
<span class="input-help">{{ $t('alert.alertCountHelper') }}</span>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.hasAlert && !isProductPro">
<span>{{ $t('alert.licenseHelper') }}</span>
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</el-form-item>
<el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies">
<el-input-number
style="width: 200px"
@@ -361,6 +386,7 @@
</el-button>
</span>
</template>
<LicenseImport ref="licenseRef" />
</el-drawer>
</template>
@@ -382,6 +408,9 @@ import { listContainer } from '@/api/modules/container';
import { Database } from '@/api/interface/database';
import { ListAppInstalled } from '@/api/modules/app';
import { loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
import { storeToRefs } from 'pinia';
import { GlobalStore } from '@/store';
import LicenseImport from '@/components/license-import/index.vue';
const router = useRouter();
@@ -397,6 +426,10 @@ const dialogData = ref<DialogProps>({
title: '',
});
const globalStore = GlobalStore();
const licenseRef = ref();
const { isProductPro } = storeToRefs(globalStore);
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
if (dialogData.value.rowData?.spec) {
@@ -423,6 +456,8 @@ const acceptParams = (params: DialogProps): void => {
if (dialogData.value.rowData.dbName) {
dialogData.value.rowData.dbNameList = dialogData.value.rowData.dbName.split(',');
}
dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
dialogData.value.rowData!.command = dialogData.value.rowData!.command || 'sh';
dialogData.value.rowData!.isCustom =
dialogData.value.rowData!.command !== 'sh' &&
@@ -560,6 +595,18 @@ const verifySpec = (rule: any, value: any, callback: any) => {
callback();
};
function checkSendCount(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
const regex = /^(?:[1-9]|[12][0-9]|30)$/;
if (!regex.test(value)) {
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 30])));
}
callback();
}
const rules = reactive({
name: [Rules.requiredInput, Rules.noSpace],
type: [Rules.requiredSelect],
@@ -578,6 +625,7 @@ const rules = reactive({
backupAccounts: [Rules.requiredSelect],
defaultDownload: [Rules.requiredSelect],
retainCopies: [Rules.number],
alertCount: [Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
});
type FormInstance = InstanceType<typeof ElForm>;
@@ -756,6 +804,15 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (dialogData.value?.rowData?.exclusionRules) {
dialogData.value.rowData.exclusionRules = dialogData.value.rowData.exclusionRules.replaceAll('\n', ',');
}
dialogData.value.rowData.alertCount =
dialogData.value.rowData!.hasAlert && isProductPro.value ? dialogData.value.rowData.alertCount : 0;
dialogData.value.rowData.alertTitle =
dialogData.value.rowData!.hasAlert && isProductPro.value
? i18n.global.t('cronjob.alertTitle', [
i18n.global.t('cronjob.' + dialogData.value.rowData.type),
dialogData.value.rowData.name,
])
: '';
if (!dialogData.value.rowData) return;
if (dialogData.value.title === 'create') {
await addCronjob(dialogData.value.rowData);
@@ -770,6 +827,10 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
});
};
const toUpload = () => {
licenseRef.value.acceptParams();
};
defineExpose({
acceptParams,
});
@@ -2,7 +2,7 @@
<el-dialog
v-model="open"
:title="$t('app.checkTitle')"
width="50%"
width="40%"
:close-on-click-modal="false"
:destroy-on-close="true"
>
@@ -10,18 +10,19 @@
<el-col :span="20" :offset="2" v-if="open">
<el-alert
type="error"
:description="$t('app.deleteHelper', [$t('app.database')])"
:title="$t('app.deleteHelper', [$t('app.database')])"
center
show-icon
:closable="false"
/>
<br />
<el-descriptions border :column="1">
<el-descriptions-item>
<el-descriptions border :column="1" class="mt-5">
<el-descriptions-item label-class-name="check-label" class-name="check-content" min-width="60px">
<template #label>
<a href="javascript:void(0);" @click="toApp()">{{ $t('app.app') }}</a>
<a href="javascript:void(0);" class="check-label-a" @click="toApp()">
{{ $t('app.app') }}
</a>
</template>
{{ installData.join(',') }}
<pre>{{ installData.join('\n') }}</pre>
</el-descriptions-item>
</el-descriptions>
</el-col>
+4 -5
View File
@@ -251,11 +251,12 @@
:close-on-click-modal="false"
:destroy-on-close="true"
>
<el-alert :closable="false" :title="$t('app.checkInstalledWarn', [dashboardName])" type="info">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', [dashboardName]) }}
<el-link icon="Position" @click="getAppDetail" type="primary">
{{ $t('database.goInstall') }}
</el-link>
</el-alert>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dashboardVisible = false">{{ $t('commons.button.cancel') }}</el-button>
@@ -387,15 +388,13 @@ const onSetting = async () => {
};
const changeDatabase = async () => {
if (currentDB.value.from === 'local') {
appStatusRef.value.onCheck();
}
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
appKey.value = item.type;
appName.value = item.database;
search();
appStatusRef.value?.onCheck(appKey.value, appName.value);
return;
}
}
@@ -2,7 +2,7 @@
<el-dialog
v-model="open"
:title="$t('app.checkTitle')"
width="50%"
width="40%"
:close-on-click-modal="false"
:destroy-on-close="true"
>
@@ -15,13 +15,14 @@
show-icon
:closable="false"
/>
<br />
<el-descriptions border :column="1">
<el-descriptions-item>
<el-descriptions border :column="1" class="mt-5">
<el-descriptions-item label-class-name="check-label" class-name="check-content" min-width="60px">
<template #label>
<a href="javascript:void(0);" @click="toApp()">{{ $t('app.app') }}</a>
<a href="javascript:void(0);" class="check-label-a" @click="toApp()">
{{ $t('app.app') }}
</a>
</template>
{{ installData.join(',') }}
<pre>{{ installData.join('\n') }}</pre>
</el-descriptions-item>
</el-descriptions>
</el-col>
@@ -211,11 +211,12 @@
:close-on-click-modal="false"
:destroy-on-close="true"
>
<el-alert :closable="false" :title="$t('app.checkInstalledWarn', [dashboardName])" type="info">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', [dashboardName]) }}
<el-link icon="Position" @click="getAppDetail" type="primary">
{{ $t('database.goInstall') }}
</el-link>
</el-alert>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dashboardVisible = false">{{ $t('commons.button.cancel') }}</el-button>
@@ -350,15 +351,13 @@ const onSetting = async () => {
};
const changeDatabase = async () => {
if (currentDB.value.from === 'local') {
appStatusRef.value.onCheck();
}
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
appKey.value = item.type;
appName.value = item.database;
search();
appStatusRef.value?.onCheck(appKey.value, appName.value);
return;
}
}
@@ -2,7 +2,7 @@
<el-dialog
v-model="open"
:title="$t('app.checkTitle')"
width="50%"
width="40%"
:close-on-click-modal="false"
:destroy-on-close="true"
>
@@ -15,13 +15,14 @@
show-icon
:closable="false"
/>
<br />
<el-descriptions border :column="1">
<el-descriptions-item>
<el-descriptions border :column="1" class="mt-5">
<el-descriptions-item label-class-name="check-label" class-name="check-content" min-width="60px">
<template #label>
<a href="javascript:void(0);" @click="toApp()">{{ $t('app.app') }}</a>
<a href="javascript:void(0);" class="check-label-a" @click="toApp()">
{{ $t('app.app') }}
</a>
</template>
{{ installData.join(',') }}
<pre>{{ installData.join('\n') }}</pre>
</el-descriptions-item>
</el-descriptions>
</el-col>
+4 -5
View File
@@ -123,11 +123,12 @@
:close-on-click-modal="false"
:destroy-on-close="true"
>
<el-alert :closable="false" :title="$t('app.checkInstalledWarn', ['Redis-Commander'])" type="info">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', ['Redis-Commander']) }}
<el-link icon="Position" @click="getAppDetail('redis-commander')" type="primary">
{{ $t('database.goInstall') }}
</el-link>
</el-alert>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="commandVisible = false">{{ $t('commons.button.cancel') }}</el-button>
@@ -222,14 +223,12 @@ const goRouter = async (target: string) => {
};
const changeDatabase = async () => {
if (currentDB.value.from === 'local') {
appStatusRef.value.onCheck();
}
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
appKey.value = item.type;
appName.value = item.database;
appStatusRef.value?.onCheck(appKey.value, appName.value);
reOpenTerminal();
return;
}
+1 -1
View File
@@ -105,7 +105,7 @@ defineExpose({
.h-app-title {
font-weight: 500;
font-size: 15px;
color: #1f2329;
color: var(--panel-text-color);
}
.h-app-desc {
+25 -6
View File
@@ -7,7 +7,17 @@
path: '/',
},
]"
/>
>
<template #route-button>
<div class="router-button">
<template v-if="!isProductPro">
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</template>
</div>
</template>
</RouterButton>
<el-alert
v-if="!isSafety && globalStore.showEntranceWarn"
@@ -249,6 +259,8 @@
</CardWithHeader>
</el-col>
</el-row>
<LicenseImport ref="licenseRef" />
</div>
</template>
@@ -257,6 +269,7 @@ import { onMounted, onBeforeUnmount, ref, reactive } from 'vue';
import Status from '@/views/home/status/index.vue';
import App from '@/views/home/app/index.vue';
import VCharts from '@/components/v-charts/index.vue';
import LicenseImport from '@/components/license-import/index.vue';
import CardWithHeader from '@/components/card-with-header/index.vue';
import i18n from '@/lang';
import { Dashboard } from '@/api/interface/dashboard';
@@ -277,7 +290,6 @@ const isSafety = ref();
const chartOption = ref('network');
let timer: NodeJS.Timer | null = null;
let isInit = ref<boolean>(true);
let isStatusInit = ref<boolean>(true);
let isActive = ref(true);
const ioReadBytes = ref<Array<number>>([]);
@@ -290,6 +302,9 @@ const timeNetDatas = ref<Array<string>>([]);
const ioOptions = ref();
const netOptions = ref();
const licenseRef = ref();
const isProductPro = ref();
const searchInfo = reactive({
ioOption: 'all',
netOption: 'all',
@@ -413,8 +428,7 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
currentInfo.value.uptime = resData.uptime;
loadAppCurrentInfo();
isStatusInit.value = false;
statusRef.value.acceptParams(currentInfo.value, baseInfo.value, isStatusInit.value);
statusRef.value.acceptParams(currentInfo.value, baseInfo.value);
appRef.value.acceptParams();
if (isInit) {
timer = setInterval(async () => {
@@ -427,7 +441,7 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
const loadAppCurrentInfo = async () => {
await Promise.all([onLoadCurrentInfo('gpu'), onLoadCurrentInfo('basic'), onLoadCurrentInfo('ioNet')]);
statusRef.value.acceptParams(currentInfo.value, baseInfo.value, isStatusInit.value);
statusRef.value.acceptParams(currentInfo.value, baseInfo.value);
};
const onLoadCurrentInfo = async (scope: string) => {
@@ -643,7 +657,12 @@ const onBlur = () => {
isActive.value = false;
};
const toUpload = () => {
licenseRef.value.acceptParams();
};
onMounted(() => {
isProductPro.value = globalStore.isProductPro;
window.addEventListener('focus', onFocus);
window.addEventListener('blur', onBlur);
loadSafeStatus();
@@ -701,7 +720,7 @@ onBeforeUnmount(() => {
.system-label {
font-weight: 400 !important;
font-size: 14px !important;
color: #1f2329;
color: var(--panel-text-color);
}
.system-content {
+38 -39
View File
@@ -112,7 +112,7 @@
<span class="input-help">{{ loadStatus(currentInfo.loadUsagePercent) }}</span>
</el-col>
<template v-for="(item, index) of currentInfo.diskData" :key="index">
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center" v-if="showMore || index < 4">
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center" v-if="isShow('disk', index)">
<el-popover placement="bottom" :width="300" trigger="hover" v-if="chartsOption[`disk${index}`]">
<el-row :gutter="5">
<el-tag style="font-weight: 500">{{ $t('home.baseInfo') }}:</el-tag>
@@ -165,15 +165,7 @@
</el-col>
</template>
<template v-for="(item, index) of currentInfo.gpuData" :key="index">
<el-col
:xs="12"
:sm="12"
:md="6"
:lg="6"
:xl="6"
align="center"
v-if="showMore || index < 4 - currentInfo.diskData.length"
>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center" v-if="isShow('gpu', index)">
<el-popover placement="bottom" :width="250" trigger="hover" v-if="chartsOption[`gpu${index}`]">
<el-row :gutter="5">
<el-tag style="font-weight: 500">{{ $t('home.baseInfo') }}:</el-tag>
@@ -218,16 +210,8 @@
</el-col>
</template>
<template v-for="(item, index) of currentInfo.xpuData" :key="index">
<el-col
:xs="12"
:sm="12"
:md="6"
:lg="6"
:xl="6"
align="center"
v-if="showMore || index < 4 - currentInfo.diskData.length"
>
<el-popover placement="bottom" :width="250" trigger="hover" v-if="chartsOption[`gpu${index}`]">
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center" v-if="isShow('xpu', index)">
<el-popover placement="bottom" :width="250" trigger="hover" v-if="chartsOption[`xpu${index}`]">
<el-row :gutter="5">
<el-tag style="font-weight: 500">{{ $t('home.baseInfo') }}:</el-tag>
</el-row>
@@ -249,10 +233,10 @@
<v-charts
@click="goGPU()"
height="160px"
:id="`gpu${index}`"
:id="`xpu${index}`"
type="pie"
:option="chartsOption[`gpu${index}`]"
v-if="chartsOption[`gpu${index}`]"
:option="chartsOption[`xpu${index}`]"
v-if="chartsOption[`xpu${index}`]"
/>
</template>
</el-popover>
@@ -262,18 +246,12 @@
<span class="input-help" v-else>{{ item.deviceName }}</span>
</el-col>
</template>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center">
<el-button v-if="!showMore" link type="primary" @click="showMore = true" class="buttonClass">
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center" v-if="totalCount > 5">
<el-button v-if="!showMore" link type="primary" @click="changeShowMore(true)" class="buttonClass">
{{ $t('tabs.more') }}
<el-icon><Bottom /></el-icon>
</el-button>
<el-button
v-if="showMore && currentInfo.diskData.length > 5"
type="primary"
link
@click="showMore = false"
class="buttonClass"
>
<el-button v-if="showMore" type="primary" link @click="changeShowMore(false)" class="buttonClass">
{{ $t('tabs.hide') }}
<el-icon><Top /></el-icon>
</el-button>
@@ -287,7 +265,8 @@ import { computeSize } from '@/utils/util';
import router from '@/routers';
import i18n from '@/lang';
import { nextTick, ref } from 'vue';
const showMore = ref(true);
const showMore = ref(false);
const totalCount = ref();
const baseInfo = ref<Dashboard.BaseInfo>({
websiteNumber: 0,
@@ -355,7 +334,7 @@ const cpuShowAll = ref();
const chartsOption = ref({ cpu: null, memory: null, load: null });
const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo, isInit: boolean): void => {
const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo): void => {
currentInfo.value = current;
baseInfo.value = base;
chartsOption.value['cpu'] = {
@@ -389,14 +368,15 @@ const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo,
}
currentInfo.value.xpuData = currentInfo.value.xpuData || [];
for (let i = 0; i < currentInfo.value.xpuData.length; i++) {
chartsOption.value['gpu' + i] = {
title: 'GPU-' + currentInfo.value.xpuData[i].deviceID,
chartsOption.value['xpu' + i] = {
title: 'XPU-' + currentInfo.value.xpuData[i].deviceID,
data: formatNumber(Number(currentInfo.value.xpuData[i].memoryUtil.replaceAll('%', ''))),
};
}
if (currentInfo.value.diskData.length + currentInfo.value.gpuData.length > 5) {
showMore.value = isInit ? false : showMore.value || false;
}
totalCount.value =
currentInfo.value.diskData.length + currentInfo.value.gpuData.length + currentInfo.value.xpuData.length;
showMore.value = localStorage.getItem('dashboard_show') === 'more';
});
};
@@ -413,10 +393,29 @@ function loadStatus(val: number) {
return i18n.global.t('home.runJam');
}
const isShow = (val: string, index: number) => {
let showCount = totalCount.value < 6 ? 5 : 4;
switch (val) {
case 'disk':
return showMore.value || index < showCount;
case 'gpu':
let gpuCount = showCount - currentInfo.value.diskData.length;
return showMore.value || index < gpuCount;
case 'xpu':
let xpuCount = showCount - currentInfo.value.diskData.length - currentInfo.value.gpuData.length;
return showMore.value || index < xpuCount;
}
};
const goGPU = () => {
router.push({ name: 'GPU' });
};
const changeShowMore = (show: boolean) => {
showMore.value = show;
localStorage.setItem('dashboard_show', show ? 'more' : 'hide');
};
const loadWidth = () => {
if (!cpuShowAll.value || currentInfo.value.cpuPercent.length < 32) {
return 310;
@@ -60,6 +60,7 @@ import { File } from '@/api/interface/file';
import { getIcon } from '@/utils/util';
import { DeleteFile, GetRecycleStatus } from '@/api/modules/files';
import { MsgSuccess, MsgWarning } from '@/utils/message';
import { loadBaseDir } from '@/api/modules/setting';
const open = ref(false);
const files = ref();
@@ -85,13 +86,21 @@ const getStatus = async () => {
} catch (error) {}
};
const onConfirm = () => {
const onConfirm = async () => {
const pros = [];
for (const s of files.value) {
if (s['path'].indexOf('.1panel_clash') > -1) {
MsgWarning(i18n.global.t('file.clashDeleteAlert'));
return;
if (s['isDir']) {
if (s['path'].indexOf('.1panel_clash') > -1) {
MsgWarning(i18n.global.t('file.clashDeleteAlert'));
return;
}
const pathRes = await loadBaseDir();
if (s['path'] === pathRes.data) {
MsgWarning(i18n.global.t('file.panelInstallDir'));
return;
}
}
pros.push(DeleteFile({ path: s['path'], isDir: s['isDir'], forceDelete: forceDelete.value }));
}
loading.value = true;
@@ -8,7 +8,7 @@
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
<div class="flex flex-wrap gap-4">
<el-button type="primary" @click="onCreate()">
{{ $t('commons.button.create') }} {{ $t('terminal.quickCommand') }}
{{ $t('commons.button.create') }}{{ $t('terminal.quickCommand') }}
</el-button>
<el-button type="primary" plain @click="onOpenGroupDialog()">
{{ $t('terminal.group') }}
@@ -3,7 +3,7 @@
<el-tabs
type="card"
class="terminal-tabs"
style="background-color: #efefef; margin-top: 20px"
style="background-color: var(--panel-terminal-tag-bg-color); margin-top: 20px"
v-model="terminalValue"
:before-leave="beforeLeave"
@tab-change="quickCmd = ''"
@@ -44,7 +44,10 @@
</span>
</template>
<Terminal
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
:style="{
height: `calc(100vh - ${loadHeight()})`,
'background-color': `var(--panel-logs-bg-color)`,
}"
:ref="'t-' + item.index"
:key="item.Refresh"
></Terminal>
@@ -427,12 +430,10 @@ onMounted(() => {
z-index: calc(var(--el-index-normal) + 1);
}
:deep(.el-tabs__item) {
color: #575758;
padding: 0 0px;
padding: 0;
}
:deep(.el-tabs__item.is-active) {
color: #ebeef5;
background-color: #575758;
background-color: var(--panel-terminal-tag-active-bg-color);
}
}
@@ -241,3 +241,11 @@ onMounted(() => {
search();
});
</script>
<style scoped lang="scss">
.tag-button {
&.no-active {
background: none;
border: none;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More