diff --git a/src/components/Header/styles/Header.module.css b/src/components/Header/styles/Header.module.css
index 56563428..79aafa4e 100644
--- a/src/components/Header/styles/Header.module.css
+++ b/src/components/Header/styles/Header.module.css
@@ -1,5 +1,5 @@
.container {
- background-color: #F1F1F1;
+ background-color: white;
display: flex;
align-items: center;
color: #495057;
@@ -7,16 +7,16 @@
font-weight: 500;
line-height: normal;
width: 100%;
- border-bottom: 1px solid var(--mantine-color-gray-3);
}
.logoContainer {
display: flex;
justify-content: center;
align-items: center;
- padding: 1rem;
- padding-right: 0;
- height: 24px;
+ transition: width 0.4s ease-in-out;
+ background-color: var(--mantine-color-gray-0);
+ border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
+ height: 100%;
}
.navContainer {
@@ -32,3 +32,27 @@
transform: scale(1.3);
background-color: #f8f9fa;
}
+
+.primaryHeaderContainer {
+ background-color: white;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ flex-direction: row;
+ background-color: var(--mantine-color-gray-0);
+}
+
+.rightSection {
+ border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
+ flex-direction: row;
+ justify-content: flex-end;
+ height: 100%;
+ flex: 1;
+ align-items: center;
+}
+
+.divider {
+ border: 1px solid;
+ height: 70%;
+ border-color: var(--mantine-color-gray-4);
+}
\ No newline at end of file
diff --git a/src/components/Header/styles/HelpModal.module.css b/src/components/Header/styles/HelpModal.module.css
new file mode 100644
index 00000000..88aa2fe0
--- /dev/null
+++ b/src/components/Header/styles/HelpModal.module.css
@@ -0,0 +1,111 @@
+.container {
+ height: 100%;
+ width: 100%;
+}
+
+.aboutTitle {
+ font-size: 1.5rem;
+ font-weight: 600;
+ text-align: center;
+}
+
+.parseableText {
+ color: #FC466B;
+ font-weight: 600;
+}
+
+.aboutDescription {
+ font-size: 1rem;
+ text-align: center;
+ color: #868e96;
+ margin-top: 0.75rem;
+
+ &::after {
+ content: '';
+ border-radius: 0.5rem;
+ display: block;
+ background-color: #FC466B;
+ width: 6.25rem;
+ height: 0.125rem;
+ margin-top: 0.75rem;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: 0.75rem;
+ }
+}
+
+.aboutTextBox {
+ margin-top: 0.75rem;
+ margin-bottom: 0.75rem;
+ width: 100%;
+ display: flex;
+ border-radius: 0.5rem;
+ border: 0.0625rem solid rgba(0, 0, 0, 0.05);
+ padding: 1rem;
+ flex-direction: column;
+}
+
+.aboutTextInnerBox {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ margin-top: 0.625rem;
+ margin-bottom: 0.625rem;
+}
+
+.aboutTextKey {
+ font-size: 0.875rem;
+ color: #343a40;
+ line-height: normal;
+ width: 15%;
+}
+
+.aboutTextValue {
+ font-size: 0.875rem;
+ color: #868e96;
+ line-height: normal;
+ width: 85%;
+}
+
+.helpTitle {
+ font-size: 1rem;
+ text-align: center;
+ color: #495057;
+ font-weight: 700;
+}
+
+.HelpIconBox {
+ &:hover {
+ color: #1F288E;
+ transform: scale(1.4);
+ }
+ height: 34px;
+ transition: transform 0.2s ease-in-out;
+ width: 34px;
+ padding: 0;
+ margin-inline-end: 0.625rem;
+ background-color: #FFFFFF;
+ color: #495057;
+ border: 0.0625rem solid rgba(0, 0, 0, 0.05);
+}
+
+.helpIconContainer {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ justify-content: center;
+}
+
+.actionBtn {
+ color: #545BEB;
+ border-radius: 0.5rem;
+ border: 1px solid #E0E0E0;
+ position: absolute;
+ right: 2.25rem;
+ transform: transform .2s ease-in-out;
+ &:hover {
+ transform: scale(1.03);
+ color: #FC466B;
+ background-color: white;
+ }
+}
diff --git a/src/components/Header/styles/LogQuery.module.css b/src/components/Header/styles/LogQuery.module.css
index 4faabe6f..75687302 100644
--- a/src/components/Header/styles/LogQuery.module.css
+++ b/src/components/Header/styles/LogQuery.module.css
@@ -208,3 +208,112 @@
.customTimeRangeApplyBtn:disabled:hover {
background-color: var(--mantine-color-gray-4);
}
+
+.streamSelectDescription {
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--mantine-color-gray-6);
+}
+
+.streamInput {
+ border: none;
+ padding-left: 0;
+ padding-right: 0;
+ height: 50px;
+ font-size: 24px;
+ font-weight: 600;
+ margin-top: -10px;
+ background-color: transparent;
+ cursor: pointer;
+ width: 200px;
+}
+
+.chevronDown {
+ color: var(--mantine-color-gray-9);
+}
+
+.streamSelect {
+ margin-top: 10px;
+ /* adjustments for neg margin for input */
+ margin-left: 0.625rem;
+}
+
+.iconBtn {
+ cursor: pointer;
+ align-items: center;
+ /* padding: 0.25rem; */
+ border-radius: 0.25rem;
+ /* padding: 0.25rem 0; */
+}
+
+.iconBtnIcon {
+ color: var(--mantine-color-brandPrimary-6);
+ /* color: white;
+ background-color: var(--mantine-color-brandPrimary-4); */
+ border-radius: 0.25rem;
+ /* padding: 0.25rem; */
+}
+
+.iconBtnLabel {
+ font-size: 0.8rem;
+ white-space: nowrap;
+}
+
+.logsPrimaryToolbar {
+ padding: 0.625rem 0;
+ border-bottom: 1px solid var(--mantine-color-gray-3);
+ width: 100%;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.logsSecondaryToolbar {
+ width: '100%';
+ flex-direction: row;
+ padding: 1rem 0.625rem;
+}
+
+.filterContainer {
+ background-color: white;
+ /* padding: 4px 12px; */
+ color: #211F1F;
+ border: 1px var(--mantine-color-gray-4) solid;
+ border-radius: rem(8px);
+ cursor: pointer;
+ /* height: 2.2rem; */
+ overflow: auto;
+ flex: 1;
+ margin-right: 0.625rem;
+ flex-direction: row;
+ /* height: fit-content; */
+}
+
+.modeContainer {
+ width: 5rem;
+ height: 100%;
+ text-align: center;
+ align-items: center;
+ justify-content: center;
+ border-right: 1px solid var(--mantine-color-gray-4);
+}
+
+.modeLabel {
+ /* color: white; */
+ font-weight: 600;
+}
+
+.modeButton {
+ border-top-right-radius: 0px !important;
+ border-bottom-right-radius: 0px !important;
+ width: 6rem;
+ border-top: none !important;
+ border-left: none !important;
+ border-bottom: none !important;
+}
+
+.placeholderText {
+ color: var(--mantine-color-gray-5);
+ font-size: 1rem;
+ font-weight: 500;
+}
\ No newline at end of file
diff --git a/src/components/Header/styles/QueryBuilder.module.css b/src/components/Header/styles/QueryBuilder.module.css
index 86e07b55..f451bc8e 100644
--- a/src/components/Header/styles/QueryBuilder.module.css
+++ b/src/components/Header/styles/QueryBuilder.module.css
@@ -90,8 +90,8 @@
cursor: pointer;
height: 2.2rem;
overflow: auto;
- min-width: 70%;
- max-width: 100%;
+ flex: 1;
+ margin-right: 0.625rem;
}
.parentCombinatorPill {
@@ -113,6 +113,7 @@
justify-content: flex-end;
padding: 1.5rem;
padding-top: 0.75rem;
+ padding-right: 0rem;
}
.queryBuilderContainer {
diff --git a/src/components/Navbar/UserModal.tsx b/src/components/Navbar/UserModal.tsx
new file mode 100644
index 00000000..40d1b3a4
--- /dev/null
+++ b/src/components/Navbar/UserModal.tsx
@@ -0,0 +1,50 @@
+import { useHeaderContext } from '@/layouts/MainLayout/Context';
+import { Modal, Stack } from '@mantine/core';
+import { Text } from '@mantine/core';
+import Cookies from 'js-cookie';
+
+const ModalTitle = () => {
+ return
User Details;
+};
+
+type UserModalProps = {
+ opened: boolean;
+ onClose: () => void;
+ userData: {[key: string]: string}
+}
+
+const UserModal = (props: UserModalProps) => {
+ const {
+ state: { subAppContext },
+ } = useHeaderContext();
+ const username = Cookies.get('username');
+
+ const userRoles = subAppContext.get().userRoles || {};
+ return (
+
}>
+
+
+ Username:
+ {username}
+
+
+ Roles:
+ {Object.entries(userRoles).map(([key, value], index) => {
+ return (
+
+ {index + 1}. {key} ({value[0].privilege})
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default UserModal;
diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx
index a3457820..3adb34e9 100644
--- a/src/components/Navbar/index.tsx
+++ b/src/components/Navbar/index.tsx
@@ -1,286 +1,180 @@
-import { NavLink, Select, Modal, Button, TextInput, Group, Box, rem, Stack } from '@mantine/core';
-import {
- IconReportAnalytics,
- IconFileAlert,
- IconReload,
- IconLogout,
- IconUser,
- IconBinaryTree2,
- IconTableShortcut,
- IconSettings,
- IconTrash,
- IconInfoCircle,
- IconUserCog,
- IconTimelineEvent,
-} from '@tabler/icons-react';
-import { FC, useEffect } from 'react';
+import { Box, Stack, Tooltip } from '@mantine/core';
+import { IconLogout, IconUser, IconBinaryTree2, IconInfoCircle, IconUserCog, IconHome } from '@tabler/icons-react';
+import { FC, useCallback, useEffect } from 'react';
import { useLocation, useParams } from 'react-router-dom';
-import { notifications } from '@mantine/notifications';
import { useNavigate } from 'react-router-dom';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
-import useMountedState from '@/hooks/useMountedState';
import { useDisclosure } from '@mantine/hooks';
-import { USERS_MANAGEMENT_ROUTE } from '@/constants/routes';
+import { HOME_ROUTE, LOGS_ROUTE, USERS_MANAGEMENT_ROUTE } from '@/constants/routes';
import InfoModal from './infoModal';
import { getStreamsSepcificAccess, getUserSepcificStreams } from './rolesHandler';
-import { LogStreamData } from '@/@types/parseable/api/stream';
import Cookies from 'js-cookie';
import { useUser } from '@/hooks/useUser';
import { useLogStream } from '@/hooks/useLogStream';
-import { signOutHandler } from '@/utils';
import styles from './styles/Navbar.module.css';
+import useCurrentRoute from '@/hooks/useCurrentRoute';
+import { NAVBAR_WIDTH, PRIMARY_HEADER_HEIGHT } from '@/constants/theme';
+import UserModal from './UserModal';
+import { signOutHandler } from '@/utils';
+
+const navItems = [
+ {
+ icon: IconHome,
+ label: 'Home',
+ path: '/',
+ route: HOME_ROUTE,
+ },
+ {
+ icon: IconBinaryTree2,
+ label: 'Stream',
+ path: '/logs',
+ route: LOGS_ROUTE,
+ },
+ {
+ icon: IconUserCog,
+ label: 'Users',
+ path: '/users',
+ route: USERS_MANAGEMENT_ROUTE,
+ },
+];
-const isSecureConnection = window.location.protocol === 'https:';
-const links = [
- { icon: IconTableShortcut, label: 'Explore', pathname: '/logs', requiredAccess: ['Query', 'GetSchema'] },
- ...(!isSecureConnection
- ? [{ icon: IconTimelineEvent, label: 'Live tail', pathname: '/live-tail', requiredAccess: ['GetLiveTail'] }]
- : []),
- { icon: IconReportAnalytics, label: 'Stats', pathname: '/stats', requiredAccess: ['GetStats'] },
- { icon: IconSettings, label: 'Config', pathname: '/config', requiredAccess: ['PutAlert'] },
+const navActions = [
+ {
+ icon: IconUser,
+ label: 'Profile',
+ key: 'user',
+ },
+ {
+ icon: IconInfoCircle,
+ label: 'About',
+ key: 'about',
+ },
+ {
+ icon: IconLogout,
+ label: 'Logout',
+ key: 'logout',
+ },
];
const Navbar: FC = () => {
const navigate = useNavigate();
const { streamName } = useParams();
const location = useLocation();
-
+ const currentRoute = useCurrentRoute();
const username = Cookies.get('username');
-
const {
- state: { subAppContext },
- methods: { streamChangeCleanup, setUserRoles, setSelectedStream },
+ state: { subAppContext, maximized, userSpecficStreams, userSpecificAccessMap },
+ methods: { streamChangeCleanup, setUserRoles, setUserSpecficStreams, updateUserSpecificAccess },
} = useHeaderContext();
const selectedStream = subAppContext.get().selectedStream;
- const [currentPage, setCurrentPage] = useMountedState('/');
- const [deleteStream, setDeleteStream] = useMountedState('');
- const [userSepecficStreams, setUserSepecficStreams] = useMountedState
(null);
- const [userSepecficAccess, setUserSepecficAccess] = useMountedState(null);
+ const [userModalOpened, { toggle: toggleUserModal }] = useDisclosure(false);
+ const [infoModalOpened, { toggle: toggleInfoModal }] = useDisclosure(false);
- const [disableLink, setDisableLink] = useMountedState(false);
- const [opened, { open, close }] = useDisclosure(false);
- const [openedDelete, { close: closeDelete, open: openDelete }] = useDisclosure();
-
- const { deleteLogStreamMutation, getLogStreamListData, getLogStreamListIsError, getLogStreamListRefetch } =
- useLogStream();
+ const { getLogStreamListData } = useLogStream();
const { getUserRolesData, getUserRolesMutation } = useUser();
- useEffect(() => {
- if (location.pathname.split('/')[2]) {
- setCurrentPage(`/${location.pathname.split('/')[2]}`);
- }
- if (location.pathname === '/') {
- setSelectedStream('');
- setCurrentPage('/');
- setUserSepecficAccess(getStreamsSepcificAccess(getUserRolesData?.data));
- } else if (userSepecficStreams && userSepecficStreams.length === 0) {
- setSelectedStream('');
- setDisableLink(true);
- navigate('/');
- } else if (streamName) {
- if (streamName === deleteStream && userSepecficStreams) {
- setDeleteStream('');
- handleChange(userSepecficStreams[0].name);
- } else if (userSepecficStreams && !userSepecficStreams.find((stream: any) => stream.name === streamName)) {
- notifications.show({
- id: 'getLogStreamListIsError-data',
- color: 'red',
- title: 'Error occurred',
- message: `${streamName} stream not found`,
- icon: ,
- autoClose: 5000,
- });
- handleChange(userSepecficStreams[0].name);
- } else if (userSepecficStreams?.find((stream: any) => stream.name === streamName)) {
- handleChange(streamName);
- }
- } else if (userSepecficStreams && Boolean(userSepecficStreams.length)) {
- if (location.pathname === USERS_MANAGEMENT_ROUTE) {
- handleChangeWithoutRiderection(userSepecficStreams[0].name, location.pathname);
- navigate('/users');
- } else {
- handleChange(userSepecficStreams[0].name);
- }
- }
- }, [userSepecficStreams]);
-
- const handleChange = (value: string, page: string = currentPage) => {
- const targetPage = page === '/' ? '/logs' : page;
- handleChangeWithoutRiderection(value, targetPage);
- setUserSepecficAccess(getStreamsSepcificAccess(getUserRolesData?.data, value));
- if (page !== '/users') {
- navigate(`/${value}${targetPage}`);
- }
- };
-
- const handleChangeWithoutRiderection = (value: string, page: string = currentPage) => {
- setSelectedStream(value);
- setCurrentPage(page);
- streamChangeCleanup(value);
- setDisableLink(false);
- };
- const handleCloseDelete = () => {
- closeDelete();
- setDeleteStream('');
- };
-
- const handleDelete = () => {
- deleteLogStreamMutation({ deleteStream });
- closeDelete();
- };
-
useEffect(() => {
if (getLogStreamListData?.data && getLogStreamListData?.data.length > 0 && getUserRolesData?.data) {
getUserRolesData?.data && setUserRoles(getUserRolesData?.data); // TODO: move user context main context
const userStreams = getUserSepcificStreams(getUserRolesData?.data, getLogStreamListData?.data as any);
- setUserSepecficStreams(userStreams as any);
+ setUserSpecficStreams(userStreams as any);
} else {
- setUserSepecficStreams(null);
- setUserSepecficAccess(getStreamsSepcificAccess(getUserRolesData?.data));
+ setUserSpecficStreams(null);
}
+ updateUserSpecificAccess(getStreamsSepcificAccess(getUserRolesData?.data));
}, [getUserRolesData?.data, getLogStreamListData?.data]);
useEffect(() => {
getUserRolesMutation({ userName: username ? username : '' });
}, [username]);
+ const navigateToPage = useCallback(
+ (route: string) => {
+ if (route === LOGS_ROUTE) {
+ if (
+ !userSpecficStreams ||
+ userSpecficStreams.length === 0 ||
+ (streamName && !userSpecficStreams.find((stream: any) => stream.name === streamName))
+ ) {
+ return navigate('/');
+ }
+ const defaultStream =
+ selectedStream && selectedStream.length !== 0 ? selectedStream : userSpecficStreams[0].name;
+ const stream = !streamName || streamName.length === 0 ? defaultStream : streamName;
+ const path = `/${stream}/logs`;
+ streamChangeCleanup(stream);
+
+ if (path !== location.pathname) {
+ navigate(path);
+ }
+ } else {
+ return navigate(route);
+ }
+ },
+ [userSpecficStreams, streamName],
+ );
+
+ useEffect(() => {
+ if (streamName && streamName.length !== 0 && userSpecficStreams && userSpecficStreams.length !== 0) {
+ navigateToPage(LOGS_ROUTE);
+ }
+ }, [streamName, userSpecficStreams]);
+
+ if (maximized) return null;
+
return (
-
diff --git a/src/components/Navbar/infoModal.tsx b/src/components/Navbar/infoModal.tsx
index 0659c361..11caba53 100644
--- a/src/components/Navbar/infoModal.tsx
+++ b/src/components/Navbar/infoModal.tsx
@@ -1,56 +1,10 @@
-import { Box, Button, Modal, Text, Tooltip, px } from '@mantine/core';
+import { Box, Button, Modal, Text, px } from '@mantine/core';
import { FC, useEffect, useMemo } from 'react';
import { useAbout } from '@/hooks/useGetAbout';
-import { IconAlertCircle, IconBook2, IconBrandGithub, IconBrandSlack, IconBusinessplan } from '@tabler/icons-react';
+import { IconAlertCircle } from '@tabler/icons-react';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
import styles from './styles/InfoModal.module.css'
-const helpResources = [
- {
- icon: IconBusinessplan,
- title: 'Production support',
- description: 'Get production support',
- href: 'mailto:sales@parseable.io?subject=Production%20Support%20Query', //https://www.parseable.io/pricing
- },
- {
- icon: IconBrandSlack,
- title: 'Slack',
- description: 'Join the Slack community',
- href: 'https://join.slack.com/t/parseable/shared_invite/zt-23t505gz7-zX4T10OvkS8RAhnme4gDZQ',
- },
- {
- icon: IconBrandGithub,
- title: 'GitHub',
- description: 'Find resources on GitHub',
- href: 'https://github.com/parseablehq/parseable',
- },
- {
- icon: IconBook2,
- title: 'Documentation',
- description: 'Refer the documentation',
- href: 'https://www.parseable.com/docs',
- },
-];
-
-type HelpCardProps = {
- data: (typeof helpResources)[number];
-};
-
-const HelpCard: FC = (props) => {
- const { data } = props;
-
- const classes = styles;
- const { HelpIconBox } = classes;
-
- return (
-
-
-
- );
-};
-
type InfoModalProps = {
opened: boolean;
close(): void;
@@ -84,7 +38,6 @@ const InfoModal: FC = (props) => {
aboutTitle,
aboutDescription,
actionBtn,
- helpIconContainer,
aboutTextBox,
aboutTextKey,
aboutTextValue,
@@ -172,15 +125,6 @@ const InfoModal: FC = (props) => {
>
) : null}
-
- Need help?
- Ensure uninterrupted deployment
-
-
- {helpResources.map((data) => (
-
- ))}
-
);
diff --git a/src/components/Navbar/styles/Navbar.module.css b/src/components/Navbar/styles/Navbar.module.css
index 12f031b4..fa3df0af 100644
--- a/src/components/Navbar/styles/Navbar.module.css
+++ b/src/components/Navbar/styles/Navbar.module.css
@@ -8,23 +8,9 @@
font-size: 16px;
font-weight: 400;
line-height: normal;
-
- & .mantine-Select-group {
- padding: 23rem !important;
- }
+ position: relative;
}
-.streamsBtn {
- cursor: default;
- color: #4d4d4d;
- padding-left: 0;
- padding-right: 0;
- &:hover {
- background-color: white;
- }
-}
-
-
.userManagementBtn {
color: #4d4d4d;
padding-left: 0;
@@ -100,12 +86,12 @@
.navbar {
height: calc(100vh - 50px);
- padding: var(--mantine-spacing-md) var(--mantine-spacing-md);
display: flex;
flex-direction: column;
border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
transition: width 0.4s ease-in-out;
flex: '0 0 auto';
+ background-color: var(--mantine-color-gray-0);
}
.navbarMain {
@@ -126,6 +112,39 @@
}
.option[aria-selected="true"] {
- background-color: var(--mantine-color-brandPrimary-4);
+ background-color: var(--mantine-color-brandPrimary-6);
color: white;
}
+
+.navItemContainer {
+ width: 100%;
+ align-items: center;
+ padding: 0.8rem 0;
+ cursor: pointer;
+ color: var(--mantine-color-gray-6);
+}
+
+.navItemContainer:last-child {
+ border: none;
+}
+
+.navItemLabel {
+ font-size: 0.75rem;
+}
+
+.navItemActive {
+ /* color: #ff0000; */
+ border-color: white;
+ color: var(--mantine-color-gray-8);
+}
+
+.lowerContainer {
+ color: var(--mantine-color-gray-5);
+ align-items: center;
+}
+
+.maximizeToggleContainer {
+ top: 4%;
+ right: -14%;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index e9ac2b98..e23b1e37 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -7,3 +7,15 @@ export const STATS_ROUTE = '/:streamName/stats';
export const CONFIG_ROUTE = '/:streamName/config';
export const USERS_MANAGEMENT_ROUTE = '/users';
export const OIDC_NOT_CONFIGURED_ROUTE = '/oidc-not-configured';
+
+export const PATHS = {
+ all: '/*',
+ home: '/',
+ logs: '/:streamName/logs',
+ login: '/login',
+ liveTail: '/:streamName/live-tail',
+ stats: '/:streamName/stats',
+ config: '/:streamName/config',
+ users: '/users',
+ oidcNotConfigured: '/oidc-not-configured'
+} as {[key: string]: string}
\ No newline at end of file
diff --git a/src/constants/theme.ts b/src/constants/theme.ts
index 9276131b..0546b4ab 100644
--- a/src/constants/theme.ts
+++ b/src/constants/theme.ts
@@ -1,3 +1,6 @@
export const HEADER_HEIGHT = 50;
-export const NAVBAR_WIDTH = 200;
+export const PRIMARY_HEADER_HEIGHT = 52;
+export const NAVBAR_WIDTH = 60;
export const APP_MIN_WIDTH = 650;
+export const LOGS_PRIMARY_TOOLBAR_HEIGHT = 80;
+export const LOGS_SECONDARY_TOOLBAR_HEIGHT = 68;
diff --git a/src/constants/timeConstants.ts b/src/constants/timeConstants.ts
index 398c0604..e8cf74f0 100644
--- a/src/constants/timeConstants.ts
+++ b/src/constants/timeConstants.ts
@@ -3,6 +3,7 @@ import dayjs from 'dayjs';
type FixedDuration = {
name: string;
milliseconds: number;
+ label: string;
};
export const REFRESH_INTERVALS: number[] = [10000, 30000, 60000, 300000, 600000, 1200000];
@@ -11,25 +12,34 @@ export const FIXED_DURATIONS: ReadonlyArray = [
{
name: 'last 10 minutes',
milliseconds: dayjs.duration({ minutes: 10 }).asMilliseconds(),
+ label: '10M'
},
{
name: 'last 1 hour',
milliseconds: dayjs.duration({ hours: 1 }).asMilliseconds(),
+ label: '1H'
},
{
name: 'last 5 hours',
milliseconds: dayjs.duration({ hours: 5 }).asMilliseconds(),
+ label: '5H'
},
{
name: 'last 24 hours',
milliseconds: dayjs.duration({ days: 1 }).asMilliseconds(),
+ label: '1D'
},
{
name: 'last 3 days',
milliseconds: dayjs.duration({ days: 3 }).asMilliseconds(),
- },
- {
- name: 'last 7 days',
- milliseconds: dayjs.duration({ days: 7 }).asMilliseconds(),
+ label: '3D'
},
] as const;
+
+export const FIXED_DURATIONS_LABEL: { [key: string]: string } = {
+ 'last 10 minutes': '10M',
+ 'last 1 hour': '1H',
+ 'last 5 hours': '5H',
+ 'last 24 hours': '1D',
+ 'last 3 days': '3D',
+} as const;
\ No newline at end of file
diff --git a/src/hooks/useCurrentRoute.ts b/src/hooks/useCurrentRoute.ts
new file mode 100644
index 00000000..bc2bfe8d
--- /dev/null
+++ b/src/hooks/useCurrentRoute.ts
@@ -0,0 +1,20 @@
+import { PATHS } from "@/constants/routes"
+import { matchRoutes, useLocation } from "react-router-dom"
+
+const routes = Object.keys(PATHS).map((key: string) => {
+ const value = PATHS[key];
+ return {path: value};
+});
+
+const useCurrentRoute = () => {
+ const location = useLocation();
+ const match = matchRoutes(routes, location);
+
+ if (!match) {
+ return '';
+ } else {
+ return match[0].route.path;
+ }
+};
+
+export default useCurrentRoute;
\ No newline at end of file
diff --git a/src/hooks/useQueryLogs.ts b/src/hooks/useQueryLogs.ts
index 29cea13d..0ea6500c 100644
--- a/src/hooks/useQueryLogs.ts
+++ b/src/hooks/useQueryLogs.ts
@@ -3,7 +3,7 @@ import { getQueryLogs, getQueryResult } from '@/api/query';
import { StatusCodes } from 'http-status-codes';
import useMountedState from './useMountedState';
import { useCallback, useEffect, useMemo, useRef, useTransition } from 'react';
-import { LOG_QUERY_LIMITS, useLogsPageContext } from '@/pages/Logs/Context';
+import { LOG_QUERY_LIMITS, useLogsPageContext } from '@/pages/Logs/logsContextProvider';
import { parseLogData } from '@/utils';
type QueryLogs = {
diff --git a/src/hooks/useUser.tsx b/src/hooks/useUser.tsx
index 28ec8e41..296a5b91 100644
--- a/src/hooks/useUser.tsx
+++ b/src/hooks/useUser.tsx
@@ -14,14 +14,20 @@ export const useUser = () => {
isLoading: createUserIsLoading,
data: createUserData,
reset: createUserReset,
- } = useMutation((data: { userName: string; roles: object[] }) => postUser(data.userName, data.roles), {
- onError: (data: AxiosError) => {
- if (isAxiosError(data) && data.response) {
- const error = data.response.data as string;
- setCreateUserError(error);
- }
+ } = useMutation(
+ (data: { userName: string; roles: object[]; onSuccess?: () => void }) => postUser(data.userName, data.roles),
+ {
+ onError: (data: AxiosError) => {
+ if (isAxiosError(data) && data.response) {
+ const error = data.response.data as string;
+ setCreateUserError(error);
+ }
+ },
+ onSuccess: (_data, variables) => {
+ variables.onSuccess && variables.onSuccess();
+ },
},
- });
+ );
const {
mutate: deleteUserMutation,
@@ -54,16 +60,6 @@ export const useUser = () => {
},
});
- const {
- data: getUserData,
- isError: getUserIsError,
- isSuccess: getUserIsSuccess,
- isLoading: getUserIsLoading,
- refetch: getUserRefetch,
- } = useQuery(['fetch-user', createUserIsSuccess, deleteUserIsSuccess], () => getUsers(), {
- retry: false,
- });
-
const {
data: getUserRolesData,
isError: getUserRolesIsError,
@@ -81,11 +77,6 @@ export const useUser = () => {
createUserIsLoading,
createUserData,
createUserReset,
- getUserRefetch,
- getUserData,
- getUserIsError,
- getUserIsSuccess,
- getUserIsLoading,
deleteUserMutation,
deleteUserIsSuccess,
deleteUserIsError,
@@ -110,3 +101,22 @@ export const useUser = () => {
createUserError,
};
};
+
+export const useGetUser = () => {
+ const {
+ data: getUserData,
+ isError: getUserIsError,
+ isSuccess: getUserIsSuccess,
+ isLoading: getUserIsLoading,
+ refetch: getUserRefetch,
+ } = useQuery(['fetch-user'], () => getUsers(), {
+ retry: false,
+ });
+ return {
+ getUserRefetch,
+ getUserData,
+ getUserIsError,
+ getUserIsSuccess,
+ getUserIsLoading,
+ };
+};
diff --git a/src/layouts/MainLayout/Context.tsx b/src/layouts/MainLayout/Context.tsx
index 584d7d25..c2d48fbb 100644
--- a/src/layouts/MainLayout/Context.tsx
+++ b/src/layouts/MainLayout/Context.tsx
@@ -4,9 +4,10 @@ import { LogStreamData } from '@/@types/parseable/api/stream';
import { getStreamsSepcificAccess } from '@/components/Navbar/rolesHandler';
import { FIXED_DURATIONS } from '@/constants/timeConstants';
import useSubscribeState, { SubData } from '@/hooks/useSubscribeState';
+import { useDisclosure } from '@mantine/hooks';
import dayjs from 'dayjs';
-import type { FC } from 'react';
-import { ReactNode, createContext, useCallback, useContext } from 'react';
+import type { Dispatch, FC, SetStateAction } from 'react';
+import { ReactNode, createContext, useCallback, useContext, useEffect, useState } from 'react';
const Context = createContext({});
@@ -33,6 +34,10 @@ interface HeaderContextState {
subCreateUserModalTogle: SubData;
subInstanceConfig: SubData;
subAppContext: SubData;
+ maximized: boolean;
+ userSpecficStreams: LogStreamData | null;
+ userSpecificAccessMap: { [key: string]: boolean };
+ helpModalOpen: boolean;
}
export type UserRoles = {
@@ -61,6 +66,10 @@ interface HeaderContextMethods {
streamChangeCleanup: (streamName: string) => void;
setUserRoles: (userRoles: UserRoles) => void;
setSelectedStream: (stream: string) => void;
+ setUserSpecficStreams: Dispatch>;
+ toggleMaximize: () => void;
+ updateUserSpecificAccess: (accessRoles: string[] | null) => void;
+ toggleHelpModal: () => void;
}
interface HeaderContextValue {
@@ -72,6 +81,23 @@ interface HeaderProviderProps {
children: ReactNode;
}
+const accessKeyMap: { [key: string]: string } = {
+ hasUserAccess: 'ListUser',
+ hasDeleteAccess: 'DeleteStream',
+ hasUpdateAlertAccess: 'PutAlert',
+ hasGetAlertAccess: 'GetAlert',
+};
+
+const generateUserAcccessMap = (accessRoles: string[] | null) => {
+ return Object.keys(accessKeyMap).reduce((acc, accessKey: string) => {
+ return {
+ ...acc,
+ [accessKey]:
+ accessRoles !== null && accessKeyMap.hasOwnProperty(accessKey) && accessRoles.includes(accessKeyMap[accessKey]),
+ };
+ }, {});
+};
+
const MainLayoutPageProvider: FC = ({ children }) => {
const subAppContext = useSubscribeState({
selectedStream: '',
@@ -108,6 +134,10 @@ const MainLayoutPageProvider: FC = ({ children }) => {
const subNavbarTogle = useSubscribeState(false);
const subCreateUserModalTogle = useSubscribeState(false);
const subInstanceConfig = useSubscribeState(null);
+ const [maximized, { toggle: toggleMaximize }] = useDisclosure(false);
+ const [helpModalOpen, { toggle: toggleHelpModal }] = useDisclosure(false);
+ const [userSpecficStreams, setUserSpecficStreams] = useState(null);
+ const [userSpecificAccessMap, setUserSpecificMap] = useState<{ [key: string]: boolean }>({});
const state: HeaderContextState = {
subLogQuery,
@@ -119,8 +149,26 @@ const MainLayoutPageProvider: FC = ({ children }) => {
subInstanceConfig,
subAppContext,
subLiveTailsData,
+ maximized,
+ userSpecficStreams,
+ userSpecificAccessMap,
+ helpModalOpen,
};
+ useEffect(() => {
+ const handleEscKeyPress = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ maximized && toggleMaximize();
+ }
+ };
+
+ window.addEventListener('keydown', handleEscKeyPress);
+
+ return () => {
+ window.removeEventListener('keydown', handleEscKeyPress);
+ };
+ }, [maximized]);
+
const resetTimeInterval = useCallback(() => {
if (subLogSelectedTimeRange.get().state === 'fixed') {
const now = dayjs();
@@ -166,7 +214,20 @@ const MainLayoutPageProvider: FC = ({ children }) => {
});
}, []);
- const methods: HeaderContextMethods = { resetTimeInterval, streamChangeCleanup, setUserRoles, setSelectedStream };
+ const updateUserSpecificAccess = useCallback((accessRoles: string[] | null) => {
+ setUserSpecificMap(generateUserAcccessMap(accessRoles));
+ }, []);
+
+ const methods: HeaderContextMethods = {
+ resetTimeInterval,
+ streamChangeCleanup,
+ setUserRoles,
+ setSelectedStream,
+ toggleMaximize,
+ setUserSpecficStreams,
+ updateUserSpecificAccess,
+ toggleHelpModal,
+ };
return {children};
};
diff --git a/src/layouts/MainLayout/index.tsx b/src/layouts/MainLayout/index.tsx
index 317f9f71..52596ffb 100644
--- a/src/layouts/MainLayout/index.tsx
+++ b/src/layouts/MainLayout/index.tsx
@@ -1,22 +1,29 @@
import { PrimaryHeader } from '@/components/Header';
import Navbar from '@/components/Navbar';
-import { HEADER_HEIGHT, NAVBAR_WIDTH } from '@/constants/theme';
+import { NAVBAR_WIDTH, PRIMARY_HEADER_HEIGHT } from '@/constants/theme';
import { Box } from '@mantine/core';
import type { FC } from 'react';
import { Outlet } from 'react-router-dom';
import { heights } from '@/components/Mantine/sizing';
+import { useHeaderContext } from './Context';
const MainLayout: FC = () => {
+ const {
+ state: { maximized },
+ } = useHeaderContext();
+ const primaryHeaderHeight = !maximized ? PRIMARY_HEADER_HEIGHT : 0;
+ const navbarWidth = !maximized ? NAVBAR_WIDTH : 0;
return (
-
+
diff --git a/src/main.tsx b/src/main.tsx
index 7bd65527..dc1193ab 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -4,6 +4,7 @@ import '@mantine/notifications/styles.css';
import '@mantine/notifications/styles.css';
import '@mantine/dates/styles.css';
import '@mantine/code-highlight/styles.css';
+import '@mantine/charts/styles.css';
import './utils/dayjsLoader';
import React from 'react';
import ReactDOM from 'react-dom/client';
diff --git a/src/pages/AccessManagement/Roles.tsx b/src/pages/AccessManagement/Roles.tsx
index 2733282d..b4bce033 100644
--- a/src/pages/AccessManagement/Roles.tsx
+++ b/src/pages/AccessManagement/Roles.tsx
@@ -4,9 +4,16 @@ import { FC, useEffect, useState } from 'react';
import { useGetLogStreamList } from '@/hooks/useGetLogStreamList';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
import PrivilegeTR from './PrivilegeTR';
-import { IconPencil, IconUserPlus } from '@tabler/icons-react';
+import { IconBook2, IconPencil, IconUserPlus } from '@tabler/icons-react';
import { useRole } from '@/hooks/useRole';
import styles from './styles/AccessManagement.module.css'
+import IconButton from '@/components/Button/IconButton';
+
+const navigateToDocs = () => {
+ return window.open('https://www.parseable.io/docs/rbac', '_blank');
+}
+
+const renderDocsIcon = () =>
const Roles: FC = () => {
useDocumentTitle('Parseable | Users');
@@ -178,37 +185,32 @@ const Roles: FC = () => {
const classes = styles;
return (
-
-
+
+
Roles
-
+
}
onClick={() => {
setModalOpen(true);
- }}
- rightSection={}>
- Create role
+ }}>
+ Create Role
- {oidcActive ? (
+ {oidcActive && (
}
onClick={() => {
setDefaultRoleModalOpen(true);
- }}
- rightSection={}>
- Set Default OIDC Role
+ }}>
+ Set Default OIDC Role{' '}
- ) : (
- ''
)}
-
-
+
+
+
@@ -259,7 +261,13 @@ const Roles: FC = () => {
-
+
{
+ return window.open('https://www.parseable.io/docs/rbac', '_blank');
+}
+
+const renderDocsIcon = () =>
const Users: FC = () => {
useDocumentTitle('Parseable | Users');
@@ -32,9 +39,6 @@ const Users: FC = () => {
const [roleSearchValue, setRoleSearchValue] = useState('');
const {
- getUserData,
- getUserIsSuccess,
- getUserIsLoading,
createUserMutation,
createUserIsError,
createUserIsLoading,
@@ -51,6 +55,8 @@ const Users: FC = () => {
createUserReset,
} = useUser();
+ const { getUserData, getUserIsSuccess, getUserIsLoading, getUserRefetch } = useGetUser();
+
const { getRolesData } = useRole();
const rows =
@@ -94,7 +100,7 @@ const Users: FC = () => {
if (SelectedRole !== '') {
userRole.push(SelectedRole);
}
- createUserMutation({ userName: createUserInput, roles: userRole });
+ createUserMutation({ userName: createUserInput, roles: userRole, onSuccess: getUserRefetch });
};
const createVaildtion = () => {
@@ -111,23 +117,23 @@ const Users: FC = () => {
return false;
};
+
+
return (
-
-
-
+
+
+
Users
-
-
+
+
+
+
+
@@ -141,7 +147,13 @@ const Users: FC = () => {
{rows}
-
+
{
) : createUserData?.data ? (
Password
- {
- useDocumentTitle('Parseable | Config');
-
- const {
- state: { subLogQuery },
- } = useHeaderContext();
-
- const [streamName, setStreamName] = useMountedState(subLogQuery.get().streamName ?? '');
-
- useEffect(() => {
- const subQuery = subLogQuery.subscribe((value: any) => {
- setStreamName(value.streamName);
- });
-
- return () => {
- subQuery();
- };
- }, [subLogQuery]);
-
- const { handleCacheToggle, isCacheEnabled } = useCacheToggle(streamName);
-
- const { handleAlertQueryChange, submitAlertQuery, getLogAlertData } = useAlertsEditor(streamName);
-
- const { handleRetentionQueryChange, submitRetentionQuery, getLogRetentionData } = useRetentionEditor(streamName);
-
- // const { classes } = useConfigStyles();
- const classes = configStyles;
- const { container, submitBtn, accordionSt, innerContainer, containerWrapper, trackStyle } = classes;
-
- const switchStyles = {
- track: isCacheEnabled ? trackStyle : {},
- };
-
- return (
-
-
-
-
-
-
- Alert
-
-
-
-
-
-
-
-
-
-
-
- {!subLogQuery.get().access?.some((access: string) => ['PutRetention'].includes(access)) ? null : (
-
-
-
- Retention
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
- );
-};
-
-export default Config;
diff --git a/src/pages/Config/styles/Config.module.css b/src/pages/Config/styles/Config.module.css
deleted file mode 100644
index fa6bc5d6..00000000
--- a/src/pages/Config/styles/Config.module.css
+++ /dev/null
@@ -1,46 +0,0 @@
-.container {
- display: flex;
- flex-direction: column;
- flex: 1;
- margin: 1.25rem;
- gap: 1.25rem;
-}
-
-.trackStyle {
- background-color: #545BEB;
-}
-
-.containerWrapper {
- display: flex;
- gap: 20px;
-}
-
-.primaryBtn {
- margin-top: 1rem;
- background-color: #545BEB;
- color: #fff;
- width: max-content;
-}
-
-.submitBtn {
- margin-top: 1rem;
- background-color: #545BEB;
- color: #fff;
-}
-
-.accordionSt {
- border-radius: 60px;
- border: none;
-}
-
-.accordionSt {
- .mantine-Accordion-item {
- border: 10px #f8f9fa solid;
- }
-}
-
-.innerContainer {
- width: 50%;
- justify-content: center;
- display: flex;
-}
diff --git a/src/pages/LiveTail/LogTable.tsx b/src/pages/LiveTail/LogTable.tsx
index fb4c00b9..4a5084c2 100644
--- a/src/pages/LiveTail/LogTable.tsx
+++ b/src/pages/LiveTail/LogTable.tsx
@@ -7,12 +7,13 @@ import Column from './Column';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
import { useDoGetLiveTail } from '@/hooks/useDoGetLiveTail';
import EmptyBox from '@/components/Empty';
-import styles from './styles/Logs.module.css'
+import styles from './styles/Logs.module.css';
+import { LOGS_PRIMARY_TOOLBAR_HEIGHT, LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT } from '@/constants/theme';
const LogTable: FC = () => {
const { finalData: data, doGetLiveTail, resetData, abort, loading, schema } = useDoGetLiveTail();
const {
- state: { subInstanceConfig, subLogQuery, subLiveTailsData },
+ state: { subInstanceConfig, subLogQuery, subLiveTailsData, maximized },
} = useHeaderContext();
const [grpcPort, setGrpcPort] = useMountedState(subInstanceConfig.get()?.grpcPort ?? null);
@@ -88,10 +89,20 @@ const LogTable: FC = () => {
const { container, tableStyle, theadStyle, tableContainer, innerContainer } = classes;
+ const primaryHeaderHeight = !maximized
+ ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT
+ : 0;
+
return (
-
-
-
+
+
+
{data.length > 0 ? (
({
diff --git a/src/pages/LiveTail/styles/Logs.module.css b/src/pages/LiveTail/styles/Logs.module.css
index 89639950..bfb4d64d 100644
--- a/src/pages/LiveTail/styles/Logs.module.css
+++ b/src/pages/LiveTail/styles/Logs.module.css
@@ -161,6 +161,7 @@
flex-direction: row;
justify-content: space-between;
border-top: 0.0625rem solid rgba(0, 0, 0, 0.1);
+ align-items: center;
}
.errorContainer {
diff --git a/src/pages/Logs/AlertsModal.tsx b/src/pages/Logs/AlertsModal.tsx
new file mode 100644
index 00000000..be9b1f5e
--- /dev/null
+++ b/src/pages/Logs/AlertsModal.tsx
@@ -0,0 +1,58 @@
+import { Box, Button, Modal, Stack } from '@mantine/core';
+import { useLogsPageContext } from './logsContextProvider';
+import { Text } from '@mantine/core';
+import classes from './styles/Logs.module.css';
+import { Editor } from '@monaco-editor/react';
+
+const ModalTitle = () => {
+ return Alerts;
+};
+
+type AlertsModalProps = {
+ data: any;
+ handleChange: (value: string | undefined) => void;
+ handleSubmit: () => void;
+};
+
+const AlertsModal = (props: AlertsModalProps) => {
+ const {
+ state: { alertsModalOpen },
+ methods: { closeAlertsModal },
+ } = useLogsPageContext();
+
+ return (
+ }>
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AlertsModal;
diff --git a/src/pages/Logs/CarouselSlide.tsx b/src/pages/Logs/CarouselSlide.tsx
index b411b789..0f229c73 100644
--- a/src/pages/Logs/CarouselSlide.tsx
+++ b/src/pages/Logs/CarouselSlide.tsx
@@ -3,7 +3,7 @@ import { useHeaderContext } from '@/layouts/MainLayout/Context';
import { Box, Button, Modal, Text, Tooltip } from '@mantine/core';
import dayjs from 'dayjs';
import { useEffect } from 'react';
-import { useLogsPageContext } from './Context';
+import { useLogsPageContext } from './logsContextProvider';
import useMountedState from '@/hooks/useMountedState';
import { Carousel } from '@mantine/carousel';
import carouselStyles from './styles/CarouselSlide.module.css';
diff --git a/src/pages/Logs/Context.tsx b/src/pages/Logs/Context.tsx
deleted file mode 100644
index d120882a..00000000
--- a/src/pages/Logs/Context.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import type { Log } from '@/@types/parseable/api/query';
-import useSubscribeState, { SubData } from '@/hooks/useSubscribeState';
-import type { Dispatch, FC, SetStateAction } from 'react';
-import { ReactNode, createContext, useCallback, useContext, useMemo, useState } from 'react';
-import { LogStreamSchemaData } from '@/@types/parseable/api/stream';
-import { sanitizeCSVData } from '@/utils/exportHelpers';
-
-const Context = createContext({});
-
-const { Provider } = Context;
-
-export const LOG_QUERY_LIMITS = [30, 50, 100, 150, 200];
-export const LOAD_LIMIT = 9000;
-
-type GapTime = {
- startTime: Date;
- endTime: Date;
- id: number | null;
-};
-interface LogsPageContextState {
- subLogStreamError: SubData;
- subViewLog: SubData;
- subGapTime: SubData;
- subLogQueryData: SubData;
- subLogStreamSchema: SubData;
- subSchemaToggle: SubData;
- pageOffset: number;
- custQuerySearchState: CustQuerySearchState;
-}
-
-type LogQueryData = {
- rawData: Log[];
- filteredData: Log[];
-};
-
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-interface LogsPageContextMethods {
- makeExportData: (type: string) => Log[];
- toggleShowQueryEditor: () => void;
- resetQuerySearch: () => void;
- setPageOffset: Dispatch>;
- setCustSearchQuery: (query: string, mode: custQuerySearchMode) => void;
-}
-
-interface LogsPageContextValue {
- state: LogsPageContextState;
- methods: LogsPageContextMethods;
-}
-
-interface LogsPageProviderProps {
- children: ReactNode;
-}
-
-type custQuerySearchMode = null | 'sql' | 'filters'
-
-type CustQuerySearchState = {
- showQueryEditor: boolean;
- isQuerySearchActive: boolean;
- custSearchQuery: string;
- mode: custQuerySearchMode;
-};
-
-export const defaultQueryResult = '';
-
-const defaultCustQuerySearchState = { showQueryEditor: false, isQuerySearchActive: false, custSearchQuery: '', mode: null };
-
-const LogsPageProvider: FC = ({ children }) => {
- const subLogStreamError = useSubscribeState(null);
- const subViewLog = useSubscribeState(null);
- const subGapTime = useSubscribeState(null);
- const subLogQueryData = useSubscribeState({
- rawData: [],
- filteredData: [],
- });
- const subLogStreamSchema = useSubscribeState(null);
- const subSchemaToggle = useSubscribeState(false);
- const [pageOffset, setPageOffset] = useState(0);
- const [custQuerySearchState, setCustQuerySearchState] = useState(defaultCustQuerySearchState);
-
- // state
- const state: LogsPageContextState = {
- subLogStreamError,
- subViewLog,
- subGapTime,
- subLogQueryData,
- subLogStreamSchema,
- subSchemaToggle,
- custQuerySearchState,
- pageOffset,
- };
-
- // getters & setters
- const toggleShowQueryEditor = useCallback(() => {
- setCustQuerySearchState((prev) => ({ ...prev, showQueryEditor: !prev.showQueryEditor }));
- }, []);
-
- const resetQuerySearch = useCallback(() => {
- setCustQuerySearchState(defaultCustQuerySearchState);
- // setPageOffset(0); wont the LogTable handle this ?
- }, []);
-
- const setCustSearchQuery = useCallback((query: string, mode: custQuerySearchMode) => {
- setCustQuerySearchState((prev) => ({ ...prev, mode, custSearchQuery: query, isQuerySearchActive: true, showQueryEditor: false}));
- }, [])
-
- // handlers
- const makeExportData = useCallback((type: string): Log[] => {
- const { rawData, filteredData: _filteredData } = subLogQueryData.get(); // filteredData - records filtered with in-page search
- if (type === 'JSON') {
- return rawData;
- } else if (type === 'CSV') {
- const fields = subLogStreamSchema.get()?.fields;
- const headers = !custQuerySearchState.isQuerySearchActive
- ? Array.isArray(fields)
- ? fields.map((field) => field.name)
- : []
- : typeof rawData[0] === 'object'
- ? Object.keys(rawData[0])
- : [];
-
- const sanitizedCSVData = sanitizeCSVData(rawData, headers);
- return [headers, ...sanitizedCSVData];
- } else {
- return [];
- }
- }, [custQuerySearchState.isQuerySearchActive]);
-
- const methods = {
- makeExportData,
- toggleShowQueryEditor,
- resetQuerySearch,
- setPageOffset,
- setCustSearchQuery,
- };
-
- const value = useMemo(() => ({ state, methods }), [state, methods]);
-
- return {children};
-};
-
-export const useLogsPageContext = () => useContext(Context) as LogsPageContextValue;
-
-export default LogsPageProvider;
diff --git a/src/pages/Logs/DeleteStreamModal.tsx b/src/pages/Logs/DeleteStreamModal.tsx
new file mode 100644
index 00000000..3974d008
--- /dev/null
+++ b/src/pages/Logs/DeleteStreamModal.tsx
@@ -0,0 +1,56 @@
+import { Button, Group, Modal, TextInput } from '@mantine/core';
+import { useLogsPageContext } from './logsContextProvider';
+import styles from './styles/Logs.module.css';
+import { useCallback, useState } from 'react';
+import { useLogStream } from '@/hooks/useLogStream';
+
+const DeleteStreamModal = () => {
+ const {
+ state: { deleteModalOpen, currentStream },
+ methods: { closeDeleteModal },
+ } = useLogsPageContext();
+ const [confirmInputValue, setConfirmInputValue] = useState('');
+ const handleInputChange = useCallback((e: React.ChangeEvent) => {
+ setConfirmInputValue(e.target.value);
+ }, []);
+
+ const { deleteLogStreamMutation } = useLogStream();
+
+ const handleDeleteStream = useCallback(() => {
+ deleteLogStreamMutation({ deleteStream: currentStream });
+ }, [currentStream]);
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default DeleteStreamModal;
diff --git a/src/pages/Logs/EventTimeLineGraph.tsx b/src/pages/Logs/EventTimeLineGraph.tsx
new file mode 100644
index 00000000..7b487117
--- /dev/null
+++ b/src/pages/Logs/EventTimeLineGraph.tsx
@@ -0,0 +1,81 @@
+import { Skeleton, Stack, Text } from '@mantine/core';
+import classes from './styles/EventTimeLineGraph.module.css';
+import { useQueryResult } from '@/hooks/useQueryResult';
+import { useEffect } from 'react';
+import { useLogsPageContext } from './logsContextProvider';
+import dayjs from 'dayjs';
+import { AreaChart } from '@mantine/charts';
+import { HumanizeNumber } from '@/utils/formatBytes';
+
+const generateCountQuery = (streamName: string, startTime: string, endTime: string) => {
+ return `SELECT DATE_TRUNC('minute', p_timestamp) AS minute_range, COUNT(*) AS log_count FROM ${streamName} WHERE p_timestamp BETWEEN '${startTime}' AND '${endTime}' GROUP BY minute_range ORDER BY minute_range`;
+};
+
+const NoDataView = () => {
+ return (
+
+
+ No new events in the last 10 minutes.
+
+
+ );
+};
+
+const EventTimeLineGraph = () => {
+ const { fetchQueryMutation } = useQueryResult();
+ const {
+ state: { currentStream },
+ } = useLogsPageContext();
+ const endTime = dayjs().subtract(1, 'minute').startOf('minute');
+ const startTime = endTime.subtract(10, 'minute').startOf('minute');
+
+ useEffect(() => {
+ if (!currentStream || currentStream.length === 0) return;
+
+ const logsQuery = {
+ streamName: currentStream,
+ startTime: startTime.toDate(),
+ endTime: endTime.toDate(),
+ access: [],
+ };
+ const query = generateCountQuery(currentStream, startTime.toISOString(), endTime.toISOString());
+ fetchQueryMutation.mutate({
+ logsQuery,
+ query,
+ });
+ }, [currentStream]);
+
+ const graphData = fetchQueryMutation?.data;
+ const isLoading = fetchQueryMutation.isLoading;
+ const hasData = Array.isArray(graphData) && graphData.length !== 0;
+
+ return (
+
+
+ {hasData ? (
+ new Intl.NumberFormat('en-US').format(value)}
+ withXAxis={false}
+ withYAxis={hasData}
+ curveType="linear"
+ yAxisProps={{ tickCount: 2, tickFormatter: (value) => `${HumanizeNumber(value)}` }}
+ gridAxis="xy"
+ withGradient={false}
+ />
+ ) : (
+
+ )}
+
+
+ );
+};
+
+export default EventTimeLineGraph;
diff --git a/src/components/Header/Querier.tsx b/src/pages/Logs/FilterQueryBuilder.tsx
similarity index 75%
rename from src/components/Header/Querier.tsx
rename to src/pages/Logs/FilterQueryBuilder.tsx
index e44ba5a1..7e48573e 100644
--- a/src/components/Header/Querier.tsx
+++ b/src/pages/Logs/FilterQueryBuilder.tsx
@@ -1,25 +1,34 @@
import {
- Stack,
+ Button,
Group,
ScrollArea,
+ Stack,
Box,
ThemeIcon,
- Text,
Select,
Input,
- Button,
CloseIcon,
Pill,
ActionIcon,
- Modal,
} from '@mantine/core';
-import { useCallback } from 'react';
-import classes from './styles/QueryBuilder.module.css';
-import { useLogsPageContext } from '@/pages/Logs/Context';
+import { useLogsPageContext } from './logsContextProvider';
import { IconFilter, IconPlus } from '@tabler/icons-react';
-import { operatorLabelMap, useQueryFilterContext } from '@/providers/QueryFilterProvider';
+import classes from './styles/Querier.module.css';
+import { Text } from '@mantine/core';
+import { useQueryFilterContext, operatorLabelMap } from '@/providers/QueryFilterProvider';
+
+export const FilterPlaceholder = () => {
+ return (
+
+
+ Click to add filter
+
+ );
+};
+
+import { useCallback } from 'react';
import { noValueOperators, textFieldOperators, numberFieldOperators } from '@/providers/QueryFilterProvider';
-import { RuleTypeOverride, RuleGroupTypeOverride, QueryType, Combinator } from '@/providers/QueryFilterProvider';
+import { RuleTypeOverride, RuleGroupTypeOverride, Combinator } from '@/providers/QueryFilterProvider';
type RuleSetProps = {
ruleSet: RuleGroupTypeOverride;
@@ -81,7 +90,7 @@ const RuleView = (props: RuleViewType) => {
type={type}
disabled={isDisabled}
/>
-
+
@@ -163,10 +172,6 @@ const AddRuleGroupBtn = ({ createRuleGroup }: { createRuleGroup: () => void }) =
);
-type QueryPillProps = {
- query: QueryType;
-};
-
type RuleSetPillProps = {
ruleSet: RuleGroupTypeOverride;
};
@@ -196,9 +201,11 @@ const RuleSetPills = (props: RuleSetPillProps) => {
);
};
-const QueryPills = (props: QueryPillProps) => {
- const { query } = props;
- const { combinator, rules: ruleSets } = query;
+export const QueryPills = () => {
+ const {
+ state: { appliedQuery },
+ } = useQueryFilterContext();
+ const { combinator, rules: ruleSets } = appliedQuery;
return (
@@ -216,60 +223,35 @@ const QueryPills = (props: QueryPillProps) => {
);
};
-const FilterBtnPlaceholder = () => {
- return (
-
-
- Click to add filter
-
- );
-};
-
-const ModalTitle = () => {
- return Filters;
-};
-
-const Querier = () => {
+export const FilterQueryBuilder = () => {
const { state: queryBuilderState, methods: queryBuilderMethods } = useQueryFilterContext();
- const { isModalOpen, query, isSumbitDisabled, appliedQuery } = queryBuilderState;
- const { createRuleGroup, clearFilters, applyQuery, closeBuilderModal, openBuilderModal } = queryBuilderMethods;
+ const { query, isSumbitDisabled } = queryBuilderState;
+ const { createRuleGroup, clearFilters, applyQuery } = queryBuilderMethods;
const {
- state: { custQuerySearchState },
- methods: {},
+ state: {
+ custQuerySearchState: { isQuerySearchActive, mode },
+ },
} = useLogsPageContext();
+ const isFiltersApplied = isQuerySearchActive && mode === 'filters';
- const isFiltersApplied = custQuerySearchState.mode === 'filters' && custQuerySearchState.isQuerySearchActive;
return (
- <>
-
- {!isFiltersApplied ? : }
-
- }>
-
-
-
- {query.rules.map((ruleSet) => {
- return ;
- })}
-
-
-
+
+
+
+ {query.rules.map((ruleSet) => {
+ return ;
+ })}
+
-
-
-
-
-
- >
+
+
+
+
+
+
);
};
-
-export default Querier;
diff --git a/src/pages/Logs/HeaderPagination.tsx b/src/pages/Logs/HeaderPagination.tsx
index e1ce6e68..342387d3 100644
--- a/src/pages/Logs/HeaderPagination.tsx
+++ b/src/pages/Logs/HeaderPagination.tsx
@@ -6,7 +6,7 @@ import { Box, Button, Text, Tooltip, px } from '@mantine/core';
import dayjs from 'dayjs';
import { FC, useEffect } from 'react';
import FillCarousel from './CarouselSlide';
-import { useLogsPageContext } from './Context';
+import { useLogsPageContext } from './logsContextProvider';
import Loading from '@/components/Loading';
import { IconZoomIn, IconZoomOut } from '@tabler/icons-react';
import headerPaginationStyles from './styles/HeaderPagination.module.css';
diff --git a/src/pages/Logs/LogRow.tsx b/src/pages/Logs/LogRow.tsx
index 2f9529a9..6a35c82d 100644
--- a/src/pages/Logs/LogRow.tsx
+++ b/src/pages/Logs/LogRow.tsx
@@ -2,7 +2,7 @@ import { parseLogData } from '@/utils';
import { Box, px } from '@mantine/core';
import { IconArrowNarrowRight } from '@tabler/icons-react';
import { FC, Fragment } from 'react';
-import { useLogsPageContext } from './Context';
+import { useLogsPageContext } from './logsContextProvider';
import { Log } from '@/@types/parseable/api/query';
import tableStyles from './styles/Logs.module.css'
diff --git a/src/pages/Logs/LogTable.tsx b/src/pages/Logs/LogTable.tsx
index 05556459..16c47a2e 100644
--- a/src/pages/Logs/LogTable.tsx
+++ b/src/pages/Logs/LogTable.tsx
@@ -16,10 +16,11 @@ import {
Pagination,
Loader,
Group,
+ Stack,
} from '@mantine/core';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import type { FC } from 'react';
-import { LOG_QUERY_LIMITS, useLogsPageContext, LOAD_LIMIT as loadLimit } from './Context';
+import { LOG_QUERY_LIMITS, useLogsPageContext, LOAD_LIMIT as loadLimit, LOAD_LIMIT } from './logsContextProvider';
import LogRow from './LogRow';
import useMountedState from '@/hooks/useMountedState';
import { IconSelector, IconGripVertical, IconPin, IconPinFilled, IconSettings } from '@tabler/icons-react';
@@ -30,11 +31,13 @@ import Column from './Column';
import FilterPills from './FilterPills';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
import dayjs from 'dayjs';
-import { Log, SortOrder } from '@/@types/parseable/api/query';
+import { SortOrder } from '@/@types/parseable/api/query';
import { usePagination } from '@mantine/hooks';
import { LogStreamSchemaData } from '@/@types/parseable/api/stream';
import tableStyles from './styles/Logs.module.css';
-import { HEADER_HEIGHT } from '@/constants/theme';
+import { LOGS_PRIMARY_TOOLBAR_HEIGHT, LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT } from '@/constants/theme';
+import { useQueryResult } from '@/hooks/useQueryResult';
+import { HumanizeNumber } from '@/utils/formatBytes';
const skipFields = ['p_metadata', 'p_tags'];
@@ -47,12 +50,33 @@ const makeHeadersFromSchema = (schema: LogStreamSchemaData | null): string[] =>
}
};
-const makeHeadersfromData = (data: Log[] | null): string[] => {
- if (Array.isArray(data) && data.length > 0) {
- return typeof data[0] === 'object' ? Object.keys(data[0]) : [];
- } else {
- return [];
- }
+const makeHeadersfromData = (schema: LogStreamSchemaData | null, custSearchQuery: string | null): string[] => {
+ const allColumns = makeHeadersFromSchema(schema);
+ if (custSearchQuery === null) return allColumns;
+
+ const selectClause = custSearchQuery.match(/SELECT(.*?)FROM/i)?.[1];
+ if (!selectClause || selectClause.includes('*')) return allColumns;
+
+ const commonColumns = allColumns.filter((column) => selectClause.includes(column));
+ return commonColumns;
+};
+
+type TotalLogsCountProps = {
+ totalCount: number | null;
+ loadedCount: number | null;
+};
+
+const TotalLogsCount = (props: TotalLogsCountProps) => {
+ const { totalCount, loadedCount } = props;
+ if (typeof totalCount !== 'number' || typeof loadedCount !== 'number') return ;
+
+ return (
+
+ {`Showing ${loadedCount < LOAD_LIMIT ? loadedCount : LOAD_LIMIT} out of ${HumanizeNumber(
+ totalCount,
+ )} records`}
+
+ );
};
const LogTable: FC = () => {
@@ -66,7 +90,7 @@ const LogTable: FC = () => {
methods: { setPageOffset, resetQuerySearch },
} = useLogsPageContext();
const {
- state: { subLogSearch, subLogQuery, subRefreshInterval, subLogSelectedTimeRange },
+ state: { subLogSearch, subLogQuery, subRefreshInterval, subLogSelectedTimeRange, maximized },
} = useHeaderContext();
const [refreshInterval, setRefreshInterval] = useMountedState(null);
const [logStreamError, setLogStreamError] = useMountedState(null);
@@ -95,15 +119,38 @@ const LogTable: FC = () => {
sort,
} = useQueryLogs();
- const tableHeaders = isQuerySearchActive ? makeHeadersfromData(logs) : makeHeadersFromSchema(logsSchema);
+ const tableHeaders = isQuerySearchActive
+ ? makeHeadersfromData(logsSchema, custSearchQuery)
+ : makeHeadersFromSchema(logsSchema);
const appliedFilter = (key: string) => {
return subLogSearch.get().filters[key] ?? [];
};
const currentStreamName = subLogQuery.get().streamName;
+ const { fetchQueryMutation } = useQueryResult();
+ const fetchCount = useCallback(() => {
+ const queryContext = subLogQuery.get();
+ const defaultQuery = `select count(*) as count from ${currentStreamName}`;
+ const query = isQuerySearchActive
+ ? custSearchQuery.replace(/SELECT[\s\S]*?FROM/i, 'SELECT COUNT(*) as count FROM')
+ : defaultQuery;
+ if (queryContext && query?.length > 0) {
+ const logsQuery = {
+ streamName: queryContext.streamName,
+ startTime: queryContext.startTime,
+ endTime: queryContext.endTime,
+ access: [],
+ };
+ fetchQueryMutation.mutate({
+ logsQuery,
+ query,
+ });
+ }
+ }, [currentStreamName, isQuerySearchActive, custSearchQuery]);
+
useEffect(() => {
resetQuerySearch();
- }, [currentStreamName])
+ }, [currentStreamName]);
const applyFilter = (key: string, value: string[]) => {
subLogSearch.set((state) => {
@@ -206,6 +253,12 @@ const LogTable: FC = () => {
}
}, [custSearchQuery]);
+ useEffect(() => {
+ if (pageOffset === 0 && subLogQuery.get()) {
+ fetchCount();
+ }
+ }, [currentStreamName, isQuerySearchActive, custSearchQuery]);
+
useEffect(() => {
const streamErrorListener = subLogStreamError.subscribe(setLogStreamError);
const logSearchListener = subLogSearch.subscribe(setQuerySearch);
@@ -340,19 +393,25 @@ const LogTable: FC = () => {
}
}, [pinnedContianerRef, pinnedColumns]);
+ const primaryHeaderHeight = !maximized
+ ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT
+ : 0;
+
+ const totalCount = Array.isArray(fetchQueryMutation?.data) ? fetchQueryMutation.data[0]?.count : null;
+ const loadedCount = pageLogData?.data.length || null;
return (
{!(logStreamError || logStreamSchemaError || logsError) ? (
Boolean(tableHeaders.length) && Boolean(pageLogData?.data.length) ? (
-
+
+ style={{ display: 'flex', flexDirection: 'row', maxHeight: `calc(100vh - ${primaryHeaderHeight}px )` }}>
{
)}
-
+
{!loading && !logsLoading ? (
;
+const renderSettingsIcon = () => ;
+const renderLiveTailIcon = () => ;
+const renderDeleteIcon = () => ;
+
+const PrimaryToolbar = () => {
+ const {
+ methods: { openDeleteModal, openAlertsModal, openRetentionModal, toggleLiveTail },
+ state: { liveTailToggled },
+ } = useLogsPageContext();
+ const {
+ state: { userSpecificAccessMap },
+ } = useHeaderContext();
+ const isSecureConnection = window.location.protocol === 'https:';
+ return (
+
+
+
+
+ {!isSecureConnection && (
+
+ )}
+ {userSpecificAccessMap.hasUpdateAlertAccess && (
+
+ )}
+
+ {userSpecificAccessMap.hasDeleteAccess && (
+
+ )}
+
+
+ );
+};
+
+export default PrimaryToolbar;
diff --git a/src/pages/Logs/Querier.tsx b/src/pages/Logs/Querier.tsx
new file mode 100644
index 00000000..4514227a
--- /dev/null
+++ b/src/pages/Logs/Querier.tsx
@@ -0,0 +1,107 @@
+import { Group, Menu, Modal, Stack, px } from '@mantine/core';
+import { useLogsPageContext } from './logsContextProvider';
+import { ToggleButton } from '@/components/Button/ToggleButton';
+import { IconChevronDown, IconCodeCircle, IconFilter } from '@tabler/icons-react';
+import classes from './styles/Querier.module.css';
+import { Text } from '@mantine/core';
+import { FilterQueryBuilder, QueryPills } from './FilterQueryBuilder';
+import { AppliedSQLQuery } from './QueryEditor';
+import QueryCodeEditor from './QueryCodeEditor';
+
+const getLabel = (mode: string | null) => {
+ return mode === 'filters' ? 'Filters' : mode === 'sql' ? 'SQL' : '';
+};
+
+const FilterPlaceholder = () => {
+ return (
+
+
+ Click to add filter
+
+ );
+};
+
+const SQLEditorPlaceholder = () => {
+ return (
+
+
+ Click to write query
+
+ );
+};
+
+const ModalTitle = ({ title }: { title: string }) => {
+ return {title};
+};
+
+const QuerierModal = () => {
+ const {
+ methods: { toggleBuilderModal },
+ state: {
+ custQuerySearchState: { viewMode },
+ builderModalOpen,
+ },
+ } = useLogsPageContext();
+
+ return (
+ }>
+
+ {viewMode === 'filters' ? : }
+
+
+ );
+};
+
+const Querier = () => {
+ const {
+ methods: { toggleCustQuerySearchMode, toggleBuilderModal },
+ state: {
+ custQuerySearchState: { isQuerySearchActive, mode, viewMode },
+ },
+ } = useLogsPageContext();
+ const isFiltersApplied = mode === 'filters' && isQuerySearchActive;
+ const isSqlSearchActive = mode === 'sql' && isQuerySearchActive;
+ return (
+
+
+
+
+ {viewMode === 'filters' && (isFiltersApplied ? : )}
+ {viewMode === 'sql' && (isSqlSearchActive ? : )}
+
+
+ );
+};
+
+export default Querier;
diff --git a/src/pages/Logs/QueryCodeEditor.tsx b/src/pages/Logs/QueryCodeEditor.tsx
index 518e7b5d..ed841e42 100644
--- a/src/pages/Logs/QueryCodeEditor.tsx
+++ b/src/pages/Logs/QueryCodeEditor.tsx
@@ -1,20 +1,15 @@
-import React, { FC, MutableRefObject, useCallback, useEffect } from 'react';
+import React, { FC, useCallback, useEffect } from 'react';
import Editor from '@monaco-editor/react';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
-import { Box, Button, Flex, Text, TextInput, Tooltip, px } from '@mantine/core';
+import { Box, Button, Flex, ScrollArea, Stack, Text, TextInput } from '@mantine/core';
import { ErrorMarker, errChecker } from './ErrorMarker';
-import { IconPlayerPlayFilled, IconRotate } from '@tabler/icons-react';
import useMountedState from '@/hooks/useMountedState';
import { notify } from '@/utils/notification';
import { usePostLLM } from '@/hooks/usePostLLM';
import { sanitiseSqlString } from '@/utils/sanitiseSqlString';
-import { LOAD_LIMIT, useLogsPageContext } from '../Logs/Context';
+import { LOAD_LIMIT, useLogsPageContext } from './logsContextProvider';
import { Field } from '@/@types/parseable/dataType';
-import queryCodeStyles from './styles/QueryCode.module.css'
-
-type QueryCodeEditorProps = {
- inputRef: MutableRefObject;
-};
+import queryCodeStyles from './styles/QueryCode.module.css';
const genColumnConfig = (fields: Field[]) => {
const columnConfig = { leftColumns: [], rightColumns: [] };
@@ -33,16 +28,17 @@ const genColumnConfig = (fields: Field[]) => {
}, columnConfig);
};
-const QueryCodeEditor: FC = (props) => {
+const QueryCodeEditor: FC = () => {
const {
state: { subLogQuery, subInstanceConfig },
} = useHeaderContext();
const {
state: {
- custQuerySearchState: { isQuerySearchActive },
+ custQuerySearchState: { isQuerySearchActive, mode },
subLogStreamSchema,
+ queryCodeEditorRef,
},
- methods: { resetQuerySearch, setCustSearchQuery },
+ methods: { resetQuerySearch, setCustSearchQuery, closeBuilderModal },
} = useLogsPageContext();
const fields = subLogStreamSchema.get()?.fields || [];
@@ -55,11 +51,12 @@ const QueryCodeEditor: FC = (props) => {
const { data: resAIQuery, postLLMQuery } = usePostLLM();
const currentStreamName = subLogQuery.get().streamName;
const isLlmActive = !!subInstanceConfig.get()?.llmActive;
+ const isSqlSearchActive = isQuerySearchActive && mode === 'sql';
const updateQuery = useCallback((query: string) => {
- props.inputRef.current = query;
- setQuery(query)
- }, [])
+ queryCodeEditorRef.current = query;
+ setQuery(query);
+ }, []);
const handleAIGenerate = useCallback(() => {
if (!aiQuery?.length) {
@@ -73,7 +70,7 @@ const QueryCodeEditor: FC = (props) => {
if (resAIQuery) {
const warningMsg =
'-- LLM generated query is experimental and may produce incorrect answers\n-- Always verify the generated SQL before executing\n\n';
- updateQuery(warningMsg + resAIQuery);
+ updateQuery(warningMsg + resAIQuery);
}
}, [resAIQuery]);
@@ -86,21 +83,21 @@ const QueryCodeEditor: FC = (props) => {
useEffect(() => {
if (currentStreamName !== localStreamName) {
setlocalStreamName(currentStreamName);
- const query = `SELECT * FROM ${currentStreamName} LIMIT ${LOAD_LIMIT}; `
+ const query = `SELECT * FROM ${currentStreamName} LIMIT ${LOAD_LIMIT}; `;
updateQuery(query);
}
setlocalLlmActive(isLlmActive);
}, [currentStreamName, isLlmActive]);
useEffect(() => {
- updateQuery(props.inputRef.current);
+ updateQuery(queryCodeEditorRef.current);
}, []);
function handleEditorDidMount(editor: any, monaco: any) {
editorRef.current = editor;
monacoRef.current = monaco;
editor.addCommand(monaco.KeyMod.CtrlCmd + monaco.KeyCode.Enter, async () => {
- runQuery(props.inputRef.current);
+ runQuery(queryCodeEditorRef.current);
});
}
@@ -108,104 +105,63 @@ const QueryCodeEditor: FC = (props) => {
const query = sanitiseSqlString(inputQuery);
const parsedQuery = query.replace(/(\r\n|\n|\r)/gm, '');
setCustSearchQuery(parsedQuery, 'sql');
+ closeBuilderModal();
};
- const classes = queryCodeStyles;
- const { container, runQueryBtn, textContext, clearQueryBtn } = classes;
-
return (
-
-
- Search Query
-
-
- }>
- Reset
-
-
-
-
-
-
-
-
- {localLlmActive ? (
- setAiQuery(e.target.value)}
- placeholder="Enter plain text to generate SQL query using OpenAI"
- rightSectionWidth={'auto'}
- style={{
- '& .mantine-Input-input': {
- border: 'none',
- borderRadius: 0,
- backgroundColor: 'rgba(84,91,235,.2)',
- '::placeholder': {},
- },
- '& .mantine-TextInput-rightSection ': {
- height: '100%',
- },
- }}
- rightSection={
-
-
+ )}
+
-
-
-
-
-
+
+
+
+
+
+
+ Clear
+
+ runQuery(query)}>Apply
+
+
);
};
@@ -225,12 +181,20 @@ const SchemaList = (props: { currentStreamName: string; fields: Field[] }) => {
{leftColumns.map((config, index) => {
- return {`${config}\n\n`};
+ return (
+ {`${config}\n\n`}
+ );
})}
{rightColumns.map((config, index) => {
- return {`${config}\n\n`};
+ return (
+ {`${config}\n\n`}
+ );
})}
diff --git a/src/pages/Logs/QueryEditor.tsx b/src/pages/Logs/QueryEditor.tsx
index bc5d7afb..4852604f 100644
--- a/src/pages/Logs/QueryEditor.tsx
+++ b/src/pages/Logs/QueryEditor.tsx
@@ -1,41 +1,18 @@
-import { Box, Drawer } from '@mantine/core';
-import type { FC } from 'react';
-import { LOAD_LIMIT, useLogsPageContext } from './Context';
-import React, { useEffect } from 'react';
-import { useHeaderContext } from '@/layouts/MainLayout/Context';
-import QueryCodeEditor from './QueryCodeEditor';
-import viewLogStyles from './styles/ViewLogs.module.css'
+import { useLogsPageContext } from './logsContextProvider';
+import { CodeHighlight } from '@mantine/code-highlight';
-const QueryEditor: FC = () => {
+export const AppliedSQLQuery = () => {
const {
state: {
- custQuerySearchState: { showQueryEditor },
+ custQuerySearchState: { custSearchQuery },
},
- methods: { toggleShowQueryEditor },
} = useLogsPageContext();
- const {
- state: { subLogQuery },
- } = useHeaderContext();
- const onClose = () => toggleShowQueryEditor();
- const classes = viewLogStyles;
- const inputRef = React.useRef(); // to store input value even after the editor unmounts
- const currentStreamName = subLogQuery.get().streamName;
- useEffect(() => {
- if (currentStreamName) {
- const defaultSearchQuery = `SELECT * FROM ${currentStreamName} LIMIT ${LOAD_LIMIT};`;
- inputRef.current = defaultSearchQuery;
- } else {
- inputRef.current = '';
- }
- }, [currentStreamName]);
-
return (
-
-
-
-
-
+
);
};
-
-export default QueryEditor;
diff --git a/src/pages/Logs/RetentionModal.tsx b/src/pages/Logs/RetentionModal.tsx
new file mode 100644
index 00000000..2576d79c
--- /dev/null
+++ b/src/pages/Logs/RetentionModal.tsx
@@ -0,0 +1,75 @@
+import { Box, Button, Modal, Stack, Switch } from '@mantine/core';
+import { useLogsPageContext } from './logsContextProvider';
+import { Text } from '@mantine/core';
+import classes from './styles/Logs.module.css';
+import { Editor } from '@monaco-editor/react';
+
+const ModalTitle = () => {
+ return Settings;
+};
+
+type RetentionModalProps = {
+ data: any;
+ handleChange: (value: string | undefined) => void;
+ handleSubmit: () => void;
+ handleCacheToggle: () => void;
+ isCacheEnabled: boolean;
+};
+
+const RententionModal = (props: RetentionModalProps) => {
+ const {
+ state: { retentionModalOpen },
+ methods: { closeRetentionModal },
+ } = useLogsPageContext();
+ const { isCacheEnabled, handleCacheToggle } = props;
+ const switchStyles = {
+ track: isCacheEnabled ? classes.trackStyle : {},
+ };
+
+ return (
+ }>
+
+
+ Allow Cache
+
+
+ Retention
+
+
+
+
+
+ Submit
+
+
+
+
+ );
+};
+
+export default RententionModal;
diff --git a/src/pages/Logs/SecondaryToolbar.tsx b/src/pages/Logs/SecondaryToolbar.tsx
new file mode 100644
index 00000000..7a8edee5
--- /dev/null
+++ b/src/pages/Logs/SecondaryToolbar.tsx
@@ -0,0 +1,72 @@
+import { Menu, Stack, px } from '@mantine/core';
+import IconButton from '@/components/Button/IconButton';
+import { useLogsPageContext } from './logsContextProvider';
+import { useHeaderContext } from '@/layouts/MainLayout/Context';
+import { downloadDataAsCSV, downloadDataAsJson } from '@/utils/exportHelpers';
+import classes from './styles/Toolbar.module.css';
+import { IconDownload, IconMaximize } from '@tabler/icons-react';
+import { LOGS_SECONDARY_TOOLBAR_HEIGHT } from '@/constants/theme';
+import TimeRange from '@/components/Header/TimeRange';
+import RefreshInterval from '@/components/Header/RefreshInterval';
+import RefreshNow from '@/components/Header/RefreshNow';
+import StreamingButton from '@/components/Header/StreamingButton';
+import Querier from './Querier';
+
+const renderExportIcon = () => ;
+const renderMaximizeIcon = () => ;
+
+const SecondaryToolbar = () => {
+ const {
+ methods: { makeExportData },
+ state: { liveTailToggled },
+ } = useLogsPageContext();
+ const {
+ state: { subLogQuery },
+ methods: { resetTimeInterval, toggleMaximize },
+ } = useHeaderContext();
+ const exportHandler = (fileType: string | null) => {
+ const query = subLogQuery.get();
+ const filename = `${query.streamName}-logs`;
+ if (fileType === 'CSV') {
+ downloadDataAsCSV(makeExportData('CSV'), filename);
+ } else if (fileType === 'JSON') {
+ downloadDataAsJson(makeExportData('JSON'), filename);
+ }
+ };
+ return (
+
+ {!liveTailToggled && (
+
+
+
+
+
+
+
+
+ )}
+ {liveTailToggled && (
+
+
+
+
+ )}
+
+ );
+};
+
+export default SecondaryToolbar;
diff --git a/src/pages/Logs/ViewLog.tsx b/src/pages/Logs/ViewLog.tsx
index 62475d3d..aa9a7d07 100644
--- a/src/pages/Logs/ViewLog.tsx
+++ b/src/pages/Logs/ViewLog.tsx
@@ -2,7 +2,7 @@ import useMountedState from '@/hooks/useMountedState';
import { Box, Chip, CloseButton, Divider, Drawer, Text, px } from '@mantine/core';
import type { FC } from 'react';
import { useEffect, Fragment, useMemo } from 'react';
-import { useLogsPageContext } from './Context';
+import { useLogsPageContext } from './logsContextProvider';
import dayjs from 'dayjs';
import viewLogStyles from './styles/ViewLogs.module.css'
import { CodeHighlight } from '@mantine/code-highlight';
diff --git a/src/pages/Logs/index.tsx b/src/pages/Logs/index.tsx
index aa7d804c..9cef4972 100644
--- a/src/pages/Logs/index.tsx
+++ b/src/pages/Logs/index.tsx
@@ -1,20 +1,53 @@
import { Box } from '@mantine/core';
import { useDocumentTitle } from '@mantine/hooks';
import { FC } from 'react';
-import LogTable from './LogTable';
+import StaticLogTable from './LogTable';
+import LiveLogTable from '../LiveTail/LogTable';
import ViewLog from './ViewLog';
-import QueryEditor from './QueryEditor';
-// import HeaderPagination from './HeaderPagination';
+import DeleteStreamModal from './DeleteStreamModal';
+import AlertsModal from './AlertsModal';
+import RententionModal from './RetentionModal';
+import { useLogsPageContext } from './logsContextProvider';
+import PrimaryToolbar from './PrimaryToolbar';
+import SecondaryToolbar from './SecondaryToolbar';
+import { useHeaderContext } from '@/layouts/MainLayout/Context';
+import { useAlertsEditor } from '@/hooks/useAlertsEditor';
+import { useRetentionEditor } from '@/hooks/useRetentionEditor';
+import { useCacheToggle } from '@/hooks/useCacheToggle';
const Logs: FC = () => {
useDocumentTitle('Parseable | Logs');
+ const {
+ state: { maximized },
+ } = useHeaderContext();
+ const {
+ state: { liveTailToggled, currentStream },
+ } = useLogsPageContext();
+
+ const { handleAlertQueryChange, submitAlertQuery, getLogAlertData } = useAlertsEditor(currentStream);
+ const { handleRetentionQueryChange, submitRetentionQuery, getLogRetentionData } = useRetentionEditor(currentStream);
+ const { handleCacheToggle, isCacheEnabled } = useCacheToggle(currentStream);
return (
- {/* */}
-
+
+
+
+ {!maximized && (
+ <>
+
+
+ >
+ )}
+ {liveTailToggled ? : }
+ {/* TODO: need to move the live logtable into the Logs folder */}
-
);
};
diff --git a/src/pages/Logs/logsContextProvider.tsx b/src/pages/Logs/logsContextProvider.tsx
new file mode 100644
index 00000000..6ed456b6
--- /dev/null
+++ b/src/pages/Logs/logsContextProvider.tsx
@@ -0,0 +1,264 @@
+import type { Log } from '@/@types/parseable/api/query';
+import useSubscribeState, { SubData } from '@/hooks/useSubscribeState';
+import type { Dispatch, FC, MutableRefObject, SetStateAction } from 'react';
+import React, { ReactNode, createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
+import { LogStreamSchemaData } from '@/@types/parseable/api/stream';
+import { sanitizeCSVData } from '@/utils/exportHelpers';
+import { useHeaderContext } from '@/layouts/MainLayout/Context';
+import { useDisclosure } from '@mantine/hooks';
+
+const Context = createContext({});
+
+const { Provider } = Context;
+
+export const LOG_QUERY_LIMITS = [30, 50, 100, 150, 200];
+export const LOAD_LIMIT = 9000;
+
+type GapTime = {
+ startTime: Date;
+ endTime: Date;
+ id: number | null;
+};
+interface LogsPageContextState {
+ subLogStreamError: SubData;
+ subViewLog: SubData;
+ subGapTime: SubData;
+ subLogQueryData: SubData;
+ subLogStreamSchema: SubData;
+ subSchemaToggle: SubData;
+ pageOffset: number;
+ custQuerySearchState: CustQuerySearchState;
+ deleteModalOpen: boolean;
+ currentStream: string;
+ alertsModalOpen: boolean;
+ retentionModalOpen: boolean;
+ maximized: boolean;
+ liveTailToggled: boolean;
+ builderModalOpen: boolean;
+ queryCodeEditorRef: MutableRefObject;
+}
+
+type LogQueryData = {
+ rawData: Log[];
+ filteredData: Log[];
+};
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+interface LogsPageContextMethods {
+ makeExportData: (type: string) => Log[];
+ toggleShowQueryEditor: () => void;
+ resetQuerySearch: () => void;
+ setPageOffset: Dispatch>;
+ setCustSearchQuery: (query: string, mode: custQuerySearchMode) => void;
+ closeRetentionModal: () => void;
+ openDeleteModal: () => void;
+ openAlertsModal: () => void;
+ openRetentionModal: () => void;
+ toggleLiveTail: () => void;
+ closeAlertsModal: () => void;
+ toggleBuilderModal: () => void;
+ toggleCustQuerySearchMode: (mode: custQuerySearchMode) => void;
+ closeBuilderModal: () => void;
+ closeDeleteModal: () => void;
+}
+
+interface LogsPageContextValue {
+ state: LogsPageContextState;
+ methods: LogsPageContextMethods;
+}
+
+interface LogsPageProviderProps {
+ children: ReactNode;
+}
+
+type custQuerySearchMode = 'sql' | 'filters';
+
+type CustQuerySearchState = {
+ showQueryEditor: boolean;
+ isQuerySearchActive: boolean;
+ custSearchQuery: string;
+ mode: string;
+ viewMode: string;
+};
+
+export const defaultQueryResult = '';
+
+const defaultCustQuerySearchState = {
+ showQueryEditor: false,
+ isQuerySearchActive: false,
+ custSearchQuery: '',
+ mode: 'filters',
+ viewMode: 'filters',
+};
+
+const defaultCustSQLQuery = (streamName: string) => {
+ if (streamName && streamName.length > 0) {
+ return `SELECT * FROM ${streamName} LIMIT ${LOAD_LIMIT};`
+ } else {
+ return ''
+ }
+}
+
+const LogsPageProvider: FC = ({ children }) => {
+ const {
+ state: { subLogQuery },
+ } = useHeaderContext();
+ const subLogStreamError = useSubscribeState(null);
+ const subViewLog = useSubscribeState(null);
+ const subGapTime = useSubscribeState(null);
+ const subLogQueryData = useSubscribeState({
+ rawData: [],
+ filteredData: [],
+ });
+ const subLogStreamSchema = useSubscribeState(null);
+ const subSchemaToggle = useSubscribeState(false);
+ const [pageOffset, setPageOffset] = useState(0);
+ const [custQuerySearchState, setCustQuerySearchState] = useState(defaultCustQuerySearchState);
+ const [currentStream, setCurrentStream] = useState(subLogQuery.get().streamName);
+ const [deleteModalOpen, setDeleteModalOpen] = useState(false);
+ const [alertsModalOpen, setAlertsModalOpen] = useState(false);
+ const [retentionModalOpen, setRetentionModalOpen] = useState(false);
+
+ const [maximized, { toggle: toggleMaximize }] = useDisclosure(false);
+ const [liveTailToggled, { toggle: toggleLiveTail }] = useDisclosure(false);
+ const [builderModalOpen, { toggle: toggleBuilderModal, close: closeBuilderModal }] = useDisclosure(false);
+ const queryCodeEditorRef = React.useRef(defaultCustSQLQuery(subLogQuery.get().streamName)); // to store input value even after the editor unmounts
+
+ // TODO: rm this after context refactor
+ useEffect(() => {
+ const streamlistener = subLogQuery.subscribe((state) => {
+ if (state.streamName) {
+ setCurrentStream(state.streamName);
+ const defaultSearchQuery = `SELECT * FROM ${state.streamName} LIMIT ${LOAD_LIMIT};`;
+ queryCodeEditorRef.current = defaultSearchQuery;
+ } else {
+ queryCodeEditorRef.current = '';
+ }
+ });
+
+ return () => {
+ streamlistener();
+ };
+ }, [subLogQuery]);
+
+ // state
+ const state: LogsPageContextState = {
+ subLogStreamError,
+ subViewLog,
+ subGapTime,
+ subLogQueryData,
+ subLogStreamSchema,
+ subSchemaToggle,
+ custQuerySearchState,
+ pageOffset,
+ deleteModalOpen,
+ currentStream,
+ alertsModalOpen,
+ retentionModalOpen,
+ maximized,
+ liveTailToggled,
+ builderModalOpen,
+ queryCodeEditorRef
+ };
+
+ // getters & setters
+ const toggleShowQueryEditor = useCallback(() => {
+ setCustQuerySearchState((prev) => ({ ...prev, showQueryEditor: !prev.showQueryEditor }));
+ }, []);
+
+ const resetQuerySearch = useCallback(() => {
+ closeBuilderModal();
+ setCustQuerySearchState((prev) => ({ ...defaultCustQuerySearchState, viewMode: prev.viewMode }));
+ // setPageOffset(0); wont the LogTable handle this ?
+ }, []);
+
+ const setCustSearchQuery = useCallback((query: string, mode: custQuerySearchMode) => {
+ setCustQuerySearchState((prev) => ({
+ ...prev,
+ mode,
+ custSearchQuery: query,
+ isQuerySearchActive: true,
+ showQueryEditor: false,
+ }));
+ }, []);
+
+ const toggleCustQuerySearchMode = useCallback((viewMode: custQuerySearchMode) => {
+ setCustQuerySearchState((prev) => ({ ...prev, viewMode }));
+ }, []);
+
+ const closeDeleteModal = useCallback(() => {
+ return setDeleteModalOpen(false);
+ }, []);
+
+ const openDeleteModal = useCallback(() => {
+ return setDeleteModalOpen(true);
+ }, []);
+
+ const closeAlertsModal = useCallback(() => {
+ return setAlertsModalOpen(false);
+ }, []);
+
+ const openAlertsModal = useCallback(() => {
+ return setAlertsModalOpen(true);
+ }, []);
+
+ const closeRetentionModal = useCallback(() => {
+ return setRetentionModalOpen(false);
+ }, []);
+
+ const openRetentionModal = useCallback(() => {
+ return setRetentionModalOpen(true);
+ }, []);
+
+ // handlers
+ const makeExportData = useCallback(
+ (type: string): Log[] => {
+ const { rawData, filteredData: _filteredData } = subLogQueryData.get(); // filteredData - records filtered with in-page search
+ if (type === 'JSON') {
+ return rawData;
+ } else if (type === 'CSV') {
+ const fields = subLogStreamSchema.get()?.fields;
+ const headers = !custQuerySearchState.isQuerySearchActive
+ ? Array.isArray(fields)
+ ? fields.map((field) => field.name)
+ : []
+ : typeof rawData[0] === 'object'
+ ? Object.keys(rawData[0])
+ : [];
+
+ const sanitizedCSVData = sanitizeCSVData(rawData, headers);
+ return [headers, ...sanitizedCSVData];
+ } else {
+ return [];
+ }
+ },
+ [custQuerySearchState.isQuerySearchActive],
+ );
+
+ const methods = {
+ makeExportData,
+ toggleShowQueryEditor,
+ resetQuerySearch,
+ setPageOffset,
+ setCustSearchQuery,
+ closeDeleteModal,
+ openDeleteModal,
+ openAlertsModal,
+ closeAlertsModal,
+ openRetentionModal,
+ closeRetentionModal,
+ toggleMaximize,
+ toggleLiveTail,
+ toggleCustQuerySearchMode,
+ toggleBuilderModal,
+ closeBuilderModal,
+ };
+
+ const value = useMemo(() => ({ state, methods }), [state, methods]);
+
+ return {children};
+};
+
+export const useLogsPageContext = () => useContext(Context) as LogsPageContextValue;
+
+export default LogsPageProvider;
diff --git a/src/pages/Logs/styles/EventTimeLineGraph.module.css b/src/pages/Logs/styles/EventTimeLineGraph.module.css
new file mode 100644
index 00000000..1b826222
--- /dev/null
+++ b/src/pages/Logs/styles/EventTimeLineGraph.module.css
@@ -0,0 +1,19 @@
+.graphContainer {
+ width: 100%;
+ height: 100%;
+ margin-left: -2.8rem;
+}
+
+.noDataContainer {
+ width: 98%;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+ border: 1px dashed var(--mantine-color-gray-4);
+}
+
+.noDataText {
+ text-align: center;
+ color: var(--mantine-color-gray-6);
+ font-size: var(--mantine-font-size-sm);
+}
\ No newline at end of file
diff --git a/src/pages/Logs/styles/Logs.module.css b/src/pages/Logs/styles/Logs.module.css
index 13b077d0..7eaf42e7 100644
--- a/src/pages/Logs/styles/Logs.module.css
+++ b/src/pages/Logs/styles/Logs.module.css
@@ -206,4 +206,8 @@
&:hover {
background: #E0E0E0;
}
+}
+
+.trackStyle {
+ background-color: #545BEB;
}
\ No newline at end of file
diff --git a/src/pages/Logs/styles/Querier.module.css b/src/pages/Logs/styles/Querier.module.css
new file mode 100644
index 00000000..f4ecbfc0
--- /dev/null
+++ b/src/pages/Logs/styles/Querier.module.css
@@ -0,0 +1,182 @@
+.filterContainer {
+ padding-left: 0.6rem;
+ background-color: white;
+ color: #211F1F;
+ cursor: pointer;
+ flex: 1;
+ flex-direction: row;
+ overflow-y: hidden;
+ align-items: center;
+}
+
+.container {
+ background-color: white;
+ color: #211F1F;
+ border: 1px var(--mantine-color-gray-3 ) solid;
+ border-radius: rem(8px);
+ cursor: pointer;
+ flex: 1;
+ overflow-y: hidden;
+ flex-direction: row;
+ margin-right: 0.675rem;
+}
+
+.modeContainer {
+ width: 5rem;
+ height: 100%;
+ text-align: center;
+ align-items: center;
+ justify-content: center;
+ border-right: 1px solid var(--mantine-color-gray-4);
+}
+
+.modeLabel {
+ /* color: white; */
+ font-weight: 600;
+}
+
+.modeButton {
+ border-top-right-radius: 0px !important;
+ border-bottom-right-radius: 0px !important;
+ width: 6rem;
+ border-top: none !important;
+ border-left: none !important;
+ border-bottom: none !important;
+ margin: 0px !important;
+}
+
+.placeholderText {
+ color: var(--mantine-color-gray-5);
+ font-size: 1rem;
+ font-weight: 500;
+}
+
+.addRuleContainer {
+ border: rem(2px) solid var(--mantine-color-gray-3);
+ height: 6rem;
+ width: 100%;
+ border-style: dashed;
+ border-spacing: 400px;
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+ border-radius: rem(6px);
+ color: var(--mantine-primary-color-3);
+ font-weight: 400;
+}
+
+.ruleSet {
+ border-radius: rem(6px);
+ padding: 1rem;
+ width: 100%;
+ background-color: var(--mantine-color-gray-1);
+}
+
+.toggleBtnContainer {
+ flex-direction: row;
+ border: 1px solid black;
+ width: fit-content;
+ border-radius: rem(6px);
+ background-color: white;
+ border-color: transparent;
+ display: flex;
+ flex-direction: row;
+ cursor: pointer;
+ align-self: flex-end;
+}
+
+.toggleBtnText {
+ padding: 0.2rem 0.6rem;
+ border-radius: rem(6px);
+ font-weight: 500;
+ font-size: small;
+ &.toggleBtnActive {
+ background-color: var(--mantine-primary-color-4) !important;
+ color: white;
+ }
+}
+
+.ruleContainer {
+ flex-direction: row;
+ width: 100%;
+ align-items: center;
+}
+
+.deleteRulebtn {
+ cursor: pointer;
+}
+
+.addConditionBtn {
+ width: fit-content;
+ font-size: small;
+}
+
+.parentCombinatorToggleContainer {
+ width: fit-content;
+ background-color: var(--mantine-color-gray-2);
+ border-radius: rem(6px);
+ position: 'relative';
+ margin-left: 16px;
+ border: 1px solid var(--mantine-color-gray-4);
+ padding: 0.2rem;
+}
+
+.ruleSetConnector {
+ width: 0;
+ height: 100%;
+ margin-left: 60px;
+ border: 1px solid var(--mantine-color-gray-4);
+}
+
+.modalHeader {
+ font-weight: 500;
+ font-size: large;
+ margin-bottom: 1rem;
+}
+
+.queryBuilderBtn {
+ background-color: white;
+ padding: 4px 12px;
+ color: #211F1F;
+ border: 1px var(--mantine-color-gray-4) solid;
+ border-radius: rem(8px);
+ cursor: pointer;
+ height: 2.2rem;
+ overflow: auto;
+ flex: 1;
+ margin-right: 0.625rem;
+}
+
+.parentCombinatorPill {
+ color: white;
+ background: var(--mantine-color-teal-4);
+ text-transform: uppercase;
+ font-weight: 700;
+}
+
+.childCombinatorPill {
+ color: white;
+ background: var(--mantine-color-indigo-4);
+ text-transform: uppercase;
+ font-weight: 700;
+}
+
+.footer {
+ flex-direction: row;
+ justify-content: flex-end;
+ padding: 1.5rem;
+ padding-right: 0rem;
+ padding-top: 0.75rem;
+}
+
+.queryBuilderContainer {
+ border-top-left-radius: rem(6px);
+ border-top-right-radius: rem(6px);
+ /* border-bottom: 1px solid var(--mantine-color-gray-3); */
+}
+
+.queryBuilderBtnPlaceholder {
+ color: var(--mantine-color-gray-5);
+ font-size: 1rem;
+ font-weight: 500;
+}
diff --git a/src/pages/Logs/styles/QueryCode.module.css b/src/pages/Logs/styles/QueryCode.module.css
index e7c52353..2edc53b9 100644
--- a/src/pages/Logs/styles/QueryCode.module.css
+++ b/src/pages/Logs/styles/QueryCode.module.css
@@ -54,3 +54,11 @@
font-size: 1rem;
font-weight: 600;
}
+
+.footer {
+ border-top: 1px solid var(--mantine-color-gray-4) ;
+ flex-direction: row;
+ justify-content: flex-end;
+ padding: 1.5rem;
+ padding-right: 0rem;
+}
\ No newline at end of file
diff --git a/src/pages/Logs/styles/Toolbar.module.css b/src/pages/Logs/styles/Toolbar.module.css
new file mode 100644
index 00000000..5b8e70a4
--- /dev/null
+++ b/src/pages/Logs/styles/Toolbar.module.css
@@ -0,0 +1,42 @@
+
+.streamSelectDescription {
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--mantine-color-gray-6);
+}
+
+.streamInput {
+ border: none;
+ padding-left: 0;
+ padding-right: 0;
+ height: 50px;
+ font-size: 24px;
+ font-weight: 600;
+ margin-top: -10px;
+ background-color: transparent;
+ cursor: pointer;
+ width: 200px;
+}
+
+.chevronDown {
+ color: var(--mantine-color-gray-9);
+}
+
+.streamSelect {
+ margin-left: 0.625rem;
+}
+
+.logsPrimaryToolbar {
+ padding: 0.625rem 0;
+ border-bottom: 1px solid var(--mantine-color-gray-3);
+ width: 100%;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.logsSecondaryToolbar {
+ width: '100%';
+ flex-direction: row;
+ padding: 1rem 0.625rem;
+}
\ No newline at end of file
diff --git a/src/pages/Stats/Alerts.tsx b/src/pages/Stats/Alerts.tsx
deleted file mode 100644
index 8ce949ac..00000000
--- a/src/pages/Stats/Alerts.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { useHeaderContext } from '@/layouts/MainLayout/Context';
-import { Box, Button, Modal, ScrollArea, Text, px } from '@mantine/core';
-import { useDisclosure } from '@mantine/hooks';
-import { FC, useEffect, useRef } from 'react';
-import { IconArrowsMaximize } from '@tabler/icons-react';
-import useMountedState from '@/hooks/useMountedState';
-import { heights } from '@/components/Mantine/sizing';
-import { useAlertsEditor } from '@/hooks/useAlertsEditor';
-import { useParams } from 'react-router-dom';
-import alertStyles from './styles/Alerts.module.css'
-import { CodeHighlight } from '@mantine/code-highlight';
-import { useStatsPageContext } from './Context';
-
-const Alerts: FC = () => {
- const {
- state: { subLogQuery },
- } = useHeaderContext();
- const { streamName } = useParams();
- const {
- state: { fetchStartTime },
- } = useStatsPageContext();
- const { getLogAlertData, getLogAlertIsError, getLogAlertIsLoading } = useAlertsEditor(streamName || '', fetchStartTime);
-
- const [opened, { open, close }] = useDisclosure(false);
- const [Alert, setAlert] = useMountedState({ name: 'Loading....' });
- const AlertsWrapper = useRef(null);
- const [editorHeight, setEditorHeight] = useMountedState(0);
-
- useEffect(() => {
- setEditorHeight(AlertsWrapper.current?.offsetTop ? AlertsWrapper.current?.offsetTop + 15 : 0);
- }, [heights.full, AlertsWrapper]);
-
- const classes = alertStyles;
- const { container, headContainer, alertsText, alertsContainer, alertContainer, expandButton } = classes;
-
- return (
-
-
- Alerts
-
-
- {!getLogAlertIsLoading ? (
- getLogAlertIsError ? (
- 'ERROR'
- ) : getLogAlertData?.data && getLogAlertData?.data.alerts.length > 0 ? (
- getLogAlertData?.data.alerts.map((item: any, index: number) => {
- return (
-
- Name: {item.name}
- {
- setAlert(item);
- open();
- }}>
-
-
-
- );
- })
- ) : (
- No Alert set for {subLogQuery.get().streamName}
- )
- ) : (
- 'Loading'
- )}
-
-
-
-
-
- );
-};
-
-export default Alerts;
diff --git a/src/pages/Stats/Context.tsx b/src/pages/Stats/Context.tsx
deleted file mode 100644
index bf78004f..00000000
--- a/src/pages/Stats/Context.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Dispatch, FC, SetStateAction } from 'react';
-import { ReactNode, createContext, useCallback, useContext, useMemo, useState } from 'react';
-import dayjs, { Dayjs } from 'dayjs';
-
-const Context = createContext({});
-
-const { Provider } = Context;
-
-interface StatsPageProvider {
- children: ReactNode;
-}
-
-interface LogsPageContextValue {
- state: StatsPageContextState;
- methods: StatsPageContextMethods;
-}
-
-type StatsPageContextState = {
- fetchStartTime: Dayjs;
- statusFixedDurations: number;
-}
-
-type StatsPageContextMethods = {
- resetFetchStartTime: () => void;
- setStatusFixedDurations: Dispatch>;
-}
-
-const StatsPageProvider: FC = ({ children }) => {
- const [fetchStartTime, setFetchStartTime] = useState(dayjs());
- const [statusFixedDurations, setStatusFixedDurations] = useState(0);
-
- const resetFetchStartTime = useCallback(() => {
- setFetchStartTime(dayjs())
- }, [])
-
- const state: StatsPageContextState = {
- fetchStartTime,
- statusFixedDurations
- };
-
- const methods: StatsPageContextMethods = {
- resetFetchStartTime,
- setStatusFixedDurations
- }
-
- const value = useMemo(() => ({ state, methods }), [state, methods]);
-
- return {children};
-};
-
-export const useStatsPageContext = () => useContext(Context) as LogsPageContextValue;
-
-export default StatsPageProvider;
diff --git a/src/pages/Stats/Status.tsx b/src/pages/Stats/Status.tsx
deleted file mode 100644
index 87a5ee17..00000000
--- a/src/pages/Stats/Status.tsx
+++ /dev/null
@@ -1,252 +0,0 @@
-import { FC, useEffect } from 'react';
-import { Box, Stack, Text, ThemeIcon, Tooltip, px } from '@mantine/core';
-import dayjs from 'dayjs';
-import {
- IconClockStop,
- IconDatabase,
- IconInfoCircle,
- IconTimelineEventText,
- IconTransferIn,
- IconWindowMinimize,
-} from '@tabler/icons-react';
-import { useQueryResult } from '@/hooks/useQueryResult';
-import useMountedState from '@/hooks/useMountedState';
-import { convertToReadableScale } from '@/utils/convertToReadableScale';
-import { formatBytes } from '@/utils/formatBytes';
-import { FIXED_DURATIONS } from '@/constants/timeConstants';
-import { useRetentionEditor } from '@/hooks/useRetentionEditor';
-import { useLogStreamStats } from '@/hooks/useLogStreamStats';
-import { useParams } from 'react-router-dom';
-import statusStyles from './styles/Status.module.css';
-import statCardStyles from './styles/StatsCard.module.css';
-import { useStatsPageContext } from './Context';
-
-const Status: FC = () => {
- const { streamName } = useParams();
-
- const {
- state: { statusFixedDurations, fetchStartTime },
- methods: { setStatusFixedDurations },
- } = useStatsPageContext();
-
- const [fetchQueryStatus, setFetchQueryStatus] = useMountedState('');
-
- const { getLogRetentionIsError, getLogRetentionData, getLogRetentionIsSuccess, getLogRetentionIsLoading } =
- useRetentionEditor(streamName || '', fetchStartTime);
-
- const {
- getLogStreamStatsData,
- getLogStreamStatsDataIsSuccess,
- getLogStreamStatsDataIsLoading,
- getLogStreamStatsDataIsError,
- } = useLogStreamStats(streamName || '', fetchStartTime);
- const { fetchQueryMutation } = useQueryResult();
-
- useEffect(() => {
- if (streamName) {
- getStatus();
- setStatusFixedDurations(0);
- }
- }, [streamName, fetchStartTime]);
-
- const getStatus = async () => {
- setStatusFixedDurations(statusFixedDurations + 1);
- const LogQuery = {
- streamName: streamName || '',
- startTime: fetchStartTime.subtract(FIXED_DURATIONS[statusFixedDurations].milliseconds, 'milliseconds').toDate(),
- endTime: fetchStartTime.toDate(),
- access: [],
- };
- fetchQueryMutation.mutate({
- logsQuery: LogQuery,
- query: `SELECT count(*) as count FROM ${streamName} ;`,
- });
- };
-
- useEffect(() => {
- const updateStatus = async () => {
- if (fetchQueryMutation.isLoading) {
- setFetchQueryStatus('Loading...');
- } else if (fetchQueryMutation.isError) {
- setFetchQueryStatus(
- `Not Received any events in ${FIXED_DURATIONS[statusFixedDurations]?.name} and error occurred`,
- );
- } else if (fetchQueryMutation.isSuccess && fetchQueryMutation?.data[0].count) {
- setFetchQueryStatus(
- `${fetchQueryMutation?.data[0].count} events in ${FIXED_DURATIONS[statusFixedDurations]?.name}`,
- );
- } else {
- if (FIXED_DURATIONS.length > statusFixedDurations && fetchQueryMutation?.data[0].count === 0) {
- try {
- await getStatus();
- } catch (error: unknown) {
- let errorMessage = 'An unknown error occurred';
- if (error instanceof Error) {
- errorMessage = error.message;
- } else if (typeof error === 'string') {
- errorMessage = error;
- }
- setFetchQueryStatus(`Error in fetching status: ${errorMessage}`);
- }
- } else {
- setFetchQueryStatus(`No events received ${FIXED_DURATIONS[statusFixedDurations]?.name}`);
- }
- }
- };
-
- updateStatus();
- }, [fetchQueryMutation.isLoading, fetchQueryMutation.isError, fetchQueryMutation.isSuccess]);
-
- const generatedOn = getLogStreamStatsDataIsLoading
- ? 'Loading...'
- : getLogStreamStatsDataIsError
- ? 'ERROR'
- : getLogStreamStatsDataIsSuccess && getLogStreamStatsData?.data?.time
- ? dayjs(getLogRetentionData?.data?.time).format('HH:mm DD-MM-YYYY')
- : 'Not Found';
-
- const retentionValue = getLogRetentionIsLoading
- ? 'Loading...'
- : getLogRetentionIsError
- ? 'ERROR'
- : getLogRetentionIsSuccess && getLogRetentionData?.data[0] && getLogRetentionData?.data[0].duration
- ? `${getLogRetentionData?.data[0].duration.split('d')[0]} Days`
- : 'Not Set';
-
- const compressionValue = getLogStreamStatsDataIsLoading
- ? 'Loading..'
- : getLogStreamStatsDataIsError
- ? 'ERROR'
- : getLogStreamStatsDataIsSuccess &&
- getLogStreamStatsData?.data?.ingestion?.size &&
- getLogStreamStatsData?.data?.storage?.size
- ? `${(
- 100 -
- (parseInt(getLogStreamStatsData?.data?.storage?.size.split(' ')[0]) /
- parseInt(getLogStreamStatsData?.data?.ingestion?.size.split(' ')[0])) *
- 100
- ).toPrecision(4)} %`
- : 'Not Found';
-
- const storageValue = getLogStreamStatsDataIsLoading
- ? 'Loading..'
- : getLogStreamStatsDataIsError
- ? 'ERROR'
- : getLogStreamStatsDataIsSuccess && getLogStreamStatsData?.data?.storage?.size
- ? formatBytes(Number(getLogStreamStatsData?.data?.storage.size.split(' ')[0]))
- : '0';
-
- const ingestionValue = getLogStreamStatsDataIsLoading
- ? 'Loading..'
- : getLogStreamStatsDataIsError
- ? 'ERROR'
- : getLogStreamStatsDataIsSuccess && getLogStreamStatsData?.data?.ingestion?.size
- ? formatBytes(Number(getLogStreamStatsData?.data?.ingestion.size.split(' ')[0]))
- : '0';
-
- const eventsValue = getLogStreamStatsDataIsLoading
- ? 'Loading..'
- : getLogStreamStatsDataIsError
- ? 'ERROR'
- : getLogStreamStatsDataIsSuccess && getLogStreamStatsData?.data?.ingestion?.count
- ? convertToReadableScale(getLogStreamStatsData?.data.ingestion.count)
- : '0';
-
- const classes = statusStyles;
- const {
- container,
- headContainer,
- statusText,
- statusTextResult,
- genterateContiner,
- genterateText,
- genterateTextResult,
- StatsContainer,
- statusTextFailed,
- } = classes;
- return (
-
-
-
- {fetchQueryStatus}
-
-
-
-
- Generated at [{generatedOn}]
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-type statCardProps = {
- data: { Icon: any; title: string; description: string; value: string };
-};
-
-const StatCard: FC = (props) => {
- const { data } = props;
- const classes = statCardStyles;
- const { statCard, statCardTitle, statCardDescription, statCardDescriptionIcon, statCardIcon, statCardTitleValue } =
- classes;
-
- return (
-
-
-
-
-
-
-
-
-
- {data.value}
- {data.title}
-
- );
-};
-export default Status;
diff --git a/src/pages/Stats/index.tsx b/src/pages/Stats/index.tsx
deleted file mode 100644
index bfe7f9d5..00000000
--- a/src/pages/Stats/index.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Box } from '@mantine/core';
-import { useDocumentTitle } from '@mantine/hooks';
-import { FC } from 'react';
-import Status from './Status';
-import Alerts from './Alerts';
-
-const Stats: FC = () => {
- useDocumentTitle('Parseable | Stats');
-
- return (
-
-
-
-
- );
-};
-
-export default Stats;
diff --git a/src/pages/Stats/styles/Alerts.module.css b/src/pages/Stats/styles/Alerts.module.css
deleted file mode 100644
index 000ff75e..00000000
--- a/src/pages/Stats/styles/Alerts.module.css
+++ /dev/null
@@ -1,53 +0,0 @@
-.container {
- flex: 1 1 auto;
- overflow-y: auto;
- border-radius: 0.5rem;
- margin: 1rem;
- border: 1px solid rgba(82, 82, 82, 0.15);
-}
-
-.headContainer {
- padding: 1rem;
- width: 100%;
- height: 55px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- top: 0;
- position: sticky;
- background-color: #ffffff;
- z-index: 1;
- border-bottom: 1px solid rgba(82, 82, 82, 0.15);
-}
-
-.alertsText {
- font-size: 1rem;
- font-weight: 500;
- color: #141414;
-}
-
-.alertsContainer {
- overflow: scroll;
- color: #141414;
-}
-
-.alertContainer {
- border-bottom: 1px solid rgba(82, 82, 82, 0.15);
- padding: 1rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.expandButton {
- background: #ffffff;
- padding: 0;
- margin-right: 0.625rem;
- width: 36px;
- color: #868e96;
- border: 1px solid rgba(82, 82, 82, 0.15);
-}
-
-.expandButton:hover {
- background: #f1f3f5;
-}
diff --git a/src/pages/Stats/styles/StatsCard.module.css b/src/pages/Stats/styles/StatsCard.module.css
deleted file mode 100644
index 97d19087..00000000
--- a/src/pages/Stats/styles/StatsCard.module.css
+++ /dev/null
@@ -1,39 +0,0 @@
-.statCard {
- border: 1px solid rgba(82, 82, 82, 0.15);
- width: 100%;
- border-radius: 8px;
- text-align: center;
- padding: 0.5rem 1rem;
-}
-
-.statCardDescription {
- text-align: right;
-}
-
-.statCardDescriptionIcon {
- color: #141414;
-}
-
-.statCardIcon {
- background-color: #e7eeff;
- color: #545BEB;
-}
-
-.statCardText {
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- align-items: center;
-}
-
-.statCardTitleValue {
- font-size: 1.5rem;
- font-weight: 600;
- color: #545BEB;
-}
-
-.statCardTitle {
- font-size: 1rem;
- font-weight: 600;
- color: #141414;
-}
diff --git a/src/pages/Stats/styles/Status.module.css b/src/pages/Stats/styles/Status.module.css
deleted file mode 100644
index 6089895a..00000000
--- a/src/pages/Stats/styles/Status.module.css
+++ /dev/null
@@ -1,50 +0,0 @@
-.container {
- flex: 0 1 auto;
-}
-
-.headContainer {
- display: flex;
- justify-content: space-between;
- padding: 1rem;
- height: 55px;
- align-items: center;
- border-bottom: 1px solid rgba(82, 82, 82, 0.15);
-}
-
-.statusText {
- font-size: 1rem;
- font-weight: 600;
- color: #141414;
-}
-
-.statusTextResult {
- color: #00cc14;
-}
-
-.statusTextFailed {
- color: #ff0000;
-}
-
-.genterateContiner {
- margin-right: 0.75rem;
-}
-
-.genterateText {
- font-size: 1rem;
- font-weight: 600;
- color: #141414;
-}
-
-.genterateTextResult {
- font-size: 1rem;
- font-weight: 400;
- color: #141414;
-}
-
-.StatsContainer {
- display: flex;
- flex-direction: row;
- padding: 1rem;
- padding-bottom: 0;
- justify-content: space-between;
-}
diff --git a/src/providers/QueryFilterProvider.tsx b/src/providers/QueryFilterProvider.tsx
index 46ca283c..25154abf 100644
--- a/src/providers/QueryFilterProvider.tsx
+++ b/src/providers/QueryFilterProvider.tsx
@@ -1,5 +1,5 @@
import { useHeaderContext } from '@/layouts/MainLayout/Context';
-import { useLogsPageContext } from '@/pages/Logs/Context';
+import { useLogsPageContext } from '@/pages/Logs/logsContextProvider';
import { generateRandomId } from '@/utils';
import { ReactNode, createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Field, RuleGroupType, RuleType, formatQuery } from 'react-querybuilder';
@@ -36,7 +36,6 @@ type QueryFilterContextMethods = {
applyQuery: () => void;
clearFilters: () => void;
closeBuilderModal: () => void;
- openBuilderModal: () => void;
};
type QueryFilterContextValue = {
@@ -154,11 +153,10 @@ const defaultQuery = {
};
const QueryFilterProvider = (props: QueryFilterProviderProps) => {
- const [isModalOpen, setModalOpen] = useState(false);
const [isSumbitDisabled, setSubmitDisabled] = useState(true);
const {
- state: { subLogStreamSchema, custQuerySearchState },
- methods: { setCustSearchQuery, resetQuerySearch },
+ state: { subLogStreamSchema, custQuerySearchState, builderModalOpen: isModalOpen },
+ methods: { setCustSearchQuery, resetQuerySearch, closeBuilderModal },
} = useLogsPageContext();
const {
state: { subAppContext },
@@ -250,14 +248,6 @@ const QueryFilterProvider = (props: QueryFilterProviderProps) => {
});
}, []);
- const openBuilderModal = useCallback(() => {
- return setModalOpen(true);
- }, []);
-
- const closeBuilderModal = useCallback(() => {
- return setModalOpen(false);
- }, []);
-
const updateParentCombinator = useCallback((combinator: Combinator) => {
return setQuery((prev) => {
return { ...prev, combinator: combinator };
@@ -274,12 +264,12 @@ const QueryFilterProvider = (props: QueryFilterProviderProps) => {
const parsedQuery = parseQuery();
setCustSearchQuery(parsedQuery, 'filters');
setAppliedQuery(query);
- setModalOpen(false);
+ closeBuilderModal()
}, [query]);
const clearFilters = useCallback(() => {
resetQuerySearch();
- setModalOpen(false);
+ closeBuilderModal();
setAppliedQuery(defaultQuery);
setQuery(defaultQuery);
}, []);
@@ -361,7 +351,6 @@ const QueryFilterProvider = (props: QueryFilterProviderProps) => {
applyQuery,
clearFilters,
closeBuilderModal,
- openBuilderModal,
};
const value = useMemo(() => ({ state, methods }), [state, methods]);
diff --git a/src/routes/elements.tsx b/src/routes/elements.tsx
index 0eb92e7b..650824b4 100644
--- a/src/routes/elements.tsx
+++ b/src/routes/elements.tsx
@@ -4,18 +4,9 @@ import { lazy } from 'react';
import SuspensePage from './SuspensePage';
import MainLayoutPageProvider from '@/layouts/MainLayout/Context';
import MainLayout from '@/layouts/MainLayout';
-import {
- ConfigHeader,
- HomeHeader,
- LiveTailHeader,
- LogsHeader,
- StatsHeader,
- UsersManagementHeader,
-} from '@/components/Header/SubHeader';
// page-wise providers
-import LogsPageProvider from '@/pages/Logs/Context';
-import StatsPageProvider from '@/pages/Stats/Context';
+import LogsPageProvider from '@/pages/Logs/logsContextProvider';
// component-wise providers
import QueryFilterProvider from '@/providers/QueryFilterProvider';
@@ -23,7 +14,6 @@ import QueryFilterProvider from '@/providers/QueryFilterProvider';
export const HomeElement: FC = () => {
return (
-
);
@@ -46,7 +36,6 @@ export const LogsElement: FC = () => {
-
@@ -62,47 +51,11 @@ export const MainLayoutElement: FC = () => {
);
};
-const LiveTail = lazy(() => import('@/pages/LiveTail'));
-
-export const LiveTailElement: FC = () => {
- return (
-
-
-
-
- );
-};
-
-const Stats = lazy(() => import('@/pages/Stats'));
-
-export const StatsElement: FC = () => {
- return (
-
-
-
-
-
-
- );
-};
-
-const Config = lazy(() => import('@/pages/Config'));
-
-export const ConfigElement: FC = () => {
- return (
-
-
-
-
- );
-};
-
const Users = lazy(() => import('@/pages/AccessManagement'));
export const UsersElement: FC = () => {
return (
-
);
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index d5be1fbf..b07b8b7e 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -1,12 +1,9 @@
import {
ALL_ROUTE,
- CONFIG_ROUTE,
HOME_ROUTE,
- LIVE_TAIL_ROUTE,
LOGIN_ROUTE,
LOGS_ROUTE,
OIDC_NOT_CONFIGURED_ROUTE,
- STATS_ROUTE,
USERS_MANAGEMENT_ROUTE,
} from '@/constants/routes';
import FullPageLayout from '@/layouts/FullPageLayout';
@@ -14,48 +11,23 @@ import NotFound from '@/pages/Errors/NotFound';
import type { FC } from 'react';
import { Route, Routes } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
-import {
- HomeElement,
- LoginElement,
- LogsElement,
- MainLayoutElement,
- StatsElement,
- ConfigElement,
- UsersElement,
- LiveTailElement,
-} from './elements';
+import { HomeElement, LoginElement, LogsElement, MainLayoutElement, UsersElement } from './elements';
import AccessSpecificRoute from './AccessSpecificRoute';
import OIDCNotConFigured from '@/pages/Errors/OIDC';
const AppRouter: FC = () => {
- const isSecureConnection = window.location.protocol === 'https:';
return (
}>
}>
- {/* Cuurently working Empty Stream page sooner change to HomeElement */}
} />
-
- {/* Users Management Route */}
}>
} />
-
}>
} />
- {!isSecureConnection && (
- }>
- } />
-
- )}
- }>
- } />
-
- }>
- } />
-
} />
diff --git a/src/utils/sanitiseSqlString.ts b/src/utils/sanitiseSqlString.ts
index d41dc84d..ec8c8fbe 100644
--- a/src/utils/sanitiseSqlString.ts
+++ b/src/utils/sanitiseSqlString.ts
@@ -1,5 +1,5 @@
import { notify } from './notification';
-import { LOAD_LIMIT } from '@/pages/Logs/Context';
+import { LOAD_LIMIT } from '@/pages/Logs/logsContextProvider';
export const sanitiseSqlString = (sqlString: string): string => {
const withoutComments = sqlString.replace(/--.*$/gm, '');