telegram-invite-automation/src/renderer/tabs/ApiTraceTab.jsx
2026-02-11 17:04:20 +04:00

136 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { memo, useMemo, useState } from "react";
function prettyJson(text) {
if (!text) return "—";
try {
return JSON.stringify(JSON.parse(text), null, 2);
} catch {
return text;
}
}
function ApiTraceTab({
hasSelectedTask,
selectedTaskName,
apiTraceLogs,
formatTimestamp,
clearApiTrace,
exportApiTraceJson,
exportApiTraceCsv
}) {
const [search, setSearch] = useState("");
const query = String(search || "").trim().toLowerCase();
const allRows = Array.isArray(apiTraceLogs) ? apiTraceLogs : [];
const rows = useMemo(() => {
if (!query) return allRows;
return allRows.filter((item) => {
const hay = [
item.method,
item.phone,
item.errorText,
item.requestJson,
item.responseJson
].map((part) => String(part || "").toLowerCase()).join(" ");
return hay.includes(query);
});
}, [allRows, query]);
const okCount = rows.filter((item) => item.ok).length;
const failCount = rows.length - okCount;
const inviteRows = allRows.filter((item) => item.method === "channels.InviteToChannel");
const inviteTotal = inviteRows.length;
const inviteMissing = inviteRows.filter((item) => {
const response = String(item.responseJson || "");
return response.includes("missingInvitees") || response.includes("missing_invitees");
}).length;
const inviteRpcError = inviteRows.filter((item) => !item.ok || String(item.errorText || "").trim()).length;
const inviteOkNoMissing = Math.max(0, inviteTotal - inviteMissing - inviteRpcError);
return (
<section className="card">
<div className="row-header">
<h3>API трассировка</h3>
<div className="actions">
<button
type="button"
className="secondary"
onClick={() => exportApiTraceJson()}
>
Экспорт JSON
</button>
<button
type="button"
className="secondary"
onClick={() => exportApiTraceCsv()}
>
Экспорт CSV
</button>
<button
type="button"
className="secondary"
onClick={() => clearApiTrace()}
>
Очистить трассировку
</button>
</div>
</div>
<div className="hint">
Задача: {hasSelectedTask ? selectedTaskName : "—"}.
Для Telegram MTProto HTTPheaders отсутствуют, поэтому в лог пишется транспортный контекст.
</div>
<div className="inline-controls">
<input
className="text-input"
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder="Поиск по методу / ошибке / JSON"
/>
<span className="status-caption">Всего: {rows.length}</span>
<span className="status-caption ok">OK: {okCount}</span>
<span className="status-caption warn">Ошибок: {failCount}</span>
</div>
<div className="inline-controls">
<span className="status-caption">InviteToChannel: {inviteTotal}</span>
<span className="status-caption warn">missing_invitees: {inviteMissing}</span>
<span className="status-caption ok">без missing: {inviteOkNoMissing}</span>
<span className="status-caption">RPC errors: {inviteRpcError}</span>
</div>
<div className="api-trace-list">
{!rows.length && <div className="empty">Трассировка пуста.</div>}
{rows.map((item) => (
<details key={item.id} className={`api-trace-item ${item.ok ? "success" : "error"}`} open={false}>
<summary>
<div className="api-trace-head">
<strong>{item.method || "unknown"}</strong>
<span className="status-caption">{formatTimestamp(item.createdAt)}</span>
</div>
<div className="api-trace-body">
<div>Аккаунт: {item.phone || item.accountId || "—"}</div>
<div>Задача: {item.taskId || "—"} · {item.ok ? "OK" : "Ошибка"} · {item.durationMs || 0} ms</div>
{!item.ok && <div>Ошибка: {item.errorText || "—"}</div>}
</div>
</summary>
<div className="api-trace-details">
<div>
<strong>Запрос (JSON)</strong>
<pre>{prettyJson(item.requestJson)}</pre>
</div>
<div>
<strong>Заголовки / контекст</strong>
<pre>{prettyJson(item.headersJson)}</pre>
</div>
<div>
<strong>Ответ (JSON)</strong>
<pre>{prettyJson(item.responseJson)}</pre>
</div>
</div>
</details>
))}
</div>
</section>
);
}
export default memo(ApiTraceTab);