Исправляет ложные «Ошибки проверок»: GET /v1/ready отдаёт строки ok/memory, а не boolean. Уплотнён ReUI Frame/donut layout на вкладке Система. Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
import type { ReadyStatus } from '@/queries/monitoring'
|
||
|
||
import type { BreakdownSlice } from './types'
|
||
|
||
/** Значение check из GET /v1/ready (строка, boolean или объект). */
|
||
export type ReadyCheckValue = boolean | string | { ok?: boolean; error?: string } | null | undefined
|
||
|
||
const OK_STRINGS = new Set(['ok', 'ready', 'memory', 'true', 'healthy', 'up'])
|
||
const FAIL_STRINGS = new Set([
|
||
'unavailable',
|
||
'error',
|
||
'not_ready',
|
||
'failed',
|
||
'down',
|
||
'false',
|
||
'unhealthy',
|
||
])
|
||
|
||
/** Top-level status GET /v1/ready: API отдаёт `ready`, не `ok`. */
|
||
export function isSystemReady(status: string | null | undefined): boolean {
|
||
if (!status) return false
|
||
const normalized = status.trim().toLowerCase()
|
||
return normalized === 'ready' || normalized === 'ok'
|
||
}
|
||
|
||
/**
|
||
* Интерпретация check value по контракту handleReady:
|
||
* store/postgres → "ok" | "unavailable"; jobs → "memory"; store_backend → "memory".
|
||
*/
|
||
export function isReadyCheckOk(value: ReadyCheckValue): boolean {
|
||
if (typeof value === 'boolean') return value
|
||
if (typeof value === 'string') {
|
||
const normalized = value.trim().toLowerCase()
|
||
if (OK_STRINGS.has(normalized)) return true
|
||
if (FAIL_STRINGS.has(normalized)) return false
|
||
// неизвестная непустая строка — считать OK (информативный статус бэкенда)
|
||
return normalized.length > 0
|
||
}
|
||
if (value && typeof value === 'object') return value.ok === true
|
||
return false
|
||
}
|
||
|
||
/** Человекочитаемый статус check для UI. */
|
||
export function readyCheckStatusLabel(value: ReadyCheckValue, ok: boolean): string {
|
||
if (!ok) {
|
||
if (typeof value === 'string' && value.trim()) {
|
||
const n = value.trim().toLowerCase()
|
||
if (n === 'unavailable') return 'Недоступно'
|
||
if (n === 'not_ready') return 'Не готов'
|
||
return value
|
||
}
|
||
if (value && typeof value === 'object' && value.error) return value.error
|
||
return 'Ошибка'
|
||
}
|
||
if (typeof value === 'string') {
|
||
const n = value.trim().toLowerCase()
|
||
if (n === 'memory') return 'Memory'
|
||
if (n === 'ok' || n === 'ready' || n === 'healthy' || n === 'true') return 'В норме'
|
||
if (n) return value
|
||
}
|
||
return 'В норме'
|
||
}
|
||
|
||
export function readinessBreakdown(
|
||
ready: ReadyStatus | null | undefined,
|
||
healthOk: boolean,
|
||
): BreakdownSlice[] {
|
||
if (!healthOk) {
|
||
return [
|
||
{
|
||
key: 'health-fail',
|
||
label: 'API недоступен',
|
||
count: 1,
|
||
color: 'var(--color-destructive)',
|
||
},
|
||
]
|
||
}
|
||
|
||
const checks = ready?.checks ?? {}
|
||
let okCount = 0
|
||
let failCount = 0
|
||
|
||
for (const value of Object.values(checks)) {
|
||
if (isReadyCheckOk(value)) okCount += 1
|
||
else failCount += 1
|
||
}
|
||
|
||
const slices: BreakdownSlice[] = [
|
||
{
|
||
key: 'health',
|
||
label: 'API доступен',
|
||
count: 1,
|
||
color: 'var(--color-chart-2)',
|
||
},
|
||
]
|
||
|
||
if (okCount > 0) {
|
||
slices.push({
|
||
key: 'checks-ok',
|
||
label: 'Проверки в норме',
|
||
count: okCount,
|
||
color: 'var(--color-chart-1)',
|
||
})
|
||
}
|
||
if (failCount > 0) {
|
||
slices.push({
|
||
key: 'checks-fail',
|
||
label: 'Ошибки проверок',
|
||
count: failCount,
|
||
color: 'var(--color-warning)',
|
||
})
|
||
}
|
||
|
||
if (slices.length === 1 && okCount === 0 && failCount === 0) {
|
||
slices.push({
|
||
key: 'ready',
|
||
label: isSystemReady(ready?.status) ? 'Готов' : 'Не готов',
|
||
count: 1,
|
||
color: isSystemReady(ready?.status)
|
||
? 'var(--color-chart-1)'
|
||
: 'var(--color-warning)',
|
||
})
|
||
}
|
||
|
||
return slices
|
||
}
|