@@ -16,6 +16,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
1616import { useBlocker , useNavigate , useRouter , useSearch } from '@tanstack/react-router' ;
1717import { isSystemTag } from 'components/constants' ;
1818import { Alert , AlertDescription , AlertTitle } from 'components/redpanda-ui/components/alert' ;
19+ import { Badge } from 'components/redpanda-ui/components/badge' ;
1920import { Banner , BannerClose , BannerContent } from 'components/redpanda-ui/components/banner' ;
2021import { Button } from 'components/redpanda-ui/components/button' ;
2122import { CopyButton } from 'components/redpanda-ui/components/copy-button' ;
@@ -34,7 +35,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'components
3435import { Separator } from 'components/redpanda-ui/components/separator' ;
3536import { Skeleton } from 'components/redpanda-ui/components/skeleton' ;
3637import { Spinner } from 'components/redpanda-ui/components/spinner' ;
37- import { Heading , Text } from 'components/redpanda-ui/components/typography' ;
38+ import { Heading } from 'components/redpanda-ui/components/typography' ;
3839import { cn } from 'components/redpanda-ui/lib/utils' ;
3940import { LogExplorer } from 'components/ui/connect/log-explorer' ;
4041import { DeleteResourceAlertDialog } from 'components/ui/delete-resource-alert-dialog' ;
@@ -61,7 +62,7 @@ import {
6162 PipelineUpdateSchema ,
6263 UpdatePipelineRequestSchema as UpdatePipelineRequestSchemaDataPlane ,
6364} from 'protogen/redpanda/api/dataplane/v1/pipeline_pb' ;
64- import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
65+ import { type ReactNode , useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
6566import { type Resolver , type UseFormReturn , useForm , useWatch } from 'react-hook-form' ;
6667import {
6768 useGetPipelineServiceConfigSchemaQuery ,
@@ -476,81 +477,92 @@ function EditorSkeleton() {
476477 ) ;
477478}
478479
479- const ConfigField = ( {
480- label,
481- value,
482- copyable = false ,
483- multiline = false ,
484- } : {
485- label : string ;
486- value : string ;
487- copyable ?: boolean ;
488- multiline ?: boolean ;
489- } ) => (
490- < div className = "group/field flex min-w-0 flex-col gap-1" >
491- < Text className = "text-muted-foreground" variant = "label" >
492- { label }
493- </ Text >
494- < div className = { cn ( 'flex min-w-0 gap-1' , multiline ? 'items-start' : 'items-center' ) } >
495- < Text
496- as = { multiline ? 'p' : 'div' }
497- className = { cn ( multiline ? 'whitespace-pre-wrap break-words' : 'truncate' ) }
498- title = { multiline ? undefined : value }
499- >
500- { value }
501- </ Text >
502- { copyable && value ? (
503- < CopyButton
504- className = "shrink-0 opacity-0 transition-opacity group-hover/field:opacity-100"
505- content = { value }
506- size = "sm"
507- variant = "ghost"
508- />
509- ) : null }
510- </ div >
511- </ div >
480+ // One row in the summary's definition list: an aligned label column and a value
481+ // column that fills the remaining width. Caller renders <InfoRow>s into a
482+ // `grid grid-cols-[max-content_minmax(0,1fr)]` <dl>.
483+ const InfoRow = ( { label, children } : { label : string ; children : ReactNode } ) => (
484+ < >
485+ < dt className = "font-medium text-muted-foreground text-sm" > { label } </ dt >
486+ < dd className = "min-w-0 text-foreground text-sm" > { children } </ dd >
487+ </ >
488+ ) ;
489+
490+ // Value that reveals a copy button on hover. Used for ID / URL / service account.
491+ const CopyableValue = ( { value, mono } : { value : string ; mono ?: boolean } ) => (
492+ < span className = "group/copy flex min-w-0 items-center gap-1" >
493+ < span className = { cn ( 'min-w-0 truncate' , mono && 'font-mono' ) } title = { value } >
494+ { value }
495+ </ span >
496+ < CopyButton
497+ className = "shrink-0 opacity-0 transition-opacity group-hover/copy:opacity-100"
498+ content = { value }
499+ size = "sm"
500+ variant = "ghost"
501+ />
502+ </ span >
512503) ;
513504
505+ const TagBadges = ( { tags } : { tags : { key : string ; value : string } [ ] } ) =>
506+ tags . length > 0 ? (
507+ < div className = "flex flex-wrap gap-1.5" >
508+ { tags . map ( ( t ) => (
509+ < Badge key = { t . key } variant = "simple-outline" >
510+ { t . value ? `${ t . key } : ${ t . value } ` : t . key }
511+ </ Badge >
512+ ) ) }
513+ </ div >
514+ ) : (
515+ < span className = "text-muted-foreground italic" > None</ span >
516+ ) ;
517+
514518// Pipeline identity + metadata shown as a summary card above the main panel in
515519// view mode. The full set (including empty fields) lives in the details dialog.
516520const PipelineSummary = ( { pipeline } : { pipeline : Pipeline } ) => {
517521 const tasks = cpuToTasks ( pipeline . resources ?. cpuShares ) ?? 0 ;
518522 const description = pipeline . description ?. trim ( ) ;
523+ const tags = Object . entries ( pipeline . tags )
524+ . filter ( ( [ k ] ) => ! isSystemTag ( k ) )
525+ . map ( ( [ key , value ] ) => ( { key, value } ) ) ;
519526 return (
520- < div className = "flex flex-col gap-5 rounded-lg border bg-muted/20 px-6 py-5" >
521- < div className = "flex items-start justify-between gap-4" >
522- < div className = "flex min-w-0 flex-col gap-1" >
523- < Text className = "text-muted-foreground" variant = "label" >
524- Name
525- </ Text >
526- < Heading className = "truncate" level = { 2 } title = { pipeline . displayName || pipeline . id } >
527- { pipeline . displayName || pipeline . id }
528- </ Heading >
529- </ div >
530- < PipelineStatusBadge state = { pipeline . state } />
527+ < div className = "flex flex-col gap-4 rounded-lg border px-5 py-4" >
528+ < div className = "flex items-center justify-between gap-4" >
529+ < Heading className = "min-w-0 truncate" level = { 3 } title = { pipeline . displayName || pipeline . id } >
530+ { pipeline . displayName || pipeline . id }
531+ </ Heading >
532+ < PipelineStatusBadge size = "sm" state = { pipeline . state } />
531533 </ div >
532534 < Separator variant = "subtle" />
533- < div className = "grid grid-cols-1 gap-x-10 gap-y-4 sm:grid-cols-2 lg:grid-cols-3" >
534- < ConfigField copyable label = "ID" value = { pipeline . id } />
535- < ConfigField label = "Compute units" value = { `${ tasks } ` } />
535+ < dl className = "grid grid-cols-[max-content_minmax(0,1fr)] items-start gap-x-10 gap-y-3" >
536+ < InfoRow label = "ID" >
537+ < CopyableValue mono value = { pipeline . id } />
538+ </ InfoRow >
539+ < InfoRow label = "Compute units" > { tasks } </ InfoRow >
536540 { pipeline . serviceAccount ? (
537- < ConfigField copyable label = "Service account" value = { pipeline . serviceAccount . clientId } />
541+ < InfoRow label = "Service account" >
542+ < CopyableValue mono value = { pipeline . serviceAccount . clientId } />
543+ </ InfoRow >
538544 ) : null }
539- { pipeline . url ? < ConfigField copyable label = "URL" value = { pipeline . url } /> : null }
540- </ div >
541- { description ? (
542- < div className = "flex min-w-0 flex-col gap-1" >
543- < Text className = "text-muted-foreground" variant = "label" >
544- Description
545- </ Text >
546- < Text className = "line-clamp-3 whitespace-pre-wrap break-words text-sm" title = { description } >
547- { description }
548- </ Text >
549- </ div >
550- ) : null }
545+ { pipeline . url ? (
546+ < InfoRow label = "URL" >
547+ < CopyableValue value = { pipeline . url } />
548+ </ InfoRow >
549+ ) : null }
550+ { tags . length > 0 ? (
551+ < InfoRow label = "Tags" >
552+ < TagBadges tags = { tags } />
553+ </ InfoRow >
554+ ) : null }
555+ { description ? (
556+ < InfoRow label = "Description" >
557+ < p className = "line-clamp-2 whitespace-pre-wrap break-words" title = { description } >
558+ { description }
559+ </ p >
560+ </ InfoRow >
561+ ) : null }
562+ </ dl >
551563 < Separator variant = "subtle" />
552- { /* Run control lives at the card footer, away from the header's Edit button . */ }
553- < div className = "flex items-center justify-end" >
564+ { /* Run control at the footer-right (distinct intent from the edit-settings action) . */ }
565+ < div className = "flex justify-end" >
554566 < PipelineRunControl pipelineId = { pipeline . id } pipelineState = { pipeline . state } />
555567 </ div >
556568 </ div >
@@ -563,35 +575,31 @@ const EditSummary = ({ form, onEdit }: { form: UseFormReturn<PipelineFormValues>
563575 const name = useWatch ( { control : form . control , name : 'name' } ) ;
564576 const description = useWatch ( { control : form . control , name : 'description' } ) ?. trim ( ) ;
565577 const computeUnits = useWatch ( { control : form . control , name : 'computeUnits' } ) ;
578+ const tags = ( useWatch ( { control : form . control , name : 'tags' } ) ?? [ ] ) . filter ( ( t ) => t . key ) ;
566579 return (
567- < div className = "flex flex-col gap-5 rounded-lg border bg-muted/20 px-6 py-5" >
568- < div className = "flex min-w-0 flex-col gap-1" >
569- < Text className = "text-muted-foreground" variant = "label" >
570- Name
571- </ Text >
572- < Heading className = "truncate" level = { 2 } title = { name } >
573- { name || 'Untitled pipeline' }
574- </ Heading >
575- </ div >
580+ < div className = "flex flex-col gap-4 rounded-lg border px-5 py-4" >
581+ < Heading className = "min-w-0 truncate" level = { 3 } title = { name } >
582+ { name || 'Untitled pipeline' }
583+ </ Heading >
576584 < Separator variant = "subtle" />
577- < div className = "grid grid-cols-1 gap-x-10 gap-y-4 sm:grid-cols -3" >
578- < ConfigField label = "Compute units" value = { ` ${ computeUnits } ` } / >
579- </ div >
580- < div className = "flex min-w-0 flex-col gap-1" >
581- < Text className = "text-muted-foreground" variant = "label" >
582- Description
583- </ Text >
584- { description ? (
585- < Text className = "line-clamp-3 whitespace-pre-wrap break-words text-sm" title = { description } >
586- { description }
587- </ Text >
588- ) : (
589- < Text className = "text-muted-foreground text-sm italic" > No description </ Text >
590- ) }
591- </ div >
585+ < dl className = "grid grid-cols-[max-content_minmax(0,1fr)] items-start gap-x-10 gap-y-3" >
586+ < InfoRow label = "Compute units" > { computeUnits } </ InfoRow >
587+ < InfoRow label = "Tags" >
588+ < TagBadges tags = { tags } / >
589+ </ InfoRow >
590+ < InfoRow label = " Description" >
591+ { description ? (
592+ < p className = "line-clamp-2 whitespace-pre-wrap break-words" title = { description } >
593+ { description }
594+ </ p >
595+ ) : (
596+ < span className = "text-muted-foreground italic" > None </ span >
597+ ) }
598+ </ InfoRow >
599+ </ dl >
592600 < Separator variant = "subtle" />
593- { /* Edit action at the card footer, away from the header's Save button. */ }
594- < div className = "flex items-center justify-end " >
601+ { /* Edit action at the footer-left , away from the header's Save button. */ }
602+ < div className = "flex justify-start " >
595603 < Button icon = { < Settings /> } onClick = { onEdit } size = "sm" variant = "outline" >
596604 Edit settings
597605 </ Button >
@@ -1006,12 +1014,7 @@ export default function PipelinePage() {
10061014 } , [ mode , clearWizardStore , navigate , pipelineId , router ] ) ;
10071015
10081016 return (
1009- < div
1010- className = { cn (
1011- 'flex max-w-[calc(100dvw-(--sidebar-width))] flex-col gap-4' ,
1012- mode === 'view' ? 'h-full min-h-[calc(100dvh-10rem)]' : 'h-[calc(100dvh-10rem)]'
1013- ) }
1014- >
1017+ < div className = "flex min-h-[calc(100dvh-10rem)] max-w-[calc(100dvw-(--sidebar-width))] flex-col gap-4" >
10151018 { /* Top framing border that lines up with the content edge, matching the
10161019 listings page header. Negative margin cancels the layout's pt-8. */ }
10171020 < div className = "-mt-8 border-divider-default border-b" />
@@ -1026,7 +1029,9 @@ export default function PipelinePage() {
10261029 />
10271030 { mode === 'view' && pipeline ? < PipelineSummary pipeline = { pipeline } /> : null }
10281031 { mode !== 'view' ? < EditSummary form = { form } onEdit = { ( ) => setIsConfigDialogOpen ( true ) } /> : null }
1029- < div className = "flex min-h-0 flex-1 rounded-lg border border-border!" >
1032+ { /* Grows to fill a tall viewport but keeps a usable minimum so the editor /
1033+ flow panels aren't squished when the summary card is tall. */ }
1034+ < div className = "flex min-h-[640px] flex-1 rounded-lg border border-border!" >
10301035 < SidebarPanel
10311036 isPipelineDiagramsEnabled = { isPipelineDiagramsEnabled }
10321037 mode = { mode }
0 commit comments