mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
Support optional custom client keys across creation, storage, and authentication. Search keys and request records by name, remove credential and prefix searches, and reject duplicate names during create and update using the existing control-plane transaction lock. Simplify the request diagnostics panel and use the shared empty state. Validated with 1,029 backend tests using dedicated PostgreSQL and Redis, strict workspace Clippy, frontend lint/typecheck/build, and browser checks for key creation, name validation, search, diagnostics refresh/export, and mobile layouts. Co-authored-by: Codex <noreply@openai.com>
112 lines
2.9 KiB
TypeScript
112 lines
2.9 KiB
TypeScript
import type { Ref } from 'vue'
|
|
import type { UsageTimeRangeParams } from './useUsageTimeRange'
|
|
import { watchDebounced } from '@vueuse/core'
|
|
|
|
import { computed, onScopeDispose, shallowRef, watch } from 'vue'
|
|
import { getOpsErrors } from '@/api'
|
|
import { useStablePagedQuery } from '@/composables/useStablePagedQuery'
|
|
import { withMinimumDuration } from '@/utils/async'
|
|
|
|
interface UseOpsErrorsTableOptions {
|
|
timeRangeParams: Readonly<Ref<UsageTimeRangeParams>>
|
|
latestTimeRangeParams: () => UsageTimeRangeParams
|
|
provider: Readonly<Ref<string>>
|
|
active: Readonly<Ref<boolean>>
|
|
}
|
|
|
|
export function useOpsErrorsTable(options: UseOpsErrorsTableOptions) {
|
|
const refreshing = shallowRef(false)
|
|
const searchQuery = shallowRef('')
|
|
const search = computed(() => searchQuery.value.trim() || undefined)
|
|
let disposed = false
|
|
// 时间、平台和搜索共同构成分页快照,避免翻页混入另一组筛选结果。
|
|
let tableParams = snapshot()
|
|
|
|
function snapshot() {
|
|
return {
|
|
...options.latestTimeRangeParams(),
|
|
provider: options.provider.value || undefined,
|
|
search: search.value,
|
|
}
|
|
}
|
|
|
|
const query = useStablePagedQuery({
|
|
initialPageSize: 10,
|
|
load: ({ currentPage, pageSize }, requestOptions) => getOpsErrors({
|
|
currentPage,
|
|
pageSize,
|
|
...tableParams,
|
|
}, requestOptions),
|
|
})
|
|
const pagination = computed(() => ({
|
|
currentPage: query.currentPage.value,
|
|
pageSize: query.pageSize.value,
|
|
total: query.total.value,
|
|
}))
|
|
|
|
function handlePageChange(nextPage: number) {
|
|
if (tableParams.search !== search.value) {
|
|
void reloadLatest()
|
|
return
|
|
}
|
|
void query.execute(nextPage)
|
|
}
|
|
|
|
function handlePageSizeChange(nextPageSize: number) {
|
|
query.pageSize.value = nextPageSize
|
|
if (tableParams.search !== search.value)
|
|
void reloadLatest()
|
|
else
|
|
void query.reloadFromStart()
|
|
}
|
|
|
|
function reloadLatest() {
|
|
tableParams = snapshot()
|
|
// 筛选失败不能继续展示上一范围的数据,错误态由面板单独呈现。
|
|
query.items.value = []
|
|
return query.reloadFromStart()
|
|
}
|
|
|
|
async function refresh() {
|
|
if (refreshing.value || query.loading.value)
|
|
return
|
|
refreshing.value = true
|
|
try {
|
|
await withMinimumDuration(reloadLatest)
|
|
}
|
|
finally {
|
|
refreshing.value = false
|
|
}
|
|
}
|
|
|
|
watchDebounced(
|
|
search,
|
|
() => {
|
|
if (!disposed && options.active.value && tableParams.search !== search.value)
|
|
void reloadLatest()
|
|
},
|
|
{ debounce: 250 },
|
|
)
|
|
|
|
watch([options.timeRangeParams, options.provider, options.active], () => {
|
|
if (options.active.value)
|
|
void reloadLatest()
|
|
else
|
|
query.invalidate()
|
|
}, { immediate: true })
|
|
|
|
onScopeDispose(() => disposed = true)
|
|
|
|
return {
|
|
loading: query.loading,
|
|
error: query.error,
|
|
refreshing,
|
|
records: query.items,
|
|
searchQuery,
|
|
pagination,
|
|
handlePageChange,
|
|
handlePageSizeChange,
|
|
refresh,
|
|
}
|
|
}
|