From 958ee46cb1ad8079062805a526bd7216631db65c Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Tue, 28 Jan 2025 12:46:37 +0100 Subject: [PATCH] improve table display --- .../components/drawer/record/record-field.css | 19 ++- .../components/drawer/record/record-field.tsx | 121 ++++++++++-------- 2 files changed, 87 insertions(+), 53 deletions(-) diff --git a/web/src/components/drawer/record/record-field.css b/web/src/components/drawer/record/record-field.css index 16f755f6c..236ed4aa3 100644 --- a/web/src/components/drawer/record/record-field.css +++ b/web/src/components/drawer/record/record-field.css @@ -4,18 +4,25 @@ white-space: nowrap } +.field-text { + display: flex; + flex: 1; +} + .co-resource-item { display: flex; } /* max content lines to show */ +.field-text.s>.record-field-value, +.field-text.m>.record-field-value, +.field-text.l>.record-field-value, .co-resource-item.s>.co-resource-item__resource-name, .co-resource-item.m>.co-resource-item__resource-name, .co-resource-item.l>.co-resource-item__resource-name { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; - -webkit-line-clamp: 2; -webkit-box-orient: vertical; } @@ -32,14 +39,17 @@ width: 100%; } +.field-text.s>.record-field-value, .co-resource-item.s>.co-resource-item__resource-name { -webkit-line-clamp: 1; } +.field-text.m>.record-field-value, .co-resource-item.m>.co-resource-item__resource-name { -webkit-line-clamp: 2; } +.field-text.l>.record-field-value, .co-resource-item.l>.co-resource-item__resource-name { -webkit-line-clamp: 3; } @@ -50,6 +60,11 @@ flex-direction: column; } +.record-field-flex-container.s { + flex-direction: row; + flex-wrap: nowrap; +} + /* table tooltips - check pf-c-tooltip__content for values */ .record-field-tooltip { display: flex; @@ -71,6 +86,8 @@ } /* show tooltip on content hover */ +.field-text:hover .record-field-tooltip, +.force-truncate:hover .record-field-tooltip, .record-field-content-flex.truncated:hover .record-field-tooltip, .record-field-content.truncated:hover .record-field-tooltip { visibility: visible; diff --git a/web/src/components/drawer/record/record-field.tsx b/web/src/components/drawer/record/record-field.tsx index 1eb79c716..138122dcf 100644 --- a/web/src/components/drawer/record/record-field.tsx +++ b/web/src/components/drawer/record/record-field.tsx @@ -24,6 +24,9 @@ export type RecordFieldFilter = { isDelete: boolean; }; +export type FlexValue = 'flexDefault' | 'flexNone' | 'flex_1' | 'flex_2' | 'flex_3' | 'flex_4'; +export type FlexWrapValue = 'wrap' | 'wrapReverse' | 'nowrap'; + export interface RecordFieldProps { allowPktDrops: boolean; flow: Record; @@ -46,16 +49,7 @@ export const RecordField: React.FC = ({ isDark }) => { const { t } = useTranslation('plugin__netobserv-plugin'); - - const onMouseOver = (event: React.MouseEvent, className: string) => { - if (event.currentTarget) { - const isTruncated = - event.currentTarget.offsetHeight < event.currentTarget.scrollHeight || - event.currentTarget.offsetWidth < event.currentTarget.scrollWidth || - (event.currentTarget.children.length > 0 && event.currentTarget.children[0].className === 'force-truncate'); - event.currentTarget.className = isTruncated ? `${className} truncated ${size}` : `${className} ${size}`; - } - }; + const multiLineSize = size === 'l' ? 'm' : 's'; const errorTextValue = (value: string, text: string) => { return ( @@ -96,10 +90,13 @@ export const RecordField: React.FC = ({ ); }; - const simpleTextWithTooltip = (text?: string, color?: string, child?: JSX.Element) => { + const simpleTextWithTooltip = (text?: string, color?: string, child?: JSX.Element, forcedSize?: Size) => { if (text) { return ( - + {text} @@ -113,13 +110,13 @@ export const RecordField: React.FC = ({ return undefined; }; - const resourceIconText = (value: string, kind: string, ns?: string) => { + const resourceIconText = (value: string, kind: string, ns?: string, forcedSize?: Size) => { return ( //force ResourceLink when ResourceIcon is not defined (ie OCP < 4.12) !ResourceIcon || useLinks ? ( ) : ( - + {value} @@ -130,19 +127,24 @@ export const RecordField: React.FC = ({ }; const kubeObjContainer = (k: KubeObj) => { - const main = kubeObjContent(k.name, k.kind, k.namespace); + const main = kubeObjContent(k.name, k.kind, k.namespace, multiLineSize); if (k.showNamespace && k.namespace) { - return doubleContainer(main, kindContent('Namespace', k.namespace), false); + return doubleContainer(main, kindContent('Namespace', k.namespace, multiLineSize), false, true, 'm'); } return singleContainer(main); }; - const kubeObjContent = (value: string | undefined, kind: string | undefined, ns: string | undefined) => { + const kubeObjContent = ( + value: string | undefined, + kind: string | undefined, + ns: string | undefined, + forcedSize?: Size + ) => { // Note: namespace is not mandatory here (e.g. Node objects) if (value && kind) { return (
- {resourceIconText(value, kind, ns)} + {resourceIconText(value, kind, ns, forcedSize)} {kubeTooltip(value, kind, ns)}
); @@ -165,11 +167,11 @@ export const RecordField: React.FC = ({ ); }; - const kindContent = (kind: 'Namespace' | 'Node', value?: string) => { + const kindContent = (kind: 'Namespace' | 'Node', value?: string, forcedSize?: Size) => { if (value) { return (
- {resourceIconText(value, kind)} + {resourceIconText(value, kind, undefined, forcedSize)} {t(kind)} {value} @@ -213,43 +215,48 @@ export const RecordField: React.FC = ({ ); }; - const nthContainer = (children: (JSX.Element | undefined)[], asChild = true, childIcon = true) => { + const nthContainer = (children: (JSX.Element | undefined)[], asChild = true, childIcon = true, forcedSize?: Size) => { return ( - - {children.map((c, i) => ( - onMouseOver(e, `record-field-content`)} - flex={{ default: 'flex_1' }} - > - {i > 0 && asChild && childIcon && {'↪'}} - {c ? c : emptyText()} - - ))} + + {children.map((c, i) => { + const child = c ? c : emptyText(); + if (i > 0 && asChild && childIcon) { + const arrow = {'↪'}; + return sideBySideContainer(arrow, child, 'flexNone', 'flex_1', 'nowrap'); + } + return child; + })} ); }; - const doubleContainer = (child1?: JSX.Element, child2?: JSX.Element, asChild = true, childIcon = true) => { - return nthContainer([child1, child2], asChild, childIcon); + const doubleContainer = ( + child1?: JSX.Element, + child2?: JSX.Element, + asChild = true, + childIcon = true, + forcedSize?: Size + ) => { + return nthContainer([child1, child2], asChild, childIcon, forcedSize); }; - const sideBySideContainer = (leftElement?: JSX.Element, rightElement?: JSX.Element) => { + const sideBySideContainer = ( + leftElement?: JSX.Element, + rightElement?: JSX.Element, + leftFlex: FlexValue = 'flex_1', + rightFlex: FlexValue = 'flex_1', + wrap: FlexWrapValue = 'wrap' + ) => { return ( - - {leftElement || emptyText()} - {rightElement || emptyText()} + + {leftElement || emptyText()} + {rightElement || emptyText()} ); }; const singleContainer = (child?: JSX.Element) => { - return ( -
onMouseOver(e, 'record-field-content')}> - {child ? child : emptyText()} -
- ); + return
{child ? child : emptyText()}
; }; const clickableContent = (text: string, content: string, docUrl?: string) => { @@ -408,7 +415,8 @@ export const RecordField: React.FC = ({ return nthContainer( value.map(dir => simpleTextWithTooltip(getDirectionDisplayString(String(dir) as FlowDirection, t))), true, - false + false, + multiLineSize ); } return singleContainer(simpleTextWithTooltip(getDirectionDisplayString(String(value) as FlowDirection, t))); @@ -418,7 +426,8 @@ export const RecordField: React.FC = ({ return nthContainer( value.map(iName => simpleTextWithTooltip(String(iName))), true, - false + false, + multiLineSize ); } return singleContainer(simpleTextWithTooltip(String(value))); @@ -434,9 +443,12 @@ export const RecordField: React.FC = ({ return nthContainer( flow.fields.Interfaces.map((iName, i) => sideBySideContainer( - simpleTextWithTooltip(iName), + simpleTextWithTooltip(iName, undefined, undefined, multiLineSize), simpleTextWithTooltip( - getDirectionDisplayString(String(flow.fields.IfDirections![i]) as FlowDirection, t) + getDirectionDisplayString(String(flow.fields.IfDirections![i]) as FlowDirection, t), + undefined, + undefined, + multiLineSize ) ) ), @@ -467,13 +479,16 @@ export const RecordField: React.FC = ({ return doubleContainer( simpleTextWithTooltip( detailed ? `${sentCount} ${c.name.toLowerCase()} ${t('sent')}` : sentCount, - allowPktDrops ? (isDark ? '#3E8635' : '#1E4F18') : undefined + allowPktDrops ? (isDark ? '#3E8635' : '#1E4F18') : undefined, + undefined, + multiLineSize ), droppedCount ? ( simpleTextWithTooltip( detailed ? `${droppedCount} ${c.name.toLowerCase()} ${droppedText}` : droppedCount, isDark ? '#C9190B' : '#A30000', - child + child, + multiLineSize ) ) : ( <> @@ -545,8 +560,10 @@ export const RecordField: React.FC = ({ if (Array.isArray(value) && value.length) { // we can only show two values properly with containers if (value.length === 2) { - const contents = value.map(v => (isKubeObj(v) ? kubeObjContainer(v) : simpleTextWithTooltip(String(v)))); - return doubleContainer(contents[0], contents[1]); + const contents = value.map(v => + isKubeObj(v) ? kubeObjContainer(v) : simpleTextWithTooltip(String(v), undefined, undefined, multiLineSize) + ); + return doubleContainer(contents[0], contents[1], undefined, undefined, multiLineSize); } // else we will show values as single joigned string return singleContainer(simpleTextWithTooltip(value.map(v => String(v)).join(', ')));