mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
refactor: 重构GitHub Actions发布流程和修复管理界面问题
- 删除旧的auto-release.yml工作流,避免循环触发问题 - 新增auto-version-bump.yml:自动同步VERSION文件与release版本 - 新增release-on-version.yml:基于VERSION文件变更自动创建release - 更新docker-publish.yml:简化触发条件,仅在tag推送时构建 - 添加RELEASE_PROCESS.md文档:详细说明新的发布流程 - 修复web管理界面:解决OAuth账户token刷新和代理配置相关问题 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
94
.github/RELEASE_PROCESS.md
vendored
Normal file
94
.github/RELEASE_PROCESS.md
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# 发布流程说明
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本项目采用全自动化的版本管理和发布流程。VERSION文件由GitHub Actions自动维护,无需手动修改。
|
||||||
|
|
||||||
|
## 自动发布流程
|
||||||
|
|
||||||
|
### 1. 工作原理
|
||||||
|
|
||||||
|
1. **代码推送**: 当你推送代码到main分支时
|
||||||
|
2. **自动版本更新**: `auto-version-bump.yml`会:
|
||||||
|
- 检测是否有实质性代码变更(排除.md文件、docs/目录等)
|
||||||
|
- 如果有代码变更,自动将版本号+1并更新VERSION文件
|
||||||
|
- 提交VERSION文件更新到main分支
|
||||||
|
3. **自动发布**: `release-on-version.yml`会:
|
||||||
|
- 检测到只有VERSION文件变更的提交
|
||||||
|
- 自动创建Git tag
|
||||||
|
- 创建GitHub Release
|
||||||
|
- 构建并推送Docker镜像
|
||||||
|
- 发送Telegram通知(如果配置)
|
||||||
|
|
||||||
|
### 2. 工作流文件说明
|
||||||
|
|
||||||
|
- **auto-version-bump.yml**: 自动检测代码变更并更新VERSION文件
|
||||||
|
- **release-on-version.yml**: 检测VERSION文件单独提交并触发发布
|
||||||
|
- **docker-publish.yml**: 在tag创建时构建Docker镜像(备用)
|
||||||
|
- **release.yml**: 在tag创建时生成Release(备用)
|
||||||
|
|
||||||
|
### 3. 版本号规范
|
||||||
|
|
||||||
|
- 使用语义化版本号:`MAJOR.MINOR.PATCH`
|
||||||
|
- 默认自动递增PATCH版本(例如:1.1.10 → 1.1.11)
|
||||||
|
- VERSION文件只包含版本号,不包含`v`前缀
|
||||||
|
- Git tag会自动添加`v`前缀
|
||||||
|
|
||||||
|
### 4. 触发条件
|
||||||
|
|
||||||
|
**会触发版本更新的文件变更**:
|
||||||
|
- 源代码文件(.js, .ts, .jsx, .tsx等)
|
||||||
|
- 配置文件(package.json, Dockerfile等)
|
||||||
|
- 其他功能性文件
|
||||||
|
|
||||||
|
**不会触发版本更新的文件变更**:
|
||||||
|
- Markdown文件(*.md)
|
||||||
|
- 文档目录(docs/)
|
||||||
|
- GitHub配置(.github/)
|
||||||
|
- VERSION文件本身
|
||||||
|
- .gitignore、LICENSE等
|
||||||
|
|
||||||
|
## 使用指南
|
||||||
|
|
||||||
|
### 正常开发流程
|
||||||
|
|
||||||
|
1. 进行代码开发和修改
|
||||||
|
2. 提交并推送到main分支
|
||||||
|
3. 系统自动完成版本更新和发布
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 正常的开发流程
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: 添加新功能"
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# GitHub Actions会自动:
|
||||||
|
# 1. 检测到代码变更
|
||||||
|
# 2. 更新VERSION文件(例如:1.1.10 → 1.1.11)
|
||||||
|
# 3. 创建新的release和Docker镜像
|
||||||
|
```
|
||||||
|
|
||||||
|
### 跳过版本更新
|
||||||
|
|
||||||
|
如果只是更新文档或其他非代码文件,系统会自动识别并跳过版本更新。
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 版本没有自动更新
|
||||||
|
|
||||||
|
1. 检查是否有实质性代码变更
|
||||||
|
2. 查看GitHub Actions运行日志
|
||||||
|
3. 确认推送的是main分支
|
||||||
|
|
||||||
|
### 需要手动触发发布
|
||||||
|
|
||||||
|
如果需要手动控制版本:
|
||||||
|
1. 直接修改VERSION文件
|
||||||
|
2. 提交并推送
|
||||||
|
3. 系统会检测到VERSION变更并触发发布
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- **不要**在同一个提交中既修改代码又修改VERSION文件
|
||||||
|
- **不要**手动创建tag,让系统自动管理
|
||||||
|
- 系统会自动避免死循环(GitHub Actions的提交不会触发新的版本更新)
|
||||||
267
.github/workflows/auto-release.yml
vendored
267
.github/workflows/auto-release.yml
vendored
@@ -1,267 +0,0 @@
|
|||||||
name: Auto Release on Push to Main
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- 'docs/**'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
auto-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
|
|
||||||
- name: Get latest tag
|
|
||||||
id: get_latest_tag
|
|
||||||
run: |
|
|
||||||
# 获取最新的版本标签
|
|
||||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
|
||||||
echo "Latest tag: $LATEST_TAG"
|
|
||||||
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# 提取版本号部分(去掉 v 前缀)
|
|
||||||
VERSION=${LATEST_TAG#v}
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Calculate next version
|
|
||||||
id: next_version
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.get_latest_tag.outputs.version }}"
|
|
||||||
|
|
||||||
# 分割版本号为 major.minor.patch
|
|
||||||
IFS='.' read -r -a version_parts <<< "$VERSION"
|
|
||||||
MAJOR="${version_parts[0]:-0}"
|
|
||||||
MINOR="${version_parts[1]:-0}"
|
|
||||||
PATCH="${version_parts[2]:-0}"
|
|
||||||
|
|
||||||
# 递增 patch 版本号
|
|
||||||
NEW_PATCH=$((PATCH + 1))
|
|
||||||
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
|
||||||
NEW_TAG="v${NEW_VERSION}"
|
|
||||||
|
|
||||||
echo "New version: $NEW_VERSION"
|
|
||||||
echo "New tag: $NEW_TAG"
|
|
||||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Install git-cliff
|
|
||||||
run: |
|
|
||||||
wget -q https://github.com/orhun/git-cliff/releases/download/v1.4.0/git-cliff-1.4.0-x86_64-unknown-linux-gnu.tar.gz
|
|
||||||
tar -xzf git-cliff-1.4.0-x86_64-unknown-linux-gnu.tar.gz
|
|
||||||
chmod +x git-cliff-1.4.0/git-cliff
|
|
||||||
sudo mv git-cliff-1.4.0/git-cliff /usr/local/bin/
|
|
||||||
|
|
||||||
- name: Generate changelog
|
|
||||||
id: changelog
|
|
||||||
run: |
|
|
||||||
# 获取自上次标签以来的提交,如果 git-cliff 输出包含 [unreleased],则移除它
|
|
||||||
CHANGELOG=$(git-cliff --config .github/cliff.toml --unreleased --strip header || echo "- 代码优化和改进")
|
|
||||||
# 移除 [unreleased] 标记
|
|
||||||
CHANGELOG=$(echo "$CHANGELOG" | sed 's/\[unreleased\]//g' | sed '/^$/d')
|
|
||||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
|
||||||
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Check if there are changes to release
|
|
||||||
id: check_changes
|
|
||||||
run: |
|
|
||||||
# 检查自上次标签以来是否有新的提交(排除只修改VERSION文件的提交)
|
|
||||||
LATEST_TAG="${{ steps.get_latest_tag.outputs.latest_tag }}"
|
|
||||||
if [ "$LATEST_TAG" = "v0.0.0" ]; then
|
|
||||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
# 获取除了VERSION文件外的其他文件变更
|
|
||||||
CHANGED_FILES=$(git diff --name-only $LATEST_TAG..HEAD | grep -v "^VERSION$" || true)
|
|
||||||
|
|
||||||
if [ -n "$CHANGED_FILES" ]; then
|
|
||||||
echo "Found changes in files other than VERSION:"
|
|
||||||
echo "$CHANGED_FILES"
|
|
||||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "No significant changes found (only VERSION file changed)"
|
|
||||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create and push tag
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
run: |
|
|
||||||
NEW_TAG="${{ steps.next_version.outputs.new_tag }}"
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
|
|
||||||
git push origin "$NEW_TAG"
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ steps.next_version.outputs.new_tag }}
|
|
||||||
name: Release ${{ steps.next_version.outputs.new_version }}
|
|
||||||
body: |
|
|
||||||
## 🐳 Docker 镜像
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker pull ${{ secrets.DOCKERHUB_USERNAME || 'weishaw' }}/claude-relay-service:${{ steps.next_version.outputs.new_tag }}
|
|
||||||
docker pull ${{ secrets.DOCKERHUB_USERNAME || 'weishaw' }}/claude-relay-service:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📦 主要更新
|
|
||||||
|
|
||||||
${{ steps.changelog.outputs.content }}
|
|
||||||
|
|
||||||
## 📋 完整更新日志
|
|
||||||
|
|
||||||
查看 [所有版本](https://github.com/${{ github.repository }}/releases)
|
|
||||||
|
|
||||||
---
|
|
||||||
*此版本由 GitHub Actions 自动发布*
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
generate_release_notes: true
|
|
||||||
|
|
||||||
- name: Update VERSION file
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
run: |
|
|
||||||
# 强制更新 VERSION 文件为最新版本
|
|
||||||
echo "${{ steps.next_version.outputs.new_version }}" > VERSION
|
|
||||||
|
|
||||||
# 检查是否有更改
|
|
||||||
if git diff --quiet VERSION; then
|
|
||||||
echo "VERSION file already up to date"
|
|
||||||
else
|
|
||||||
git add VERSION
|
|
||||||
echo "Updated VERSION file to ${{ steps.next_version.outputs.new_version }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Commit VERSION update
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
run: |
|
|
||||||
# 提交 VERSION 更新
|
|
||||||
if git diff --quiet VERSION; then
|
|
||||||
echo "No changes to VERSION"
|
|
||||||
else
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add VERSION
|
|
||||||
git commit -m "chore: update VERSION to ${{ steps.next_version.outputs.new_version }} for ${{ steps.next_version.outputs.new_tag }} [skip ci]"
|
|
||||||
git push origin main
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Docker 构建和推送步骤
|
|
||||||
- name: Set up QEMU
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/claude-relay-service:${{ steps.next_version.outputs.new_tag }}
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/claude-relay-service:latest
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/claude-relay-service:${{ steps.next_version.outputs.new_version }}
|
|
||||||
labels: |
|
|
||||||
org.opencontainers.image.version=${{ steps.next_version.outputs.new_version }}
|
|
||||||
org.opencontainers.image.created=${{ steps.next_version.outputs.created }}
|
|
||||||
org.opencontainers.image.revision=${{ github.sha }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: Send Telegram Notification
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true' && env.TELEGRAM_BOT_TOKEN != '' && env.TELEGRAM_CHAT_ID != ''
|
|
||||||
env:
|
|
||||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
||||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.next_version.outputs.new_version }}"
|
|
||||||
TAG="${{ steps.next_version.outputs.new_tag }}"
|
|
||||||
REPO="${{ github.repository }}"
|
|
||||||
|
|
||||||
# 获取更新内容并限制长度
|
|
||||||
CHANGELOG="${{ steps.changelog.outputs.content }}"
|
|
||||||
CHANGELOG_TRUNCATED=$(echo "$CHANGELOG" | head -c 1000)
|
|
||||||
if [ ${#CHANGELOG} -gt 1000 ]; then
|
|
||||||
CHANGELOG_TRUNCATED="${CHANGELOG_TRUNCATED}..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 构建消息内容
|
|
||||||
MESSAGE="🚀 *Claude Relay Service 新版本发布!*"$'\n'$'\n'
|
|
||||||
MESSAGE+="📦 版本号: \`${VERSION}\`"$'\n'$'\n'
|
|
||||||
MESSAGE+="📝 *更新内容:*"$'\n'
|
|
||||||
MESSAGE+="${CHANGELOG_TRUNCATED}"$'\n'$'\n'
|
|
||||||
MESSAGE+="🐳 *Docker 部署:*"$'\n'
|
|
||||||
MESSAGE+="\`\`\`bash"$'\n'
|
|
||||||
MESSAGE+="docker pull weishaw/claude-relay-service:${TAG}"$'\n'
|
|
||||||
MESSAGE+="docker pull weishaw/claude-relay-service:latest"$'\n'
|
|
||||||
MESSAGE+="\`\`\`"$'\n'$'\n'
|
|
||||||
MESSAGE+="🔗 *相关链接:*"$'\n'
|
|
||||||
MESSAGE+="• [GitHub Release](https://github.com/${REPO}/releases/tag/${TAG})"$'\n'
|
|
||||||
MESSAGE+="• [完整更新日志](https://github.com/${REPO}/releases)"$'\n'
|
|
||||||
MESSAGE+="• [Docker Hub](https://hub.docker.com/r/weishaw/claude-relay-service)"$'\n'$'\n'
|
|
||||||
MESSAGE+="#ClaudeRelay #Update #v${VERSION//./_}"
|
|
||||||
|
|
||||||
# 使用 jq 构建 JSON 并发送
|
|
||||||
jq -n \
|
|
||||||
--arg chat_id "${TELEGRAM_CHAT_ID}" \
|
|
||||||
--arg text "${MESSAGE}" \
|
|
||||||
'{
|
|
||||||
chat_id: $chat_id,
|
|
||||||
text: $text,
|
|
||||||
parse_mode: "Markdown",
|
|
||||||
disable_web_page_preview: false
|
|
||||||
}' | \
|
|
||||||
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d @-
|
|
||||||
|
|
||||||
- name: Ensure VERSION file is in sync
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
# 在工作流结束时确保VERSION文件与最新tag同步
|
|
||||||
LATEST_TAG=$(git tag --sort=-version:refname | head -1 || echo "v0.0.0")
|
|
||||||
if [ "$LATEST_TAG" != "v0.0.0" ]; then
|
|
||||||
LATEST_VERSION=${LATEST_TAG#v}
|
|
||||||
CURRENT_VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0")
|
|
||||||
|
|
||||||
if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ]; then
|
|
||||||
echo "Updating VERSION file to match latest release: $CURRENT_VERSION -> $LATEST_VERSION"
|
|
||||||
echo "$LATEST_VERSION" > VERSION
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add VERSION
|
|
||||||
git commit -m "chore: sync VERSION file with release $LATEST_TAG [skip ci]" || echo "No changes to commit"
|
|
||||||
git push origin main || echo "Nothing to push"
|
|
||||||
else
|
|
||||||
echo "VERSION file is already in sync: $CURRENT_VERSION"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
102
.github/workflows/auto-version-bump.yml
vendored
Normal file
102
.github/workflows/auto-version-bump.yml
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
name: Auto Version Bump
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
version-bump:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# 跳过由GitHub Actions创建的提交,避免死循环
|
||||||
|
if: github.event.pusher.name != 'github-actions[bot]'
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Check if version bump is needed
|
||||||
|
id: check
|
||||||
|
run: |
|
||||||
|
# 获取当前提交的文件变更
|
||||||
|
CHANGED_FILES=$(git diff --name-only HEAD~1..HEAD 2>/dev/null || git diff --name-only $(git rev-list --max-parents=0 HEAD)..HEAD)
|
||||||
|
echo "Changed files:"
|
||||||
|
echo "$CHANGED_FILES"
|
||||||
|
|
||||||
|
# 检查是否只有无关文件(.md, docs/, .github/等)
|
||||||
|
SIGNIFICANT_CHANGES=false
|
||||||
|
while IFS= read -r file; do
|
||||||
|
# 跳过空行
|
||||||
|
[ -z "$file" ] && continue
|
||||||
|
|
||||||
|
# 检查是否是需要忽略的文件
|
||||||
|
if [[ ! "$file" =~ \.(md|txt)$ ]] &&
|
||||||
|
[[ ! "$file" =~ ^docs/ ]] &&
|
||||||
|
[[ ! "$file" =~ ^\.github/ ]] &&
|
||||||
|
[[ "$file" != "VERSION" ]] &&
|
||||||
|
[[ "$file" != ".gitignore" ]] &&
|
||||||
|
[[ "$file" != "LICENSE" ]]; then
|
||||||
|
echo "Found significant change in: $file"
|
||||||
|
SIGNIFICANT_CHANGES=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done <<< "$CHANGED_FILES"
|
||||||
|
|
||||||
|
if [ "$SIGNIFICANT_CHANGES" = true ]; then
|
||||||
|
echo "Significant changes detected, version bump needed"
|
||||||
|
echo "needs_bump=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "No significant changes, skipping version bump"
|
||||||
|
echo "needs_bump=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get current version
|
||||||
|
if: steps.check.outputs.needs_bump == 'true'
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
# 获取最新的tag版本
|
||||||
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
|
echo "Latest tag: $LATEST_TAG"
|
||||||
|
|
||||||
|
# 从tag中提取版本号
|
||||||
|
VERSION=${LATEST_TAG#v}
|
||||||
|
echo "Current version: $VERSION"
|
||||||
|
echo "current_version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Calculate next version
|
||||||
|
if: steps.check.outputs.needs_bump == 'true'
|
||||||
|
id: next_version
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.get_version.outputs.current_version }}"
|
||||||
|
|
||||||
|
# 分割版本号
|
||||||
|
IFS='.' read -r -a version_parts <<< "$VERSION"
|
||||||
|
MAJOR="${version_parts[0]:-0}"
|
||||||
|
MINOR="${version_parts[1]:-0}"
|
||||||
|
PATCH="${version_parts[2]:-0}"
|
||||||
|
|
||||||
|
# 默认递增patch版本
|
||||||
|
NEW_PATCH=$((PATCH + 1))
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
||||||
|
|
||||||
|
echo "New version: $NEW_VERSION"
|
||||||
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Update VERSION file
|
||||||
|
if: steps.check.outputs.needs_bump == 'true'
|
||||||
|
run: |
|
||||||
|
echo "${{ steps.next_version.outputs.new_version }}" > VERSION
|
||||||
|
|
||||||
|
# 配置git
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
# 提交VERSION文件
|
||||||
|
git add VERSION
|
||||||
|
git commit -m "chore: bump version to ${{ steps.next_version.outputs.new_version }}"
|
||||||
|
git push origin main
|
||||||
3
.github/workflows/docker-publish.yml
vendored
3
.github/workflows/docker-publish.yml
vendored
@@ -2,11 +2,8 @@ name: Docker Build & Push
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|||||||
185
.github/workflows/release-on-version.yml
vendored
Normal file
185
.github/workflows/release-on-version.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
name: Release on Version Change
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'VERSION'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-and-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# 只处理由GitHub Actions提交的VERSION更新
|
||||||
|
if: github.event.pusher.name == 'github-actions[bot]'
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Verify only VERSION changed
|
||||||
|
id: verify
|
||||||
|
run: |
|
||||||
|
# 获取最后一次提交变更的文件
|
||||||
|
CHANGED_FILES=$(git diff --name-only HEAD~1..HEAD)
|
||||||
|
echo "Changed files: $CHANGED_FILES"
|
||||||
|
|
||||||
|
# 检查是否只有VERSION文件
|
||||||
|
if [ "$CHANGED_FILES" = "VERSION" ]; then
|
||||||
|
echo "Only VERSION file changed, proceeding with release"
|
||||||
|
echo "should_release=true" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# 读取新版本号
|
||||||
|
NEW_VERSION=$(cat VERSION | tr -d '[:space:]')
|
||||||
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "new_tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Other files changed besides VERSION, skipping release"
|
||||||
|
echo "should_release=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install git-cliff
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
run: |
|
||||||
|
wget -q https://github.com/orhun/git-cliff/releases/download/v1.4.0/git-cliff-1.4.0-x86_64-unknown-linux-gnu.tar.gz
|
||||||
|
tar -xzf git-cliff-1.4.0-x86_64-unknown-linux-gnu.tar.gz
|
||||||
|
chmod +x git-cliff-1.4.0/git-cliff
|
||||||
|
sudo mv git-cliff-1.4.0/git-cliff /usr/local/bin/
|
||||||
|
|
||||||
|
- name: Generate changelog
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
# 获取上一个tag以来的更新日志
|
||||||
|
LATEST_TAG=$(git describe --tags --abbrev=0 --exclude="${{ steps.verify.outputs.new_tag }}" 2>/dev/null || echo "")
|
||||||
|
if [ -n "$LATEST_TAG" ]; then
|
||||||
|
# 排除VERSION文件的提交
|
||||||
|
CHANGELOG=$(git-cliff --config .github/cliff.toml $LATEST_TAG..HEAD --strip header | grep -v "bump version" | sed '/^$/d' || echo "- 代码优化和改进")
|
||||||
|
else
|
||||||
|
CHANGELOG=$(git-cliff --config .github/cliff.toml --strip header || echo "- 初始版本发布")
|
||||||
|
fi
|
||||||
|
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create and push tag
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
run: |
|
||||||
|
NEW_TAG="${{ steps.verify.outputs.new_tag }}"
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
|
||||||
|
git push origin "$NEW_TAG"
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.verify.outputs.new_tag }}
|
||||||
|
name: Release ${{ steps.verify.outputs.new_version }}
|
||||||
|
body: |
|
||||||
|
## 🐳 Docker 镜像
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull ${{ secrets.DOCKERHUB_USERNAME || 'weishaw' }}/claude-relay-service:${{ steps.verify.outputs.new_tag }}
|
||||||
|
docker pull ${{ secrets.DOCKERHUB_USERNAME || 'weishaw' }}/claude-relay-service:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 主要更新
|
||||||
|
|
||||||
|
${{ steps.changelog.outputs.content }}
|
||||||
|
|
||||||
|
## 📋 完整更新日志
|
||||||
|
|
||||||
|
查看 [所有版本](https://github.com/${{ github.repository }}/releases)
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
generate_release_notes: true
|
||||||
|
|
||||||
|
# Docker构建步骤
|
||||||
|
- name: Set up QEMU
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: docker.io
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
if: steps.verify.outputs.should_release == 'true'
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ secrets.DOCKERHUB_USERNAME }}/claude-relay-service:${{ steps.verify.outputs.new_tag }}
|
||||||
|
${{ secrets.DOCKERHUB_USERNAME }}/claude-relay-service:latest
|
||||||
|
${{ secrets.DOCKERHUB_USERNAME }}/claude-relay-service:${{ steps.verify.outputs.new_version }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.version=${{ steps.verify.outputs.new_version }}
|
||||||
|
org.opencontainers.image.revision=${{ github.sha }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Send Telegram Notification
|
||||||
|
if: steps.verify.outputs.should_release == 'true' && env.TELEGRAM_BOT_TOKEN != '' && env.TELEGRAM_CHAT_ID != ''
|
||||||
|
env:
|
||||||
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.verify.outputs.new_version }}"
|
||||||
|
TAG="${{ steps.verify.outputs.new_tag }}"
|
||||||
|
REPO="${{ github.repository }}"
|
||||||
|
|
||||||
|
# 获取更新内容并限制长度
|
||||||
|
CHANGELOG="${{ steps.changelog.outputs.content }}"
|
||||||
|
CHANGELOG_TRUNCATED=$(echo "$CHANGELOG" | head -c 1000)
|
||||||
|
if [ ${#CHANGELOG} -gt 1000 ]; then
|
||||||
|
CHANGELOG_TRUNCATED="${CHANGELOG_TRUNCATED}..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建消息内容
|
||||||
|
MESSAGE="🚀 *Claude Relay Service 新版本发布!*"$'\n'$'\n'
|
||||||
|
MESSAGE+="📦 版本号: \`${VERSION}\`"$'\n'$'\n'
|
||||||
|
MESSAGE+="📝 *更新内容:*"$'\n'
|
||||||
|
MESSAGE+="${CHANGELOG_TRUNCATED}"$'\n'$'\n'
|
||||||
|
MESSAGE+="🐳 *Docker 部署:*"$'\n'
|
||||||
|
MESSAGE+="\`\`\`bash"$'\n'
|
||||||
|
MESSAGE+="docker pull weishaw/claude-relay-service:${TAG}"$'\n'
|
||||||
|
MESSAGE+="docker pull weishaw/claude-relay-service:latest"$'\n'
|
||||||
|
MESSAGE+="\`\`\`"$'\n'$'\n'
|
||||||
|
MESSAGE+="🔗 *相关链接:*"$'\n'
|
||||||
|
MESSAGE+="• [GitHub Release](https://github.com/${REPO}/releases/tag/${TAG})"$'\n'
|
||||||
|
MESSAGE+="• [完整更新日志](https://github.com/${REPO}/releases)"$'\n'
|
||||||
|
MESSAGE+="• [Docker Hub](https://hub.docker.com/r/weishaw/claude-relay-service)"$'\n'$'\n'
|
||||||
|
MESSAGE+="#ClaudeRelay #Update #v${VERSION//./_}"
|
||||||
|
|
||||||
|
# 使用 jq 构建 JSON 并发送
|
||||||
|
jq -n \
|
||||||
|
--arg chat_id "${TELEGRAM_CHAT_ID}" \
|
||||||
|
--arg text "${MESSAGE}" \
|
||||||
|
'{
|
||||||
|
chat_id: $chat_id,
|
||||||
|
text: $text,
|
||||||
|
parse_mode: "Markdown",
|
||||||
|
disable_web_page_preview: false
|
||||||
|
}' | \
|
||||||
|
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @-
|
||||||
246
web/admin/app.js
246
web/admin/app.js
@@ -368,6 +368,40 @@ const app = createApp({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
// 统一的API请求方法,处理token过期等错误
|
||||||
|
async apiRequest(url, options = {}) {
|
||||||
|
try {
|
||||||
|
const defaultOptions = {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + this.authToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, defaultOptions);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// 检查是否是token过期错误
|
||||||
|
if (!response.ok && (response.status === 401 ||
|
||||||
|
(data.error === 'Invalid admin token' ||
|
||||||
|
data.message === 'Invalid or expired admin session'))) {
|
||||||
|
// 清理本地存储并刷新页面
|
||||||
|
localStorage.removeItem('authToken');
|
||||||
|
this.authToken = null;
|
||||||
|
this.isLoggedIn = false;
|
||||||
|
location.reload();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API request error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 显示确认弹窗
|
// 显示确认弹窗
|
||||||
showConfirm(title, message, confirmText = '继续', cancelText = '取消') {
|
showConfirm(title, message, confirmText = '继续', cancelText = '取消') {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -766,18 +800,17 @@ const app = createApp({
|
|||||||
? '/admin/gemini-accounts/generate-auth-url'
|
? '/admin/gemini-accounts/generate-auth-url'
|
||||||
: '/admin/claude-accounts/generate-auth-url';
|
: '/admin/claude-accounts/generate-auth-url';
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const data = await this.apiRequest(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
proxy: proxy
|
proxy: proxy
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
if (this.accountForm.platform === 'gemini') {
|
if (this.accountForm.platform === 'gemini') {
|
||||||
@@ -822,19 +855,18 @@ const app = createApp({
|
|||||||
this.createAccountLoading = true;
|
this.createAccountLoading = true;
|
||||||
try {
|
try {
|
||||||
// 首先交换authorization code获取token
|
// 首先交换authorization code获取token
|
||||||
const exchangeResponse = await fetch('/admin/claude-accounts/exchange-code', {
|
const exchangeData = await this.apiRequest('/admin/claude-accounts/exchange-code', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
sessionId: this.oauthData.sessionId,
|
sessionId: this.oauthData.sessionId,
|
||||||
callbackUrl: this.oauthData.callbackUrl
|
callbackUrl: this.oauthData.callbackUrl
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const exchangeData = await exchangeResponse.json();
|
if (!exchangeData) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!exchangeData.success) {
|
if (!exchangeData.success) {
|
||||||
// Display detailed error information
|
// Display detailed error information
|
||||||
@@ -857,12 +889,8 @@ const app = createApp({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建账户
|
// 创建账户
|
||||||
const createResponse = await fetch('/admin/claude-accounts', {
|
const createData = await this.apiRequest('/admin/claude-accounts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: this.accountForm.name,
|
name: this.accountForm.name,
|
||||||
description: this.accountForm.description,
|
description: this.accountForm.description,
|
||||||
@@ -872,7 +900,10 @@ const app = createApp({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const createData = await createResponse.json();
|
if (!createData) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (createData.success) {
|
if (createData.success) {
|
||||||
this.showToast('OAuth账户创建成功!', 'success', '账户创建成功');
|
this.showToast('OAuth账户创建成功!', 'success', '账户创建成功');
|
||||||
@@ -1025,18 +1056,18 @@ const app = createApp({
|
|||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/gemini-accounts/poll-auth-status', {
|
const data = await this.apiRequest('/admin/gemini-accounts/poll-auth-status', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
sessionId: this.geminiOauthData.sessionId
|
sessionId: this.geminiOauthData.sessionId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
this.stopGeminiOAuthPolling();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// 授权成功
|
// 授权成功
|
||||||
@@ -1073,19 +1104,18 @@ const app = createApp({
|
|||||||
this.createAccountLoading = true;
|
this.createAccountLoading = true;
|
||||||
try {
|
try {
|
||||||
// 首先交换授权码获取 tokens
|
// 首先交换授权码获取 tokens
|
||||||
const tokenResponse = await fetch('/admin/gemini-accounts/exchange-code', {
|
const tokenData = await this.apiRequest('/admin/gemini-accounts/exchange-code', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
code: this.geminiOauthData.code,
|
code: this.geminiOauthData.code,
|
||||||
sessionId: this.geminiOauthData.sessionId
|
sessionId: this.geminiOauthData.sessionId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const tokenData = await tokenResponse.json();
|
if (!tokenData) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tokenData.success) {
|
if (!tokenData.success) {
|
||||||
this.showToast(tokenData.message || '授权码交换失败', 'error', '交换失败');
|
this.showToast(tokenData.message || '授权码交换失败', 'error', '交换失败');
|
||||||
@@ -1105,12 +1135,8 @@ const app = createApp({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建账户
|
// 创建账户
|
||||||
const response = await fetch('/admin/gemini-accounts', {
|
const data = await this.apiRequest('/admin/gemini-accounts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: this.accountForm.name,
|
name: this.accountForm.name,
|
||||||
description: this.accountForm.description,
|
description: this.accountForm.description,
|
||||||
@@ -1121,7 +1147,10 @@ const app = createApp({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.showToast('Gemini OAuth账户创建成功!', 'success', '账户创建成功');
|
this.showToast('Gemini OAuth账户创建成功!', 'success', '账户创建成功');
|
||||||
@@ -1315,7 +1344,8 @@ const app = createApp({
|
|||||||
// 记录当前用户名(使用服务器返回的真实用户名)
|
// 记录当前用户名(使用服务器返回的真实用户名)
|
||||||
this.currentUser.username = data.username;
|
this.currentUser.username = data.username;
|
||||||
|
|
||||||
this.loadDashboard();
|
// 登录成功后刷新页面以重新加载所有数据
|
||||||
|
location.reload();
|
||||||
} else {
|
} else {
|
||||||
this.loginError = data.message;
|
this.loginError = data.message;
|
||||||
}
|
}
|
||||||
@@ -1330,11 +1360,12 @@ const app = createApp({
|
|||||||
// 加载当前用户信息
|
// 加载当前用户信息
|
||||||
async loadCurrentUser() {
|
async loadCurrentUser() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/web/auth/user', {
|
const data = await this.apiRequest('/web/auth/user');
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.currentUser.username = data.user.username;
|
this.currentUser.username = data.user.username;
|
||||||
@@ -1380,14 +1411,14 @@ const app = createApp({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用后端接口检查更新
|
// 使用后端接口检查更新
|
||||||
const response = await fetch('/admin/check-updates', {
|
const result = await this.apiRequest('/admin/check-updates');
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${this.authToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (!result) {
|
||||||
const result = await response.json();
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
const data = result.data;
|
const data = result.data;
|
||||||
|
|
||||||
this.versionInfo.current = data.current;
|
this.versionInfo.current = data.current;
|
||||||
@@ -1501,12 +1532,8 @@ const app = createApp({
|
|||||||
|
|
||||||
this.changePasswordLoading = true;
|
this.changePasswordLoading = true;
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/web/auth/change-password', {
|
const result = await this.apiRequest('/web/auth/change-password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
newUsername: this.changePasswordForm.newUsername || undefined,
|
newUsername: this.changePasswordForm.newUsername || undefined,
|
||||||
currentPassword: this.changePasswordForm.currentPassword,
|
currentPassword: this.changePasswordForm.currentPassword,
|
||||||
@@ -1514,7 +1541,10 @@ const app = createApp({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
if (!result) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.showToast('账户信息修改成功,即将退出登录', 'success');
|
this.showToast('账户信息修改成功,即将退出登录', 'success');
|
||||||
@@ -1563,24 +1593,16 @@ const app = createApp({
|
|||||||
|
|
||||||
async loadDashboard() {
|
async loadDashboard() {
|
||||||
try {
|
try {
|
||||||
const [dashboardResponse, costsResponse] = await Promise.all([
|
const [dashboardData, todayCostsData, totalCostsData] = await Promise.all([
|
||||||
fetch('/admin/dashboard', {
|
this.apiRequest('/admin/dashboard'),
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
this.apiRequest('/admin/usage-costs?period=today'),
|
||||||
}),
|
this.apiRequest('/admin/usage-costs?period=all')
|
||||||
Promise.all([
|
|
||||||
fetch('/admin/usage-costs?period=today', {
|
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
}),
|
|
||||||
fetch('/admin/usage-costs?period=all', {
|
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
})
|
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const dashboardData = await dashboardResponse.json();
|
if (!dashboardData || !todayCostsData || !totalCostsData) {
|
||||||
const [todayCostsResponse, totalCostsResponse] = costsResponse;
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
const todayCostsData = await todayCostsResponse.json();
|
return;
|
||||||
const totalCostsData = await totalCostsResponse.json();
|
}
|
||||||
|
|
||||||
if (dashboardData.success) {
|
if (dashboardData.success) {
|
||||||
const overview = dashboardData.data.overview || {};
|
const overview = dashboardData.data.overview || {};
|
||||||
@@ -1629,10 +1651,12 @@ const app = createApp({
|
|||||||
this.apiKeysLoading = true;
|
this.apiKeysLoading = true;
|
||||||
console.log('Loading API Keys...');
|
console.log('Loading API Keys...');
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/api-keys', {
|
const data = await this.apiRequest('/admin/api-keys');
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
});
|
if (!data) {
|
||||||
const data = await response.json();
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('API Keys response:', data);
|
console.log('API Keys response:', data);
|
||||||
|
|
||||||
@@ -1674,19 +1698,15 @@ const app = createApp({
|
|||||||
this.accountsLoading = true;
|
this.accountsLoading = true;
|
||||||
try {
|
try {
|
||||||
// 并行加载 Claude 和 Gemini 账户
|
// 并行加载 Claude 和 Gemini 账户
|
||||||
const [claudeResponse, geminiResponse] = await Promise.all([
|
const [claudeData, geminiData] = await Promise.all([
|
||||||
fetch('/admin/claude-accounts', {
|
this.apiRequest('/admin/claude-accounts'),
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
this.apiRequest('/admin/gemini-accounts')
|
||||||
}),
|
|
||||||
fetch('/admin/gemini-accounts', {
|
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
})
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [claudeData, geminiData] = await Promise.all([
|
if (!claudeData || !geminiData) {
|
||||||
claudeResponse.json(),
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
geminiResponse.json()
|
return;
|
||||||
]);
|
}
|
||||||
|
|
||||||
// 合并账户数据
|
// 合并账户数据
|
||||||
const allAccounts = [];
|
const allAccounts = [];
|
||||||
@@ -1728,10 +1748,12 @@ const app = createApp({
|
|||||||
async loadModelStats() {
|
async loadModelStats() {
|
||||||
this.modelStatsLoading = true;
|
this.modelStatsLoading = true;
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/model-stats?period=' + this.modelStatsPeriod, {
|
const data = await this.apiRequest('/admin/model-stats?period=' + this.modelStatsPeriod);
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
});
|
if (!data) {
|
||||||
const data = await response.json();
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.modelStats = data.data || [];
|
this.modelStats = data.data || [];
|
||||||
@@ -1749,12 +1771,8 @@ const app = createApp({
|
|||||||
async createApiKey() {
|
async createApiKey() {
|
||||||
this.createApiKeyLoading = true;
|
this.createApiKeyLoading = true;
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/api-keys', {
|
const data = await this.apiRequest('/admin/api-keys', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: this.apiKeyForm.name,
|
name: this.apiKeyForm.name,
|
||||||
tokenLimit: this.apiKeyForm.tokenLimit && this.apiKeyForm.tokenLimit.trim() ? parseInt(this.apiKeyForm.tokenLimit) : null,
|
tokenLimit: this.apiKeyForm.tokenLimit && this.apiKeyForm.tokenLimit.trim() ? parseInt(this.apiKeyForm.tokenLimit) : null,
|
||||||
@@ -1770,7 +1788,10 @@ const app = createApp({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// 设置新API Key数据并显示弹窗
|
// 设置新API Key数据并显示弹窗
|
||||||
@@ -1809,12 +1830,14 @@ const app = createApp({
|
|||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/api-keys/' + keyId, {
|
const data = await this.apiRequest('/admin/api-keys/' + keyId, {
|
||||||
method: 'DELETE',
|
method: 'DELETE'
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.showToast('API Key 删除成功', 'success', '删除成功');
|
this.showToast('API Key 删除成功', 'success', '删除成功');
|
||||||
@@ -1867,12 +1890,8 @@ const app = createApp({
|
|||||||
async updateApiKey() {
|
async updateApiKey() {
|
||||||
this.editApiKeyLoading = true;
|
this.editApiKeyLoading = true;
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/api-keys/' + this.editApiKeyForm.id, {
|
const data = await this.apiRequest('/admin/api-keys/' + this.editApiKeyForm.id, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.authToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
tokenLimit: this.editApiKeyForm.tokenLimit && this.editApiKeyForm.tokenLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.tokenLimit) : 0,
|
tokenLimit: this.editApiKeyForm.tokenLimit && this.editApiKeyForm.tokenLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.tokenLimit) : 0,
|
||||||
concurrencyLimit: this.editApiKeyForm.concurrencyLimit && this.editApiKeyForm.concurrencyLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.concurrencyLimit) : 0,
|
concurrencyLimit: this.editApiKeyForm.concurrencyLimit && this.editApiKeyForm.concurrencyLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.concurrencyLimit) : 0,
|
||||||
@@ -1886,7 +1905,10 @@ const app = createApp({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.showToast('API Key 更新成功', 'success', '更新成功');
|
this.showToast('API Key 更新成功', 'success', '更新成功');
|
||||||
@@ -2077,21 +2099,13 @@ const app = createApp({
|
|||||||
async loadDashboardModelStats() {
|
async loadDashboardModelStats() {
|
||||||
console.log('Loading dashboard model stats, period:', this.dashboardModelPeriod, 'authToken:', !!this.authToken);
|
console.log('Loading dashboard model stats, period:', this.dashboardModelPeriod, 'authToken:', !!this.authToken);
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/model-stats?period=' + this.dashboardModelPeriod, {
|
const data = await this.apiRequest('/admin/model-stats?period=' + this.dashboardModelPeriod);
|
||||||
headers: { 'Authorization': 'Bearer ' + this.authToken }
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Model stats response status:', response.status);
|
if (!data) {
|
||||||
|
// 如果token过期,apiRequest会返回null并刷新页面
|
||||||
if (!response.ok) {
|
|
||||||
console.error('Model stats API error:', response.status, response.statusText);
|
|
||||||
const errorText = await response.text();
|
|
||||||
console.error('Error response:', errorText);
|
|
||||||
this.dashboardModelStats = [];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('Model stats response data:', data);
|
console.log('Model stats response data:', data);
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
|||||||
Reference in New Issue
Block a user