mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
854 lines
24 KiB
TypeScript
854 lines
24 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import Router, { useRouter } from 'next/router';
|
|
import cx from 'classnames';
|
|
import { Button } from 'react-bootstrap';
|
|
import { useQueryClient } from 'react-query';
|
|
import {
|
|
NumberParam,
|
|
StringParam,
|
|
useQueryParam,
|
|
useQueryParams,
|
|
withDefault,
|
|
} from 'use-query-params';
|
|
import HyperDX from '@hyperdx/browser';
|
|
|
|
import api from './api';
|
|
import AuthLoadingBlocker from './AuthLoadingBlocker';
|
|
import { API_SERVER_URL } from './config';
|
|
import Icon from './Icon';
|
|
import Logo from './Logo';
|
|
import { useWindowSize } from './utils';
|
|
|
|
const APP_PERFORMANCE_DASHBOARD_CONFIG = {
|
|
id: '',
|
|
name: 'App Performance',
|
|
charts: [
|
|
{
|
|
id: '1624425',
|
|
name: 'P95 Latency by Operation',
|
|
x: 0,
|
|
y: 0,
|
|
w: 8,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'p95',
|
|
field: 'duration',
|
|
where: '',
|
|
groupBy: ['span_name'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '401924',
|
|
name: 'Operations with Errors',
|
|
x: 8,
|
|
y: 0,
|
|
w: 4,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: 'level:err',
|
|
groupBy: ['span_name'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '883200',
|
|
name: 'Count of Operations',
|
|
x: 0,
|
|
y: 3,
|
|
w: 8,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: '',
|
|
groupBy: ['span_name'],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const HTTP_SERVER_DASHBOARD_CONFIG = {
|
|
id: '',
|
|
name: 'HTTP Server',
|
|
charts: [
|
|
{
|
|
id: '312739',
|
|
name: 'P95 Latency by Endpoint',
|
|
x: 0,
|
|
y: 0,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'p95',
|
|
field: 'duration',
|
|
where: 'span.kind:server',
|
|
groupBy: ['http.route'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '434437',
|
|
name: 'HTTP Status Codes',
|
|
x: 0,
|
|
y: 2,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: 'span.kind:server',
|
|
groupBy: ['http.status_code'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '69137',
|
|
name: 'HTTP 4xx, 5xx',
|
|
x: 6,
|
|
y: 4,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: 'http.status_code:>=400 span.kind:server',
|
|
groupBy: ['http.status_code'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '34708',
|
|
name: 'HTTP 5xx by Endpoint',
|
|
x: 6,
|
|
y: 2,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: 'span.kind:server http.status_code:>=500',
|
|
groupBy: ['http.route'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '58773',
|
|
name: 'Request Volume by Endpoint',
|
|
x: 6,
|
|
y: 0,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: 'span.kind:server',
|
|
groupBy: ['http.route'],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const REDIS_DASHBOARD_CONFIG = {
|
|
id: '',
|
|
name: 'Redis',
|
|
charts: [
|
|
{
|
|
id: '38463',
|
|
name: 'GET Operations',
|
|
x: 0,
|
|
y: 0,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: 'db.system:"redis" span_name:GET',
|
|
groupBy: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '488836',
|
|
name: 'P95 GET Latency',
|
|
x: 0,
|
|
y: 2,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'p95',
|
|
field: 'duration',
|
|
where: 'db.system:"redis" span_name:GET',
|
|
groupBy: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '8355753',
|
|
name: 'SET Operations',
|
|
x: 6,
|
|
y: 0,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where: 'db.system:"redis" span_name:SET',
|
|
groupBy: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '93278',
|
|
name: 'P95 SET Latency',
|
|
x: 6,
|
|
y: 2,
|
|
w: 6,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'p95',
|
|
field: 'duration',
|
|
where: 'db.system:"redis" span_name:SET',
|
|
groupBy: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const MONGO_DASHBOARD_CONFIG = {
|
|
id: '',
|
|
name: 'MongoDB',
|
|
charts: [
|
|
{
|
|
id: '98180',
|
|
name: 'P95 Read Operation Latency by Collection',
|
|
x: 0,
|
|
y: 0,
|
|
w: 6,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'p95',
|
|
field: 'duration',
|
|
where:
|
|
'db.system:mongo (db.operation:"find" OR db.operation:"findOne" OR db.operation:"aggregate")',
|
|
groupBy: ['db.mongodb.collection'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '28877',
|
|
name: 'P95 Write Operation Latency by Collection',
|
|
x: 6,
|
|
y: 0,
|
|
w: 6,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'p95',
|
|
field: 'duration',
|
|
where:
|
|
'db.system:mongo (db.operation:"insert" OR db.operation:"findOneAndUpdate" OR db.operation:"save" OR db.operation:"findAndModify")',
|
|
groupBy: ['db.mongodb.collection'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '9901546',
|
|
name: 'Count of Write Operations by Collection',
|
|
x: 6,
|
|
y: 3,
|
|
w: 6,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where:
|
|
'db.system:mongo (db.operation:"insert" OR db.operation:"findOneAndUpdate" OR db.operation:"save" OR db.operation:"findAndModify")',
|
|
groupBy: ['db.mongodb.collection'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '6894669',
|
|
name: 'Count of Read Operations by Collection',
|
|
x: 0,
|
|
y: 3,
|
|
w: 6,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
type: 'time',
|
|
aggFn: 'count',
|
|
where:
|
|
'db.system:mongo (db.operation:"find" OR db.operation:"findOne" OR db.operation:"aggregate")',
|
|
groupBy: ['db.mongodb.collection'],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const HYPERDX_USAGE_DASHBOARD_CONFIG = {
|
|
id: '',
|
|
name: 'HyperDX Usage',
|
|
charts: [
|
|
{
|
|
id: '15gykg',
|
|
name: 'Log/Span Usage in Bytes',
|
|
x: 0,
|
|
y: 0,
|
|
w: 3,
|
|
h: 2,
|
|
series: [
|
|
{
|
|
table: 'logs',
|
|
type: 'number',
|
|
aggFn: 'sum',
|
|
field: 'hyperdx_event_size',
|
|
where: '',
|
|
groupBy: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '1k5pul',
|
|
name: 'Logs/Span Usage over Time',
|
|
x: 3,
|
|
y: 0,
|
|
w: 9,
|
|
h: 3,
|
|
series: [
|
|
{
|
|
table: 'logs',
|
|
type: 'time',
|
|
aggFn: 'sum',
|
|
field: 'hyperdx_event_size',
|
|
where: '',
|
|
groupBy: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
function PresetDashboardLink({
|
|
query,
|
|
config,
|
|
name,
|
|
}: {
|
|
query: any;
|
|
config: any;
|
|
name: string;
|
|
}) {
|
|
return (
|
|
<Link
|
|
href={`/dashboards?config=${encodeURIComponent(JSON.stringify(config))}`}
|
|
>
|
|
<a
|
|
className={cx(
|
|
'd-block ms-3 mt-2 cursor-pointer text-decoration-none text-muted-hover',
|
|
{
|
|
'text-success fw-bold':
|
|
query.config === JSON.stringify(config) &&
|
|
query.dashboardId == null,
|
|
'text-muted-hover': query.config !== JSON.stringify(config),
|
|
},
|
|
)}
|
|
>
|
|
{name}
|
|
</a>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
function PresetSearchLink({ query, name }: { query: string; name: string }) {
|
|
const { query: routerQuery } = useRouter();
|
|
const [searchedQuery] = useQueryParam('q', withDefault(StringParam, ''));
|
|
const [timeRangeQuery] = useQueryParams({
|
|
from: withDefault(NumberParam, -1),
|
|
to: withDefault(NumberParam, -1),
|
|
});
|
|
const [inputTimeQuery] = useQueryParam('tq', withDefault(StringParam, ''), {
|
|
updateType: 'pushIn',
|
|
enableBatching: true,
|
|
});
|
|
|
|
return (
|
|
<Link
|
|
href={`/search?${new URLSearchParams(
|
|
timeRangeQuery.from != -1 && timeRangeQuery.to != -1
|
|
? {
|
|
q: query,
|
|
from: timeRangeQuery.from.toString(),
|
|
to: timeRangeQuery.to.toString(),
|
|
tq: inputTimeQuery,
|
|
}
|
|
: {
|
|
q: query,
|
|
},
|
|
).toString()}`}
|
|
>
|
|
<a
|
|
className={cx('d-block ms-3 mt-2 cursor-pointer text-decoration-none', {
|
|
'text-success fw-bold':
|
|
routerQuery.savedSearchId == null && searchedQuery === query,
|
|
'text-muted-hover':
|
|
routerQuery.savedSearchId != null || searchedQuery !== query,
|
|
})}
|
|
>
|
|
{name}
|
|
</a>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export default function AppNav({ fixed = false }: { fixed?: boolean }) {
|
|
useEffect(() => {
|
|
let redirectUrl;
|
|
try {
|
|
redirectUrl = window.sessionStorage.getItem('hdx-login-redirect-url');
|
|
} catch (e: any) {
|
|
console.error(e);
|
|
}
|
|
// conditional redirect
|
|
if (redirectUrl) {
|
|
// with router.push the page may be added to history
|
|
// the browser on history back will go back to this page and then forward again to the redirected page
|
|
// you can prevent this behaviour using location.replace
|
|
window.sessionStorage.removeItem('hdx-login-redirect-url');
|
|
Router.push(redirectUrl);
|
|
}
|
|
}, []);
|
|
|
|
const {
|
|
data: logViewsData,
|
|
isLoading: isLogViewsLoading,
|
|
refetch: refetchLogViews,
|
|
} = api.useLogViews();
|
|
const logViews = logViewsData?.data ?? [];
|
|
|
|
const { data: dashboardsData, isLoading: isDashboardsLoading } =
|
|
api.useDashboards();
|
|
const dashboards = dashboardsData?.data ?? [];
|
|
|
|
const router = useRouter();
|
|
const { pathname, query } = router;
|
|
|
|
const [timeRangeQuery] = useQueryParams({
|
|
from: withDefault(NumberParam, -1),
|
|
to: withDefault(NumberParam, -1),
|
|
});
|
|
const [inputTimeQuery] = useQueryParam('tq', withDefault(StringParam, ''), {
|
|
updateType: 'pushIn',
|
|
enableBatching: true,
|
|
});
|
|
|
|
const { data: meData } = api.useMe();
|
|
|
|
const [isSearchExpanded, setIsSearchExpanded] = useState(true);
|
|
const [isDashboardsExpanded, setIsDashboardExpanded] = useState(true);
|
|
|
|
const { width } = useWindowSize();
|
|
const [isPreferCollapsed, setIsPreferCollapsed] = useState<
|
|
undefined | boolean
|
|
>(undefined);
|
|
|
|
const isSmallScreen = (width ?? 1000) < 900;
|
|
const isCollapsed = isPreferCollapsed ?? isSmallScreen;
|
|
|
|
const navWidth = isCollapsed ? 50 : 220;
|
|
|
|
const { data: team, isLoading: teamIsLoading } = api.useTeam();
|
|
|
|
useEffect(() => {
|
|
HyperDX.addAction('user navigated', {
|
|
route: pathname,
|
|
query: JSON.stringify(query),
|
|
});
|
|
}, [pathname, query]);
|
|
|
|
useEffect(() => {
|
|
if (meData != null) {
|
|
HyperDX.enableAdvancedNetworkCapture();
|
|
HyperDX.setGlobalAttributes({
|
|
userEmail: meData.email,
|
|
userName: meData.name,
|
|
teamName: meData.team.name,
|
|
});
|
|
}
|
|
}, [meData]);
|
|
return (
|
|
<>
|
|
<AuthLoadingBlocker />
|
|
{fixed && <div style={{ width: navWidth, minWidth: navWidth }}></div>}
|
|
<div
|
|
style={{
|
|
minWidth: navWidth,
|
|
width: navWidth,
|
|
maxHeight: '100vh',
|
|
overflowY: 'auto',
|
|
height: '100%',
|
|
...(fixed
|
|
? {
|
|
height: '100vh',
|
|
position: 'fixed',
|
|
}
|
|
: {}),
|
|
}}
|
|
className="p-3 border-end border-dark d-flex flex-column justify-content-between"
|
|
>
|
|
<div>
|
|
<div className="d-flex flex-wrap justify-content-between align-items-center">
|
|
<Link href="/search">
|
|
<a className="text-decoration-none">
|
|
{isCollapsed ? (
|
|
<div style={{ marginLeft: '-0.15rem' }}>
|
|
<Icon size={22} />
|
|
</div>
|
|
) : (
|
|
<Logo />
|
|
)}
|
|
</a>
|
|
</Link>
|
|
<Button
|
|
variant="dark"
|
|
size="sm"
|
|
className={isCollapsed ? 'mt-4' : ''}
|
|
style={isCollapsed ? { marginLeft: '-0.5rem' } : {}}
|
|
title="Collapse/Expand Navigation"
|
|
onClick={() => setIsPreferCollapsed(v => !v)}
|
|
>
|
|
<i className="bi bi-arrows-angle-expand"></i>
|
|
</Button>
|
|
</div>
|
|
<div className="mt-5">
|
|
<div className="d-flex align-items-center justify-content-between mb-2">
|
|
<Link href="/search">
|
|
<a
|
|
className={cx(
|
|
'text-decoration-none d-flex justify-content-between align-items-center fs-6 text-muted-hover',
|
|
{
|
|
'text-success fw-bold':
|
|
pathname.includes('/search') &&
|
|
query.savedSearchId == null,
|
|
'fw-bold':
|
|
pathname.includes('/search') &&
|
|
query.savedSearchId != null,
|
|
},
|
|
)}
|
|
>
|
|
<span>
|
|
<i className="bi bi-layout-text-sidebar-reverse" />{' '}
|
|
{!isCollapsed && <span>Search</span>}
|
|
</span>
|
|
</a>
|
|
</Link>
|
|
{!isCollapsed && (
|
|
<i
|
|
role="button"
|
|
className={`bi bi-chevron-${
|
|
isSearchExpanded ? 'down' : 'right'
|
|
} text-muted-hover`}
|
|
onClick={() => {
|
|
setIsSearchExpanded(!isSearchExpanded);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
{isSearchExpanded && !isCollapsed && (
|
|
<>
|
|
<div className="fw-bold text-light fs-8 ms-3 mt-3">
|
|
SAVED SEARCHES
|
|
</div>
|
|
{(logViews ?? []).length === 0 ? (
|
|
<div className="text-muted ms-3 mt-2">No saved searches</div>
|
|
) : null}
|
|
{(logViews ?? []).map(lv => (
|
|
<Link
|
|
href={`/search/${lv._id}?${new URLSearchParams(
|
|
timeRangeQuery.from != -1 && timeRangeQuery.to != -1
|
|
? {
|
|
from: timeRangeQuery.from.toString(),
|
|
to: timeRangeQuery.to.toString(),
|
|
tq: inputTimeQuery,
|
|
}
|
|
: {},
|
|
).toString()}`}
|
|
key={lv._id}
|
|
>
|
|
<a
|
|
className={cx(
|
|
'd-flex justify-content-between ms-3 mt-2 cursor-pointer text-decoration-none',
|
|
{
|
|
'text-success fw-bold':
|
|
lv._id === query.savedSearchId,
|
|
'text-muted-hover': lv._id !== query.savedSearchId,
|
|
},
|
|
)}
|
|
title={lv.name}
|
|
>
|
|
<div className="d-inline-block text-truncate">
|
|
{lv.name}
|
|
</div>
|
|
{Array.isArray(lv.alerts) && lv.alerts.length > 0 ? (
|
|
lv.alerts.some(a => a.state === 'ALERT') ? (
|
|
<i
|
|
className="bi bi-bell float-end text-danger"
|
|
title="Has Alerts and is in ALERT state"
|
|
></i>
|
|
) : (
|
|
<i
|
|
className="bi bi-bell float-end"
|
|
title="Has Alerts and is in OK state"
|
|
></i>
|
|
)
|
|
) : null}
|
|
</a>
|
|
</Link>
|
|
))}
|
|
<div className="fw-bold text-light fs-8 ms-3 mt-3">PRESETS</div>
|
|
<PresetSearchLink
|
|
query="level:err OR level:crit OR level:fatal OR level:emerg OR level:alert"
|
|
name="All Error Events"
|
|
/>
|
|
<PresetSearchLink
|
|
query="http.status_code:>=400"
|
|
name="HTTP Status >= 400"
|
|
/>
|
|
</>
|
|
)}
|
|
{/* <Link href="/search">
|
|
<a
|
|
className={cx(
|
|
'd-inline-block ms-3 mt-2 cursor-pointer text-decoration-none',
|
|
{
|
|
'text-success fw-bold': isLiveTail,
|
|
'text-muted-hover': !isLiveTail,
|
|
},
|
|
)}
|
|
>
|
|
<i className="bi bi-lightning-charge-fill me-2" />
|
|
Live Tail
|
|
</a>
|
|
</Link> */}
|
|
<div className="my-4">
|
|
<Link href="/chart">
|
|
<a
|
|
className={cx(
|
|
'text-decoration-none d-flex justify-content-between align-items-center fs-6 text-muted-hover',
|
|
{
|
|
'fw-bold text-success': pathname.includes('/chart'),
|
|
},
|
|
)}
|
|
>
|
|
<span>
|
|
<i className="bi bi-graph-up" />{' '}
|
|
{!isCollapsed && <span>Chart Explorer</span>}
|
|
</span>
|
|
</a>
|
|
</Link>
|
|
</div>
|
|
<div className="my-4">
|
|
<Link href="/sessions">
|
|
<a
|
|
className={cx(
|
|
'text-decoration-none d-flex justify-content-between align-items-center fs-6 text-muted-hover',
|
|
{
|
|
'fw-bold text-success': pathname.includes('/sessions'),
|
|
},
|
|
)}
|
|
>
|
|
<span>
|
|
<i className="bi bi-laptop" />{' '}
|
|
{!isCollapsed && <span>Client Sessions</span>}
|
|
</span>
|
|
</a>
|
|
</Link>
|
|
</div>
|
|
<div>
|
|
<div
|
|
className={cx(
|
|
'text-decoration-none d-flex justify-content-between align-items-center fs-6 text-muted mb-2',
|
|
{
|
|
'fw-bold': pathname.includes('/dashboard'),
|
|
},
|
|
)}
|
|
>
|
|
<Link href="/dashboards">
|
|
<a className="text-decoration-none d-flex justify-content-between align-items-center fs-6 text-muted-hover">
|
|
<span>
|
|
<i className="bi bi-grid-1x2" />{' '}
|
|
{!isCollapsed && <span>Dashboards</span>}
|
|
</span>
|
|
</a>
|
|
</Link>
|
|
{!isCollapsed && (
|
|
<i
|
|
role="button"
|
|
className={`bi bi-chevron-${
|
|
isDashboardsExpanded ? 'down' : 'right'
|
|
} text-muted-hover`}
|
|
onClick={() => {
|
|
setIsDashboardExpanded(!isDashboardsExpanded);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{isDashboardsExpanded && !isCollapsed && (
|
|
<>
|
|
<Link href="/dashboards">
|
|
<a
|
|
className={cx(
|
|
'd-block ms-3 mt-2 cursor-pointer text-decoration-none',
|
|
pathname.includes('/dashboard') &&
|
|
query.dashboardId == null &&
|
|
query.config !=
|
|
JSON.stringify(APP_PERFORMANCE_DASHBOARD_CONFIG) &&
|
|
query.config !=
|
|
JSON.stringify(HTTP_SERVER_DASHBOARD_CONFIG) &&
|
|
query.config !=
|
|
JSON.stringify(REDIS_DASHBOARD_CONFIG) &&
|
|
query.config != JSON.stringify(MONGO_DASHBOARD_CONFIG)
|
|
? 'text-success fw-bold'
|
|
: 'text-muted-hover',
|
|
)}
|
|
>
|
|
<i className="bi bi-plus me-2" />
|
|
New Dashboard
|
|
</a>
|
|
</Link>
|
|
<div className="fw-bold text-light fs-8 ms-3 mt-3">
|
|
SAVED DASHBOARDS
|
|
</div>
|
|
{(dashboards ?? []).length === 0 ? (
|
|
<div className="text-muted ms-3 mt-2">0 saved dashboards</div>
|
|
) : null}
|
|
{(dashboards ?? []).map((dashboard: any) => (
|
|
<Link
|
|
href={`/dashboards/${dashboard._id}`}
|
|
key={dashboard._id}
|
|
>
|
|
<a
|
|
className={cx(
|
|
'd-block ms-3 mt-2 cursor-pointer text-decoration-none',
|
|
{
|
|
'text-success fw-bold':
|
|
dashboard._id === query.dashboardId,
|
|
'text-muted-hover':
|
|
dashboard._id !== query.dashboardId,
|
|
},
|
|
)}
|
|
>
|
|
{dashboard.name}
|
|
</a>
|
|
</Link>
|
|
))}
|
|
<div className="fw-bold text-light fs-8 ms-3 mt-3">PRESETS</div>
|
|
<PresetDashboardLink
|
|
query={query}
|
|
config={HYPERDX_USAGE_DASHBOARD_CONFIG}
|
|
name="HyperDX Usage"
|
|
/>
|
|
<PresetDashboardLink
|
|
query={query}
|
|
config={APP_PERFORMANCE_DASHBOARD_CONFIG}
|
|
name="App Performance"
|
|
/>
|
|
<PresetDashboardLink
|
|
query={query}
|
|
config={HTTP_SERVER_DASHBOARD_CONFIG}
|
|
name="HTTP Server"
|
|
/>
|
|
<PresetDashboardLink
|
|
query={query}
|
|
config={REDIS_DASHBOARD_CONFIG}
|
|
name="Redis"
|
|
/>
|
|
<PresetDashboardLink
|
|
query={query}
|
|
config={MONGO_DASHBOARD_CONFIG}
|
|
name="Mongo"
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="mb-4 mt-4">
|
|
<div className="my-3 bg-hdx-dark rounded p-2 text-center">
|
|
<span className="">Ready to use HyperDX Cloud?</span>
|
|
<div className="mt-3 mb-2">
|
|
<Link href="https://www.hyperdx.io/register" passHref>
|
|
<Button variant="outline-success" className="inter" size="sm">
|
|
Get Started for Free
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
<div className="my-3">
|
|
<Link href="/team">
|
|
<a
|
|
className={cx(
|
|
'text-decoration-none d-flex justify-content-between align-items-center text-muted-hover',
|
|
{
|
|
'fw-bold text-success': pathname.includes('/team'),
|
|
},
|
|
)}
|
|
>
|
|
<span>
|
|
<i className="bi bi-gear" />{' '}
|
|
{!isCollapsed && <span>Team Settings</span>}
|
|
</span>
|
|
</a>
|
|
</Link>
|
|
</div>
|
|
<div className="my-3">
|
|
<Link href="https://hyperdx.io/docs">
|
|
<a
|
|
className={cx(
|
|
'text-decoration-none d-flex justify-content-between align-items-center text-muted-hover',
|
|
)}
|
|
target="_blank"
|
|
>
|
|
<span>
|
|
<i className="bi bi-book" />{' '}
|
|
{!isCollapsed && <span>Documentation</span>}
|
|
</span>
|
|
</a>
|
|
</Link>
|
|
</div>
|
|
<div className="my-4">
|
|
<Link href={`${API_SERVER_URL}/logout`}>
|
|
<span role="button" className="text-muted-hover">
|
|
<i className="bi bi-box-arrow-left" />{' '}
|
|
{!isCollapsed && <span>Logout</span>}
|
|
</span>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|