mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-08-11 07:33:28 +08:00
chore(repo): 完善仓库维护与自动化 (#108)
统一 Node.js 24 LTS、CI、文档和发布工作流配置。 完善仓库维护规范、Dependabot 与贡献文档,清理生成产物并统一既有 Go 代码格式。
This commit is contained in:
25
.editorconfig
Normal file
25
.editorconfig
Normal file
@@ -0,0 +1,25 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{go,mod,sum}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.{js,jsx,ts,tsx,json,yml,yaml,css,scss,html,md,mdx}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.sh]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
15
.gitattributes
vendored
Normal file
15
.gitattributes
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.png binary
|
||||
*.webp binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,14 +1,14 @@
|
||||
---
|
||||
name: "\U0001F41B Bug 报告"
|
||||
about: "报告一个 Bug,帮助我们改进 BackupX"
|
||||
name: "Bug 报告"
|
||||
about: "提交可复现的缺陷报告"
|
||||
title: "[Bug] "
|
||||
labels: ["bug"]
|
||||
assignees: []
|
||||
---
|
||||
|
||||
## 描述
|
||||
## 问题描述
|
||||
|
||||
<!-- 清晰简洁地描述这个 Bug -->
|
||||
<!-- 清晰说明发生了什么,以及它为什么是缺陷。 -->
|
||||
|
||||
## 复现步骤
|
||||
|
||||
@@ -18,26 +18,39 @@ assignees: []
|
||||
|
||||
## 期望行为
|
||||
|
||||
<!-- 描述你期望发生什么 -->
|
||||
<!-- 描述正确结果。 -->
|
||||
|
||||
## 实际行为
|
||||
|
||||
<!-- 描述实际发生了什么 -->
|
||||
<!-- 描述实际结果,包括稳定复现还是偶发。 -->
|
||||
|
||||
## 环境信息
|
||||
|
||||
- **OS**: <!-- 例如 Ubuntu 22.04 -->
|
||||
- **Go 版本**: <!-- 例如 1.21 -->
|
||||
- **Node.js 版本**: <!-- 例如 18.17 -->
|
||||
- **浏览器**: <!-- 例如 Chrome 120 -->
|
||||
- **BackupX 版本**: <!-- 例如 v1.0.0 -->
|
||||
- BackupX 版本或提交:
|
||||
- 部署方式(Docker、裸机、集群):
|
||||
- 操作系统与架构:
|
||||
- 数据库或备份类型:
|
||||
- 浏览器版本(仅界面问题):
|
||||
- 是否使用代理、私有 CA 或堡垒机:
|
||||
|
||||
## 最小配置
|
||||
|
||||
<!-- 仅保留复现所需字段。删除密码、Token、域名、IP 和存储凭据。 -->
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
## 相关日志
|
||||
|
||||
```
|
||||
<!-- 粘贴相关日志输出 -->
|
||||
<!-- 附问题发生前后的最小日志片段,并先删除所有敏感信息。 -->
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## 截图
|
||||
## 补充信息
|
||||
|
||||
<!-- 如有截图请附上 -->
|
||||
<!-- 可附截图、相关 Issue、临时缓解方式或其他上下文。 -->
|
||||
|
||||
请勿提交数据库文件、完整配置、备份数据、API Key、Agent Token、安装命令或未脱敏日志。安全漏洞请使用私密漏洞报告入口。
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 讨论区 / Discussions
|
||||
url: https://github.com/Awuqing/GoogleDriverBackupEveryDay/discussions
|
||||
about: 提问、讨论功能想法或分享使用经验
|
||||
- name: 安全漏洞 / Security vulnerability
|
||||
url: https://github.com/Awuqing/BackupX/security/advisories/new
|
||||
about: 请通过私密渠道报告安全问题,不要创建公开 Issue。
|
||||
|
||||
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,23 +1,36 @@
|
||||
---
|
||||
name: "\U0001F680 功能请求"
|
||||
about: "建议一个新功能或改进"
|
||||
name: "功能请求"
|
||||
about: "建议一个可落地的新功能或改进"
|
||||
title: "[Feature] "
|
||||
labels: ["enhancement"]
|
||||
assignees: []
|
||||
---
|
||||
|
||||
## 需求描述
|
||||
|
||||
<!-- 清晰简洁地描述你希望的功能 -->
|
||||
|
||||
## 使用场景
|
||||
|
||||
<!-- 描述你在什么场景下需要这个功能 -->
|
||||
<!-- 描述谁在什么环境中遇到了什么问题。 -->
|
||||
|
||||
## 建议的解决方案
|
||||
## 期望结果
|
||||
|
||||
<!-- 描述你认为应该如何实现(可选) -->
|
||||
<!-- 说明用户最终需要完成的任务,不要只描述界面元素。 -->
|
||||
|
||||
## 建议方案
|
||||
|
||||
<!-- 可选:描述 API、配置、工作流或界面交互。 -->
|
||||
|
||||
## 替代方案
|
||||
|
||||
<!-- 说明当前绕过方式,以及它为什么不足。 -->
|
||||
|
||||
## 兼容性与风险
|
||||
|
||||
<!-- 是否影响现有配置、数据格式、部署方式、权限或集群节点。 -->
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ]
|
||||
- [ ]
|
||||
|
||||
## 补充信息
|
||||
|
||||
<!-- 任何其他相关信息、截图或上下文 -->
|
||||
<!-- 可附相关文档、日志、截图或同类实现。不要附敏感信息。 -->
|
||||
|
||||
57
.github/PULL_REQUEST_TEMPLATE.md
vendored
57
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,29 +1,48 @@
|
||||
## Pull Request
|
||||
## 变更说明 / Summary
|
||||
|
||||
### 变更类型 / Type of Change
|
||||
<!-- 说明做了什么、为什么需要,以及对用户或运维人员的影响。 -->
|
||||
|
||||
- [ ] 🐛 Bug 修复 (非破坏性变更)
|
||||
- [ ] ✨ 新功能 (非破坏性变更)
|
||||
- [ ] 💥 破坏性变更 (修复或功能导致现有功能变更)
|
||||
- [ ] 📝 文档更新
|
||||
- [ ] ♻️ 代码重构 (不影响功能)
|
||||
- [ ] ⚡ 性能优化
|
||||
## 变更类型 / Type of Change
|
||||
|
||||
### 描述 / Description
|
||||
- [ ] 缺陷修复
|
||||
- [ ] 新功能
|
||||
- [ ] 破坏性变更
|
||||
- [ ] 性能优化
|
||||
- [ ] 重构或工程化
|
||||
- [ ] 文档更新
|
||||
|
||||
<!-- 描述你做了什么更改以及为什么 -->
|
||||
## 相关 Issue / Related Issue
|
||||
|
||||
### 相关 Issue / Related Issue
|
||||
<!-- 例如:Closes #123。没有关联 Issue 时写“无”。 -->
|
||||
|
||||
<!-- 关联的 Issue 编号,如 Fixes #123 -->
|
||||
## 验证 / Validation
|
||||
|
||||
### 测试 / Testing
|
||||
<!-- 只勾选实际执行过的项目,并在下方补充无法执行的原因。 -->
|
||||
|
||||
- [ ] 后端测试通过 (`go test ./...`)
|
||||
- [ ] 前端测试通过 (`npm run test`)
|
||||
- [ ] 前端构建成功 (`npm run build`)
|
||||
- [ ] 已在本地环境手动测试
|
||||
- [ ] 后端格式、静态检查与测试通过
|
||||
- [ ] 前端测试与生产构建通过
|
||||
- [ ] 文档类型检查与中英文生产构建通过
|
||||
- [ ] 配置、迁移或部署兼容性已验证
|
||||
- [ ] 已完成与变更范围相符的手动验证
|
||||
|
||||
### 截图 / Screenshots
|
||||
验证命令与结果:
|
||||
|
||||
<!-- 如有 UI 变更请附上截图 -->
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## 风险与回滚 / Risk and Rollback
|
||||
|
||||
<!-- 说明兼容性风险、数据迁移、配置变化和回滚方法。无风险时写“无”。 -->
|
||||
|
||||
## 界面变更 / UI Changes
|
||||
|
||||
<!-- 有界面变更时附前后截图;无界面变更时写“无”。 -->
|
||||
|
||||
## 提交前检查 / Checklist
|
||||
|
||||
- [ ] 提交信息符合 Conventional Commits
|
||||
- [ ] 未提交密钥、Token、真实配置、数据库或日志
|
||||
- [ ] 新行为已补充测试和中英文文档
|
||||
- [ ] 前端未使用 Emoji、渐变、阴影、独立字体或超过 4px 的圆角
|
||||
- [ ] 没有夹带与本 PR 无关的格式化或重构
|
||||
|
||||
50
.github/SECURITY.md
vendored
50
.github/SECURITY.md
vendored
@@ -1,34 +1,40 @@
|
||||
# 安全漏洞披露政策 / Security Policy
|
||||
# 安全策略 / Security Policy
|
||||
|
||||
## 支持的版本 / Supported Versions
|
||||
## 支持范围 / Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| latest | ✅ |
|
||||
| < latest | ❌ |
|
||||
安全修复面向最新稳定版本和 `main` 分支。旧版本不会单独维护安全补丁;升级前请先阅读对应 Release Notes 和升级恢复文档。
|
||||
|
||||
## 报告安全漏洞 / Reporting a Vulnerability
|
||||
Security fixes target the latest stable release and the `main` branch. Older versions do not receive separate security patches. Review the release notes and upgrade documentation before updating.
|
||||
|
||||
如果您发现了安全漏洞,**请不要通过公开 Issue 报告**。
|
||||
| Version | Status |
|
||||
|---------|--------|
|
||||
| Latest stable release | Supported |
|
||||
| `main` | Development support |
|
||||
| Older releases | Unsupported |
|
||||
|
||||
请发送邮件至项目维护者,包含以下信息:
|
||||
## 报告漏洞 / Reporting a Vulnerability
|
||||
|
||||
1. 漏洞描述
|
||||
2. 复现步骤
|
||||
3. 受影响的版本
|
||||
4. 可能的影响范围
|
||||
请勿通过公开 Issue、Discussion 或 Pull Request 披露安全漏洞。使用 GitHub 的[私密漏洞报告入口](https://github.com/Awuqing/BackupX/security/advisories/new)提交报告。
|
||||
|
||||
我们会在 48 小时内确认收到并开始处理。
|
||||
Do not disclose vulnerabilities in a public Issue, Discussion, or Pull Request. Submit the report through GitHub's [private vulnerability reporting form](https://github.com/Awuqing/BackupX/security/advisories/new).
|
||||
|
||||
---
|
||||
报告应包含:
|
||||
|
||||
If you discover a security vulnerability, **please do NOT open a public issue**.
|
||||
1. 受影响版本或提交;
|
||||
2. 漏洞描述、攻击前提和影响范围;
|
||||
3. 最小复现步骤或验证代码;
|
||||
4. 已知缓解措施;
|
||||
5. 希望使用的署名信息。
|
||||
|
||||
Instead, email the project maintainer with the following details:
|
||||
不要上传真实密钥、Token、备份数据、数据库或包含客户信息的日志。必要时请先脱敏,并使用最小化测试数据。
|
||||
|
||||
1. Description of the vulnerability
|
||||
2. Steps to reproduce
|
||||
3. Affected versions
|
||||
4. Potential impact
|
||||
Do not upload real credentials, tokens, backup data, databases, or logs containing customer information. Redact sensitive values and use minimal test data.
|
||||
|
||||
We will acknowledge receipt within 48 hours and begin working on a fix.
|
||||
## 处理流程 / Response Process
|
||||
|
||||
- 维护者会尽快确认报告并进行初步分级;
|
||||
- 修复期间请保持细节私密,避免影响仍未升级的部署;
|
||||
- 修复发布后会在安全公告或 Release Notes 中说明受影响范围、缓解措施和升级版本;
|
||||
- 披露时间由报告者与维护者协调确定。
|
||||
|
||||
The maintainer will acknowledge and triage the report as soon as practical. Details should remain private until a fix and coordinated disclosure are ready.
|
||||
|
||||
86
.github/dependabot.yml
vendored
Normal file
86
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "03:00"
|
||||
timezone: Asia/Shanghai
|
||||
open-pull-requests-limit: 5
|
||||
commit-message:
|
||||
prefix: chore(deps)
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: gomod
|
||||
directory: /server
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "03:10"
|
||||
timezone: Asia/Shanghai
|
||||
open-pull-requests-limit: 5
|
||||
commit-message:
|
||||
prefix: chore(deps)
|
||||
groups:
|
||||
go-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /web
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "03:20"
|
||||
timezone: Asia/Shanghai
|
||||
open-pull-requests-limit: 5
|
||||
commit-message:
|
||||
prefix: chore(deps)
|
||||
groups:
|
||||
web-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /docs-site
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "03:30"
|
||||
timezone: Asia/Shanghai
|
||||
open-pull-requests-limit: 5
|
||||
commit-message:
|
||||
prefix: chore(deps)
|
||||
groups:
|
||||
docs-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "03:40"
|
||||
timezone: Asia/Shanghai
|
||||
open-pull-requests-limit: 5
|
||||
commit-message:
|
||||
prefix: chore(deps)
|
||||
groups:
|
||||
container-images:
|
||||
patterns:
|
||||
- "*"
|
||||
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
@@ -2,18 +2,24 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# 最小权限:构建/测试仅需读取仓库内容,显式声明以收敛默认的可写令牌。
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
name: Go Build & Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -23,6 +29,23 @@ jobs:
|
||||
go-version: '1.25'
|
||||
cache-dependency-path: server/go.sum
|
||||
|
||||
- name: Verify modules
|
||||
working-directory: server
|
||||
run: go mod verify
|
||||
|
||||
- name: Check formatting
|
||||
working-directory: server
|
||||
run: |
|
||||
unformatted="$(gofmt -l .)"
|
||||
if [ -n "$unformatted" ]; then
|
||||
printf '%s\n' "$unformatted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Vet
|
||||
working-directory: server
|
||||
run: go vet ./...
|
||||
|
||||
- name: Build
|
||||
working-directory: server
|
||||
run: go build ./...
|
||||
@@ -34,13 +57,14 @@ jobs:
|
||||
frontend:
|
||||
name: React Build & Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
@@ -48,14 +72,10 @@ jobs:
|
||||
working-directory: web
|
||||
run: npm ci
|
||||
|
||||
- name: Type Check
|
||||
working-directory: web
|
||||
run: npx tsc --noEmit -p tsconfig.json
|
||||
|
||||
- name: Test
|
||||
working-directory: web
|
||||
run: npm run test
|
||||
|
||||
- name: Build
|
||||
- name: Type Check & Build
|
||||
working-directory: web
|
||||
run: npm run build
|
||||
|
||||
31
.github/workflows/docs.yml
vendored
31
.github/workflows/docs.yml
vendored
@@ -1,9 +1,16 @@
|
||||
name: Deploy Docs
|
||||
|
||||
# 触发条件:
|
||||
# - 推送 main 时,如果 docs-site/ 或站点相关 README 有变化
|
||||
# - PR 修改 docs-site/ 时执行类型检查和构建
|
||||
# - 推送 main 时构建并部署 GitHub Pages
|
||||
# - 手动触发(在 Actions 页面)
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'docs-site/**'
|
||||
- '.github/workflows/docs.yml'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -12,20 +19,20 @@ on:
|
||||
- '.github/workflows/docs.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
# 允许写入 Pages,用于发布到 github.com/Awuqing/BackupX 的 Pages 站点
|
||||
# 默认只读;Pages 写权限仅授予部署任务。
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# 同时只保留一个部署任务
|
||||
# 同一分支只保留最新一次构建,避免旧提交覆盖新结果。
|
||||
concurrency:
|
||||
group: pages-${{ github.ref }}
|
||||
group: docs-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs-site
|
||||
@@ -36,24 +43,34 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: docs-site/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Type check
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Build site
|
||||
run: npm run build
|
||||
|
||||
- name: Upload artifact
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs-site/build
|
||||
|
||||
deploy:
|
||||
name: Deploy Docs
|
||||
if: github.event_name != 'pull_request'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
75
.github/workflows/release.yml
vendored
75
.github/workflows/release.yml
vendored
@@ -27,25 +27,85 @@ on:
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
# 统一版本号:tag 推送取 ref_name,手动触发取 inputs.version
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version || github.ref_name }}
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}-${{ github.event.inputs.version || 'tag' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# ─── Job 0: 校验版本与测试 ───
|
||||
verify:
|
||||
name: Validate Release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Validate release input
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_VERSION: ${{ env.VERSION }}
|
||||
RELEASE_EVENT: ${{ github.event_name }}
|
||||
RELEASE_REF: ${{ github.ref }}
|
||||
run: |
|
||||
if [[ ! "$RELEASE_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then
|
||||
echo "Version must be a SemVer tag such as v1.2.3 or v1.2.3-rc.1"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$RELEASE_EVENT" == "workflow_dispatch" && "$RELEASE_REF" != "refs/heads/main" ]]; then
|
||||
echo "Manual releases must run from the main branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache-dependency-path: server/go.sum
|
||||
|
||||
- name: Verify backend
|
||||
working-directory: server
|
||||
run: |
|
||||
go mod verify
|
||||
unformatted="$(gofmt -l .)"
|
||||
if [ -n "$unformatted" ]; then
|
||||
printf '%s\n' "$unformatted"
|
||||
exit 1
|
||||
fi
|
||||
go vet ./...
|
||||
go test ./...
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Verify frontend
|
||||
working-directory: web
|
||||
run: |
|
||||
npm ci
|
||||
npm run test
|
||||
|
||||
# ─── Job 1: 构建前端 ───
|
||||
build-web:
|
||||
name: Build Frontend
|
||||
needs: verify
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
@@ -67,6 +127,9 @@ jobs:
|
||||
name: Build ${{ matrix.goarch }}
|
||||
needs: build-web
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -98,13 +161,13 @@ jobs:
|
||||
run: |
|
||||
go build \
|
||||
-trimpath \
|
||||
-ldflags "-s -w -X main.version=${{ env.VERSION }}" \
|
||||
-ldflags "-s -w -X main.version=${VERSION}" \
|
||||
-o ../backupx \
|
||||
./cmd/backupx
|
||||
|
||||
- name: Package release
|
||||
run: |
|
||||
ARCHIVE_NAME="backupx-${{ env.VERSION }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
ARCHIVE_NAME="backupx-${VERSION}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
mkdir -p "${ARCHIVE_NAME}"
|
||||
cp backupx "${ARCHIVE_NAME}/"
|
||||
cp -r web/dist "${ARCHIVE_NAME}/web"
|
||||
@@ -130,6 +193,7 @@ jobs:
|
||||
backupx-${{ env.VERSION }}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256
|
||||
backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||
backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256
|
||||
fail_on_unmatched_files: true
|
||||
generate_release_notes: true
|
||||
|
||||
# ─── Job 3: Docker 多架构 → Docker Hub ───
|
||||
@@ -137,6 +201,7 @@ jobs:
|
||||
name: Build & Push Docker
|
||||
needs: build-web
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
36
.gitignore
vendored
36
.gitignore
vendored
@@ -1,4 +1,38 @@
|
||||
# Dependencies
|
||||
web/node_modules/
|
||||
docs-site/node_modules/
|
||||
|
||||
# Build and test artifacts
|
||||
web/dist/
|
||||
docs-site/build/
|
||||
docs-site/.docusaurus/
|
||||
server/bin/
|
||||
.claude/
|
||||
coverage/
|
||||
*.out
|
||||
*.tsbuildinfo
|
||||
web/vite.config.js
|
||||
web/vite.config.d.ts
|
||||
|
||||
# Runtime data and local configuration
|
||||
data/
|
||||
tmp/
|
||||
temp/
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
*.log
|
||||
.env
|
||||
.env.*
|
||||
!.env*.example
|
||||
|
||||
# Editors, operating systems, and local tools
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.claude/
|
||||
.codex/
|
||||
|
||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
24
|
||||
@@ -8,8 +8,8 @@ Thanks for your interest in contributing to BackupX! This guide covers how to se
|
||||
### 依赖 / Prerequisites
|
||||
|
||||
- **Go** 1.25+(见 `server/go.mod`)
|
||||
- **Node.js** 20+(CI 与 Docker 使用 Node 20)
|
||||
- **npm** 9+
|
||||
- **Node.js** 24 LTS(见 `.node-version`,CI 与 Docker 使用同一主版本)
|
||||
- **npm** 11+
|
||||
|
||||
### 快速开始 / Quick Start
|
||||
|
||||
@@ -17,6 +17,8 @@ Thanks for your interest in contributing to BackupX! This guide covers how to se
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Awuqing/BackupX.git && cd BackupX
|
||||
npm --prefix web ci
|
||||
npm --prefix docs-site ci
|
||||
|
||||
# 终端 1 —— 后端(默认 http://localhost:8340)
|
||||
make dev-server
|
||||
@@ -31,6 +33,7 @@ make dev-web
|
||||
make build # 同时构建后端与前端
|
||||
make build-server # 仅后端 → server/bin/backupx
|
||||
make build-web # 仅前端 → web/dist
|
||||
make build-docs # 仅文档站 → docs-site/build
|
||||
make docker # 构建 Docker 镜像
|
||||
make docker-cn # 国内镜像源加速构建
|
||||
```
|
||||
@@ -39,15 +42,17 @@ make docker-cn # 国内镜像源加速构建
|
||||
|
||||
## 测试 / Testing
|
||||
|
||||
提交前请确保测试通过:
|
||||
提交前应执行与 CI 一致的完整验证:
|
||||
|
||||
```bash
|
||||
make test # 后端 + 前端全部测试
|
||||
make verify # 格式、依赖、静态检查、测试与三端构建
|
||||
make test # 仅后端 + 前端测试
|
||||
make test-server # 仅后端:cd server && go test ./...
|
||||
make test-web # 仅前端:cd web && npm run test(vitest)
|
||||
make check-docs # 文档类型检查 + 中英文站点构建
|
||||
```
|
||||
|
||||
新增功能或修复缺陷时,请尽量补充对应测试。
|
||||
新增功能或修复缺陷时,请补充对应测试。文档变更也必须通过严格断链检查,不能只依赖合并后的 Pages 部署结果。
|
||||
|
||||
## 提交信息规范 / Commit Messages
|
||||
|
||||
@@ -82,7 +87,7 @@ docs: 补充 CONTRIBUTING 指南
|
||||
|
||||
1. **Fork** 仓库并从最新的 `main` 切出特性分支;
|
||||
2. **开发**功能或修复,必要时补充测试;
|
||||
3. **自测**:确保 `make test` 通过;
|
||||
3. **自测**:确保 `make verify` 通过;
|
||||
4. **提交**:使用上述 Conventional Commits(中文);
|
||||
5. **推送**并对着 `main` 发起 PR。
|
||||
|
||||
@@ -91,15 +96,17 @@ docs: 补充 CONTRIBUTING 指南
|
||||
- 清晰说明本 PR 做了什么;
|
||||
- 对新功能/修复,补充动机与背景;
|
||||
- 关联相关 Issue(如 `Closes #62`);
|
||||
- 纯文档 PR 可不附测试。
|
||||
- 纯文档 PR 至少应附上 `make check-docs` 的结果。
|
||||
|
||||
> 请保持分支基于较新的 `main`:基线过旧的分支容易产生大范围冲突,难以评审与合入。
|
||||
|
||||
## 编码规范 / Coding Conventions
|
||||
|
||||
- **Go**:所有错误必须处理(禁止 `_ = err`),日志使用项目已有库(`zap`),禁止 `fmt.Println`;提交前执行 `gofmt`。
|
||||
- **前端**:遵循项目 ESLint/Prettier/tsconfig 配置,不擅自引入新的 CSS 框架或 UI 库。
|
||||
- **包管理**:`web/` 使用 npm,请提交对应的 `package-lock.json`。
|
||||
- **Go**:遵循现有 handler → service → repository 分层;避免把连续业务流程拆成大量无复用价值的独立函数。所有错误必须处理,日志使用 `zap`,禁止 `fmt.Println`,提交前执行 `gofmt` 与 `go vet`。
|
||||
- **前端组件**:复用并组合现有组件,不在页面内复制同类交互逻辑,不擅自改变共享组件的基础样式,不引入新的 CSS 框架或 UI 库。
|
||||
- **前端视觉**:不使用 Emoji、渐变和 `box-shadow`;图标统一使用项目 SVG 图标组件;圆角为 0–4px;不引入独立字体,不用加粗字体制造层级,减少 Card,不使用数据左侧装饰竖线。
|
||||
- **包管理**:`web/` 与 `docs-site/` 均使用 npm;依赖变更必须同步提交对应的 `package-lock.json`。
|
||||
- **文档**:英文源文件与 `zh-CN` 翻译应同步维护;新增页面必须加入侧边栏并通过两种语言的生产构建。
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ ARG USE_CHINA_MIRROR=false
|
||||
|
||||
|
||||
# ---- Stage 1: Build frontend ----
|
||||
FROM node:20-alpine AS web-builder
|
||||
FROM node:24-alpine AS web-builder
|
||||
ARG USE_CHINA_MIRROR
|
||||
|
||||
# 国内镜像:npm 使用淘宝源
|
||||
|
||||
29
Makefile
29
Makefile
@@ -1,4 +1,6 @@
|
||||
.PHONY: build dev test clean docker docker-cn
|
||||
.PHONY: build build-server build-web build-docs dev-server dev-web \
|
||||
test test-server test-web check-docs format-check vet-server \
|
||||
verify verify-server verify-web verify-docs clean docker docker-cn
|
||||
|
||||
# 自动获取版本号(从 git tag 或 commit hash)
|
||||
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
|
||||
@@ -12,6 +14,9 @@ build-server:
|
||||
build-web:
|
||||
cd web && npm run build
|
||||
|
||||
build-docs:
|
||||
cd docs-site && npm run build
|
||||
|
||||
# ── 开发模式(分别在两个终端运行)──
|
||||
dev-server:
|
||||
cd server && go run ./cmd/backupx
|
||||
@@ -28,6 +33,28 @@ test-server:
|
||||
test-web:
|
||||
cd web && npm run test
|
||||
|
||||
check-docs:
|
||||
cd docs-site && npm run typecheck && npm run build
|
||||
|
||||
format-check:
|
||||
@unformatted="$$(gofmt -l server)"; \
|
||||
if [ -n "$$unformatted" ]; then \
|
||||
printf '%s\n' "$$unformatted"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
vet-server:
|
||||
cd server && go mod verify && go vet ./...
|
||||
|
||||
# ── 提交前完整验证 ──
|
||||
verify: verify-server verify-web verify-docs
|
||||
|
||||
verify-server: format-check vet-server test-server build-server
|
||||
|
||||
verify-web: test-web build-web
|
||||
|
||||
verify-docs: check-docs
|
||||
|
||||
# ── Docker 构建 ──
|
||||
docker:
|
||||
docker build --build-arg VERSION=$(VERSION) -t backupx:$(VERSION) -t backupx:latest .
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/Awuqing/BackupX/stargazers"><img src="https://img.shields.io/github/stars/Awuqing/BackupX?style=flat-square&color=f5c542" alt="Stars"></a>
|
||||
<a href="https://github.com/Awuqing/BackupX/releases"><img src="https://img.shields.io/github/v/release/Awuqing/BackupX?style=flat-square&color=brightgreen" alt="Release"></a>
|
||||
<a href="https://github.com/Awuqing/BackupX/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/Awuqing/BackupX/ci.yml?branch=main&style=flat-square" alt="CI"></a>
|
||||
<a href="https://github.com/Awuqing/BackupX/actions/workflows/docs.yml"><img src="https://img.shields.io/github/actions/workflow/status/Awuqing/BackupX/docs.yml?branch=main&style=flat-square&label=docs" alt="Docs"></a>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat-square&logo=go" alt="Go">
|
||||
<img src="https://img.shields.io/badge/React-18-61DAFB?style=flat-square&logo=react" alt="React">
|
||||
<img src="https://img.shields.io/badge/SQLite-embedded-003B57?style=flat-square&logo=sqlite" alt="SQLite">
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/Awuqing/BackupX/stargazers"><img src="https://img.shields.io/github/stars/Awuqing/BackupX?style=flat-square&color=f5c542" alt="Stars"></a>
|
||||
<a href="https://github.com/Awuqing/BackupX/releases"><img src="https://img.shields.io/github/v/release/Awuqing/BackupX?style=flat-square&color=brightgreen" alt="Release"></a>
|
||||
<a href="https://github.com/Awuqing/BackupX/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/Awuqing/BackupX/ci.yml?branch=main&style=flat-square" alt="CI"></a>
|
||||
<a href="https://github.com/Awuqing/BackupX/actions/workflows/docs.yml"><img src="https://img.shields.io/github/actions/workflow/status/Awuqing/BackupX/docs.yml?branch=main&style=flat-square&label=docs" alt="Docs"></a>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat-square&logo=go" alt="Go">
|
||||
<img src="https://img.shields.io/badge/React-18-61DAFB?style=flat-square&logo=react" alt="React">
|
||||
<img src="https://img.shields.io/badge/SQLite-embedded-003B57?style=flat-square&logo=sqlite" alt="SQLite">
|
||||
|
||||
@@ -6,7 +6,7 @@ description: Get a BackupX dev environment running — backend, frontend, tests.
|
||||
|
||||
# Development Setup
|
||||
|
||||
**Requirements:** Go ≥ 1.25, Node.js ≥ 20, npm.
|
||||
**Requirements:** Go ≥ 1.25, Node.js 24 LTS, npm 11 or later.
|
||||
|
||||
## Clone & install
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ The installer:
|
||||
|
||||
## From source
|
||||
|
||||
Requires Go ≥ 1.25 and Node.js ≥ 20.
|
||||
Requires Go ≥ 1.25, Node.js 24 LTS, and npm 11 or later.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Awuqing/BackupX.git && cd BackupX
|
||||
|
||||
@@ -6,7 +6,7 @@ description: 搭建 BackupX 本地开发环境 — 后端、前端、测试。
|
||||
|
||||
# 开发环境
|
||||
|
||||
**环境要求:** Go ≥ 1.25,Node.js ≥ 20,npm。
|
||||
**环境要求:** Go ≥ 1.25、Node.js 24 LTS、npm 11 或更高版本。
|
||||
|
||||
## 克隆与依赖
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置
|
||||
|
||||
## 从源码构建
|
||||
|
||||
依赖:Go ≥ 1.25,Node.js ≥ 20。
|
||||
依赖:Go ≥ 1.25、Node.js 24 LTS、npm 11 或更高版本。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Awuqing/BackupX.git && cd BackupX
|
||||
|
||||
2
docs-site/package-lock.json
generated
2
docs-site/package-lock.json
generated
@@ -25,7 +25,7 @@
|
||||
"typescript": "~6.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0"
|
||||
"node": ">=24.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@11ty/gray-matter": {
|
||||
|
||||
@@ -44,6 +44,6 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0"
|
||||
"node": ">=24.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,4 +95,3 @@ func redirectStderr(path string) (func(), error) {
|
||||
_ = f.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -357,4 +357,3 @@ func buildStorageRegistry() *storage.Registry {
|
||||
storageRclone.RegisterAllBackends(registry)
|
||||
return registry
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
type Function string
|
||||
|
||||
const (
|
||||
FunctionBackup Function = "backup"
|
||||
FunctionRestore Function = "restore"
|
||||
FunctionInquire Function = "inquire"
|
||||
FunctionDelete Function = "delete"
|
||||
FunctionBackup Function = "backup"
|
||||
FunctionRestore Function = "restore"
|
||||
FunctionInquire Function = "inquire"
|
||||
FunctionDelete Function = "delete"
|
||||
)
|
||||
|
||||
// BackupRequest 是 BACKUP 操作的单条请求。
|
||||
|
||||
@@ -160,7 +160,7 @@ func TestFileRunnerSelectiveRestore(t *testing.T) {
|
||||
}
|
||||
diffAssertContent(t, filepath.Join(restoreSrc, "a.txt"), "alpha")
|
||||
diffAssertContent(t, filepath.Join(restoreSrc, "sub", "c.txt"), "charlie") // 选中目录 → 子项一并恢复
|
||||
diffAssertAbsent(t, filepath.Join(restoreSrc, "b.txt")) // 未选中 → 不恢复
|
||||
diffAssertAbsent(t, filepath.Join(restoreSrc, "b.txt")) // 未选中 → 不恢复
|
||||
}
|
||||
|
||||
// TestFileRunnerDifferentialWithoutBaseIsFull 验证无基线时差异请求回退为全量(产出清单、含全部文件)。
|
||||
|
||||
@@ -160,4 +160,3 @@ func formatFileSize(size int64) string {
|
||||
return fmt.Sprintf("%d B", size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ func writeTestTar(t *testing.T, entries map[string][]byte) string {
|
||||
|
||||
func TestVerifyTarArchive_Valid(t *testing.T) {
|
||||
path := writeTestTar(t, map[string][]byte{
|
||||
"readme.md": []byte("hello"),
|
||||
"data.bin": []byte("world!!!"),
|
||||
"readme.md": []byte("hello"),
|
||||
"data.bin": []byte("world!!!"),
|
||||
})
|
||||
report, err := VerifyTarArchive(path, "")
|
||||
if err != nil {
|
||||
|
||||
@@ -24,10 +24,11 @@ type MaintenanceWindow struct {
|
||||
// 简化语法:多个窗口以 ';' 分隔,每个窗口按 "[days=xxx;]time=HH:MM-HH:MM" 格式。
|
||||
// Days 缺省 = 全周;若不合法,跳过该段而非抛错(让调用方尽力工作)。
|
||||
// 示例:
|
||||
// "time=01:00-05:00" 每天 1 点到 5 点
|
||||
// "days=sat,sun;time=00:00-23:59" 仅周末全天
|
||||
// "time=22:00-06:00" 每天跨夜
|
||||
// "days=mon,tue,wed,thu,fri;time=22:00-06:00" 工作日跨夜
|
||||
//
|
||||
// "time=01:00-05:00" 每天 1 点到 5 点
|
||||
// "days=sat,sun;time=00:00-23:59" 仅周末全天
|
||||
// "time=22:00-06:00" 每天跨夜
|
||||
// "days=mon,tue,wed,thu,fri;time=22:00-06:00" 工作日跨夜
|
||||
func ParseMaintenanceWindows(value string) []MaintenanceWindow {
|
||||
v := strings.TrimSpace(value)
|
||||
if v == "" {
|
||||
|
||||
@@ -6,12 +6,12 @@ import "time"
|
||||
// 任一 Notification 可订阅多个事件,EventTypes 字段存 CSV。
|
||||
// 空 EventTypes + OnSuccess/OnFailure=true 时沿用旧语义(仅备份成功/失败)。
|
||||
const (
|
||||
NotificationEventBackupSuccess = "backup_success"
|
||||
NotificationEventBackupFailed = "backup_failed"
|
||||
NotificationEventBackupSuccess = "backup_success"
|
||||
NotificationEventBackupFailed = "backup_failed"
|
||||
NotificationEventRestoreSuccess = "restore_success"
|
||||
NotificationEventRestoreFailed = "restore_failed"
|
||||
NotificationEventVerifyFailed = "verify_failed"
|
||||
NotificationEventSLAViolation = "sla_violation"
|
||||
NotificationEventVerifyFailed = "verify_failed"
|
||||
NotificationEventSLAViolation = "sla_violation"
|
||||
// NotificationEventStorageUnhealthy 存储目标连接失败(后台健康扫描触发)。
|
||||
NotificationEventStorageUnhealthy = "storage_unhealthy"
|
||||
// NotificationEventReplicationFailed 备份复制失败。
|
||||
@@ -23,13 +23,13 @@ const (
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Type string `gorm:"size:20;index;not null" json:"type"`
|
||||
Name string `gorm:"size:100;uniqueIndex;not null" json:"name"`
|
||||
ConfigCiphertext string `gorm:"column:config_ciphertext;type:text;not null" json:"-"`
|
||||
Enabled bool `gorm:"not null;default:true" json:"enabled"`
|
||||
OnSuccess bool `gorm:"column:on_success;not null;default:false" json:"onSuccess"`
|
||||
OnFailure bool `gorm:"column:on_failure;not null;default:true" json:"onFailure"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Type string `gorm:"size:20;index;not null" json:"type"`
|
||||
Name string `gorm:"size:100;uniqueIndex;not null" json:"name"`
|
||||
ConfigCiphertext string `gorm:"column:config_ciphertext;type:text;not null" json:"-"`
|
||||
Enabled bool `gorm:"not null;default:true" json:"enabled"`
|
||||
OnSuccess bool `gorm:"column:on_success;not null;default:false" json:"onSuccess"`
|
||||
OnFailure bool `gorm:"column:on_failure;not null;default:true" json:"onFailure"`
|
||||
// EventTypes 逗号分隔,订阅的事件类型。
|
||||
// 空 = 仅监听备份成功/失败(兼容旧配置);非空则严格按订阅触发。
|
||||
EventTypes string `gorm:"column:event_types;size:500" json:"eventTypes"`
|
||||
|
||||
@@ -24,19 +24,19 @@ type ReplicationRecord struct {
|
||||
SourceTargetID uint `gorm:"column:source_target_id;index;not null" json:"sourceTargetId"`
|
||||
SourceTarget StorageTarget `gorm:"foreignKey:SourceTargetID;references:ID" json:"sourceTarget,omitempty"`
|
||||
// DestTargetID 目标存储(复制过去)
|
||||
DestTargetID uint `gorm:"column:dest_target_id;index;not null" json:"destTargetId"`
|
||||
DestTarget StorageTarget `gorm:"foreignKey:DestTargetID;references:ID" json:"destTarget,omitempty"`
|
||||
Status string `gorm:"size:20;index;not null" json:"status"`
|
||||
StoragePath string `gorm:"column:storage_path;size:500" json:"storagePath"`
|
||||
FileSize int64 `gorm:"column:file_size;not null;default:0" json:"fileSize"`
|
||||
Checksum string `gorm:"column:checksum;size:64" json:"checksum"`
|
||||
ErrorMessage string `gorm:"column:error_message;size:2000" json:"errorMessage"`
|
||||
DurationSeconds int `gorm:"column:duration_seconds;not null;default:0" json:"durationSeconds"`
|
||||
TriggeredBy string `gorm:"column:triggered_by;size:100" json:"triggeredBy"`
|
||||
StartedAt time.Time `gorm:"column:started_at;index;not null" json:"startedAt"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;index" json:"completedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DestTargetID uint `gorm:"column:dest_target_id;index;not null" json:"destTargetId"`
|
||||
DestTarget StorageTarget `gorm:"foreignKey:DestTargetID;references:ID" json:"destTarget,omitempty"`
|
||||
Status string `gorm:"size:20;index;not null" json:"status"`
|
||||
StoragePath string `gorm:"column:storage_path;size:500" json:"storagePath"`
|
||||
FileSize int64 `gorm:"column:file_size;not null;default:0" json:"fileSize"`
|
||||
Checksum string `gorm:"column:checksum;size:64" json:"checksum"`
|
||||
ErrorMessage string `gorm:"column:error_message;size:2000" json:"errorMessage"`
|
||||
DurationSeconds int `gorm:"column:duration_seconds;not null;default:0" json:"durationSeconds"`
|
||||
TriggeredBy string `gorm:"column:triggered_by;size:100" json:"triggeredBy"`
|
||||
StartedAt time.Time `gorm:"column:started_at;index;not null" json:"startedAt"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;index" json:"completedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (ReplicationRecord) TableName() string {
|
||||
|
||||
@@ -11,10 +11,10 @@ import "time"
|
||||
// - name
|
||||
// - sourcePath / sourcePaths 中的 {{.Host}} / {{.Env}} 等占位符
|
||||
type TaskTemplate struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:128;uniqueIndex;not null" json:"name"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
TaskType string `gorm:"column:task_type;size:20;not null" json:"taskType"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:128;uniqueIndex;not null" json:"name"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
TaskType string `gorm:"column:task_type;size:20;not null" json:"taskType"`
|
||||
// Payload JSON,存完整 BackupTaskUpsertInput 的序列化
|
||||
Payload string `gorm:"type:text;not null" json:"payload"`
|
||||
CreatedBy string `gorm:"column:created_by;size:128" json:"createdBy"`
|
||||
|
||||
@@ -126,14 +126,14 @@ func (s *DashboardService) Timeline(ctx context.Context, days int) ([]repository
|
||||
// 判定规则:任务设置了 SLAHoursRPO > 0,且距最近一次 success 备份的时间 > SLAHoursRPO。
|
||||
// 从未成功过的任务(LastSuccessAt = nil)若启用也视为违约(from createdAt 起算)。
|
||||
type SLAViolation struct {
|
||||
TaskID uint `json:"taskId"`
|
||||
TaskName string `json:"taskName"`
|
||||
NodeID uint `json:"nodeId"`
|
||||
NodeName string `json:"nodeName,omitempty"`
|
||||
SLAHoursRPO int `json:"slaHoursRpo"`
|
||||
LastSuccessAt *time.Time `json:"lastSuccessAt,omitempty"`
|
||||
HoursSinceLastSuccess float64 `json:"hoursSinceLastSuccess"`
|
||||
NeverSucceeded bool `json:"neverSucceeded"`
|
||||
TaskID uint `json:"taskId"`
|
||||
TaskName string `json:"taskName"`
|
||||
NodeID uint `json:"nodeId"`
|
||||
NodeName string `json:"nodeName,omitempty"`
|
||||
SLAHoursRPO int `json:"slaHoursRpo"`
|
||||
LastSuccessAt *time.Time `json:"lastSuccessAt,omitempty"`
|
||||
HoursSinceLastSuccess float64 `json:"hoursSinceLastSuccess"`
|
||||
NeverSucceeded bool `json:"neverSucceeded"`
|
||||
}
|
||||
|
||||
// SLAComplianceReport Dashboard 的 SLA 合规概览。
|
||||
@@ -204,15 +204,15 @@ func roundHours(value float64) float64 {
|
||||
|
||||
// ClusterNodeSummary 集群节点简报(Dashboard 用)。
|
||||
type ClusterNodeSummary struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
Status string `json:"status"`
|
||||
IsLocal bool `json:"isLocal"`
|
||||
AgentVersion string `json:"agentVersion"`
|
||||
VersionStatus string `json:"versionStatus"` // current | outdated | unknown
|
||||
LastSeen time.Time `json:"lastSeen"`
|
||||
TaskCount int64 `json:"taskCount"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
Status string `json:"status"`
|
||||
IsLocal bool `json:"isLocal"`
|
||||
AgentVersion string `json:"agentVersion"`
|
||||
VersionStatus string `json:"versionStatus"` // current | outdated | unknown
|
||||
LastSeen time.Time `json:"lastSeen"`
|
||||
TaskCount int64 `json:"taskCount"`
|
||||
}
|
||||
|
||||
// ClusterOverview Dashboard 集群概览卡片。
|
||||
@@ -312,9 +312,9 @@ func (s *DashboardService) Breakdown(ctx context.Context, days int) (*BreakdownS
|
||||
}
|
||||
}
|
||||
result := &BreakdownStats{
|
||||
ByType: makeBreakdown(typeCounts, typeLabel),
|
||||
ByNode: makeBreakdownByUint(nodeCounts, nodeNames, "节点 #"),
|
||||
ByStatus: []BreakdownItem{},
|
||||
ByType: makeBreakdown(typeCounts, typeLabel),
|
||||
ByNode: makeBreakdownByUint(nodeCounts, nodeNames, "节点 #"),
|
||||
ByStatus: []BreakdownItem{},
|
||||
ByStorage: []BreakdownItem{},
|
||||
}
|
||||
// 按状态(最近 days 天记录)
|
||||
|
||||
@@ -74,11 +74,11 @@ func (s *SystemService) CheckUpdate(ctx context.Context) (*UpdateCheckResult, er
|
||||
}
|
||||
|
||||
var release struct {
|
||||
TagName string `json:"tag_name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Body string `json:"body"`
|
||||
Published string `json:"published_at"`
|
||||
Assets []struct {
|
||||
TagName string `json:"tag_name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Body string `json:"body"`
|
||||
Published string `json:"published_at"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
@@ -132,4 +132,3 @@ func (s *SystemService) GetInfo(_ context.Context) *SystemInfo {
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
// TaskExportService 管理备份任务的 JSON 导入 / 导出。
|
||||
// 用途:
|
||||
// 1. 集群迁移(旧 Master → 新 Master 的任务配置搬迁)
|
||||
// 2. 灾备恢复(任务配置本地文件化,Master 宕机后重建)
|
||||
// 3. 配置审计(版本化 Git 管理 JSON 快照)
|
||||
// 1. 集群迁移(旧 Master → 新 Master 的任务配置搬迁)
|
||||
// 2. 灾备恢复(任务配置本地文件化,Master 宕机后重建)
|
||||
// 3. 配置审计(版本化 Git 管理 JSON 快照)
|
||||
//
|
||||
// 出于安全考虑,导出/导入不包含任何敏感字段:
|
||||
// - 数据库密码(DBPasswordCiphertext):跳过,导入后需人工填补
|
||||
@@ -40,35 +40,35 @@ func NewTaskExportService(
|
||||
|
||||
// ExportedTask 导出格式:按名称引用存储/节点,不含敏感数据。
|
||||
type ExportedTask struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CronExpr string `json:"cronExpr,omitempty"`
|
||||
SourcePath string `json:"sourcePath,omitempty"`
|
||||
SourcePaths []string `json:"sourcePaths,omitempty"`
|
||||
ExcludePatterns []string `json:"excludePatterns,omitempty"`
|
||||
DBHost string `json:"dbHost,omitempty"`
|
||||
DBPort int `json:"dbPort,omitempty"`
|
||||
DBUser string `json:"dbUser,omitempty"`
|
||||
DBName string `json:"dbName,omitempty"`
|
||||
DBPath string `json:"dbPath,omitempty"`
|
||||
ExtraConfig map[string]any `json:"extraConfig,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CronExpr string `json:"cronExpr,omitempty"`
|
||||
SourcePath string `json:"sourcePath,omitempty"`
|
||||
SourcePaths []string `json:"sourcePaths,omitempty"`
|
||||
ExcludePatterns []string `json:"excludePatterns,omitempty"`
|
||||
DBHost string `json:"dbHost,omitempty"`
|
||||
DBPort int `json:"dbPort,omitempty"`
|
||||
DBUser string `json:"dbUser,omitempty"`
|
||||
DBName string `json:"dbName,omitempty"`
|
||||
DBPath string `json:"dbPath,omitempty"`
|
||||
ExtraConfig map[string]any `json:"extraConfig,omitempty"`
|
||||
// 按名称引用:导入时按名称查找对应 ID
|
||||
StorageTargetNames []string `json:"storageTargetNames"`
|
||||
ReplicationTargetNames []string `json:"replicationTargetNames,omitempty"`
|
||||
NodeName string `json:"nodeName,omitempty"`
|
||||
DependsOnTaskNames []string `json:"dependsOnTaskNames,omitempty"`
|
||||
Tags string `json:"tags,omitempty"`
|
||||
Compression string `json:"compression,omitempty"`
|
||||
Encrypt bool `json:"encrypt,omitempty"`
|
||||
RetentionDays int `json:"retentionDays,omitempty"`
|
||||
MaxBackups int `json:"maxBackups,omitempty"`
|
||||
VerifyEnabled bool `json:"verifyEnabled,omitempty"`
|
||||
VerifyCronExpr string `json:"verifyCronExpr,omitempty"`
|
||||
VerifyMode string `json:"verifyMode,omitempty"`
|
||||
SLAHoursRPO int `json:"slaHoursRpo,omitempty"`
|
||||
AlertOnConsecutiveFails int `json:"alertOnConsecutiveFails,omitempty"`
|
||||
MaintenanceWindows string `json:"maintenanceWindows,omitempty"`
|
||||
StorageTargetNames []string `json:"storageTargetNames"`
|
||||
ReplicationTargetNames []string `json:"replicationTargetNames,omitempty"`
|
||||
NodeName string `json:"nodeName,omitempty"`
|
||||
DependsOnTaskNames []string `json:"dependsOnTaskNames,omitempty"`
|
||||
Tags string `json:"tags,omitempty"`
|
||||
Compression string `json:"compression,omitempty"`
|
||||
Encrypt bool `json:"encrypt,omitempty"`
|
||||
RetentionDays int `json:"retentionDays,omitempty"`
|
||||
MaxBackups int `json:"maxBackups,omitempty"`
|
||||
VerifyEnabled bool `json:"verifyEnabled,omitempty"`
|
||||
VerifyCronExpr string `json:"verifyCronExpr,omitempty"`
|
||||
VerifyMode string `json:"verifyMode,omitempty"`
|
||||
SLAHoursRPO int `json:"slaHoursRpo,omitempty"`
|
||||
AlertOnConsecutiveFails int `json:"alertOnConsecutiveFails,omitempty"`
|
||||
MaintenanceWindows string `json:"maintenanceWindows,omitempty"`
|
||||
}
|
||||
|
||||
// ExportPayload 导出整体结构,带元信息。
|
||||
@@ -233,67 +233,67 @@ func (s *TaskExportService) toExported(item *model.BackupTask, targetNames, node
|
||||
nodeName = nodeNames[item.NodeID]
|
||||
}
|
||||
return ExportedTask{
|
||||
Name: item.Name,
|
||||
Type: item.Type,
|
||||
Enabled: item.Enabled,
|
||||
CronExpr: item.CronExpr,
|
||||
SourcePath: item.SourcePath,
|
||||
SourcePaths: sourcePaths,
|
||||
ExcludePatterns: excludes,
|
||||
DBHost: item.DBHost,
|
||||
DBPort: item.DBPort,
|
||||
DBUser: item.DBUser,
|
||||
DBName: item.DBName,
|
||||
DBPath: item.DBPath,
|
||||
ExtraConfig: extra,
|
||||
StorageTargetNames: storageNames,
|
||||
ReplicationTargetNames: replicationNames,
|
||||
NodeName: nodeName,
|
||||
DependsOnTaskNames: dependsOnNames,
|
||||
Tags: item.Tags,
|
||||
Compression: item.Compression,
|
||||
Encrypt: item.Encrypt,
|
||||
RetentionDays: item.RetentionDays,
|
||||
MaxBackups: item.MaxBackups,
|
||||
VerifyEnabled: item.VerifyEnabled,
|
||||
VerifyCronExpr: item.VerifyCronExpr,
|
||||
VerifyMode: item.VerifyMode,
|
||||
SLAHoursRPO: item.SLAHoursRPO,
|
||||
Name: item.Name,
|
||||
Type: item.Type,
|
||||
Enabled: item.Enabled,
|
||||
CronExpr: item.CronExpr,
|
||||
SourcePath: item.SourcePath,
|
||||
SourcePaths: sourcePaths,
|
||||
ExcludePatterns: excludes,
|
||||
DBHost: item.DBHost,
|
||||
DBPort: item.DBPort,
|
||||
DBUser: item.DBUser,
|
||||
DBName: item.DBName,
|
||||
DBPath: item.DBPath,
|
||||
ExtraConfig: extra,
|
||||
StorageTargetNames: storageNames,
|
||||
ReplicationTargetNames: replicationNames,
|
||||
NodeName: nodeName,
|
||||
DependsOnTaskNames: dependsOnNames,
|
||||
Tags: item.Tags,
|
||||
Compression: item.Compression,
|
||||
Encrypt: item.Encrypt,
|
||||
RetentionDays: item.RetentionDays,
|
||||
MaxBackups: item.MaxBackups,
|
||||
VerifyEnabled: item.VerifyEnabled,
|
||||
VerifyCronExpr: item.VerifyCronExpr,
|
||||
VerifyMode: item.VerifyMode,
|
||||
SLAHoursRPO: item.SLAHoursRPO,
|
||||
AlertOnConsecutiveFails: item.AlertOnConsecutiveFails,
|
||||
MaintenanceWindows: item.MaintenanceWindows,
|
||||
MaintenanceWindows: item.MaintenanceWindows,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TaskExportService) toUpsertInput(t ExportedTask, targetsByName, nodesByName map[string]uint, deps []uint) BackupTaskUpsertInput {
|
||||
return BackupTaskUpsertInput{
|
||||
Name: t.Name,
|
||||
Type: t.Type,
|
||||
Enabled: t.Enabled,
|
||||
CronExpr: t.CronExpr,
|
||||
SourcePath: t.SourcePath,
|
||||
SourcePaths: t.SourcePaths,
|
||||
ExcludePatterns: t.ExcludePatterns,
|
||||
DBHost: t.DBHost,
|
||||
DBPort: t.DBPort,
|
||||
DBUser: t.DBUser,
|
||||
DBName: t.DBName,
|
||||
DBPath: t.DBPath,
|
||||
ExtraConfig: t.ExtraConfig,
|
||||
StorageTargetIDs: idsFromNames(t.StorageTargetNames, targetsByName),
|
||||
ReplicationTargetIDs: idsFromNames(t.ReplicationTargetNames, targetsByName),
|
||||
NodeID: nodesByName[t.NodeName],
|
||||
Tags: t.Tags,
|
||||
Compression: t.Compression,
|
||||
Encrypt: t.Encrypt,
|
||||
RetentionDays: t.RetentionDays,
|
||||
MaxBackups: t.MaxBackups,
|
||||
VerifyEnabled: t.VerifyEnabled,
|
||||
VerifyCronExpr: t.VerifyCronExpr,
|
||||
VerifyMode: t.VerifyMode,
|
||||
SLAHoursRPO: t.SLAHoursRPO,
|
||||
Name: t.Name,
|
||||
Type: t.Type,
|
||||
Enabled: t.Enabled,
|
||||
CronExpr: t.CronExpr,
|
||||
SourcePath: t.SourcePath,
|
||||
SourcePaths: t.SourcePaths,
|
||||
ExcludePatterns: t.ExcludePatterns,
|
||||
DBHost: t.DBHost,
|
||||
DBPort: t.DBPort,
|
||||
DBUser: t.DBUser,
|
||||
DBName: t.DBName,
|
||||
DBPath: t.DBPath,
|
||||
ExtraConfig: t.ExtraConfig,
|
||||
StorageTargetIDs: idsFromNames(t.StorageTargetNames, targetsByName),
|
||||
ReplicationTargetIDs: idsFromNames(t.ReplicationTargetNames, targetsByName),
|
||||
NodeID: nodesByName[t.NodeName],
|
||||
Tags: t.Tags,
|
||||
Compression: t.Compression,
|
||||
Encrypt: t.Encrypt,
|
||||
RetentionDays: t.RetentionDays,
|
||||
MaxBackups: t.MaxBackups,
|
||||
VerifyEnabled: t.VerifyEnabled,
|
||||
VerifyCronExpr: t.VerifyCronExpr,
|
||||
VerifyMode: t.VerifyMode,
|
||||
SLAHoursRPO: t.SLAHoursRPO,
|
||||
AlertOnConsecutiveFails: t.AlertOnConsecutiveFails,
|
||||
MaintenanceWindows: t.MaintenanceWindows,
|
||||
DependsOnTaskIDs: deps,
|
||||
MaintenanceWindows: t.MaintenanceWindows,
|
||||
DependsOnTaskIDs: deps,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ type LocalDiskFactory struct{}
|
||||
func NewLocalDiskFactory() LocalDiskFactory { return LocalDiskFactory{} }
|
||||
|
||||
func (LocalDiskFactory) Type() storage.ProviderType { return storage.ProviderTypeLocalDisk }
|
||||
func (LocalDiskFactory) SensitiveFields() []string { return nil }
|
||||
func (LocalDiskFactory) SensitiveFields() []string { return nil }
|
||||
|
||||
func (LocalDiskFactory) New(ctx context.Context, rawConfig map[string]any) (storage.StorageProvider, error) {
|
||||
cfg, err := storage.DecodeConfig[storage.LocalDiskConfig](rawConfig)
|
||||
@@ -66,7 +66,7 @@ type S3Factory struct{}
|
||||
func NewS3Factory() S3Factory { return S3Factory{} }
|
||||
|
||||
func (S3Factory) Type() storage.ProviderType { return storage.ProviderTypeS3 }
|
||||
func (S3Factory) SensitiveFields() []string { return []string{"accessKeyId", "secretAccessKey"} }
|
||||
func (S3Factory) SensitiveFields() []string { return []string{"accessKeyId", "secretAccessKey"} }
|
||||
|
||||
func (S3Factory) New(ctx context.Context, rawConfig map[string]any) (storage.StorageProvider, error) {
|
||||
cfg, err := storage.DecodeConfig[storage.S3Config](rawConfig)
|
||||
@@ -116,7 +116,7 @@ type WebDAVFactory struct{}
|
||||
func NewWebDAVFactory() WebDAVFactory { return WebDAVFactory{} }
|
||||
|
||||
func (WebDAVFactory) Type() storage.ProviderType { return storage.ProviderTypeWebDAV }
|
||||
func (WebDAVFactory) SensitiveFields() []string { return []string{"username", "password"} }
|
||||
func (WebDAVFactory) SensitiveFields() []string { return []string{"username", "password"} }
|
||||
|
||||
func (WebDAVFactory) New(ctx context.Context, rawConfig map[string]any) (storage.StorageProvider, error) {
|
||||
cfg, err := storage.DecodeConfig[storage.WebDAVConfig](rawConfig)
|
||||
@@ -187,7 +187,7 @@ type FTPFactory struct{}
|
||||
func NewFTPFactory() FTPFactory { return FTPFactory{} }
|
||||
|
||||
func (FTPFactory) Type() storage.ProviderType { return storage.ProviderTypeFTP }
|
||||
func (FTPFactory) SensitiveFields() []string { return []string{"username", "password"} }
|
||||
func (FTPFactory) SensitiveFields() []string { return []string{"username", "password"} }
|
||||
|
||||
func (FTPFactory) New(ctx context.Context, rawConfig map[string]any) (storage.StorageProvider, error) {
|
||||
cfg, err := storage.DecodeConfig[storage.FTPConfig](rawConfig)
|
||||
@@ -228,7 +228,7 @@ type AliyunOSSFactory struct{}
|
||||
func NewAliyunOSSFactory() AliyunOSSFactory { return AliyunOSSFactory{} }
|
||||
|
||||
func (AliyunOSSFactory) Type() storage.ProviderType { return storage.ProviderTypeAliyunOSS }
|
||||
func (AliyunOSSFactory) SensitiveFields() []string { return []string{"accessKeyId", "secretAccessKey"} }
|
||||
func (AliyunOSSFactory) SensitiveFields() []string { return []string{"accessKeyId", "secretAccessKey"} }
|
||||
|
||||
// AliyunConfig 是阿里云 OSS 的用户配置。
|
||||
type AliyunConfig struct {
|
||||
@@ -269,7 +269,9 @@ type TencentCOSFactory struct{}
|
||||
func NewTencentCOSFactory() TencentCOSFactory { return TencentCOSFactory{} }
|
||||
|
||||
func (TencentCOSFactory) Type() storage.ProviderType { return storage.ProviderTypeTencentCOS }
|
||||
func (TencentCOSFactory) SensitiveFields() []string { return []string{"accessKeyId", "secretAccessKey"} }
|
||||
func (TencentCOSFactory) SensitiveFields() []string {
|
||||
return []string{"accessKeyId", "secretAccessKey"}
|
||||
}
|
||||
|
||||
// TencentConfig 是腾讯云 COS 的用户配置。
|
||||
type TencentConfig struct {
|
||||
@@ -305,7 +307,7 @@ type QiniuKodoFactory struct{}
|
||||
func NewQiniuKodoFactory() QiniuKodoFactory { return QiniuKodoFactory{} }
|
||||
|
||||
func (QiniuKodoFactory) Type() storage.ProviderType { return storage.ProviderTypeQiniuKodo }
|
||||
func (QiniuKodoFactory) SensitiveFields() []string { return []string{"accessKeyId", "secretAccessKey"} }
|
||||
func (QiniuKodoFactory) SensitiveFields() []string { return []string{"accessKeyId", "secretAccessKey"} }
|
||||
|
||||
// QiniuConfig 是七牛云 Kodo 的用户配置。
|
||||
type QiniuConfig struct {
|
||||
@@ -355,7 +357,9 @@ type RcloneFactory struct{}
|
||||
func NewRcloneFactory() RcloneFactory { return RcloneFactory{} }
|
||||
|
||||
func (RcloneFactory) Type() storage.ProviderType { return storage.ProviderTypeRclone }
|
||||
func (RcloneFactory) SensitiveFields() []string { return []string{"pass", "password", "secret_access_key", "client_secret", "token"} }
|
||||
func (RcloneFactory) SensitiveFields() []string {
|
||||
return []string{"pass", "password", "secret_access_key", "client_secret", "token"}
|
||||
}
|
||||
|
||||
func (RcloneFactory) New(ctx context.Context, rawConfig map[string]any) (storage.StorageProvider, error) {
|
||||
backend, _ := rawConfig["backend"].(string)
|
||||
@@ -462,8 +466,10 @@ func NewBackendFactory(backendType string) GenericBackendFactory {
|
||||
return GenericBackendFactory{backendType: backendType, sensitive: sensitive}
|
||||
}
|
||||
|
||||
func (f GenericBackendFactory) Type() storage.ProviderType { return storage.ProviderType(f.backendType) }
|
||||
func (f GenericBackendFactory) SensitiveFields() []string { return f.sensitive }
|
||||
func (f GenericBackendFactory) Type() storage.ProviderType {
|
||||
return storage.ProviderType(f.backendType)
|
||||
}
|
||||
func (f GenericBackendFactory) SensitiveFields() []string { return f.sensitive }
|
||||
|
||||
func (f GenericBackendFactory) New(ctx context.Context, rawConfig map[string]any) (storage.StorageProvider, error) {
|
||||
root, _ := rawConfig["root"].(string)
|
||||
|
||||
19
web/package-lock.json
generated
19
web/package-lock.json
generated
@@ -23,7 +23,7 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/node": "^24.13.3",
|
||||
"@types/react": "^18.3.20",
|
||||
"@types/react-dom": "^18.3.6",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
@@ -31,6 +31,9 @@
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.4.3",
|
||||
"vitest": "^3.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
@@ -1529,13 +1532,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.15",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.15.tgz",
|
||||
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
|
||||
"version": "24.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
|
||||
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
@@ -3468,9 +3471,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=24.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit -p tsconfig.json && vite build",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.node.json",
|
||||
"build": "npm run typecheck && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run"
|
||||
},
|
||||
@@ -25,7 +29,7 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/node": "^24.13.3",
|
||||
"@types/react": "^18.3.20",
|
||||
"@types/react-dom": "^18.3.6",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
|
||||
@@ -29,6 +29,5 @@
|
||||
"src/pages/login/page.tsx",
|
||||
"src/pages/system-info/page.tsx",
|
||||
"src/stores/auth.test.ts"
|
||||
],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"root":["./src/rootapp.tsx","./src/main.tsx","./src/components/authbootstrap.tsx","./src/components/fullpageloading.tsx","./src/layouts/applayout.tsx","./src/pages/dashboard/dashboardpage.tsx","./src/pages/login/loginpage.tsx","./src/pages/system-info/systeminfopage.tsx","./src/router/protectedroute.tsx","./src/router/index.tsx","./src/services/auth.ts","./src/services/http.ts","./src/services/system.ts","./src/stores/auth.ts","./src/test/setup.ts","./src/types/api.ts","./src/types/auth.ts","./src/types/system.ts"],"version":"5.9.3"}
|
||||
2
web/vite.config.d.ts
vendored
2
web/vite.config.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
declare const _default: import("vite").UserConfig;
|
||||
export default _default;
|
||||
@@ -1,32 +0,0 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import react from '@vitejs/plugin-react';
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8340',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
|
||||
'vendor-arco': ['@arco-design/web-react'],
|
||||
'vendor-echarts': ['echarts', 'echarts-for-react'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
setupFiles: './src/test/setup.ts',
|
||||
globals: true,
|
||||
include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
|
||||
exclude: ['src/components/auth-guard.test.tsx', 'src/stores/auth.test.ts'],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user