-
+
+
+
+
-
- {t('containers_visualization_message')}
-
-
+
+ {t('containers_visualization_message')}
+
+
-
-
-
- {t('working_message')}
-
+
+
+
+ {t('working_message')}
+
+
+
+
+
);
diff --git a/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx b/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx
index 7a3ab8c0ab..ee61e687cb 100644
--- a/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx
+++ b/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx
@@ -11,6 +11,7 @@ import {
Typography,
} from 'antd';
import { RadioChangeEvent } from 'antd/lib';
+import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import {
initialQueryBuilderFormValuesMap,
@@ -118,6 +119,13 @@ function HostMetricsDetails({
initialFilters,
);
+ useEffect(() => {
+ logEvent('Infra Monitoring: Hosts list details page visited', {
+ host: host?.hostName,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
useEffect(() => {
setLogFilters(initialFilters);
setTracesFilters(initialFilters);
@@ -143,6 +151,7 @@ function HostMetricsDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
setSelectedInterval(interval as Time);
+
if (interval === 'custom' && dateTimeRange) {
setModalTimeRange({
startTime: Math.floor(dateTimeRange[0] / 1000),
@@ -156,7 +165,13 @@ function HostMetricsDetails({
endTime: Math.floor(maxTime / 1000000000),
});
}
+
+ logEvent('Infra Monitoring: Hosts list details time updated', {
+ host: host?.hostName,
+ interval,
+ });
},
+ // eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
@@ -171,6 +186,10 @@ function HostMetricsDetails({
(item) => item.key?.key !== 'id' && item.key?.key !== 'host.name',
);
+ logEvent('Infra Monitoring: Hosts list details logs filters applied', {
+ host: host?.hostName,
+ });
+
return {
op: 'AND',
items: [
@@ -181,6 +200,7 @@ function HostMetricsDetails({
};
});
},
+ // eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
@@ -190,6 +210,11 @@ function HostMetricsDetails({
const hostNameFilter = prevFilters.items.find(
(item) => item.key?.key === 'host.name',
);
+
+ logEvent('Infra Monitoring: Hosts list details traces filters applied', {
+ host: host?.hostName,
+ });
+
return {
op: 'AND',
items: [
@@ -199,6 +224,7 @@ function HostMetricsDetails({
};
});
},
+ // eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
@@ -211,6 +237,11 @@ function HostMetricsDetails({
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
}
+ logEvent('Infra Monitoring: Hosts list details explore clicked', {
+ host: host?.hostName,
+ view: selectedView,
+ });
+
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logFilters,
diff --git a/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss b/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss
index e2f182d04d..b1e4fda451 100644
--- a/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss
+++ b/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss
@@ -1,7 +1,24 @@
.host-processes {
- max-width: 600px;
- margin: 150px auto;
- padding: 0 16px;
+ gap: 24px;
+ height: 60vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ margin: 0 auto;
+ box-sizing: border-box;
+
+ .infra-container-card-container {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ }
+
+ .dev-status-container {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
.infra-container-card {
display: flex;
@@ -17,6 +34,7 @@
width: 400px;
font-family: 'Inter';
margin-top: 12px;
+ font-weight: 300;
}
.infra-container-working-msg {
diff --git a/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx b/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx
index cd5baf14ca..19da46a0e2 100644
--- a/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx
+++ b/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx
@@ -3,6 +3,8 @@ import './Processes.styles.scss';
import { Space, Typography } from 'antd';
import { useTranslation } from 'react-i18next';
+import WaitlistFragment from '../WaitlistFragment/WaitlistFragment';
+
const { Text } = Typography;
function Processes(): JSX.Element {
@@ -10,23 +12,29 @@ function Processes(): JSX.Element {
return (
-
-
-
- {t('processes_visualization_message')}
-
-
+
+
+
+
+
+ {t('processes_visualization_message')}
+
+
-
-
-
- {t('working_message')}
-
+
+
+
+ {t('working_message')}
+
+
+
+
+
);
diff --git a/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitListFragment.styles.scss b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitListFragment.styles.scss
new file mode 100644
index 0000000000..3cad900e6b
--- /dev/null
+++ b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitListFragment.styles.scss
@@ -0,0 +1,15 @@
+.wait-list-container {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ .wait-list-text {
+ font-weight: 300;
+ }
+
+ .join-waitlist-btn {
+ width: 160px;
+ border-radius: 2px;
+ background: var(--slate-500);
+ }
+}
diff --git a/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitlistFragment.tsx b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitlistFragment.tsx
new file mode 100644
index 0000000000..08c15db2b7
--- /dev/null
+++ b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitlistFragment.tsx
@@ -0,0 +1,75 @@
+import './WaitListFragment.styles.scss';
+
+import { Color } from '@signozhq/design-tokens';
+import { Button, Typography } from 'antd';
+import logEvent from 'api/common/logEvent';
+import { useNotifications } from 'hooks/useNotifications';
+import { CheckCircle2, HandPlatter } from 'lucide-react';
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import AppReducer from 'types/reducer/app';
+
+export default function WaitlistFragment({
+ entityType,
+}: {
+ entityType: string;
+}): JSX.Element {
+ const { user } = useSelector
((state) => state.app);
+ const { t } = useTranslation(['infraMonitoring']);
+ const { notifications } = useNotifications();
+
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [isSuccess, setIsSuccess] = useState(false);
+
+ const handleJoinWaitlist = (): void => {
+ if (!user || !user.email) return;
+
+ setIsSubmitting(true);
+
+ logEvent('Infra Monitoring: Get Early Access Clicked', {
+ entity_type: entityType,
+ userEmail: user.email,
+ })
+ .then(() => {
+ notifications.success({
+ message: t('waitlist_success_message'),
+ });
+
+ setIsSubmitting(false);
+ setIsSuccess(true);
+
+ setTimeout(() => {
+ setIsSuccess(false);
+ }, 4000);
+ })
+ .catch((error) => {
+ console.error('Error logging event:', error);
+ });
+ };
+
+ return (
+
+
+ {t('waitlist_message')}
+
+
+
+ ) : (
+
+ )
+ }
+ onClick={handleJoinWaitlist}
+ >
+ Get early access
+
+
+ );
+}
diff --git a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss
index 070d440781..72ceaef26a 100644
--- a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss
+++ b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss
@@ -3,7 +3,7 @@
position: absolute;
right: -2px;
margin: 6px 0;
- width: 160px;
+ width: 240px;
border-radius: 4px;
@@ -24,7 +24,7 @@
.back-btn {
display: flex;
align-items: center;
- gap: 6px;
+ gap: 4px;
padding: 12px;
border: none !important;
box-shadow: none !important;
@@ -32,14 +32,16 @@
.icon {
flex-shrink: 0;
}
+
.text {
- color: var(--bg-vanilla-400);
+ color: var(--bg-slate-50);
font-family: Inter;
- font-size: 13px;
+ font-size: 11px;
font-style: normal;
- font-weight: 400;
+ font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: 0.14px;
+ text-transform: uppercase;
}
}
@@ -252,6 +254,75 @@
}
}
+ .add-new-column-container {
+ display: flex;
+ flex-direction: column;
+
+ .add-new-column-header {
+ display: flex;
+ flex-direction: column;
+ padding: 8px;
+ gap: 8px;
+
+ .back-icon {
+ cursor: pointer;
+ }
+
+ .title {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+ color: var(--bg-slate-50);
+ text-transform: uppercase;
+ font-size: 11px;
+ font-weight: 500;
+ line-height: 18px;
+ letter-spacing: 0.88px;
+ }
+ }
+
+ .add-new-column-content {
+ display: flex;
+ flex-direction: column;
+
+ padding-bottom: 16px;
+
+ min-height: 240px;
+ max-height: 400px;
+
+ .loading-container {
+ padding: 8px;
+ }
+
+ .column-format-new-options {
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ .column-name {
+ padding: 4px 8px;
+ border-radius: 1px;
+ color: var(--bg-vanilla-400, #c0c1c3);
+ font-family: Inter;
+ font-size: 13px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+ letter-spacing: -0.07px;
+
+ &.selected {
+ background-color: var(--bg-ink-200);
+ cursor: pointer;
+ }
+ }
+
+ &::-webkit-scrollbar {
+ height: 1rem;
+ width: 0.2rem;
+ }
+ }
+ }
+ }
+
.selected-item-content-container {
.add-new-column-header {
padding: 8px;
@@ -314,6 +385,22 @@
cursor: pointer;
+ &.default-column {
+ color: var(--bg-vanilla-400, #c0c1c3);
+ cursor: not-allowed;
+ }
+
+ &.no-columns-selected {
+ color: var(--bg-slate-100);
+ font-size: 12px;
+ cursor: not-allowed;
+ }
+
+ &.add-new-column-btn {
+ color: var(--bg-vanilla-400, #c0c1c3);
+ cursor: pointer;
+ }
+
.name {
flex: 1;
overflow: hidden;
@@ -428,6 +515,30 @@
}
}
+ .add-new-column-container {
+ .add-new-column-header {
+ .title {
+ color: var(--bg-ink-100);
+ }
+ }
+
+ .add-new-column-content {
+ .column-format-new-options {
+ .column-name {
+ color: var(--bg-ink-400);
+
+ &.selected {
+ background-color: var(--bg-vanilla-400);
+ }
+ }
+ }
+
+ .loading-container {
+ color: var(--bg-ink-400);
+ }
+ }
+ }
+
.font-size-container {
.title {
color: var(--bg-ink-100);
diff --git a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx
index 527c77c6af..740ceaf5b7 100644
--- a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx
+++ b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx
@@ -3,13 +3,14 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import './LogsFormatOptionsMenu.styles.scss';
-import { Button, Divider, Input, InputNumber, Tooltip, Typography } from 'antd';
+import { Button, Input, InputNumber, Tooltip, Typography } from 'antd';
+import { DefaultOptionType } from 'antd/es/select';
import cx from 'classnames';
import { LogViewMode } from 'container/LogsTable';
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { Check, ChevronLeft, ChevronRight, Minus, Plus, X } from 'lucide-react';
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
interface LogsFormatOptionsMenuProps {
title: string;
@@ -35,7 +36,13 @@ export default function LogsFormatOptionsMenu({
false,
);
- const [addNewColumn, setAddNewColumn] = useState(false);
+ const [showAddNewColumnContainer, setShowAddNewColumnContainer] = useState(
+ false,
+ );
+
+ const [selectedValue, setSelectedValue] = useState(null);
+ const listRef = useRef(null);
+ const initialMouseEnterRef = useRef(false);
const onChange = useCallback(
(key: LogViewMode) => {
@@ -49,7 +56,7 @@ export default function LogsFormatOptionsMenu({
const handleMenuItemClick = (key: LogViewMode): void => {
setSelectedItem(key);
onChange(key);
- setAddNewColumn(false);
+ setShowAddNewColumnContainer(false);
};
const incrementMaxLinesPerRow = (): void => {
@@ -75,7 +82,8 @@ export default function LogsFormatOptionsMenu({
}, 300);
const handleToggleAddNewColumn = (): void => {
- setAddNewColumn(!addNewColumn);
+ addColumn?.onSearch?.('');
+ setShowAddNewColumnContainer(!showAddNewColumnContainer);
};
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
@@ -100,9 +108,106 @@ export default function LogsFormatOptionsMenu({
}
}, [fontSizeValue]);
+ function handleColumnSelection(
+ currentIndex: number,
+ optionsData: DefaultOptionType[],
+ ): void {
+ const currentItem = optionsData[currentIndex];
+ const itemLength = optionsData.length;
+ if (addColumn && addColumn?.onSelect) {
+ addColumn?.onSelect(selectedValue, {
+ label: currentItem.label,
+ disabled: false,
+ });
+
+ // if the last element is selected then select the previous one
+ if (currentIndex === itemLength - 1) {
+ // there should be more than 1 element in the list
+ if (currentIndex - 1 >= 0) {
+ const prevValue = optionsData[currentIndex - 1]?.value || null;
+ setSelectedValue(prevValue as string | null);
+ } else {
+ // if there is only one element then just select and do nothing
+ setSelectedValue(null);
+ }
+ } else {
+ // selecting any random element from the list except the last one
+ const nextIndex = currentIndex + 1;
+
+ const nextValue = optionsData[nextIndex]?.value || null;
+
+ setSelectedValue(nextValue as string | null);
+ }
+ }
+ }
+
+ const handleKeyDown = (e: KeyboardEvent): void => {
+ if (!selectedValue) return;
+
+ const optionsData = addColumn?.options || [];
+
+ const currentIndex = optionsData.findIndex(
+ (item) => item?.value === selectedValue,
+ );
+
+ const itemLength = optionsData.length;
+
+ switch (e.key) {
+ case 'ArrowUp': {
+ const newValue = optionsData[Math.max(0, currentIndex - 1)]?.value;
+
+ setSelectedValue(newValue as string | null);
+ e.preventDefault();
+ break;
+ }
+ case 'ArrowDown': {
+ const newValue =
+ optionsData[Math.min(itemLength - 1, currentIndex + 1)]?.value;
+
+ setSelectedValue(newValue as string | null);
+ e.preventDefault();
+ break;
+ }
+ case 'Enter':
+ e.preventDefault();
+ handleColumnSelection(currentIndex, optionsData);
+ break;
+ default:
+ break;
+ }
+ };
+
+ useEffect(() => {
+ // Scroll the selected item into view
+ const listNode = listRef.current;
+ if (listNode && selectedValue) {
+ const optionsData = addColumn?.options || [];
+ const currentIndex = optionsData.findIndex(
+ (item) => item?.value === selectedValue,
+ );
+ const itemNode = listNode.children[currentIndex] as HTMLElement;
+ if (itemNode) {
+ itemNode.scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest',
+ });
+ }
+ }
+ }, [selectedValue]);
+
+ useEffect(() => {
+ window.addEventListener('keydown', handleKeyDown);
+ return (): void => {
+ window.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [selectedValue]);
+
return (
{
// this is to restrict click events to propogate to parent
event.stopPropagation();
@@ -158,8 +263,72 @@ export default function LogsFormatOptionsMenu({
- ) : (
- <>
+ ) : null}
+
+ {showAddNewColumnContainer && (
+
+
+
+
+ {addColumn?.isFetching && (
+
Loading ...
+ )}
+
+
+ {addColumn?.options?.map(({ label, value }, index) => (
+
{
+ if (!initialMouseEnterRef.current) {
+ setSelectedValue(value as string | null);
+ }
+
+ initialMouseEnterRef.current = true;
+ }}
+ onMouseMove={(): void => {
+ // this is added to handle the mouse move explicit event and not the re-rendered on mouse enter event
+ setSelectedValue(value as string | null);
+ }}
+ onClick={(eve): void => {
+ eve.stopPropagation();
+ handleColumnSelection(index, addColumn?.options || []);
+ }}
+ >
+
+
+ {label}
+
+
+
+ ))}
+
+
+
+ )}
+
+ {!isFontSizeOptionsOpen && !showAddNewColumnContainer && (
+
Font Size
- {!addNewColumn &&
}
-
- {addNewColumn && (
-
-
- {' '}
- columns
- {' '}
-
-
-
-
- )}
+ {!showAddNewColumnContainer &&
}
- {!addNewColumn && (
+ {!showAddNewColumnContainer && (
columns
{' '}
@@ -274,48 +424,17 @@ export default function LogsFormatOptionsMenu({
/>
))}
-
-
- {addColumn?.isFetching && (
-
Loading ...
- )}
-
- {addNewColumn &&
- addColumn &&
- addColumn.value.length > 0 &&
- addColumn.options &&
- addColumn?.options?.length > 0 && (
-
+ {addColumn && addColumn?.value?.length === 0 && (
+
+ No columns selected
+
)}
-
- {addNewColumn && (
-
- {addColumn?.options?.map(({ label, value }) => (
-
{
- eve.stopPropagation();
-
- if (addColumn && addColumn?.onSelect) {
- addColumn?.onSelect(value, { label, disabled: false });
- }
- }}
- >
-
-
- {label}
-
-
-
- ))}
-
- )}
+
>
)}
- >
+
)}
);
diff --git a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss
index c46d9975f4..34bdd0508e 100644
--- a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss
+++ b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss
@@ -8,6 +8,7 @@
display: flex;
align-items: center;
justify-content: space-between;
+ cursor: pointer;
.left-action {
display: flex;
diff --git a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx
index dcf3cc8f3e..bd8b9b1d38 100644
--- a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx
+++ b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx
@@ -396,23 +396,22 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
return (