Files
han-app/VM1_app/codebase/backend/frontend-test-site/app/diagnostics.tsx
T

77 lines
3.6 KiB
TypeScript

import React, { useEffect, useState } from "react";
import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
import { Feather } from "@expo/vector-icons";
import { useRouter } from "expo-router";
import { getDiagnostics, sessionMemory } from "../src/api";
import { getTokenInfo } from "../src/auth";
import { useApp } from "../src/app-context";
import { isProduction } from "../src/config";
import { getRealtimeDiagnostics } from "../src/realtime";
import { ScreenShell } from "../src/components/ScreenShell";
import { styles } from "../src/ui";
import { colors, radii, spacing } from "../src/theme";
export default function DiagnosticsScreen() {
const app = useApp();
const router = useRouter();
const [, render] = useState(0);
useEffect(() => {
const timer = setInterval(() => render((value) => value + 1), 1000);
return () => clearInterval(timer);
}, []);
if (isProduction) {
return (
<ScreenShell>
<View style={stylesLocal.topBar}>
<Pressable onPress={() => router.back()} style={stylesLocal.backButton}>
<Feather name="arrow-left" size={20} color={colors.foreground} />
</Pressable>
<Text accessibilityRole="header" style={styles.title}>Диагностика</Text>
</View>
<View style={{ padding: spacing.lg }}>
<Text style={styles.error}>Экран отключён в production.</Text>
</View>
</ScreenShell>
);
}
const token = getTokenInfo();
return (
<ScreenShell>
<ScrollView contentContainerStyle={{ padding: spacing.lg, gap: spacing.lg, paddingBottom: spacing.xl }}>
<View style={stylesLocal.topBar}>
<Pressable onPress={() => router.back()} style={stylesLocal.backButton}>
<Feather name="arrow-left" size={20} color={colors.foreground} />
</Pressable>
<Text accessibilityRole="header" style={styles.title}>Безопасная диагностика</Text>
</View>
<View style={styles.card}>
<Text style={styles.text}>Режим: {app.authStatus}</Text>
<Text style={styles.text}>Realtime: {app.realtimeState}</Text>
<Text style={styles.text}>UX-сессия: {sessionMemory.id ?? "нет"}</Text>
<Text style={styles.text}>Access token истекает: {token ? new Date(token.expiresAt).toLocaleString("ru-RU") : "нет"}</Text>
{getRealtimeDiagnostics().map((item) =>
<Text key={item.dialog} style={styles.text}>Cursor {item.dialog}: {item.cursor}</Text>,
)}
<Text style={styles.muted}>Токены, OTP, персональные данные, сообщения и presigned URL здесь никогда не отображаются.</Text>
</View>
<View style={styles.card}>
<Text accessibilityRole="header" style={styles.heading}>Последние запросы</Text>
{!getDiagnostics().length && <Text style={styles.muted}>Запросов ещё нет.</Text>}
{getDiagnostics().map((item) => <View key={`${item.at}-${item.requestId}`} style={styles.row}>
<Text style={styles.badge}>{item.status}</Text>
<Text style={styles.text}>{item.method} {item.path}</Text>
<Text style={styles.muted}>request_id: {item.requestId}</Text>
</View>)}
</View>
</ScrollView>
</ScreenShell>
);
}
const stylesLocal = StyleSheet.create({
topBar: { flexDirection: "row", alignItems: "center", gap: spacing.md, marginBottom: spacing.sm },
backButton: { width: 36, height: 36, borderRadius: radii.full, alignItems: "center", justifyContent: "center" },
});