mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
feat: Spotlight (#235)
https://github.com/hyperdxio/hyperdx/assets/149748269/a2622a4e-c7c2-402e-a9ad-19e3f736b2df
This commit is contained in:
parent
39ac7eaa95
commit
ac667cd164
8 changed files with 227 additions and 10 deletions
5
.changeset/curvy-coats-jog.md
Normal file
5
.changeset/curvy-coats-jog.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hyperdx/app': minor
|
||||
---
|
||||
|
||||
Add Spotlight
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
"@mantine/core": "6.0.21",
|
||||
"@mantine/hooks": "6.0.21",
|
||||
"@mantine/next": "6.0.21",
|
||||
"@mantine/spotlight": "6",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@tanstack/react-table": "^8.7.9",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const mantineTheme: MantineThemeOverride = {
|
|||
colorScheme: 'dark',
|
||||
fontFamily: 'IBM Plex Mono, sans-serif',
|
||||
primaryColor: 'green',
|
||||
primaryShade: 9,
|
||||
primaryShade: 8,
|
||||
white: '#fff',
|
||||
fontSizes: {
|
||||
xs: '12px',
|
||||
|
|
|
|||
|
|
@ -444,6 +444,19 @@ function SearchInput({
|
|||
onChange: (arg0: string) => void;
|
||||
onEnterDown?: () => void;
|
||||
}) {
|
||||
const kbdShortcut = useMemo(() => {
|
||||
return (
|
||||
<div className={styles.kbd}>
|
||||
{window.navigator.platform?.toUpperCase().includes('MAC') ? (
|
||||
<i className="bi bi-command" />
|
||||
) : (
|
||||
<span style={{ letterSpacing: -2 }}>Ctrl</span>
|
||||
)}
|
||||
K
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
|
@ -461,13 +474,15 @@ function SearchInput({
|
|||
icon={<i className="bi bi-search fs-8 ps-1" />}
|
||||
onKeyDown={handleKeyDown}
|
||||
rightSection={
|
||||
value && (
|
||||
value ? (
|
||||
<CloseButton
|
||||
tabIndex={-1}
|
||||
size="xs"
|
||||
radius="xl"
|
||||
onClick={() => onChange('')}
|
||||
/>
|
||||
) : (
|
||||
kbdShortcut
|
||||
)
|
||||
}
|
||||
mt={8}
|
||||
|
|
@ -517,8 +532,6 @@ function useSearchableList<T extends { name: string }>({
|
|||
}
|
||||
|
||||
export default function AppNav({ fixed = false }: { fixed?: boolean }) {
|
||||
// TODO enable this once the alerts page is ready for public consumption
|
||||
const showAlertSidebar = false;
|
||||
useEffect(() => {
|
||||
let redirectUrl;
|
||||
try {
|
||||
|
|
@ -1059,7 +1072,16 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
|
|||
</span>
|
||||
<div className="mt-2 mb-2">
|
||||
<Link href="https://www.hyperdx.io/register" passHref>
|
||||
<MButton variant="light" size="xs">
|
||||
<MButton
|
||||
variant="light"
|
||||
size="xs"
|
||||
component="a"
|
||||
sx={{
|
||||
':hover': {
|
||||
color: 'white',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Get Started for Free
|
||||
</MButton>
|
||||
</Link>
|
||||
|
|
|
|||
168
packages/app/src/Spotlights.tsx
Normal file
168
packages/app/src/Spotlights.tsx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import * as React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { SpotlightAction, SpotlightProvider } from '@mantine/spotlight';
|
||||
|
||||
import api from './api';
|
||||
import { SERVICE_DASHBOARD_ENABLED } from './config';
|
||||
import Logo from './Icon';
|
||||
|
||||
const useSpotlightActions = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const { data: logViewsData } = api.useLogViews();
|
||||
const { data: dashboardsData } = api.useDashboards();
|
||||
|
||||
const actions = React.useMemo<SpotlightAction[]>(() => {
|
||||
const logViews = logViewsData?.data ?? [];
|
||||
const dashboards = dashboardsData?.data ?? [];
|
||||
|
||||
const logViewActions: SpotlightAction[] = [];
|
||||
|
||||
// Saved searches
|
||||
logViews.forEach(logView => {
|
||||
logViewActions.push({
|
||||
group: 'Saved searches',
|
||||
icon: <i className="bi bi-layout-text-sidebar-reverse" />,
|
||||
description: logView.query,
|
||||
title: logView.name,
|
||||
keywords: ['search', 'log', 'saved'],
|
||||
onTrigger: () => {
|
||||
router.push(`/search/${logView._id}`);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboards
|
||||
dashboards.forEach(dashboard => {
|
||||
logViewActions.push({
|
||||
group: 'Dashboards',
|
||||
icon: <i className="bi bi-grid-1x2" />,
|
||||
title: dashboard.name,
|
||||
keywords: ['dashboard'],
|
||||
onTrigger: () => {
|
||||
router.push(`/dashboards/${dashboard._id}`);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
logViewActions.push(
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <i className="bi bi-layout-text-sidebar-reverse" />,
|
||||
title: 'Search',
|
||||
description: 'Start a new search',
|
||||
keywords: ['log', 'events', 'logs'],
|
||||
onTrigger: () => {
|
||||
router.push('/search');
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <i className="bi bi-graph-up" />,
|
||||
title: 'Chart Explorer',
|
||||
description: 'Explore your data',
|
||||
keywords: ['graph', 'metrics'],
|
||||
onTrigger: () => {
|
||||
router.push('/chart');
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <i className="bi bi-grid-1x2" />,
|
||||
title: 'New Dashboard',
|
||||
description: 'Create a new dashboard',
|
||||
keywords: ['graph'],
|
||||
onTrigger: () => {
|
||||
router.push('/dashboards');
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <i className="bi bi-laptop" />,
|
||||
title: 'Client Sessions',
|
||||
description: 'View client sessions',
|
||||
keywords: ['browser', 'web'],
|
||||
onTrigger: () => {
|
||||
router.push('/sessions');
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <i className="bi bi-bell" />,
|
||||
title: 'Alerts',
|
||||
description: 'View and manage alerts',
|
||||
onTrigger: () => {
|
||||
router.push('/alerts');
|
||||
},
|
||||
},
|
||||
...(SERVICE_DASHBOARD_ENABLED
|
||||
? [
|
||||
{
|
||||
group: 'Menu',
|
||||
title: 'Service Health',
|
||||
icon: <i className="bi bi-heart-pulse" />,
|
||||
description: 'HTTP, Database and Infrastructure metrics',
|
||||
onTrigger: () => {
|
||||
router.push('/services');
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <i className="bi bi-gear" />,
|
||||
title: 'Team Settings',
|
||||
|
||||
onTrigger: () => {
|
||||
router.push('/team');
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <i className="bi bi-question-circle" />,
|
||||
title: 'Documentation',
|
||||
keywords: ['help', 'docs'],
|
||||
onTrigger: () => {
|
||||
router.push('https://www.hyperdx.io/docs');
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'Menu',
|
||||
icon: <Logo />,
|
||||
title: 'HyperDX Cloud',
|
||||
description: 'Ready to use HyperDX Cloud? Get started for free.',
|
||||
keywords: ['account', 'profile'],
|
||||
onTrigger: () => {
|
||||
router.push('https://hyperdx.io/register');
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return logViewActions;
|
||||
}, [logViewsData, dashboardsData, router]);
|
||||
|
||||
return { actions };
|
||||
};
|
||||
|
||||
export const HDXSpotlightProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { actions } = useSpotlightActions();
|
||||
|
||||
return (
|
||||
<SpotlightProvider
|
||||
shortcut="mod + K"
|
||||
searchPlaceholder="Search"
|
||||
searchIcon={<i className="bi bi-search" />}
|
||||
nothingFoundMessage="Nothing found"
|
||||
zIndex={200001} // above the autocomplete
|
||||
tagsToIgnore={[]}
|
||||
highlightQuery
|
||||
actions={actions}
|
||||
>
|
||||
{children}
|
||||
</SpotlightProvider>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import AppNav from './AppNav';
|
||||
import { HDXSpotlightProvider } from './Spotlights';
|
||||
|
||||
/**
|
||||
* Next.js layout for pages that use the AppNav component. Using the same layout
|
||||
|
|
@ -13,9 +14,11 @@ import AppNav from './AppNav';
|
|||
*/
|
||||
export const withAppNav = (page: React.ReactNode) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<AppNav fixed />
|
||||
<div className="w-100">{page}</div>
|
||||
</div>
|
||||
<HDXSpotlightProvider>
|
||||
<div className="d-flex">
|
||||
<AppNav fixed />
|
||||
<div className="w-100">{page}</div>
|
||||
</div>
|
||||
</HDXSpotlightProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,3 +57,14 @@
|
|||
color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
.kbd {
|
||||
white-space: nowrap;
|
||||
letter-spacing: -1px;
|
||||
color: $slate-400;
|
||||
background: $slate-950;
|
||||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
font-size: 10px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2369,6 +2369,13 @@
|
|||
"@mantine/ssr" "6.0.21"
|
||||
"@mantine/styles" "6.0.21"
|
||||
|
||||
"@mantine/spotlight@6":
|
||||
version "6.0.21"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-6.0.21.tgz#98f507bd3429fee1f2b57ad5ef9f88d1d8d8ff32"
|
||||
integrity sha512-xJqF2Vpn8s6I4mSF+iCi7IzqL8iaqbvq0RcYlF1usLZYW2HrArX31s1r11DmzqM1PIuBQUhquW8jUXx/MZy3oA==
|
||||
dependencies:
|
||||
"@mantine/utils" "6.0.21"
|
||||
|
||||
"@mantine/ssr@6.0.21":
|
||||
version "6.0.21"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/ssr/-/ssr-6.0.21.tgz#3e6f347af944324b01f16d852ec5850f6555ce5f"
|
||||
|
|
@ -4987,7 +4994,7 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@>=16", "@types/react@>=16.9.11", "@types/react@^17", "@types/react@^17.0.52":
|
||||
"@types/react@*", "@types/react@17.0.52", "@types/react@>=16", "@types/react@>=16.9.11", "@types/react@^17", "@types/react@^17.0.52":
|
||||
version "17.0.52"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.52.tgz#10d8b907b5c563ac014a541f289ae8eaa9bf2e9b"
|
||||
integrity sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A==
|
||||
|
|
|
|||
Loading…
Reference in a new issue