Files
Genarrative/apps/admin-web/src/pages/AdminDebugHttpPage.tsx

215 lines
6.5 KiB
TypeScript

import {Plus, Send, Trash2} from 'lucide-react';
import {FormEvent, useMemo, useState} from 'react';
import {debugAdminHttp} from '../api/adminApiClient';
import type {
AdminDebugHeaderInput,
AdminDebugHttpMethod,
AdminDebugHttpResponse,
} from '../api/adminApiTypes';
import {formatUnknownJson, handlePageError} from './pageUtils';
interface AdminDebugHttpPageProps {
token: string;
onUnauthorized: (message?: string) => void;
}
const httpMethods: AdminDebugHttpMethod[] = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
];
export function AdminDebugHttpPage({
token,
onUnauthorized,
}: AdminDebugHttpPageProps) {
const [method, setMethod] = useState<AdminDebugHttpMethod>('GET');
const [path, setPath] = useState('/healthz');
const [body, setBody] = useState('');
const [headers, setHeaders] = useState<AdminDebugHeaderInput[]>([]);
const [result, setResult] = useState<AdminDebugHttpResponse | null>(null);
const [errorMessage, setErrorMessage] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const jsonPreview = useMemo(
() => formatUnknownJson(result?.bodyJson),
[result?.bodyJson],
);
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (isSubmitting) {
return;
}
setErrorMessage('');
setIsSubmitting(true);
try {
const response = await debugAdminHttp(token, {
method,
path: path.trim(),
headers: headers.filter((header) => header.name.trim()),
body,
});
setResult(response);
} catch (error: unknown) {
handlePageError(error, onUnauthorized, setErrorMessage);
} finally {
setIsSubmitting(false);
}
}
return (
<section className="admin-page">
<div className="admin-page-heading">
<div>
<h2>API </h2>
<p></p>
</div>
</div>
<div className="admin-two-column">
<form className="admin-panel admin-form" onSubmit={handleSubmit}>
<div className="admin-form-row">
<label className="admin-field admin-field-compact">
<span>Method</span>
<select
value={method}
onChange={(event) =>
setMethod(event.target.value as AdminDebugHttpMethod)
}
>
{httpMethods.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</label>
<label className="admin-field admin-field-fill">
<span>Path</span>
<input
value={path}
onChange={(event) => setPath(event.target.value)}
/>
</label>
</div>
<section className="admin-subsection">
<div className="admin-subsection-heading">
<span>Headers</span>
<button
className="admin-ghost-button"
type="button"
onClick={() =>
setHeaders((current) => [
...current,
{name: '', value: ''},
])
}
>
<Plus size={16} aria-hidden="true" />
</button>
</div>
<div className="admin-header-editor">
{headers.map((header, index) => (
<div className="admin-header-row" key={index}>
<input
value={header.name}
onChange={(event) =>
setHeaders((current) =>
current.map((item, itemIndex) =>
itemIndex === index
? {...item, name: event.target.value}
: item,
),
)
}
/>
<input
value={header.value}
onChange={(event) =>
setHeaders((current) =>
current.map((item, itemIndex) =>
itemIndex === index
? {...item, value: event.target.value}
: item,
),
)
}
/>
<button
className="admin-ghost-button"
title="移除"
type="button"
onClick={() =>
setHeaders((current) =>
current.filter((_, itemIndex) => itemIndex !== index),
)
}
>
<Trash2 size={16} aria-hidden="true" />
</button>
</div>
))}
</div>
</section>
<label className="admin-field">
<span>Body</span>
<textarea
rows={9}
value={body}
onChange={(event) => setBody(event.target.value)}
/>
</label>
{errorMessage ? (
<div className="admin-alert" role="status">
{errorMessage}
</div>
) : null}
<button
className="admin-primary-button"
disabled={isSubmitting || !path.trim().startsWith('/')}
type="submit"
>
<Send size={17} aria-hidden="true" />
<span>{isSubmitting ? '发送中' : '发送'}</span>
</button>
</form>
<section className="admin-panel admin-result-panel">
<div className="admin-panel-heading">
<h3></h3>
<span>{result ? `${result.status} ${result.statusText}` : '-'}</span>
</div>
{result ? (
<>
<dl className="admin-info-list">
<div>
<dt>Status</dt>
<dd>{result.status}</dd>
</div>
<div>
<dt>Headers</dt>
<dd>{result.headers.length}</dd>
</div>
</dl>
<pre className="admin-code-block">
{jsonPreview || result.bodyText || '(empty)'}
</pre>
</>
) : (
<div className="admin-empty-state"></div>
)}
</section>
</div>
</section>
);
}