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