mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-20 19:29:09 +08:00
feat: open left nav items in new tab on cmd ctrl click (#4561)
This commit is contained in:
parent
b10f17de78
commit
0c59953cb5
@ -20,7 +20,7 @@ import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
|||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { routePermission } from 'utils/permission';
|
import { routePermission } from 'utils/permission';
|
||||||
|
|
||||||
import routes from './routes';
|
import routes, { LIST_LICENSES } from './routes';
|
||||||
import afterLogin from './utils';
|
import afterLogin from './utils';
|
||||||
|
|
||||||
function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||||
@ -29,7 +29,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
const mapRoutes = useMemo(
|
const mapRoutes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Map(
|
new Map(
|
||||||
routes.map((e) => {
|
[...routes, LIST_LICENSES].map((e) => {
|
||||||
const currentPath = matchPath(pathname, {
|
const currentPath = matchPath(pathname, {
|
||||||
path: e.path,
|
path: e.path,
|
||||||
});
|
});
|
||||||
|
@ -100,7 +100,10 @@ function TableView({
|
|||||||
value: JSON.stringify(flattenLogData[key]),
|
value: JSON.stringify(flattenLogData[key]),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onTraceHandler = (record: DataType) => (): void => {
|
const onTraceHandler = (
|
||||||
|
record: DataType,
|
||||||
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||||
|
) => (): void => {
|
||||||
if (flattenLogData === null) return;
|
if (flattenLogData === null) return;
|
||||||
|
|
||||||
const traceId = flattenLogData[record.field];
|
const traceId = flattenLogData[record.field];
|
||||||
@ -119,8 +122,13 @@ function TableView({
|
|||||||
|
|
||||||
const route = spanId ? `${basePath}?spanId=${spanId}` : basePath;
|
const route = spanId ? `${basePath}?spanId=${spanId}` : basePath;
|
||||||
|
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
// open the trace in new tab
|
||||||
|
window.open(route, '_blank');
|
||||||
|
} else {
|
||||||
history.push(route);
|
history.push(route);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
@ -148,17 +156,20 @@ function TableView({
|
|||||||
|
|
||||||
{traceId && (
|
{traceId && (
|
||||||
<Tooltip title="Inspect in Trace">
|
<Tooltip title="Inspect in Trace">
|
||||||
<div
|
<Button
|
||||||
style={{ cursor: 'pointer' }}
|
className="periscope-btn"
|
||||||
role="presentation"
|
onClick={(
|
||||||
onClick={onTraceHandler(record)}
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||||
|
): void => {
|
||||||
|
onTraceHandler(record, event);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<LinkOutlined
|
<LinkOutlined
|
||||||
style={{
|
style={{
|
||||||
width: '15px',
|
width: '15px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
import './LogsError.styles.scss';
|
import './LogsError.styles.scss';
|
||||||
|
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
|
import history from 'lib/history';
|
||||||
import { ArrowRight } from 'lucide-react';
|
import { ArrowRight } from 'lucide-react';
|
||||||
|
import { isCloudUser } from 'utils/app';
|
||||||
|
|
||||||
export default function LogsError(): JSX.Element {
|
export default function LogsError(): JSX.Element {
|
||||||
|
const handleContactSupport = (): void => {
|
||||||
|
if (isCloudUser()) {
|
||||||
|
history.push('/support');
|
||||||
|
} else {
|
||||||
|
window.open('https://signoz.io/slack', '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="logs-error-container">
|
<div className="logs-error-container">
|
||||||
<div className="logs-error-content">
|
<div className="logs-error-content">
|
||||||
@ -16,10 +27,12 @@ export default function LogsError(): JSX.Element {
|
|||||||
<span className="aww-snap">Aw snap :/ </span> Something went wrong. Please
|
<span className="aww-snap">Aw snap :/ </span> Something went wrong. Please
|
||||||
try again or contact support.
|
try again or contact support.
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<section className="contact-support">
|
|
||||||
|
<div className="contact-support" onClick={handleContactSupport}>
|
||||||
<Typography.Link className="text">Contact Support </Typography.Link>
|
<Typography.Link className="text">Contact Support </Typography.Link>
|
||||||
|
|
||||||
<ArrowRight size={14} />
|
<ArrowRight size={14} />
|
||||||
</section>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -9,13 +9,17 @@ export default function NoLogs(): JSX.Element {
|
|||||||
<div className="no-logs-container-content">
|
<div className="no-logs-container-content">
|
||||||
<img className="eyes-emoji" src="/Images/eyesEmoji.svg" alt="eyes emoji" />
|
<img className="eyes-emoji" src="/Images/eyesEmoji.svg" alt="eyes emoji" />
|
||||||
<Typography className="no-logs-text">
|
<Typography className="no-logs-text">
|
||||||
No logs yet.{' '}
|
No logs yet.
|
||||||
<span className="sub-text">
|
<span className="sub-text">
|
||||||
When we receive logs, they would show up here
|
When we receive logs, they would show up here
|
||||||
</span>
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography.Link className="send-logs-link">
|
<Typography.Link
|
||||||
|
className="send-logs-link"
|
||||||
|
href="https://signoz.io/docs/userguide/logs/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
Sending Logs to SigNoz <ArrowUpRight size={16} />
|
Sending Logs to SigNoz <ArrowUpRight size={16} />
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
.nav-item-active-marker {
|
.nav-item-active-marker {
|
||||||
|
@ -16,13 +16,16 @@ export default function NavItem({
|
|||||||
isCollapsed: boolean;
|
isCollapsed: boolean;
|
||||||
item: SidebarItem;
|
item: SidebarItem;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
onClick: () => void;
|
onClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { label, icon } = item;
|
const { label, icon } = item;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={isCollapsed ? label : ''} placement="right">
|
<Tooltip title={isCollapsed ? label : ''} placement="right">
|
||||||
<div className={cx('nav-item', isActive ? 'active' : '')} onClick={onClick}>
|
<div
|
||||||
|
className={cx('nav-item', isActive ? 'active' : '')}
|
||||||
|
onClick={(event): void => onClick(event)}
|
||||||
|
>
|
||||||
<div className="nav-item-active-marker" />
|
<div className="nav-item-active-marker" />
|
||||||
<div className="nav-item-data">
|
<div className="nav-item-data">
|
||||||
<div className="nav-item-icon">{icon}</div>
|
<div className="nav-item-icon">{icon}</div>
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
RocketIcon,
|
RocketIcon,
|
||||||
UserCircle,
|
UserCircle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@ -40,7 +40,7 @@ import defaultMenuItems, {
|
|||||||
trySignozCloudMenuItem,
|
trySignozCloudMenuItem,
|
||||||
} from './menuItems';
|
} from './menuItems';
|
||||||
import NavItem from './NavItem/NavItem';
|
import NavItem from './NavItem/NavItem';
|
||||||
import { SecondaryMenuItemKey } from './sideNav.types';
|
import { SecondaryMenuItemKey, SidebarItem } from './sideNav.types';
|
||||||
import { getActiveMenuKeyFromPath } from './sideNav.utils';
|
import { getActiveMenuKeyFromPath } from './sideNav.utils';
|
||||||
|
|
||||||
interface UserManagementMenuItems {
|
interface UserManagementMenuItems {
|
||||||
@ -88,10 +88,6 @@ function SideNav({
|
|||||||
window.open('https://signoz.io/slack', '_blank');
|
window.open('https://signoz.io/slack', '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickVersionHandler = (): void => {
|
|
||||||
history.push(ROUTES.VERSION);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isLatestVersion = checkVersionState(currentVersion, latestVersion);
|
const isLatestVersion = checkVersionState(currentVersion, latestVersion);
|
||||||
|
|
||||||
const [inviteMembers] = useComponentPermission(['invite_members'], role);
|
const [inviteMembers] = useComponentPermission(['invite_members'], role);
|
||||||
@ -164,24 +160,50 @@ function SideNav({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickShortcuts = (): void => {
|
const isCtrlMetaKey = (e: MouseEvent): boolean => e.ctrlKey || e.metaKey;
|
||||||
history.push(`/shortcuts`);
|
|
||||||
|
const openInNewTab = (path: string): void => {
|
||||||
|
window.open(path, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickGetStarted = (): void => {
|
const onClickShortcuts = (e: MouseEvent): void => {
|
||||||
|
if (isCtrlMetaKey(e)) {
|
||||||
|
openInNewTab('/shortcuts');
|
||||||
|
} else {
|
||||||
|
history.push(`/shortcuts`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickGetStarted = (event: MouseEvent): void => {
|
||||||
|
if (isCtrlMetaKey(event)) {
|
||||||
|
openInNewTab('/get-started');
|
||||||
|
} else {
|
||||||
history.push(`/get-started`);
|
history.push(`/get-started`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickVersionHandler = (event: MouseEvent): void => {
|
||||||
|
if (isCtrlMetaKey(event)) {
|
||||||
|
openInNewTab(ROUTES.VERSION);
|
||||||
|
} else {
|
||||||
|
history.push(ROUTES.VERSION);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickHandler = useCallback(
|
const onClickHandler = useCallback(
|
||||||
(key: string) => {
|
(key: string, event: MouseEvent | null) => {
|
||||||
const params = new URLSearchParams(search);
|
const params = new URLSearchParams(search);
|
||||||
const availableParams = routeConfig[key];
|
const availableParams = routeConfig[key];
|
||||||
|
|
||||||
const queryString = getQueryString(availableParams || [], params);
|
const queryString = getQueryString(availableParams || [], params);
|
||||||
|
|
||||||
if (pathname !== key) {
|
if (pathname !== key) {
|
||||||
|
if (event && isCtrlMetaKey(event)) {
|
||||||
|
openInNewTab(`${key}?${queryString.join('&')}`);
|
||||||
|
} else {
|
||||||
history.push(`${key}?${queryString.join('&')}`);
|
history.push(`${key}?${queryString.join('&')}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[pathname, search],
|
[pathname, search],
|
||||||
);
|
);
|
||||||
@ -220,16 +242,19 @@ function SideNav({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentVersion, latestVersion]);
|
}, [currentVersion, latestVersion]);
|
||||||
|
|
||||||
const handleUserManagentMenuItemClick = (key: string): void => {
|
const handleUserManagentMenuItemClick = (
|
||||||
|
key: string,
|
||||||
|
event: MouseEvent,
|
||||||
|
): void => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case SecondaryMenuItemKey.Slack:
|
case SecondaryMenuItemKey.Slack:
|
||||||
onClickSlackHandler();
|
onClickSlackHandler();
|
||||||
break;
|
break;
|
||||||
case SecondaryMenuItemKey.Version:
|
case SecondaryMenuItemKey.Version:
|
||||||
onClickVersionHandler();
|
onClickVersionHandler(event);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
onClickHandler(key);
|
onClickHandler(key, event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -255,29 +280,41 @@ function SideNav({
|
|||||||
? ROUTES.ORG_SETTINGS
|
? ROUTES.ORG_SETTINGS
|
||||||
: ROUTES.SETTINGS;
|
: ROUTES.SETTINGS;
|
||||||
|
|
||||||
|
const handleMenuItemClick = (event: MouseEvent, item: SidebarItem): void => {
|
||||||
|
if (item.key === ROUTES.SETTINGS) {
|
||||||
|
if (isCtrlMetaKey(event)) {
|
||||||
|
openInNewTab(settingsRoute);
|
||||||
|
} else {
|
||||||
|
history.push(settingsRoute);
|
||||||
|
}
|
||||||
|
} else if (item) {
|
||||||
|
onClickHandler(item?.key as string, event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerShortcut(GlobalShortcuts.SidebarCollapse, onCollapse);
|
registerShortcut(GlobalShortcuts.SidebarCollapse, onCollapse);
|
||||||
|
|
||||||
registerShortcut(GlobalShortcuts.NavigateToServices, () =>
|
registerShortcut(GlobalShortcuts.NavigateToServices, () =>
|
||||||
onClickHandler(ROUTES.APPLICATION),
|
onClickHandler(ROUTES.APPLICATION, null),
|
||||||
);
|
);
|
||||||
registerShortcut(GlobalShortcuts.NavigateToTraces, () =>
|
registerShortcut(GlobalShortcuts.NavigateToTraces, () =>
|
||||||
onClickHandler(ROUTES.TRACE),
|
onClickHandler(ROUTES.TRACE, null),
|
||||||
);
|
);
|
||||||
|
|
||||||
registerShortcut(GlobalShortcuts.NavigateToLogs, () =>
|
registerShortcut(GlobalShortcuts.NavigateToLogs, () =>
|
||||||
onClickHandler(ROUTES.LOGS),
|
onClickHandler(ROUTES.LOGS, null),
|
||||||
);
|
);
|
||||||
|
|
||||||
registerShortcut(GlobalShortcuts.NavigateToDashboards, () =>
|
registerShortcut(GlobalShortcuts.NavigateToDashboards, () =>
|
||||||
onClickHandler(ROUTES.ALL_DASHBOARD),
|
onClickHandler(ROUTES.ALL_DASHBOARD, null),
|
||||||
);
|
);
|
||||||
|
|
||||||
registerShortcut(GlobalShortcuts.NavigateToAlerts, () =>
|
registerShortcut(GlobalShortcuts.NavigateToAlerts, () =>
|
||||||
onClickHandler(ROUTES.LIST_ALL_ALERT),
|
onClickHandler(ROUTES.LIST_ALL_ALERT, null),
|
||||||
);
|
);
|
||||||
registerShortcut(GlobalShortcuts.NavigateToExceptions, () =>
|
registerShortcut(GlobalShortcuts.NavigateToExceptions, () =>
|
||||||
onClickHandler(ROUTES.ALL_ERROR),
|
onClickHandler(ROUTES.ALL_ERROR, null),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (): void => {
|
return (): void => {
|
||||||
@ -297,9 +334,9 @@ function SideNav({
|
|||||||
<div
|
<div
|
||||||
className="brand-logo"
|
className="brand-logo"
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
// eslint-disable-next-line react/no-unknown-property
|
||||||
onClick={(): void => {
|
onClick={(event: MouseEvent): void => {
|
||||||
// Current home page
|
// Current home page
|
||||||
onClickHandler(ROUTES.APPLICATION);
|
onClickHandler(ROUTES.APPLICATION, event);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src="/Logos/signoz-brand-logo.svg" alt="SigNoz" />
|
<img src="/Logos/signoz-brand-logo.svg" alt="SigNoz" />
|
||||||
@ -314,7 +351,12 @@ function SideNav({
|
|||||||
|
|
||||||
{isCloudUserVal && (
|
{isCloudUserVal && (
|
||||||
<div className="get-started-nav-items">
|
<div className="get-started-nav-items">
|
||||||
<Button className="get-started-btn" onClick={onClickGetStarted}>
|
<Button
|
||||||
|
className="get-started-btn"
|
||||||
|
onClick={(event: MouseEvent): void => {
|
||||||
|
onClickGetStarted(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<RocketIcon size={16} />
|
<RocketIcon size={16} />
|
||||||
|
|
||||||
{!collapsed && <> Get Started </>}
|
{!collapsed && <> Get Started </>}
|
||||||
@ -329,12 +371,8 @@ function SideNav({
|
|||||||
key={item.key || index}
|
key={item.key || index}
|
||||||
item={item}
|
item={item}
|
||||||
isActive={activeMenuKey === item.key}
|
isActive={activeMenuKey === item.key}
|
||||||
onClick={(): void => {
|
onClick={(event): void => {
|
||||||
if (item.key === ROUTES.SETTINGS) {
|
handleMenuItemClick(event, item);
|
||||||
history.push(settingsRoute);
|
|
||||||
} else if (item) {
|
|
||||||
onClickHandler(item?.key as string);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@ -366,8 +404,8 @@ function SideNav({
|
|||||||
key={item?.key || index}
|
key={item?.key || index}
|
||||||
item={item}
|
item={item}
|
||||||
isActive={activeMenuKey === item?.key}
|
isActive={activeMenuKey === item?.key}
|
||||||
onClick={(): void => {
|
onClick={(event: MouseEvent): void => {
|
||||||
handleUserManagentMenuItemClick(item?.key as string);
|
handleUserManagentMenuItemClick(item?.key as string, event);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -379,8 +417,12 @@ function SideNav({
|
|||||||
key={inviteMemberMenuItem.key}
|
key={inviteMemberMenuItem.key}
|
||||||
item={inviteMemberMenuItem}
|
item={inviteMemberMenuItem}
|
||||||
isActive={activeMenuKey === inviteMemberMenuItem?.key}
|
isActive={activeMenuKey === inviteMemberMenuItem?.key}
|
||||||
onClick={(): void => {
|
onClick={(event: React.MouseEvent): void => {
|
||||||
|
if (isCtrlMetaKey(event)) {
|
||||||
|
openInNewTab(`${inviteMemberMenuItem.key}`);
|
||||||
|
} else {
|
||||||
history.push(`${inviteMemberMenuItem.key}`);
|
history.push(`${inviteMemberMenuItem.key}`);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -391,8 +433,11 @@ function SideNav({
|
|||||||
key={ROUTES.MY_SETTINGS}
|
key={ROUTES.MY_SETTINGS}
|
||||||
item={userSettingsMenuItem}
|
item={userSettingsMenuItem}
|
||||||
isActive={activeMenuKey === userSettingsMenuItem?.key}
|
isActive={activeMenuKey === userSettingsMenuItem?.key}
|
||||||
onClick={(): void => {
|
onClick={(event: MouseEvent): void => {
|
||||||
handleUserManagentMenuItemClick(userSettingsMenuItem?.key as string);
|
handleUserManagentMenuItemClick(
|
||||||
|
userSettingsMenuItem?.key as string,
|
||||||
|
event,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user