mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-04 00:42:14 +00:00
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:
commit
d92edc01be
352 files changed
+25184
-2128
No files matched your search
@@ -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 }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user