Skip to content
Merged

Dev #17

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

name: CI/CD Pipeline

concurrency:
group: ci-cd-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches: [main, dev]
Expand Down Expand Up @@ -266,6 +270,17 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Resolve application version
id: app-version
shell: bash
run: |
if [ "$GITHUB_REF" = "refs/heads/main" ]; then
VERSION="v$(node -p "require('./package.json').version")"
else
VERSION="dev-${GITHUB_SHA: -4}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

- name: Build and push by digest
id: build
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.13.0
Expand All @@ -290,6 +305,7 @@ jobs:
build-args: |
NODE_ENV=production
USE_PREBUILT_AGENT=true
APP_VERSION=${{ steps.app-version.outputs.version }}

- name: Export digest
run: |
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ jobs:
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
echo "📦 Version: ${VERSION}, Tag: v${VERSION}"

- name: Verify source version
env:
RELEASE_VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
if [ "$RELEASE_VERSION" != "$PACKAGE_VERSION" ]; then
echo "Release version ${RELEASE_VERSION} does not match package.json ${PACKAGE_VERSION}." >&2
exit 1
fi

- name: Create tag (manual trigger only)
if: github.event_name == 'workflow_dispatch'
env:
Expand Down Expand Up @@ -349,6 +360,15 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Resolve application version
id: app-version
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
VERSION="${INPUT_VERSION:-${GITHUB_REF_NAME#v}}"
echo "version=v${VERSION}" >> "$GITHUB_OUTPUT"

- name: Extract metadata
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.6.1
Expand Down Expand Up @@ -384,6 +404,7 @@ jobs:
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
build-args: |
NODE_ENV=production
APP_VERSION=${{ steps.app-version.outputs.version }}

- name: Export digest
run: |
Expand Down
93 changes: 93 additions & 0 deletions .github/workflows/version-bump.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Keep the application version in package.json aligned with main merges.
# The merge commit is bumped in a follow-up commit, then CI is dispatched
# explicitly because pushes made with GITHUB_TOKEN do not trigger workflows.
name: Bump Main Version

on:
push:
branches: [main]
pull_request:
branches: [main]
types: [closed]

permissions:
contents: write
actions: write

concurrency:
group: version-bump-main
cancel-in-progress: false

jobs:
bump:
if: >-
(github.event_name == 'push' && github.actor != 'github-actions[bot]' &&
!contains(github.event.head_commit.message, 'chore(release): bump version')) ||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true)
runs-on: ubuntu-latest
steps:
- name: Checkout full history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.2.2
with:
fetch-depth: 2
ref: main

- name: Detect merge commit
id: merge
env:
EVENT_NAME: ${{ github.event_name }}
shell: bash
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
echo "parent_count=pr" >> "$GITHUB_OUTPUT"
echo "is_merge=true" >> "$GITHUB_OUTPUT"
exit 0
fi

PARENT_COUNT=$(git rev-list --parents -n 1 HEAD | awk '{print NF - 1}')
echo "parent_count=${PARENT_COUNT}" >> "$GITHUB_OUTPUT"
if [ "$PARENT_COUNT" -ge 2 ]; then
echo "is_merge=true" >> "$GITHUB_OUTPUT"
else
echo "is_merge=false" >> "$GITHUB_OUTPUT"
fi

- name: Resolve next version
if: steps.merge.outputs.is_merge == 'true'
id: version
shell: bash
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
BASE_VERSION=$(git show HEAD^1:package.json | node -e "let input=''; process.stdin.on('data', chunk => input += chunk); process.stdin.on('end', () => console.log(JSON.parse(input).version));")

if node -e "const a='$CURRENT_VERSION'.split('.').map(Number); const b='$BASE_VERSION'.split('.').map(Number); process.exit(a[0] > b[0] || (a[0] === b[0] && (a[1] > b[1] || (a[1] === b[1] && a[2] > b[2]))) ? 0 : 1)"; then
VERSION="$CURRENT_VERSION"
CHANGED=false
else
npm version patch --no-git-tag-version --ignore-scripts
VERSION=$(node -p "require('./package.json').version")
CHANGED=true
fi

echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "changed=${CHANGED}" >> "$GITHUB_OUTPUT"

- name: Commit version bump
if: steps.merge.outputs.is_merge == 'true' && steps.version.outputs.changed == 'true'
env:
ACTOR: ${{ github.actor }}
ACTOR_ID: ${{ github.actor_id }}
VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
git config user.name "$ACTOR"
git config user.email "${ACTOR_ID}+${ACTOR}@users.noreply.github.com"
git add package.json package-lock.json
git commit -m "chore(release): bump version to v${VERSION} [skip ci]"
git push origin HEAD:main

- name: Dispatch CI for bumped main
if: steps.merge.outputs.is_merge == 'true' && steps.version.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run ci-cd.yml --ref main
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# 阶段 1: 构建前端 (Frontend Builder) - 始终在构建主机平台运行
FROM --platform=$BUILDPLATFORM node:20-slim AS frontend-builder
ARG APP_VERSION=dev-local
# 安装构建工具
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
Expand Down Expand Up @@ -32,7 +33,8 @@
# 显式设置 PATH (虽然 npm run 通常不需要,但以防万一)
# 禁用 CDN 模式,所有依赖打包到本地
ENV PATH=/app/node_modules/.bin:$PATH \
VITE_USE_CDN=false
VITE_USE_CDN=false \
VITE_APP_VERSION=${APP_VERSION}
RUN npm run build

# 阶段 2: 构建 Go 后端 (Go Backend Builder)
Expand Down Expand Up @@ -60,7 +62,7 @@
-o api-monitor ./cmd/api-monitor

# 阶段 3: 构建 Rust Agent 二进制 (Agent Builder) - 优化为基于 TARGETARCH 进行条件式本机编译,以最大化编译性能并防止复杂的跨平台交叉编译错误
FROM --platform=$TARGETPLATFORM rust:slim AS agent-builder

Check warning on line 65 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-docker (linux/arm64, ubuntu-24.04-arm, arm64)

Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior

RedundantTargetPlatform: Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior More info: https://docs.docker.com/go/dockerfile/rule/redundant-target-platform/

Check warning on line 65 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-docker (linux/amd64, ubuntu-latest, amd64)

Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior

RedundantTargetPlatform: Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior More info: https://docs.docker.com/go/dockerfile/rule/redundant-target-platform/
ARG USE_PREBUILT_AGENT=false
RUN if [ "$USE_PREBUILT_AGENT" != "true" ]; then \
apt-get update && apt-get install -y --no-install-recommends \
Expand Down Expand Up @@ -110,7 +112,7 @@
# 注意:deps-builder 阶段已移除,Go 后端不需要 Node.js 依赖

# 阶段 4: 运行时镜像 (Runner) - 纯净的运行环境
FROM --platform=$TARGETPLATFORM alpine:3.24.1 AS runner

Check warning on line 115 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-docker (linux/arm64, ubuntu-24.04-arm, arm64)

Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior

RedundantTargetPlatform: Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior More info: https://docs.docker.com/go/dockerfile/rule/redundant-target-platform/

Check warning on line 115 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-docker (linux/amd64, ubuntu-latest, amd64)

Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior

RedundantTargetPlatform: Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior More info: https://docs.docker.com/go/dockerfile/rule/redundant-target-platform/

LABEL org.opencontainers.image.title="API Monitor"
LABEL org.opencontainers.image.description="API聚合监控面板"
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

API Monitor 是一个自托管的 API 管理、云资源管理与主机监控面板。

集中管理服务器、DNS、对象存储、PaaS、文件分享等
集中管理服务器、DNS、对象存储、PaaS、文件分享等各种分散服务

## 功能概览

Expand All @@ -12,6 +12,9 @@ API Monitor 是一个自托管的 API 管理、云资源管理与主机监控面
- OpenAI 兼容接口、模型调用记录与用量统计
- 可用性监测、公开状态页、自定义域名与首页快捷入口
- 文件中转、TOTP、备份、定时任务、通知模板与系统日志
- 还有很多待定功能>>>>>>

![image](https://image.dooo.ng/t/2026/07/23/6a61fba454686.webp)

## 快速部署

Expand Down Expand Up @@ -73,8 +76,6 @@ npm run backend-go:build
| `JWT_SECRET` | 会话密钥,建议使用长随机字符串 |
| `LOG_LEVEL` | 日志级别:`DEBUG`、`INFO`、`WARN`、`ERROR` |

不要把真实密码、Token、Cookie、私钥或云厂商凭证提交到仓库。

## 技术栈

- 后端:Go + SQLite
Expand All @@ -89,8 +90,6 @@ npm run backend-go:build
- [API 接口文档](./docs/API接口文档.md)
- [Kumo UI 规则](./docs/Kumo%20UI%20规则.md)

前端如果遇到 Kumo `Tabs` 描边被裁、tab 切换左右抖动、双栏页面高度填充不稳定等问题,先看 [docs/README.md](./docs/README.md) 里的“前端布局约定”。

## 许可证

[MIT](./LICENSE)
38 changes: 38 additions & 0 deletions docs/versioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 版本管理

API Monitor 使用语义化版本(Semantic Versioning),格式为 `MAJOR.MINOR.PATCH`。

## 版本来源

- `package.json` 中的 `version` 是正式版本的单一来源。
- GitHub Release 和 Git tag 使用相同版本,并增加 `v` 前缀,例如 `v2.0.1`。
- `main` 构建显示正式版本,例如 `v2.0.1`。
- `dev` 和其他开发分支显示 `dev-xxxx`,其中 `xxxx` 是完整提交哈希的末四位。
- Docker 构建通过 `APP_VERSION` 构建参数把版本写入前端,不在运行时请求 GitHub。

## 日常发布流程

1. 在 `dev` 完成功能开发和验证。
2. 将 `dev` 合并到 `main`,普通 merge、squash merge 和 rebase merge 都可以。
3. `Bump Main Version` 工作流比较合并前后的版本:
- 版本未变化时,自动执行 patch 递增,例如 `2.0.0` 到 `2.0.1`。
- 版本已人工提升到更高的 minor 或 major 时,保留人工版本。
4. 工作流提交 `package.json` 和 `package-lock.json`,随后重新触发主分支 CI/CD。
5. 创建 Release 时,输入版本必须与 `package.json` 一致;不一致时工作流会停止。

## 主版本和次版本

需要不兼容更新或较大功能版本时,在合并到 `main` 前运行:

```bash
npm run version:major
npm run version:minor
```

普通修复版本通常不需要手动执行;主分支合并工作流会自动处理。需要本地手动递增 patch 时可运行:

```bash
npm run version:patch
```

版本修改应与对应功能一起提交,不要单独修改 Git tag 来代替源代码版本。
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"test": "vitest run --passWithNoTests",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"version:patch": "npm version patch --no-git-tag-version --ignore-scripts",
"version:minor": "npm version minor --no-git-tag-version --ignore-scripts",
"version:major": "npm version major --no-git-tag-version --ignore-scripts",
"backend-go:dev": "cd backend-go && go run ./cmd/api-monitor",
"backend-go:start": "cd backend-go && ./api-monitor.exe",
"backend-go:test": "cd backend-go && go test ./...",
Expand Down
10 changes: 9 additions & 1 deletion src/js/components/MainLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Tooltip } from '@cloudflare/kumo/components/tooltip';
import { Button } from '@cloudflare/kumo/components/button';
import { Tabs } from '@cloudflare/kumo';
import { TOOL_TABS_PROPS } from '../modules/kumoTabs.js';
import { APP_VERSION } from '../modules/appVersion.js';
import AppPageHeader, { AppBreadcrumbs } from './AppPageHeader.jsx';
import { AppCard } from './ui/AppPrimitives.jsx';
import {
Expand Down Expand Up @@ -754,7 +755,7 @@ function MainLayout() {
· 已运行 {appProcessUptimeMeasuredAt > 0 ? formatAppProcessUptime(displayedAppProcessUptime) : '加载中'}
</span>
</div>
<div className="flex min-w-0 items-center justify-end gap-4">
<div className="flex min-w-0 items-center justify-end gap-3">
{dashboardFooterRecordNumber ? (
<a
href="https://beian.miit.gov.cn/"
Expand All @@ -773,6 +774,13 @@ function MainLayout() {
>
GitHub
</a>
<span
className="shrink-0 font-mono text-[10px] tabular-nums text-kumo-subtle"
aria-label={`版本 ${APP_VERSION}`}
title={`API Monitor ${APP_VERSION}`}
>
{APP_VERSION}
</span>
</div>
</footer>
)}
Expand Down
1 change: 1 addition & 0 deletions src/js/modules/appVersion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const APP_VERSION = import.meta.env.VITE_APP_VERSION || 'dev-local';
49 changes: 49 additions & 0 deletions src/js/modules/appVersionResolver.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from 'vitest';
import {
formatDevelopmentVersion,
normalizeReleaseVersion,
resolveAppVersion,
} from '../../../tools/app-version.mjs';

describe('application version resolver', () => {
it('formats development versions from the final four commit characters', () => {
expect(formatDevelopmentVersion('0123456789ABCDEF')).toBe('dev-cdef');
expect(formatDevelopmentVersion('')).toBe('dev-local');
});

it('normalizes release versions to a v-prefixed semantic version', () => {
expect(normalizeReleaseVersion('2.0.1')).toBe('v2.0.1');
expect(normalizeReleaseVersion('v2.1.0-beta.1')).toBe('v2.1.0-beta.1');
});

it('uses the package version for main branch builds', () => {
expect(
resolveAppVersion({
branchName: 'main',
commitSha: 'abcdef12',
packageVersion: '2.0.1',
})
).toBe('v2.0.1');
});

it('uses the tag for release builds and the hash for development builds', () => {
expect(
resolveAppVersion({
refName: 'refs/tags/v3.0.0',
packageVersion: '2.0.1',
})
).toBe('v3.0.0');
expect(
resolveAppVersion({
branchName: 'dev',
commitSha: '0123456789abcdef',
packageVersion: '2.0.1',
})
).toBe('dev-cdef');
});

it('allows CI and Docker builds to provide an explicit version', () => {
expect(resolveAppVersion({ explicitVersion: '2.0.2' })).toBe('v2.0.2');
expect(resolveAppVersion({ explicitVersion: 'dev-9abc' })).toBe('dev-9abc');
});
});
3 changes: 2 additions & 1 deletion src/js/pages/SettingsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import useStore, {
normalizeUserSettings,
} from '../store.js';
import { MODULE_TABS_PROPS } from '../modules/kumoTabs.js';
import { APP_VERSION } from '../modules/appVersion.js';
import { AppCard, SectionCard, cx } from '../components/ui/AppPrimitives.jsx';
import CodeEditor from '../components/ui/CodeEditor.jsx';
import { BackupPanel } from './BackupPage.jsx';
Expand Down Expand Up @@ -1466,7 +1467,7 @@ function SettingsPage() {
<div className="grid items-start gap-4 overflow-auto px-px py-px pr-px lg:grid-cols-1">
<SectionCard
title="API Monitor"
description="React 前端 + Go 后端"
description={APP_VERSION}
icon={<img src="/logo.svg" alt="" className="h-6 w-6 object-contain" />}
bodyPadding="lg"
>
Expand Down
Loading
Loading