diff --git a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx index 89360955a..0991e1761 100644 --- a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx +++ b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; import Image from 'next/image'; import styled from 'styled-components'; +import { extractMonitors } from '@/utils'; import { DeleteWarning } from '@/components'; import { IAppState, useAppStore } from '@/store'; import { OVERVIEW_ENTITY_TYPES, type ConfiguredDestination } from '@/types'; -import { Button, DataCardFields, Divider, ExtendIcon, Text } from '@/reuseable-components'; +import { DataCardFields, DataTab, IconButton, Text } from '@/reuseable-components'; const Container = styled.div` display: flex; @@ -18,128 +19,26 @@ const Container = styled.div` overflow-y: scroll; `; -const ListItem = styled.div` - width: 100%; - border-radius: 16px; - background: ${({ theme }) => theme.colors.translucent_bg}; -`; - -const ListItemBody = styled.div` - width: 100%; - padding: 16px; -`; - -const ListItemHeader = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 16px 0px; -`; - -const ListItemContent = styled.div` - display: flex; - gap: 12px; - margin-left: 16px; -`; - -const DestinationIconWrapper = styled.div` - display: flex; - width: 36px; - height: 36px; - justify-content: center; - align-items: center; - gap: 8px; - border-radius: 8px; - background: linear-gradient(180deg, rgba(249, 249, 249, 0.06) 0%, rgba(249, 249, 249, 0.02) 100%); -`; - -const SignalsWrapper = styled.div` - display: flex; - align-items: center; - gap: 4px; -`; - -const SignalText = styled(Text)` - color: rgba(249, 249, 249, 0.8); - font-size: 10px; - text-transform: capitalize; -`; - -const TextWrapper = styled.div` - display: flex; - flex-direction: column; - height: 36px; - justify-content: space-between; -`; - -const IconsContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - margin-right: 16px; -`; - -const IconButton = styled(Button)<{ $expand?: boolean }>` - transition: background 0.3s ease 0s, transform 0.3s ease 0s; - transform: ${({ $expand }) => ($expand ? 'rotate(-180deg)' : 'rotate(0deg)')}; -`; - -const ConfiguredDestinationsListItem: React.FC<{ item: ConfiguredDestination; isLastItem: boolean }> = ({ item, isLastItem }) => { - const [expand, setExpand] = useState(false); - const [deleteWarning, setDeleteWarning] = useState(false); +const ListItem: React.FC<{ item: ConfiguredDestination; isLastItem: boolean }> = ({ item, isLastItem }) => { const { removeConfiguredDestination } = useAppStore((state) => state); - - function renderSupportedSignals(item: ConfiguredDestination) { - const supportedSignals = item.exportedSignals; - const signals = Object.keys(supportedSignals); - const supportedSignalsList = signals.filter((signal) => supportedSignals[signal].supported); - - return Object.keys(supportedSignals).map( - (signal, index) => - supportedSignals[signal] && ( - - monitor - - {signal} - {index < supportedSignalsList.length - 1 && ·} - - ), - ); - } + const [deleteWarning, setDeleteWarning] = useState(false); return ( <> - - - - - destination - - - {item.displayName} - {renderSupportedSignals(item)} - - - - - setDeleteWarning(true)}> - delete - - - setExpand(!expand)}> - - - - - - {expand && ( - - - - + } + renderActions={() => ( + setDeleteWarning(true)}> + delete + )} - + /> {data.map(({ stored }) => ( - + ))} ); diff --git a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx index b1feeeb42..5a86fbf4e 100644 --- a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx +++ b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx @@ -8,7 +8,7 @@ import { useDescribeSource, useSourceCRUD } from '@/hooks'; import OverviewDrawer from '../../overview/overview-drawer'; import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type K8sActualSource } from '@/types'; import { ConditionDetails, DataCard, DataCardRow, DataCardFieldTypes } from '@/reuseable-components'; -import { ACTION, DATA_CARDS, getMainContainerLanguage, getProgrammingLanguageIcon, safeJsonStringify } from '@/utils'; +import { ACTION, BACKEND_BOOLEAN, DATA_CARDS, getMainContainerLanguage, getProgrammingLanguageIcon, safeJsonStringify } from '@/utils'; interface Props {} @@ -79,13 +79,17 @@ export const SourceDrawer: React.FC = () => { const { item } = selectedItem as { item: K8sActualSource }; + const hasPresenceOfOtherAgent = item.instrumentedApplicationDetails.conditions.some( + (condition) => condition.status === BACKEND_BOOLEAN.FALSE && condition.message.includes('device not added to any container due to the presence of another agent'), + ); + return ( item.instrumentedApplicationDetails.containers.map( (container) => ({ type: DataCardFieldTypes.SOURCE_CONTAINER, width: '100%', - value: JSON.stringify(container), + value: JSON.stringify({ ...container, hasPresenceOfOtherAgent }), } as DataCardRow), ) || [] ); diff --git a/frontend/webapp/reuseable-components/condition-details/index.tsx b/frontend/webapp/reuseable-components/condition-details/index.tsx index 392a66c06..3c91cb800 100644 --- a/frontend/webapp/reuseable-components/condition-details/index.tsx +++ b/frontend/webapp/reuseable-components/condition-details/index.tsx @@ -60,7 +60,7 @@ export const ConditionDetails: React.FC = ({ conditions }) => { ({hasErrors ? errors.length : conditions.length}/{conditions.length}) - + {extend && ( diff --git a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx index c09ce002e..cfdfb8ceb 100644 --- a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx +++ b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx @@ -1,7 +1,8 @@ import React, { useId } from 'react'; import styled from 'styled-components'; -import { ActiveStatus, Code, DataTab, Divider, InstrumentStatus, MonitorsIcons, Text, Tooltip } from '@/reuseable-components'; +import { ActiveStatus, Code, DataTab, Divider, InstrumentStatus, MonitorsIcons, NotificationNote, Text, Tooltip } from '@/reuseable-components'; import { capitalizeFirstLetter, getProgrammingLanguageIcon, parseJsonStringToPrettyString, safeJsonParse, WORKLOAD_PROGRAMMING_LANGUAGES } from '@/utils'; +import { NOTIFICATION_TYPE } from '@/types'; export enum DataCardFieldTypes { DIVIDER = 'divider', @@ -81,20 +82,37 @@ const renderValue = (type: DataCardRow['type'], value: DataCardRow['value']) => return ; case DataCardFieldTypes.SOURCE_CONTAINER: { - const { containerName, language, runtimeVersion } = safeJsonParse(value, { + const { containerName, language, runtimeVersion, otherAgent, hasPresenceOfOtherAgent } = safeJsonParse(value, { containerName: '-', language: WORKLOAD_PROGRAMMING_LANGUAGES.UNKNOWN, runtimeVersion: '-', + otherAgent: null, + hasPresenceOfOtherAgent: false, }); + // Determine if running concurrently is possible based on language and other_agent + const canRunInParallel = !hasPresenceOfOtherAgent && (language === WORKLOAD_PROGRAMMING_LANGUAGES.PYTHON || language === WORKLOAD_PROGRAMMING_LANGUAGES.JAVA); + return ( - - + isExtended={!!otherAgent} + renderExtended={() => ( + + )} + renderActions={() => } + /> ); } diff --git a/frontend/webapp/reuseable-components/data-tab/index.tsx b/frontend/webapp/reuseable-components/data-tab/index.tsx index 71575cc7e..1fa6cbf4b 100644 --- a/frontend/webapp/reuseable-components/data-tab/index.tsx +++ b/frontend/webapp/reuseable-components/data-tab/index.tsx @@ -1,24 +1,28 @@ -import React, { PropsWithChildren, useCallback } from 'react'; +import React, { Fragment, useCallback, useState } from 'react'; import Image from 'next/image'; -import { FlexColumn } from '@/styles'; +import { FlexColumn, FlexRow } from '@/styles'; import styled, { css } from 'styled-components'; -import { ActiveStatus, MonitorsIcons, Text } from '@/reuseable-components'; +import { ActiveStatus, Divider, ExtendIcon, IconButton, MonitorsIcons, Text } from '@/reuseable-components'; -interface Props extends PropsWithChildren { +interface Props { title: string; subTitle: string; logo: string; monitors?: string[]; + monitorsWithLabels?: boolean; isActive?: boolean; isError?: boolean; + withExtend?: boolean; + isExtended?: boolean; + renderExtended?: () => JSX.Element; + renderActions?: () => JSX.Element; onClick?: () => void; } const Container = styled.div<{ $withClick: boolean; $isError: Props['isError'] }>` display: flex; - align-items: center; + flex-direction: column; align-self: stretch; - gap: 8px; padding: 16px; width: calc(100% - 32px); border-radius: 16px; @@ -51,6 +55,7 @@ const Title = styled(Text)` overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + font-size: 14px; `; const SubTitleWrapper = styled.div` @@ -71,45 +76,72 @@ const ActionsWrapper = styled.div` margin-left: auto; `; -export const DataTab: React.FC = ({ title, subTitle, logo, monitors, isActive, isError, onClick, children }) => { - const renderMonitors = useCallback(() => { - if (!monitors) return null; - - return ( - <> - {'•'} - - - ); - }, [monitors]); - - const renderActiveStatus = useCallback(() => { - if (typeof isActive !== 'boolean') return null; - - return ( - <> - {'•'} - - - ); - }, [isActive]); +export const DataTab: React.FC = ({ title, subTitle, logo, monitors, monitorsWithLabels, isActive, isError, withExtend, isExtended, renderExtended, renderActions, onClick }) => { + const [extend, setExtend] = useState(isExtended || false); + + const renderMonitors = useCallback( + (withSeperator: boolean) => { + if (!monitors || !monitors.length) return null; + + return ( + <> + {withSeperator && {'•'}} + + + ); + }, + [monitors], + ); + + const renderActiveStatus = useCallback( + (withSeperator: boolean) => { + if (typeof isActive !== 'boolean') return null; + + return ( + <> + {withSeperator && {'•'}} + + + ); + }, + [isActive], + ); return ( - - - - - - {title} - - {subTitle} - {renderMonitors()} - {renderActiveStatus()} - - - - {children} + + + + + + + {title} + + {subTitle && {subTitle}} + {renderMonitors(!!subTitle)} + {renderActiveStatus(!!monitors?.length)} + + + + + {renderActions && renderActions()} + {withExtend && ( + + + setExtend((prev) => !prev)}> + + + + )} + + + + {extend && renderExtended && ( + + + {renderExtended()} + + )} ); }; diff --git a/frontend/webapp/reuseable-components/extend-icon/index.tsx b/frontend/webapp/reuseable-components/extend-icon/index.tsx index c36caae5e..62b93d8e4 100644 --- a/frontend/webapp/reuseable-components/extend-icon/index.tsx +++ b/frontend/webapp/reuseable-components/extend-icon/index.tsx @@ -5,10 +5,11 @@ import styled from 'styled-components'; interface Props { extend: boolean; size?: number; - align?: 'left' | 'right'; + align?: 'left' | 'right' | 'center'; } const Icon = styled(Image)<{ $align?: Props['align'] }>` + margin: ${({ $align }) => ($align === 'right' ? 'auto 0 auto auto' : $align === 'left' ? 'auto auto auto 0' : 'auto')}; &.open { transform: rotate(180deg); } @@ -16,9 +17,8 @@ const Icon = styled(Image)<{ $align?: Props['align'] }>` transform: rotate(0deg); } transition: transform 0.3s; - margin-${({ $align }) => ($align === 'right' ? 'left' : 'right')}: auto; `; -export const ExtendIcon: React.FC = ({ extend, size = 14, align = 'right' }) => { +export const ExtendIcon: React.FC = ({ extend, size = 14, align = 'center' }) => { return ; }; diff --git a/frontend/webapp/reuseable-components/monitors-icons/index.tsx b/frontend/webapp/reuseable-components/monitors-icons/index.tsx index 9c580d451..ccd8ae1fa 100644 --- a/frontend/webapp/reuseable-components/monitors-icons/index.tsx +++ b/frontend/webapp/reuseable-components/monitors-icons/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import Image from 'next/image'; +import theme from '@/styles/theme'; import { FlexRow } from '@/styles'; import { capitalizeFirstLetter } from '@/utils'; import { Text, Tooltip } from '@/reuseable-components'; @@ -15,14 +16,18 @@ export const MonitorsIcons: React.FC = ({ monitors, withTooltips, withLab return ( {monitors.map((str) => { - const signal = str.toLocaleLowerCase(); + const signal = str.toLowerCase(); const signalDisplayName = capitalizeFirstLetter(signal); return ( {signal} - {withLabels && {signalDisplayName}} + {withLabels && ( + + {signalDisplayName} + + )} ); diff --git a/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx b/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx index 583103e5b..e7ea1a084 100644 --- a/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx +++ b/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx @@ -74,10 +74,7 @@ const BaseNode: React.FC = ({ id: nodeId, data }) => { return ( - {}}> - {renderActions()} - - + {}} renderActions={renderActions} />