Dialog portalem do body, hlavicky odpovedi u chyby
Dialog se vykresloval uvnitr karty konektoru misto pres obrazovku. Samo position: fixed nestaci: rodic s backdrop-filter (nase .glass, tedy skoro kazdy panel a karta) je pro fixed potomka containing block. Modal proto jde portalem do document.body. Tykalo se to vsech dialogu, videt to bylo az u Logu, ktere jsou v male karte. K chybe se zapisuji vybrane hlavicky odpovedi: server, via, content-type, www-authenticate, retry-after, x-request-id, date. Rikaji, kdo odpoved vydal. Server: Kestrel je sama aplikace, Via: 1.1 Caddy proxy pred ni. U 403 od proxy byva telo prazdne a bez hlavicek by nezbylo vubec nic. Allowlist, ne vsechno: Set-Cookie a podobne do zaznamu nepatri. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5a124a53d8
commit
ab88979627
@@ -16,6 +16,7 @@ export function ErrorDetail({
|
||||
detail,
|
||||
status,
|
||||
request,
|
||||
responseHeaders,
|
||||
retryable,
|
||||
kind,
|
||||
defaultOpen = false,
|
||||
@@ -27,6 +28,11 @@ export function ErrorDetail({
|
||||
status?: number;
|
||||
/** `url` je cela adresa vcetne serveru. Bez ni nejde poznat, kam to slo. */
|
||||
request?: { method: string; path: string; url?: string };
|
||||
/**
|
||||
* Vybrane hlavicky odpovedi. Rikaji, kdo odpoved vydal - aplikace, nebo
|
||||
* proxy pred ni. U kodu bez tela je to jedina stopa, ktera zbyde.
|
||||
*/
|
||||
responseHeaders?: Record<string, string> | null;
|
||||
retryable?: boolean;
|
||||
kind?: string;
|
||||
/**
|
||||
@@ -39,6 +45,10 @@ export function ErrorDetail({
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const headerLines = Object.entries(responseHeaders ?? {}).map(
|
||||
([name, value]) => `${name}: ${value}`,
|
||||
);
|
||||
|
||||
/** Co se zkopiruje: vsechno, co o chybe vime, at to jde poslat dal. */
|
||||
const fullText = [
|
||||
message,
|
||||
@@ -48,9 +58,12 @@ export function ErrorDetail({
|
||||
retryable !== undefined
|
||||
? `Opakování: ${retryable ? 'má smysl zkusit znovu' : 'opakování nepomůže'}`
|
||||
: null,
|
||||
// Hlavicky rikaji, kdo odpoved vydal. Do schranky patri s tim ostatnim,
|
||||
// protoze prave tohle se posila dal, kdyz to ma nekdo resit.
|
||||
...(headerLines.length > 0 ? ['', 'Hlavičky odpovědi:', ...headerLines] : []),
|
||||
detail ? `\nOdpověď služby:\n${detail}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.filter((line) => line !== null)
|
||||
.join('\n');
|
||||
|
||||
async function copy() {
|
||||
@@ -64,7 +77,8 @@ export function ErrorDetail({
|
||||
}
|
||||
}
|
||||
|
||||
const hasMore = Boolean(detail) || request !== undefined || status !== undefined;
|
||||
const hasMore =
|
||||
Boolean(detail) || headerLines.length > 0 || request !== undefined || status !== undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -92,7 +106,7 @@ export function ErrorDetail({
|
||||
|
||||
{hasMore && (
|
||||
<div className="mt-2 flex flex-wrap items-center gap-3">
|
||||
{detail && (
|
||||
{(detail || headerLines.length > 0) && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((previous) => !previous)}
|
||||
@@ -113,6 +127,14 @@ export function ErrorDetail({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{open && headerLines.length > 0 && (
|
||||
<div className="mt-2 max-h-40 overflow-auto rounded bg-ink-950/70 p-2.5 font-mono text-xs break-words text-white/50">
|
||||
{headerLines.map((line) => (
|
||||
<div key={line}>{line}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{open && detail && (
|
||||
<pre className="mt-2 max-h-80 overflow-auto rounded bg-ink-950/70 p-2.5 font-mono text-xs whitespace-pre-wrap break-words text-white/70">
|
||||
{detail}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { X } from 'lucide-react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import type { ReactNode } from 'react';
|
||||
import { cn } from '@/lib/cn';
|
||||
|
||||
/**
|
||||
* Dialog nad obsahem. Zavira se Esc, klikem na pozadi i krizkem.
|
||||
* Pri otevreni zamkne scroll stranky a preda focus dovnitr.
|
||||
*
|
||||
* **Vykresluje se portalem do `document.body`, ne na miste volani.** Samo
|
||||
* `position: fixed` nestaci: rodic s `backdrop-filter` (nase `.glass`, tedy
|
||||
* skoro kazdy panel a karta) je pro fixed potomka containing block a dialog
|
||||
* se pak vejde do te karty misto pres celou obrazovku. Bez portalu je tedy
|
||||
* dialog v karte konektoru maly a nepouzitelny.
|
||||
*/
|
||||
export function Modal({
|
||||
open,
|
||||
@@ -50,7 +57,7 @@ export function Modal({
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-[60] flex items-start justify-center overflow-y-auto p-4 sm:items-center sm:p-6">
|
||||
<div
|
||||
className="fixed inset-0 bg-ink-950/80 backdrop-blur-sm"
|
||||
@@ -85,6 +92,7 @@ export function Modal({
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,6 +318,7 @@ function ConnectorCard({
|
||||
detail={test.detail}
|
||||
status={test.status}
|
||||
request={test.request}
|
||||
responseHeaders={test.responseHeaders}
|
||||
defaultOpen
|
||||
/>
|
||||
)}
|
||||
@@ -399,6 +400,7 @@ function ConnectorLogs({
|
||||
onClose={onClose}
|
||||
title={`Logy: ${connector.name}`}
|
||||
description="Posledních pět ověření. U neúspěchu je vidět celá odpověď služby."
|
||||
className="max-w-4xl"
|
||||
>
|
||||
{targetUrl && (
|
||||
<p className="border-b border-ink-600/60 px-5 py-3 font-mono text-xs break-all text-white/40">
|
||||
@@ -442,6 +444,7 @@ function ConnectorLogs({
|
||||
detail={check.detail ?? undefined}
|
||||
status={check.status ?? undefined}
|
||||
request={check.request ?? undefined}
|
||||
responseHeaders={check.responseHeaders}
|
||||
defaultOpen
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -395,6 +395,8 @@ export interface ConnectorCheck {
|
||||
detail: string | null;
|
||||
/** Cela adresa vcetne serveru, bez query. */
|
||||
request: { method: string; path: string; url: string } | null;
|
||||
/** Vybrane hlavicky odpovedi. Rikaji, kdo odpoved vydal. */
|
||||
responseHeaders: Record<string, string> | null;
|
||||
}
|
||||
|
||||
export interface ConnectorTestResult {
|
||||
@@ -409,6 +411,8 @@ export interface ConnectorTestResult {
|
||||
detail?: string;
|
||||
/** Zaklad adresy, na kterou konektor miri. Vraci se i kdyz overeni projde. */
|
||||
baseUrl?: string;
|
||||
/** Vybrane hlavicky odpovedi. Rikaji, kdo odpoved vydal. */
|
||||
responseHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ServiceCatalog {
|
||||
|
||||
Reference in New Issue
Block a user