Adopt shared WebUI structural primitives
This commit is contained in:
@@ -23,14 +23,24 @@ import { FormGrid, DialogSection, ToolbarGroup, ActionToolbar,
|
|||||||
ActionBlockerHint,
|
ActionBlockerHint,
|
||||||
Button,
|
Button,
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
|
ContentSection,
|
||||||
Dialog,
|
Dialog,
|
||||||
DocumentationHelpLink,
|
DocumentationHelpLink,
|
||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
|
FilterBar,
|
||||||
FormField,
|
FormField,
|
||||||
IconButton,
|
IconButton,
|
||||||
LoadingFrame,
|
LoadingFrame,
|
||||||
|
MetricCard,
|
||||||
|
MetricGrid,
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
|
SelectionList,
|
||||||
|
SelectionListItem,
|
||||||
|
SelectionListItemContent,
|
||||||
|
StatePanel,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
|
WorkspaceFrame,
|
||||||
|
WorkspaceLayout,
|
||||||
hasScope,
|
hasScope,
|
||||||
isApiError,
|
isApiError,
|
||||||
useUnsavedChanges,
|
useUnsavedChanges,
|
||||||
@@ -284,10 +294,18 @@ export default function DatasourcesPage({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="datasources-page">
|
<WorkspaceFrame as="main" height="viewport" surface="plain" className="datasources-page" label="Datasource workspace">
|
||||||
<div className="datasources-shell">
|
<WorkspaceLayout
|
||||||
<aside className="datasources-sidebar" aria-label="Datasource catalogue">
|
variant="split"
|
||||||
<ActionToolbar className="datasources-sidebar-toolbar">
|
primarySize="compact"
|
||||||
|
surface="contained"
|
||||||
|
primaryScrollable={false}
|
||||||
|
contentScrollable={false}
|
||||||
|
primaryLabel="Datasource catalogue"
|
||||||
|
contentLabel="Datasource workspace"
|
||||||
|
contentClassName="datasources-workspace"
|
||||||
|
primary={<>
|
||||||
|
<ActionToolbar surface="panel-header">
|
||||||
<strong>Data</strong>
|
<strong>Data</strong>
|
||||||
<ToolbarGroup align="end" className="datasources-toolbar-actions">
|
<ToolbarGroup align="end" className="datasources-toolbar-actions">
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -326,7 +344,7 @@ export default function DatasourcesPage({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="datasources-search">
|
<FilterBar surface="panel">
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
value={search}
|
value={search}
|
||||||
@@ -334,71 +352,56 @@ export default function DatasourcesPage({
|
|||||||
placeholder={`Search ${view}`}
|
placeholder={`Search ${view}`}
|
||||||
aria-label={`Search ${view}`}
|
aria-label={`Search ${view}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</FilterBar>
|
||||||
<LoadingFrame
|
<LoadingFrame
|
||||||
loading={loading}
|
loading={loading}
|
||||||
className="datasources-list-frame"
|
className="datasources-list-frame"
|
||||||
>
|
>
|
||||||
<div className="datasources-list">
|
<SelectionList variant="navigation" label="Datasources">
|
||||||
{view === "catalogue" ? visibleDatasources.map((item) => (
|
{view === "catalogue" ? visibleDatasources.map((item) => (
|
||||||
<button
|
<SelectionListItem
|
||||||
key={item.ref}
|
key={item.ref}
|
||||||
type="button"
|
selected={item.ref === selectedDatasourceRef}
|
||||||
className={item.ref === selectedDatasourceRef ? "is-selected" : ""}
|
|
||||||
onClick={() => setSelectedDatasourceRef(item.ref)}
|
onClick={() => setSelectedDatasourceRef(item.ref)}
|
||||||
>
|
>
|
||||||
<span>
|
<SelectionListItemContent title={item.name} description={`${item.source_name} · ${item.kind}`} />
|
||||||
<strong>{item.name}</strong>
|
|
||||||
<small>{item.source_name} · {item.kind}</small>
|
|
||||||
</span>
|
|
||||||
<StatusBadge status={item.mode} label={item.mode} />
|
<StatusBadge status={item.mode} label={item.mode} />
|
||||||
</button>
|
</SelectionListItem>
|
||||||
)) : null}
|
)) : null}
|
||||||
{view === "staging" ? visibleStages.map((item) => (
|
{view === "staging" ? visibleStages.map((item) => (
|
||||||
<button
|
<SelectionListItem
|
||||||
key={item.ref}
|
key={item.ref}
|
||||||
type="button"
|
selected={item.ref === selectedStageRef}
|
||||||
className={item.ref === selectedStageRef ? "is-selected" : ""}
|
|
||||||
onClick={() => setSelectedStageRef(item.ref)}
|
onClick={() => setSelectedStageRef(item.ref)}
|
||||||
>
|
>
|
||||||
<span>
|
<SelectionListItemContent title={item.name} description={`${item.source_name} · ${formatCount(item.row_count, "row")}`} />
|
||||||
<strong>{item.name}</strong>
|
|
||||||
<small>{item.source_name} · {formatCount(item.row_count, "row")}</small>
|
|
||||||
</span>
|
|
||||||
<StatusBadge status={item.state} label={item.state} />
|
<StatusBadge status={item.state} label={item.state} />
|
||||||
</button>
|
</SelectionListItem>
|
||||||
)) : null}
|
)) : null}
|
||||||
{view === "origins" ? visibleOrigins.map((item) => (
|
{view === "origins" ? visibleOrigins.map((item) => (
|
||||||
<button
|
<SelectionListItem
|
||||||
key={item.ref}
|
key={item.ref}
|
||||||
type="button"
|
selected={item.ref === selectedOriginRef}
|
||||||
className={item.ref === selectedOriginRef ? "is-selected" : ""}
|
|
||||||
onClick={() => setSelectedOriginRef(item.ref)}
|
onClick={() => setSelectedOriginRef(item.ref)}
|
||||||
>
|
>
|
||||||
<span>
|
<SelectionListItemContent title={item.name} description={`${item.provider} · ${item.kind}`} />
|
||||||
<strong>{item.name}</strong>
|
|
||||||
<small>{item.provider} · {item.kind}</small>
|
|
||||||
</span>
|
|
||||||
<StatusBadge status={item.shape} label={item.shape} />
|
<StatusBadge status={item.shape} label={item.shape} />
|
||||||
</button>
|
</SelectionListItem>
|
||||||
)) : null}
|
)) : null}
|
||||||
{view === "catalogue" && !visibleDatasources.length ? (
|
{view === "catalogue" && !visibleDatasources.length ? (
|
||||||
<div className="datasources-list-empty">No datasources</div>
|
<StatePanel size="compact" description="No datasources" />
|
||||||
) : null}
|
) : null}
|
||||||
{view === "staging" && !visibleStages.length ? (
|
{view === "staging" && !visibleStages.length ? (
|
||||||
<div className="datasources-list-empty">No staged data</div>
|
<StatePanel size="compact" description="No staged data" />
|
||||||
) : null}
|
) : null}
|
||||||
{view === "origins" && !visibleOrigins.length ? (
|
{view === "origins" && !visibleOrigins.length ? (
|
||||||
<div className="datasources-list-empty">
|
<StatePanel size="compact" description={originsAvailable ? "No connector origins" : "Connectors unavailable"} />
|
||||||
{originsAvailable ? "No connector origins" : "Connectors unavailable"}
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</SelectionList>
|
||||||
</LoadingFrame>
|
</LoadingFrame>
|
||||||
</aside>
|
</>}
|
||||||
|
>
|
||||||
<section className="datasources-workspace">
|
<ActionToolbar surface="panel-header" className="datasources-workspace-toolbar">
|
||||||
<ActionToolbar className="datasources-workspace-toolbar">
|
|
||||||
<span className="datasources-current-title">
|
<span className="datasources-current-title">
|
||||||
{view === "catalogue" ? <DatabaseZap size={19} />
|
{view === "catalogue" ? <DatabaseZap size={19} />
|
||||||
: view === "staging" ? <Layers3 size={19} />
|
: view === "staging" ? <Layers3 size={19} />
|
||||||
@@ -522,8 +525,7 @@ export default function DatasourcesPage({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{working ? <div className="datasources-working" role="status">Working...</div> : null}
|
{working ? <div className="datasources-working" role="status">Working...</div> : null}
|
||||||
</section>
|
</WorkspaceLayout>
|
||||||
</div>
|
|
||||||
|
|
||||||
<AddDatasourceDialog
|
<AddDatasourceDialog
|
||||||
open={addOpen}
|
open={addOpen}
|
||||||
@@ -607,7 +609,7 @@ export default function DatasourcesPage({
|
|||||||
onCancel={() => setRetireOpen(false)}
|
onCancel={() => setRetireOpen(false)}
|
||||||
onConfirm={() => void retireSelected()}
|
onConfirm={() => void retireSelected()}
|
||||||
/>
|
/>
|
||||||
</main>
|
</WorkspaceFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,24 +626,24 @@ function DatasourceDetail({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="datasources-metrics">
|
<MetricGrid columns={5} density="compact" minimum="compact">
|
||||||
<Metric label="Mode" value={datasource.mode} />
|
<MetricCard density="compact" label="Mode" value={datasource.mode} valueTitle={datasource.mode} />
|
||||||
<Metric label="Rows" value={formatNumber(datasource.row_count)} />
|
<MetricCard density="compact" label="Rows" value={formatNumber(datasource.row_count)} />
|
||||||
<Metric label="Fields" value={String(datasource.schema.length)} />
|
<MetricCard density="compact" label="Fields" value={String(datasource.schema.length)} />
|
||||||
<Metric label="Revisions" value={String(materializations.length)} />
|
<MetricCard density="compact" label="Revisions" value={String(materializations.length)} />
|
||||||
<Metric label="Updated" value={formatDate(datasource.updated_at)} />
|
<MetricCard density="compact" label="Updated" value={formatDate(datasource.updated_at)} />
|
||||||
</div>
|
</MetricGrid>
|
||||||
{datasource.description ? (
|
{datasource.description ? (
|
||||||
<div className="datasources-description">{datasource.description}</div>
|
<div className="datasources-description">{datasource.description}</div>
|
||||||
) : null}
|
) : null}
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span><ShieldCheck size={16} /> Governance</span>
|
<span><ShieldCheck size={16} /> Governance</span>
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={datasource.governance.publication_state}
|
status={datasource.governance.publication_state}
|
||||||
label={datasource.governance.publication_state}
|
label={datasource.governance.publication_state}
|
||||||
/>
|
/>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<div className="datasources-key-values">
|
<div className="datasources-key-values">
|
||||||
<span><small>Authority</small><strong>{readableToken(datasource.governance.authority_mode)}</strong></span>
|
<span><small>Authority</small><strong>{readableToken(datasource.governance.authority_mode)}</strong></span>
|
||||||
<span><small>Classification</small><strong>{datasource.governance.classification}</strong></span>
|
<span><small>Classification</small><strong>{datasource.governance.classification}</strong></span>
|
||||||
@@ -653,25 +655,25 @@ function DatasourceDetail({
|
|||||||
{datasource.governance.semantic_definition ? (
|
{datasource.governance.semantic_definition ? (
|
||||||
<p className="datasources-dialog-copy">{datasource.governance.semantic_definition}</p>
|
<p className="datasources-dialog-copy">{datasource.governance.semantic_definition}</p>
|
||||||
) : null}
|
) : null}
|
||||||
</section>
|
</ContentSection>
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span><Eye size={16} /> Preview</span>
|
<span><Eye size={16} /> Preview</span>
|
||||||
<small>{preview ? `${formatNumber(preview.total_rows)} total rows` : ""}</small>
|
<small>{preview ? `${formatNumber(preview.total_rows)} total rows` : ""}</small>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="datasources-inline-loading">Loading preview...</div>
|
<div className="datasources-inline-loading">Loading preview...</div>
|
||||||
) : preview ? (
|
) : preview ? (
|
||||||
<PreviewTable datasource={datasource} rows={preview.rows} />
|
<PreviewTable datasource={datasource} rows={preview.rows} />
|
||||||
) : (
|
) : (
|
||||||
<div className="datasources-inline-empty">No preview available</div>
|
<StatePanel size="inline" description="No preview available" />
|
||||||
)}
|
)}
|
||||||
</section>
|
</ContentSection>
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span><Archive size={16} /> Materializations</span>
|
<span><Archive size={16} /> Materializations</span>
|
||||||
<small>Immutable revisions</small>
|
<small>Immutable revisions</small>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<div className="datasources-table-scroll">
|
<div className="datasources-table-scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -708,14 +710,14 @@ function DatasourceDetail({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</ContentSection>
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span>Schema</span>
|
<span>Schema</span>
|
||||||
<small>Version {datasource.schema_version}</small>
|
<small>Version {datasource.schema_version}</small>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<SchemaTable fields={datasource.schema} />
|
<SchemaTable fields={datasource.schema} />
|
||||||
</section>
|
</ContentSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -727,21 +729,21 @@ function StageDetail({ stage }: { stage: DatasourceStage }) {
|
|||||||
const schemaClassification = stage.validation.schema_change?.classification ?? "new";
|
const schemaClassification = stage.validation.schema_change?.classification ?? "new";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="datasources-metrics">
|
<MetricGrid columns={5} density="compact" minimum="compact">
|
||||||
<Metric label="State" value={stage.state} />
|
<MetricCard density="compact" label="State" value={stage.state} valueTitle={stage.state} />
|
||||||
<Metric label="Mode" value={stage.mode} />
|
<MetricCard density="compact" label="Mode" value={stage.mode} valueTitle={stage.mode} />
|
||||||
<Metric label="Rows" value={formatNumber(stage.row_count)} />
|
<MetricCard density="compact" label="Rows" value={formatNumber(stage.row_count)} />
|
||||||
<Metric label="Fields" value={String(stage.schema.length)} />
|
<MetricCard density="compact" label="Fields" value={String(stage.schema.length)} />
|
||||||
<Metric label="Created" value={formatDate(stage.created_at)} />
|
<MetricCard density="compact" label="Created" value={formatDate(stage.created_at)} />
|
||||||
</div>
|
</MetricGrid>
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span>Validation</span>
|
<span>Validation</span>
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={stage.validation.valid === false ? "invalid" : "ready"}
|
status={stage.validation.valid === false ? "invalid" : "ready"}
|
||||||
label={stage.validation.valid === false ? "Invalid" : "Ready"}
|
label={stage.validation.valid === false ? "Invalid" : "Ready"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<div className="datasources-key-values">
|
<div className="datasources-key-values">
|
||||||
<span><small>Fingerprint</small><strong>{shortFingerprint(stage.fingerprint)}</strong></span>
|
<span><small>Fingerprint</small><strong>{shortFingerprint(stage.fingerprint)}</strong></span>
|
||||||
<span><small>Target</small><strong>{stage.target_datasource_ref || "New datasource"}</strong></span>
|
<span><small>Target</small><strong>{stage.target_datasource_ref || "New datasource"}</strong></span>
|
||||||
@@ -770,27 +772,27 @@ function StageDetail({ stage }: { stage: DatasourceStage }) {
|
|||||||
All configured quality rules passed and no blocking schema change was detected.
|
All configured quality rules passed and no blocking schema change was detected.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</ContentSection>
|
||||||
{schemaChanges.length ? (
|
{schemaChanges.length ? (
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span>Schema comparison</span>
|
<span>Schema comparison</span>
|
||||||
<StatusBadge status={schemaClassification} label={readableToken(schemaClassification)} />
|
<StatusBadge status={schemaClassification} label={readableToken(schemaClassification)} />
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<ul className="datasources-schema-changes">
|
<ul className="datasources-schema-changes">
|
||||||
{schemaChanges.map((change, index) => (
|
{schemaChanges.map((change, index) => (
|
||||||
<SchemaChangeItem key={`${change.code}-${change.field ?? index}`} change={change} />
|
<SchemaChangeItem key={`${change.code}-${change.field ?? index}`} change={change} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</ContentSection>
|
||||||
) : null}
|
) : null}
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span>Detected schema</span>
|
<span>Detected schema</span>
|
||||||
<small>{stage.shape}</small>
|
<small>{stage.shape}</small>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<SchemaTable fields={stage.schema} />
|
<SchemaTable fields={stage.schema} />
|
||||||
</section>
|
</ContentSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -834,22 +836,22 @@ function SchemaChangeItem({ change }: { change: DatasourceSchemaChange }) {
|
|||||||
function OriginDetail({ origin }: { origin: DatasourceOrigin }) {
|
function OriginDetail({ origin }: { origin: DatasourceOrigin }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="datasources-metrics">
|
<MetricGrid columns="auto" density="compact" minimum="compact">
|
||||||
<Metric label="Provider" value={origin.provider} />
|
<MetricCard density="compact" label="Provider" value={origin.provider} valueTitle={origin.provider} />
|
||||||
<Metric label="Mode" value={readableToken(origin.source_mode)} />
|
<MetricCard density="compact" label="Mode" value={readableToken(origin.source_mode)} />
|
||||||
<Metric label="Health" value={readableToken(origin.health.status)} />
|
<MetricCard density="compact" label="Health" value={readableToken(origin.health.status)} />
|
||||||
<Metric label="Rows" value={formatNumber(origin.row_count)} />
|
<MetricCard density="compact" label="Rows" value={formatNumber(origin.row_count)} />
|
||||||
<Metric label="Fields" value={String(origin.schema.length)} />
|
<MetricCard density="compact" label="Fields" value={String(origin.schema.length)} />
|
||||||
<Metric label="Updated" value={formatDate(origin.updated_at)} />
|
<MetricCard density="compact" label="Updated" value={formatDate(origin.updated_at)} />
|
||||||
</div>
|
</MetricGrid>
|
||||||
{origin.description ? (
|
{origin.description ? (
|
||||||
<div className="datasources-description">{origin.description}</div>
|
<div className="datasources-description">{origin.description}</div>
|
||||||
) : null}
|
) : null}
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span>Registration options</span>
|
<span>Registration options</span>
|
||||||
<small>{origin.supported_modes.join(", ")}</small>
|
<small>{origin.supported_modes.join(", ")}</small>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<div className="datasources-key-values">
|
<div className="datasources-key-values">
|
||||||
<span><small>Origin reference</small><strong>{origin.ref}</strong></span>
|
<span><small>Origin reference</small><strong>{origin.ref}</strong></span>
|
||||||
<span><small>Kind</small><strong>{readableToken(origin.kind)}</strong></span>
|
<span><small>Kind</small><strong>{readableToken(origin.kind)}</strong></span>
|
||||||
@@ -858,14 +860,14 @@ function OriginDetail({ origin }: { origin: DatasourceOrigin }) {
|
|||||||
<span><small>Health</small><strong>{origin.health.summary}</strong></span>
|
<span><small>Health</small><strong>{origin.health.summary}</strong></span>
|
||||||
<span><small>Pushdown</small><strong>{pushdownSummary(origin)}</strong></span>
|
<span><small>Pushdown</small><strong>{pushdownSummary(origin)}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</ContentSection>
|
||||||
<section className="datasources-detail-section">
|
<ContentSection>
|
||||||
<div className="datasources-section-heading">
|
<ActionToolbar surface="section-header" className="datasources-section-heading">
|
||||||
<span>Discovered schema</span>
|
<span>Discovered schema</span>
|
||||||
<small>Version {origin.schema_version}</small>
|
<small>Version {origin.schema_version}</small>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<SchemaTable fields={origin.schema} />
|
<SchemaTable fields={origin.schema} />
|
||||||
</section>
|
</ContentSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1479,17 +1481,8 @@ function SchemaTable({ fields }: { fields: Datasource["schema"] }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Metric({ label, value }: { label: string; value: string }) {
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<small>{label}</small>
|
|
||||||
<strong title={value}>{value}</strong>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EmptyWorkspace({ icon, label }: { icon: React.ReactNode; label: string }) {
|
function EmptyWorkspace({ icon, label }: { icon: React.ReactNode; label: string }) {
|
||||||
return <div className="datasources-workspace-empty">{icon}<strong>{label}</strong></div>;
|
return <StatePanel size="fill" icon={icon} title={label} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterItems<T>(
|
function filterItems<T>(
|
||||||
|
|||||||
@@ -1,65 +1,9 @@
|
|||||||
.datasources-page {
|
|
||||||
position: relative;
|
|
||||||
height: calc(100vh - 115px);
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
color: var(--text);
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-page *,
|
|
||||||
.datasources-page *::before,
|
|
||||||
.datasources-page *::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-shell {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(250px, 300px) minmax(0, 1fr);
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
border: var(--border-line);
|
|
||||||
background: var(--panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-sidebar,
|
|
||||||
.datasources-workspace,
|
|
||||||
.datasources-content,
|
.datasources-content,
|
||||||
.datasources-list-frame {
|
.datasources-list-frame {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
border-right: var(--border-line);
|
|
||||||
background: var(--panel-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-sidebar-toolbar,
|
|
||||||
.datasources-workspace-toolbar,
|
|
||||||
.datasources-section-heading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
background: var(--panel-header);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-sidebar-toolbar {
|
|
||||||
min-height: 52px;
|
|
||||||
padding: 8px 10px 8px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-toolbar-actions,
|
.datasources-toolbar-actions,
|
||||||
.datasources-current-title,
|
.datasources-current-title,
|
||||||
.datasources-current-title > span,
|
.datasources-current-title > span,
|
||||||
@@ -75,80 +19,11 @@
|
|||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-search {
|
|
||||||
padding: 9px;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-search input {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 34px;
|
|
||||||
padding: 7px 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list-frame {
|
.datasources-list-frame {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-list {
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list > button {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 56px;
|
|
||||||
padding: 8px 9px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text);
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list > button:hover,
|
|
||||||
.datasources-list > button:focus-visible {
|
|
||||||
background: var(--primary-soft);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list > button.is-selected {
|
|
||||||
background: var(--primary-soft-strong);
|
|
||||||
box-shadow: inset 3px 0 0 var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list > button > span:first-child {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list strong,
|
|
||||||
.datasources-list small {
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list strong {
|
|
||||||
color: var(--text-strong);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list small {
|
|
||||||
margin-top: 4px;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-list-empty,
|
|
||||||
.datasources-inline-empty,
|
|
||||||
.datasources-inline-loading {
|
.datasources-inline-loading {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
@@ -158,17 +33,8 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-workspace {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-workspace-toolbar {
|
.datasources-workspace-toolbar {
|
||||||
min-height: 58px;
|
min-width: 0;
|
||||||
padding: 8px 10px 8px 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-current-title {
|
.datasources-current-title {
|
||||||
@@ -218,43 +84,6 @@
|
|||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-metrics {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, minmax(100px, 1fr));
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-metrics > span {
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 61px;
|
|
||||||
padding: 9px 10px;
|
|
||||||
border: var(--border-line);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: var(--panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-metrics small,
|
|
||||||
.datasources-metrics strong {
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-metrics small {
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 10px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-metrics strong {
|
|
||||||
margin-top: 6px;
|
|
||||||
color: var(--text-strong);
|
|
||||||
font-size: 14px;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-description {
|
.datasources-description {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
@@ -265,18 +94,6 @@
|
|||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-detail-section {
|
|
||||||
min-width: 0;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
border: var(--border-line);
|
|
||||||
background: var(--panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-section-heading {
|
|
||||||
min-height: 42px;
|
|
||||||
padding: 7px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-section-heading > span {
|
.datasources-section-heading > span {
|
||||||
color: var(--text-strong);
|
color: var(--text-strong);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -501,26 +318,10 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-shell {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
height: auto;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-sidebar {
|
|
||||||
max-height: 42vh;
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-workspace {
|
.datasources-workspace {
|
||||||
min-height: 58vh;
|
min-height: 58vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datasources-metrics {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasources-workspace-toolbar {
|
.datasources-workspace-toolbar {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -532,7 +333,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
.datasources-metrics,
|
|
||||||
.datasources-key-values {
|
.datasources-key-values {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user