Refactor renamed the component groups to something less obtuse:
Elements -> Groups -> Screens -> Views
This commit is contained in:
parent
ca6de91a96
commit
dd46acba87
2 changed files with 151 additions and 144 deletions
|
|
@ -0,0 +1,123 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { ExperimentalFeaturesPanel } from "./ExperimentalFeaturesPanel";
|
||||
|
||||
const meta: Meta<typeof ExperimentalFeaturesPanel> = {
|
||||
title: "Groups/ExperimentalFeaturesPanel",
|
||||
component: ExperimentalFeaturesPanel,
|
||||
args: {
|
||||
onToggleClientCapability: fn(),
|
||||
onRequestChange: fn(),
|
||||
onSendRequest: fn(),
|
||||
onAddHeader: fn(),
|
||||
onRemoveHeader: fn(),
|
||||
onHeaderChange: fn(),
|
||||
onCopyResponse: fn(),
|
||||
onTestCapability: fn(),
|
||||
clientCapabilities: [
|
||||
{ name: "experimental/customSampling", enabled: false },
|
||||
{ name: "experimental/batchRequests", enabled: true },
|
||||
],
|
||||
customHeaders: [],
|
||||
requestHistory: [],
|
||||
requestJson: JSON.stringify(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: "experimental/myMethod",
|
||||
params: {},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ExperimentalFeaturesPanel>;
|
||||
|
||||
export const WithServerCaps: Story = {
|
||||
args: {
|
||||
serverCapabilities: [
|
||||
{
|
||||
name: "experimental/streaming",
|
||||
description: "Supports streaming responses for long-running operations",
|
||||
methods: ["experimental/stream.start", "experimental/stream.cancel"],
|
||||
},
|
||||
{
|
||||
name: "experimental/caching",
|
||||
description: "Server-side response caching with TTL support",
|
||||
methods: [
|
||||
"experimental/cache.get",
|
||||
"experimental/cache.set",
|
||||
"experimental/cache.clear",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const NoServerCaps: Story = {
|
||||
args: {
|
||||
serverCapabilities: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithResponse: Story = {
|
||||
args: {
|
||||
serverCapabilities: [
|
||||
{
|
||||
name: "experimental/echo",
|
||||
description: "Echoes back the request for testing",
|
||||
methods: ["experimental/echo"],
|
||||
},
|
||||
],
|
||||
responseJson: JSON.stringify(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
result: {
|
||||
echo: "Hello from experimental endpoint",
|
||||
timestamp: "2026-03-17T10:30:00Z",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
customHeaders: [
|
||||
{ key: "X-Custom-Auth", value: "Bearer token123" },
|
||||
{ key: "X-Request-Id", value: "req-abc-456" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithHistory: Story = {
|
||||
args: {
|
||||
serverCapabilities: [
|
||||
{
|
||||
name: "experimental/metrics",
|
||||
description: "Exposes server metrics",
|
||||
},
|
||||
],
|
||||
requestHistory: [
|
||||
{
|
||||
timestamp: "2026-03-17 10:30:15",
|
||||
method: "experimental/metrics.get",
|
||||
status: "success",
|
||||
durationMs: 42,
|
||||
},
|
||||
{
|
||||
timestamp: "2026-03-17 10:29:50",
|
||||
method: "experimental/echo",
|
||||
status: "success",
|
||||
durationMs: 15,
|
||||
},
|
||||
{
|
||||
timestamp: "2026-03-17 10:28:30",
|
||||
method: "experimental/unknown",
|
||||
status: "error",
|
||||
durationMs: 120,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Group,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
TextInput,
|
||||
Textarea,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
|
||||
export interface ExperimentalCapability {
|
||||
name: string;
|
||||
description?: string;
|
||||
methods?: string[];
|
||||
}
|
||||
|
||||
export interface ClientExperimentalCapability {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface KeyValuePair {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface RequestHistoryItem {
|
||||
timestamp: string;
|
||||
method: string;
|
||||
status: string;
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
export interface ExperimentalFeaturesPanelProps {
|
||||
serverCapabilities: ExperimentalCapability[];
|
||||
clientCapabilities: ClientExperimentalCapability[];
|
||||
requestJson: string;
|
||||
responseJson?: string;
|
||||
customHeaders: KeyValuePair[];
|
||||
requestHistory: RequestHistoryItem[];
|
||||
onToggleClientCapability: (name: string, enabled: boolean) => void;
|
||||
onRequestChange: (json: string) => void;
|
||||
onSendRequest: () => void;
|
||||
onAddHeader: () => void;
|
||||
onRemoveHeader: (index: number) => void;
|
||||
onHeaderChange: (index: number, key: string, value: string) => void;
|
||||
onCopyResponse: () => void;
|
||||
onTestCapability: (name: string) => void;
|
||||
}
|
||||
|
||||
const HintText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const MetaText = Text.withProps({
|
||||
size: "xs",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const CapCard = Card.withProps({
|
||||
withBorder: true,
|
||||
p: "sm",
|
||||
});
|
||||
|
||||
const CompactButton = Button.withProps({
|
||||
size: "xs",
|
||||
variant: "light",
|
||||
});
|
||||
|
||||
const RemoveIcon = ActionIcon.withProps({
|
||||
variant: "light",
|
||||
color: "red",
|
||||
});
|
||||
|
||||
function formatMethods(methods: string[]): string {
|
||||
return `Methods: ${methods.join(", ")}`;
|
||||
}
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
return `${ms}ms`;
|
||||
}
|
||||
|
||||
export function ExperimentalFeaturesPanel({
|
||||
serverCapabilities,
|
||||
clientCapabilities,
|
||||
requestJson,
|
||||
responseJson,
|
||||
customHeaders,
|
||||
requestHistory,
|
||||
onToggleClientCapability,
|
||||
onRequestChange,
|
||||
onSendRequest,
|
||||
onAddHeader,
|
||||
onRemoveHeader,
|
||||
onHeaderChange,
|
||||
onCopyResponse,
|
||||
onTestCapability,
|
||||
}: ExperimentalFeaturesPanelProps) {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Alert color="yellow">
|
||||
These features are non-standard and may change or be removed.
|
||||
</Alert>
|
||||
|
||||
<Title order={5}>Server Experimental Capabilities:</Title>
|
||||
|
||||
{serverCapabilities.length === 0 ? (
|
||||
<Text c="dimmed">No experimental capabilities</Text>
|
||||
) : (
|
||||
serverCapabilities.map((cap) => (
|
||||
<CapCard key={cap.name}>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600}>{cap.name}</Text>
|
||||
{cap.description && <HintText>{cap.description}</HintText>}
|
||||
{cap.methods && cap.methods.length > 0 && (
|
||||
<MetaText>{formatMethods(cap.methods)}</MetaText>
|
||||
)}
|
||||
<Group>
|
||||
<CompactButton onClick={() => onTestCapability(cap.name)}>
|
||||
Test →
|
||||
</CompactButton>
|
||||
</Group>
|
||||
</Stack>
|
||||
</CapCard>
|
||||
))
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title order={5}>Client Experimental Capabilities:</Title>
|
||||
|
||||
{clientCapabilities.map((clientCap) => (
|
||||
<Checkbox
|
||||
key={clientCap.name}
|
||||
label={clientCap.name}
|
||||
checked={clientCap.enabled}
|
||||
onChange={(e) =>
|
||||
onToggleClientCapability(clientCap.name, e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title order={5}>Advanced JSON-RPC Tester</Title>
|
||||
|
||||
<HintText>Send raw JSON-RPC requests to test ANY method</HintText>
|
||||
|
||||
{customHeaders.map((header, index) => (
|
||||
<Group key={index}>
|
||||
<TextInput
|
||||
placeholder="Header name"
|
||||
value={header.key}
|
||||
onChange={(e) =>
|
||||
onHeaderChange(index, e.currentTarget.value, header.value)
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Header value"
|
||||
value={header.value}
|
||||
onChange={(e) =>
|
||||
onHeaderChange(index, header.key, e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<RemoveIcon onClick={() => onRemoveHeader(index)}>
|
||||
<Text size="xs">✕</Text>
|
||||
</RemoveIcon>
|
||||
</Group>
|
||||
))}
|
||||
|
||||
<Group>
|
||||
<CompactButton onClick={onAddHeader}>+ Add Header</CompactButton>
|
||||
</Group>
|
||||
|
||||
<Textarea
|
||||
label="Request"
|
||||
ff="monospace"
|
||||
value={requestJson}
|
||||
onChange={(e) => onRequestChange(e.currentTarget.value)}
|
||||
autosize
|
||||
minRows={6}
|
||||
/>
|
||||
|
||||
<Button onClick={onSendRequest}>Send Request</Button>
|
||||
|
||||
{responseJson && (
|
||||
<>
|
||||
<Textarea
|
||||
label="Response"
|
||||
ff="monospace"
|
||||
value={responseJson}
|
||||
readOnly
|
||||
autosize
|
||||
minRows={4}
|
||||
/>
|
||||
<Group>
|
||||
<CompactButton onClick={onCopyResponse}>Copy</CompactButton>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
|
||||
{requestHistory.length > 0 && (
|
||||
<>
|
||||
<Title order={5}>Request History:</Title>
|
||||
<Table>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Timestamp</Table.Th>
|
||||
<Table.Th>Method</Table.Th>
|
||||
<Table.Th>Status</Table.Th>
|
||||
<Table.Th>Duration</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{requestHistory.map((item, index) => (
|
||||
<Table.Tr key={index}>
|
||||
<Table.Td>{item.timestamp}</Table.Td>
|
||||
<Table.Td>{item.method}</Table.Td>
|
||||
<Table.Td>{item.status}</Table.Td>
|
||||
<Table.Td>{formatDuration(item.durationMs)}</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue