fix: refine create page layout spacing
This commit is contained in:
@@ -345,158 +345,165 @@ export function OperationForm() {
|
|||||||
creationMutation.mutate(values);
|
creationMutation.mutate(values);
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<section className="builder-card">
|
<div className="builder-content">
|
||||||
<header className="builder-card-header">
|
<section className="builder-card">
|
||||||
<div>
|
<header className="builder-card-header">
|
||||||
<h2>1 — Protocol</h2>
|
<div>
|
||||||
<p>One tool maps to exactly one upstream method.</p>
|
<h2>1 — Protocol</h2>
|
||||||
</div>
|
<p>One tool maps to exactly one upstream method.</p>
|
||||||
</header>
|
</div>
|
||||||
<div className="builder-card-body">
|
</header>
|
||||||
<div className="proto-grid">
|
<div className="builder-card-body">
|
||||||
{protocolCards.map((card) => {
|
<div className="proto-grid">
|
||||||
const isSelected = activeProtocol === card.protocol;
|
{protocolCards.map((card) => {
|
||||||
const selectedClassName = isSelected
|
const isSelected = activeProtocol === card.protocol;
|
||||||
? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}`
|
const selectedClassName = isSelected
|
||||||
: "proto-card";
|
? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}`
|
||||||
|
: "proto-card";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={selectedClassName}
|
className={selectedClassName}
|
||||||
key={card.protocol}
|
key={card.protocol}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.setValue("protocol", card.protocol, {
|
form.setValue("protocol", card.protocol, {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
shouldValidate: true,
|
shouldValidate: true,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div className="proto-card-top">
|
<div className="proto-card-top">
|
||||||
<div className={`proto-icon pi-${card.protocol === "graphql" ? "gql" : card.protocol}`}>
|
<div className={`proto-icon pi-${card.protocol === "graphql" ? "gql" : card.protocol}`}>
|
||||||
{card.protocol === "graphql" ? "GQL" : card.protocol.toUpperCase()}
|
{card.protocol === "graphql" ? "GQL" : card.protocol.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<div className="proto-radio" />
|
||||||
</div>
|
</div>
|
||||||
<div className="proto-radio" />
|
<div className="proto-card-copy">
|
||||||
</div>
|
<h3>{card.title}</h3>
|
||||||
<div className="proto-card-copy">
|
<p>{card.description}</p>
|
||||||
<h3>{card.title}</h3>
|
</div>
|
||||||
<p>{card.description}</p>
|
</button>
|
||||||
</div>
|
);
|
||||||
</button>
|
})}
|
||||||
);
|
</div>
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="builder-card">
|
<section className="builder-card">
|
||||||
<header className="builder-card-header">
|
<header className="builder-card-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>2 — Tool identity</h2>
|
<h2>2 — Tool identity</h2>
|
||||||
<p>Name and description visible to the LLM at runtime.</p>
|
<p>Name and description visible to the LLM at runtime.</p>
|
||||||
|
</div>
|
||||||
|
<span className="status-pill status-draft">draft</span>
|
||||||
|
</header>
|
||||||
|
<div className="builder-card-body">
|
||||||
|
<div className="form-grid">{commonFields.map(renderField)}</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="status-pill status-draft">draft</span>
|
</section>
|
||||||
</header>
|
|
||||||
<div className="builder-card-body">
|
|
||||||
<div className="form-grid">{commonFields.map(renderField)}</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="builder-card">
|
<section className="builder-card">
|
||||||
<header className="builder-card-header">
|
<header className="builder-card-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>3 — Upstream target</h2>
|
<h2>3 — Upstream target</h2>
|
||||||
<p>Protocol-specific target configuration exposed through one MCP tool.</p>
|
<p>Protocol-specific target configuration exposed through one MCP tool.</p>
|
||||||
</div>
|
</div>
|
||||||
<span className={`protocol-pill protocol-${activeProtocol}`}>{activeProtocol}</span>
|
<span className={`protocol-pill protocol-${activeProtocol}`}>{activeProtocol}</span>
|
||||||
</header>
|
</header>
|
||||||
<div className="builder-card-body">
|
<div className="builder-card-body">
|
||||||
<div className="form-grid">
|
<div className="form-grid">
|
||||||
{protocolFields.map(renderField)}
|
{protocolFields.map(renderField)}
|
||||||
{activeProtocol === "grpc" ? (
|
{activeProtocol === "grpc" ? (
|
||||||
<label className="field-block field-block-wide">
|
<label className="field-block field-block-wide">
|
||||||
<span>Descriptor set file</span>
|
<span>Descriptor set file</span>
|
||||||
<small className="field-hint">
|
<small className="field-hint">
|
||||||
Upload a compiled descriptor-set file to autofill the base64 payload.
|
Upload a compiled descriptor-set file to autofill the base64 payload.
|
||||||
</small>
|
</small>
|
||||||
<input
|
<input
|
||||||
className="input"
|
className="input"
|
||||||
type="file"
|
type="file"
|
||||||
accept=".bin,.pb,.desc"
|
accept=".bin,.pb,.desc"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
void handleDescriptorFileChange(event.target.files?.[0]);
|
void handleDescriptorFileChange(event.target.files?.[0]);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
) : null}
|
) : null}
|
||||||
{activeProtocol === "rest" ? renderField({
|
{activeProtocol === "rest"
|
||||||
name: "restStaticHeadersText",
|
? renderField({
|
||||||
label: "Static headers",
|
name: "restStaticHeadersText",
|
||||||
description:
|
label: "Static headers",
|
||||||
"Always sent for this REST target before dynamic request headers are merged.",
|
description:
|
||||||
rows: 8,
|
"Always sent for this REST target before dynamic request headers are merged.",
|
||||||
wide: true,
|
rows: 8,
|
||||||
code: true,
|
wide: true,
|
||||||
}) : null}
|
code: true,
|
||||||
</div>
|
})
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="info-banner">
|
<div className="info-banner">
|
||||||
<div className="info-dot" />
|
<div className="info-dot" />
|
||||||
<p>
|
<p>
|
||||||
Headers and auth stay separate from path and payload mapping. Configure shared
|
Headers and auth stay separate from path and payload mapping. Configure shared
|
||||||
transport headers in the execution section below.
|
transport headers in the execution section below.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="builder-card">
|
<section className="builder-card">
|
||||||
<header className="builder-card-header">
|
<header className="builder-card-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>4 — Contract schemas</h2>
|
<h2>4 — Contract schemas</h2>
|
||||||
<p>Protocol-agnostic schemas for MCP input and output payloads.</p>
|
<p>Protocol-agnostic schemas for MCP input and output payloads.</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="builder-card-body">
|
||||||
|
<div className="form-grid">{schemaFields.map(renderField)}</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</section>
|
||||||
<div className="builder-card-body">
|
|
||||||
<div className="form-grid">{schemaFields.map(renderField)}</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="builder-card">
|
<section className="builder-card">
|
||||||
<header className="builder-card-header">
|
<header className="builder-card-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>5 — Mapping and execution</h2>
|
<h2>5 — Mapping and execution</h2>
|
||||||
<p>Translate MCP input to request fields, then map upstream output back to tool output.</p>
|
<p>
|
||||||
</div>
|
Translate MCP input to request fields, then map upstream output back to tool
|
||||||
</header>
|
output.
|
||||||
<div className="builder-card-body">
|
</p>
|
||||||
<div className="section-divider">
|
</div>
|
||||||
<span className="section-divider-label">Input → Request</span>
|
</header>
|
||||||
<div className="section-divider-line" />
|
<div className="builder-card-body">
|
||||||
</div>
|
<div className="section-divider">
|
||||||
<div className="form-grid">{mappingFields.slice(0, 1).map(renderField)}</div>
|
<span className="section-divider-label">Input → Request</span>
|
||||||
|
<div className="section-divider-line" />
|
||||||
|
</div>
|
||||||
|
<div className="form-grid">{mappingFields.slice(0, 1).map(renderField)}</div>
|
||||||
|
|
||||||
<div className="section-divider">
|
<div className="section-divider">
|
||||||
<span className="section-divider-label">Response → Output</span>
|
<span className="section-divider-label">Response → Output</span>
|
||||||
<div className="section-divider-line" />
|
<div className="section-divider-line" />
|
||||||
</div>
|
</div>
|
||||||
<div className="form-grid">{mappingFields.slice(1).map(renderField)}</div>
|
<div className="form-grid">{mappingFields.slice(1).map(renderField)}</div>
|
||||||
|
|
||||||
<div className="section-divider">
|
<div className="section-divider">
|
||||||
<span className="section-divider-label">Headers And Runtime</span>
|
<span className="section-divider-label">Headers And Runtime</span>
|
||||||
<div className="section-divider-line" />
|
<div className="section-divider-line" />
|
||||||
|
</div>
|
||||||
|
<div className="form-grid">{headerFields.map(renderField)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-grid">{headerFields.map(renderField)}</div>
|
</section>
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{creationMutation.error ? (
|
{creationMutation.error ? (
|
||||||
<div className="feedback-card feedback-error">
|
<div className="feedback-card feedback-error">
|
||||||
{creationMutation.error instanceof ApiError
|
{creationMutation.error instanceof ApiError
|
||||||
? creationMutation.error.message
|
? creationMutation.error.message
|
||||||
: "Failed to create operation"}
|
: "Failed to create operation"}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="sticky-action-bar">
|
<div className="sticky-action-bar">
|
||||||
<div className="sticky-action-copy">
|
<div className="sticky-action-copy">
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export function AppShell({ children }: AppShellProps) {
|
|||||||
<p className="sidebar-label">Workspace</p>
|
<p className="sidebar-label">Workspace</p>
|
||||||
<nav className="sidebar-nav">
|
<nav className="sidebar-nav">
|
||||||
<NavLink
|
<NavLink
|
||||||
|
end
|
||||||
to="/operations"
|
to="/operations"
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
isActive ? "nav-link nav-link-active" : "nav-link"
|
isActive ? "nav-link nav-link-active" : "nav-link"
|
||||||
|
|||||||
+24
-5
@@ -324,7 +324,8 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
margin-bottom: 24px;
|
max-width: 1240px;
|
||||||
|
margin: 0 auto 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header h1 {
|
.page-header h1 {
|
||||||
@@ -405,7 +406,14 @@ button {
|
|||||||
|
|
||||||
.builder-stack {
|
.builder-stack {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1320px;
|
}
|
||||||
|
|
||||||
|
.builder-content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-section,
|
.page-section,
|
||||||
@@ -470,6 +478,11 @@ button {
|
|||||||
padding: 0 24px 24px;
|
padding: 0 24px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.builder-content .form-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(280px, 420px));
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
.proto-grid {
|
.proto-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
@@ -859,8 +872,9 @@ button {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding: 12px 20px;
|
width: 100%;
|
||||||
border-radius: var(--radius-xl);
|
padding: 12px 28px;
|
||||||
|
border-radius: 0;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
@@ -1114,7 +1128,7 @@ pre {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.builder-stack {
|
.builder-content {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1144,4 +1158,9 @@ pre {
|
|||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sticky-action-bar {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user