Merge origin/main into feat/channel-monitor-v2-ops-ui

Resolves three conflicts, all of the "both branches appended to the same
block" shape. Every one is resolved as a union of both sides; nothing from
either parent is dropped.

- handler/admin/setting_handler_update.go: keep ChannelMonitorHideThroughput
  (V2) alongside GrokDefaultTextModel / GrokCrossClientModelMapEnabled /
  GrokDefaultBaseURLMode (#5408). UpdateSettings writes every key on each
  save, so dropping either side would reset those settings to zero values.
- service/domain_constants.go: keep SettingKeyChannelMonitorHideThroughput
  and the three SettingKeyGrok* constants.
- repository/migrations_runner.go: keep the 195 checksum rule (V2) and the
  218/219/220 rules (#5408).
This commit is contained in:
shaw committed 2026-08-09 12:11:35 +08:00
commit d92edc01be
352 files changed
+25184 -2128

No files matched your search

+17 -1
View File
@@ -8,7 +8,7 @@ vi.mock('@/api/client', () => ({
apiClient: { post },
}))
import { createFromSSO, getGrokSSOImportTimeout } from '@/api/admin/grok'
import { authorizePassword, createFromSSO, getGrokSSOImportTimeout } from '@/api/admin/grok'
describe('admin Grok SSO import API', () => {
beforeEach(() => {
@@ -34,4 +34,20 @@ describe('admin Grok SSO import API', () => {
{ timeout: expectedTimeout },
)
})
it('preserves password whitespace and applies the authorization timeout', async () => {
post.mockResolvedValueOnce({ data: { access_token: 'access-token' } })
await authorizePassword(' user@example.com ---- password with spaces ', 7)
expect(post).toHaveBeenCalledWith(
'/admin/grok/oauth/password',
{
email: 'user@example.com',
password: ' password with spaces ',
proxy_id: 7,
},
{ timeout: 120_000 },
)
})
})
@@ -41,7 +41,11 @@ describe('admin system rollback API', () => {
const result = await rollback('0.1.146')
expect(post).toHaveBeenCalledWith('/admin/system/rollback', { version: '0.1.146' })
expect(post).toHaveBeenCalledWith(
'/admin/system/rollback',
{ version: '0.1.146' },
{ timeout: 15 * 60 * 1000 }
)
expect(result.need_restart).toBe(true)
})
@@ -50,6 +54,10 @@ describe('admin system rollback API', () => {
await rollback()
expect(post).toHaveBeenCalledWith('/admin/system/rollback', undefined)
expect(post).toHaveBeenCalledWith(
'/admin/system/rollback',
undefined,
{ timeout: 15 * 60 * 1000 }
)
})
})
+14
View File
@@ -317,6 +317,19 @@ export async function getUsage(id: number, source?: 'passive' | 'active', force?
return data
}
export interface BatchAccountUsageResponse {
usage: Record<string, AccountUsageInfo>
errors: Record<string, string>
}
export async function getBatchUsage(accountIds: number[], force?: boolean): Promise<BatchAccountUsageResponse> {
const { data } = await apiClient.post<BatchAccountUsageResponse>('/admin/accounts/usage/batch', {
account_ids: accountIds,
force: force === true
})
return data
}
/**
* Clear account rate limit status
* @param id - Account ID
@@ -986,6 +999,7 @@ export const accountsAPI = {
getStats,
clearError,
getUsage,
getBatchUsage,
getTodayStats,
getBatchTodayStats,
clearRateLimit,
+3
View File
@@ -56,6 +56,7 @@ export interface TrendParams {
request_type?: UsageRequestType
stream?: boolean
billing_type?: number | null
upstream_model_mismatch?: boolean
}
export interface TrendResponse {
@@ -87,6 +88,7 @@ export interface ModelStatsParams {
request_type?: UsageRequestType
stream?: boolean
billing_type?: number | null
upstream_model_mismatch?: boolean
}
export interface ModelStatsResponse {
@@ -115,6 +117,7 @@ export interface GroupStatsParams {
request_type?: UsageRequestType
stream?: boolean
billing_type?: number | null
upstream_model_mismatch?: boolean
}
export interface GroupStatsResponse {
+56 -1
View File
@@ -19,6 +19,17 @@ export interface GrokAuthUrlRequest {
redirect_uri?: string
}
export interface GrokOAuthCapabilities {
password_auth_enabled: boolean
}
const GROK_AUTHORIZATION_TIMEOUT_MS = 120_000
export async function getCapabilities(): Promise<GrokOAuthCapabilities> {
const { data } = await apiClient.get<GrokOAuthCapabilities>('/admin/grok/oauth/capabilities')
return data
}
export interface GrokExchangeCodeRequest {
session_id: string
state: string
@@ -170,4 +181,48 @@ export async function createFromSSO(payload: GrokSSOToOAuthRequest): Promise<Gro
return data
}
export default { generateAuthUrl, exchangeCode, refreshGrokToken, queryQuota, resetQuota, createFromSSO }
/** Validate a browser SSO cookie and convert to Build OAuth tokens (no raw SSO stored). */
export async function validateSSOToken(
ssoToken: string,
proxyId?: number | null
): Promise<GrokTokenInfo> {
const payload: Record<string, unknown> = { sso_token: ssoToken }
if (proxyId) payload.proxy_id = proxyId
const { data } = await apiClient.post<GrokTokenInfo>('/admin/grok/oauth/sso-token', payload, {
timeout: GROK_AUTHORIZATION_TIMEOUT_MS
})
return data
}
/**
* Password login → ephemeral SSO → Build OAuth.
* Password is only sent over the wire for this call; never persist it in credentials.
*/
export async function authorizePassword(
emailAndPassword: string,
proxyId?: number | null
): Promise<GrokTokenInfo> {
// Format: email----password (password may contain dashes).
const sep = '----'
const idx = emailAndPassword.indexOf(sep)
const email = (idx >= 0 ? emailAndPassword.slice(0, idx) : emailAndPassword).trim()
const password = idx >= 0 ? emailAndPassword.slice(idx + sep.length) : ''
const payload: Record<string, unknown> = { email, password }
if (proxyId) payload.proxy_id = proxyId
const { data } = await apiClient.post<GrokTokenInfo>('/admin/grok/oauth/password', payload, {
timeout: GROK_AUTHORIZATION_TIMEOUT_MS
})
return data
}
export default {
generateAuthUrl,
getCapabilities,
exchangeCode,
refreshGrokToken,
queryQuota,
resetQuota,
createFromSSO,
validateSSOToken,
authorizePassword,
}
+42
View File
@@ -32,6 +32,38 @@ export type DefaultPlatformQuotasMap = Partial<Record<PlatformType, PlatformQuot
const PLATFORMS: PlatformType[] = ["anthropic", "openai", "gemini", "antigravity", "grok"]
export type SchedulingThresholdPlatformType =
| "openai"
| "anthropic"
| "grok"
export type AccountSchedulingThresholdsMap = Record<SchedulingThresholdPlatformType, number>
export const SCHEDULING_THRESHOLD_PLATFORMS: SchedulingThresholdPlatformType[] = [
"openai",
"anthropic",
"grok",
]
export function normalizeAccountSchedulingThresholdsMap(
input?: Partial<Record<SchedulingThresholdPlatformType, number>> | null,
): AccountSchedulingThresholdsMap {
const result = {} as AccountSchedulingThresholdsMap
for (const platform of SCHEDULING_THRESHOLD_PLATFORMS) {
const value = input?.[platform]
result[platform] = typeof value === "number" && Number.isFinite(value)
? Math.min(100, Math.max(1, Math.trunc(value)))
: 100
}
return result
}
export function sanitizeAccountSchedulingThresholdsMap(
input?: Partial<Record<SchedulingThresholdPlatformType, number>> | null,
): AccountSchedulingThresholdsMap {
return normalizeAccountSchedulingThresholdsMap(input)
}
/** 归一化为全 4 平台 × 3 窗口(缺失填 null),供模板非空绑定 */
export function normalizePlatformQuotasMap(input?: DefaultPlatformQuotasMap | null): DefaultPlatformQuotasMap {
const result: DefaultPlatformQuotasMap = {}
@@ -556,6 +588,12 @@ export interface SystemSettings {
fallback_model_openai: string;
fallback_model_gemini: string;
fallback_model_antigravity: string;
grok_default_text_model: string;
grok_cross_client_model_map_enabled: boolean;
grok_default_base_url_mode: string;
// Per-platform account auto-pause thresholds (100 = disabled)
account_scheduling_thresholds: AccountSchedulingThresholdsMap;
// Identity patch configuration (Claude -> Gemini)
enable_identity_patch: boolean;
@@ -874,6 +912,10 @@ export interface UpdateSettingsRequest {
fallback_model_openai?: string;
fallback_model_gemini?: string;
fallback_model_antigravity?: string;
grok_default_text_model?: string;
grok_cross_client_model_map_enabled?: boolean;
grok_default_base_url_mode?: string;
account_scheduling_thresholds?: AccountSchedulingThresholdsMap;
enable_identity_patch?: boolean;
identity_patch_prompt?: string;
ops_monitoring_enabled?: boolean;
+2
View File
@@ -84,6 +84,7 @@ export interface AdminUsageQueryParams extends UsageQueryParams {
user_id?: number
exact_total?: boolean
billing_mode?: string
upstream_model_mismatch?: boolean
sort_by?: string
sort_order?: 'asc' | 'desc'
// 错误请求 tab 专属筛选(仅传给错误列表接口;共用同一 filters 对象)
@@ -123,6 +124,7 @@ export async function getStats(params: {
model?: string
request_type?: UsageRequestType
stream?: boolean
upstream_model_mismatch?: boolean
period?: string
start_date?: string
end_date?: string