这是第一次基于http域名成功的部署版本0718v1,修改了http与https都兼容的认证方式

This commit is contained in:
baozaotumao2025
2026-07-18 23:18:16 +08:00
parent 1b90e552a5
commit 789d4fc4e9
11 changed files with 62 additions and 20 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
import { normalizeError } from '../errors'
import { createIdempotencyKey } from '../idempotencyKey'
import { logger } from '../logging'
import { tokenSchema, type AuthSession } from '../schemas/auth'
import { activity } from './activity'
@@ -15,7 +16,7 @@ type PendingRefresh = { refreshToken: string; idempotencyKey: string }
const pendingFor = (refreshToken: string): PendingRefresh => {
const saved = JSON.parse(localStorage.getItem(pendingKey) || 'null') as PendingRefresh | null
if (saved?.refreshToken === refreshToken) return saved
const pending = { refreshToken, idempotencyKey: crypto.randomUUID() }
const pending = { refreshToken, idempotencyKey: createIdempotencyKey() }
localStorage.setItem(pendingKey, JSON.stringify(pending))
return pending
}
+11
View File
@@ -0,0 +1,11 @@
export function createIdempotencyKey(): string {
const source = globalThis.crypto
if (typeof source?.randomUUID === 'function') return source.randomUUID()
if (typeof source?.getRandomValues !== 'function') throw new Error('当前浏览器不支持安全随机数')
const bytes = source.getRandomValues(new Uint8Array(16))
bytes[6] = (bytes[6] & 0x0f) | 0x40
bytes[8] = (bytes[8] & 0x3f) | 0x80
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}
+3 -5
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react'
import { useMemo, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Alert, Button, Card, Empty, Input, Modal, Progress, Select, Steps, Tabs, Tag, message } from 'antd'
@@ -26,9 +26,7 @@ export function RunDetailPage() {
useFinalResultsSync(Boolean(run.data?.terminal), results.refetch)
const report = useQuery({ queryKey: ['report', id], queryFn: () => services.reports.get(id), enabled: Boolean(run.data?.terminal) })
const refresh = () => void queryClient.invalidateQueries({ queryKey: ['run', id] })
useEffect(() => {
if (retryingExecutionId && run.data?.terminal) setRetryingExecutionId(null)
}, [retryingExecutionId, run.data?.terminal])
const activeRetryingExecutionId = run.data?.terminal ? null : retryingExecutionId
const retryResult = useMutation({
mutationFn: (executionId: string) => services.runs.retryResult(id, executionId),
onSuccess: (response) => {
@@ -85,7 +83,7 @@ export function RunDetailPage() {
<div className="stat-strip"><span><strong>{data.processed_count}</strong></span><span><strong>{data.completed_count}</strong></span><span><strong>{data.error_count}</strong></span><span><strong>{data.selected_count}</strong></span></div>
</Card>
<Tabs items={[
{ key: 'results', label: `逐条结果 (${filtered.length} / ${results.data?.length || 0})`, children: <ResultsPanel rows={filtered} executionFilter={executionFilter} verdictFilter={verdictFilter} executionIdQuery={executionIdQuery} onExecutionFilter={setExecutionFilter} onVerdictFilter={setVerdictFilter} onExecutionIdQuery={setExecutionIdQuery} loading={results.isLoading} canRetry={['completed', 'completed_with_errors'].includes(data.status)} retryingExecutionId={retryingExecutionId} followedExecutionId={followedExecutionId} submittingExecutionId={retryResult.isPending ? retryResult.variables : null} onStopFollowing={() => setFollowedExecutionId(null)} onRetry={(executionId) => Modal.confirm({ title: `确定重新执行 ${executionId} 吗?`, content: '当前测试结果将被新结果替换。', onOk: () => retryResult.mutate(executionId) })} /> },
{ key: 'results', label: `逐条结果 (${filtered.length} / ${results.data?.length || 0})`, children: <ResultsPanel rows={filtered} executionFilter={executionFilter} verdictFilter={verdictFilter} executionIdQuery={executionIdQuery} onExecutionFilter={setExecutionFilter} onVerdictFilter={setVerdictFilter} onExecutionIdQuery={setExecutionIdQuery} loading={results.isLoading} canRetry={['completed', 'completed_with_errors'].includes(data.status)} retryingExecutionId={activeRetryingExecutionId} followedExecutionId={followedExecutionId} submittingExecutionId={retryResult.isPending ? retryResult.variables : null} onStopFollowing={() => setFollowedExecutionId(null)} onRetry={(executionId) => Modal.confirm({ title: `确定重新执行 ${executionId} 吗?`, content: '当前测试结果将被新结果替换。', onOk: () => retryResult.mutate(executionId) })} /> },
{ key: 'report', label: '汇总报告', children: report.data ? <ReportSummary summary={report.data.summary} /> : <Empty description={data.terminal ? '暂无报告' : '运行结束后生成报告'} /> },
]} />
</>
+2 -3
View File
@@ -1,12 +1,11 @@
import type { ApiClient } from '../api/client'
import { createIdempotencyKey } from '../idempotencyKey'
import { resultListSchema, retryErrorsSchema, retryResultSchema, runDetailSchema, runListSchema, runResponseSchema, startRunSchema, resumeRunSchema, type StartRunInput } from '../schemas/runs'
const idempotencyKey = () => crypto.randomUUID()
export const runService = (api: ApiClient) => ({
list: (limit = 50) => api.request(`/runs?limit=${limit}`, { schema: runListSchema }),
get: (id: number) => api.request(`/runs/${id}`, { schema: runDetailSchema }),
start: (input: StartRunInput, key = idempotencyKey()) =>
start: (input: StartRunInput, key = createIdempotencyKey()) =>
api.request('/runs', { method: 'POST', headers: { 'Idempotency-Key': key }, body: startRunSchema.parse(input), schema: runResponseSchema }),
cancel: (id: number) => api.request(`/runs/${id}`, { method: 'PATCH', body: { status: 'cancelled' }, schema: runDetailSchema }),
remove: (id: number) => api.request(`/runs/${id}`, { method: 'DELETE' }),