diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml
index b9887238..3180404e 100644
--- a/.github/workflows/cla.yaml
+++ b/.github/workflows/cla.yaml
@@ -1,9 +1,9 @@
-name: "CLA Assistant"
+name: 'CLA Assistant'
on:
issue_comment:
types: [created]
pull_request_target:
- types: [opened,closed,synchronize]
+ types: [opened, closed, synchronize]
# explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings
permissions:
@@ -16,7 +16,7 @@ jobs:
CLAAssistant:
runs-on: ubuntu-latest
steps:
- - name: "CLA Assistant"
+ - name: 'CLA Assistant'
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.3.0
env:
diff --git a/index.html b/index.html
index 2b07e43a..24e72330 100644
--- a/index.html
+++ b/index.html
@@ -1,21 +1,21 @@
-
-
-
-
+
+
+
+
-
- Parseable Log Storage
+
+ Parseable Log Storage
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/signatures/version1/cla.json b/signatures/version1/cla.json
index 5991bde6..0352f989 100644
--- a/signatures/version1/cla.json
+++ b/signatures/version1/cla.json
@@ -1,28 +1,28 @@
{
- "signedContributors": [
- {
- "name": "Ryiski",
- "id": 47484379,
- "comment_id": 1564572383,
- "created_at": "2023-05-26T15:34:15Z",
- "repoId": 523035254,
- "pullRequestNo": 62
- },
- {
- "name": "kartik-gupta-ij",
- "id": 84975264,
- "comment_id": 1625309802,
- "created_at": "2023-07-07T12:02:14Z",
- "repoId": 523035254,
- "pullRequestNo": 70
- },
- {
- "name": "nitisht",
- "id": 5156139,
- "comment_id": 1625358492,
- "created_at": "2023-07-07T12:41:06Z",
- "repoId": 523035254,
- "pullRequestNo": 71
- }
- ]
-}
\ No newline at end of file
+ "signedContributors": [
+ {
+ "name": "Ryiski",
+ "id": 47484379,
+ "comment_id": 1564572383,
+ "created_at": "2023-05-26T15:34:15Z",
+ "repoId": 523035254,
+ "pullRequestNo": 62
+ },
+ {
+ "name": "kartik-gupta-ij",
+ "id": 84975264,
+ "comment_id": 1625309802,
+ "created_at": "2023-07-07T12:02:14Z",
+ "repoId": 523035254,
+ "pullRequestNo": 70
+ },
+ {
+ "name": "nitisht",
+ "id": 5156139,
+ "comment_id": 1625358492,
+ "created_at": "2023-07-07T12:41:06Z",
+ "repoId": 523035254,
+ "pullRequestNo": 71
+ }
+ ]
+}
diff --git a/src/@types/parseable/api/stream.ts b/src/@types/parseable/api/stream.ts
index 9875acb0..7fac9414 100644
--- a/src/@types/parseable/api/stream.ts
+++ b/src/@types/parseable/api/stream.ts
@@ -6,3 +6,24 @@ export type LogStreamSchemaData = {
fields: Array;
metadata: Record;
};
+export type LogStreamStat = {
+ ingestion: {
+ count: number;
+ format: string;
+ size: string;
+ };
+ storage: {
+ format: string;
+ size: string;
+ };
+ stream: string;
+ time: string;
+};
+
+export type action = {
+ description: string;
+ action: string;
+ duration: string;
+};
+
+export type LogStreamRetention = Array;
diff --git a/src/api/constants.ts b/src/api/constants.ts
index 0a03ff10..64920165 100644
--- a/src/api/constants.ts
+++ b/src/api/constants.ts
@@ -4,3 +4,6 @@ export const HEALTH_LIVENESS_URL = `${API_V1}/liveness`;
export const LOG_STREAM_LIST_URL = `${API_V1}/logstream`;
export const LOG_STREAMS_SCHEMA_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/schema`;
export const LOG_QUERY_URL = `${API_V1}/query`;
+export const STATS_STREAMS_ALERTS_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/alert`;
+export const STATS_STREAMS_RETRNTION_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/retention`;
+export const STATS_STREAMS_STATS_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/stats`;
diff --git a/src/api/logStream.ts b/src/api/logStream.ts
index 7c034a99..adf8a8e7 100644
--- a/src/api/logStream.ts
+++ b/src/api/logStream.ts
@@ -1,5 +1,11 @@
import { Axios } from './axios';
-import { LOG_STREAMS_SCHEMA_URL, LOG_STREAM_LIST_URL } from './constants';
+import {
+ LOG_STREAMS_SCHEMA_URL,
+ LOG_STREAM_LIST_URL,
+ STATS_STREAMS_ALERTS_URL,
+ STATS_STREAMS_RETRNTION_URL,
+ STATS_STREAMS_STATS_URL,
+} from './constants';
import { LogStreamData, LogStreamSchemaData } from '@/@types/parseable/api/stream';
export const getLogStreamList = () => {
@@ -9,3 +15,15 @@ export const getLogStreamList = () => {
export const getLogStreamSchema = (streamName: string) => {
return Axios().get(LOG_STREAMS_SCHEMA_URL(streamName));
};
+
+export const getLogStreamAlerts = (streamName: string) => {
+ return Axios().get(STATS_STREAMS_ALERTS_URL(streamName));
+};
+
+export const getLogStreamRetention = (streamName: string) => {
+ return Axios().get(STATS_STREAMS_RETRNTION_URL(streamName));
+};
+
+export const getLogStreamStats = (streamName: string) => {
+ return Axios().get(STATS_STREAMS_STATS_URL(streamName));
+};
diff --git a/src/api/query.ts b/src/api/query.ts
index 87e573c8..398e2ecd 100644
--- a/src/api/query.ts
+++ b/src/api/query.ts
@@ -19,8 +19,7 @@ export const getQueryLogs = (logsQuery: LogsQuery) => {
};
export const getQueryResult = (logsQuery: LogsQuery, query = '') => {
- const { startTime, endTime} = logsQuery;
-
+ const { startTime, endTime } = logsQuery;
return Axios().post(
LOG_QUERY_URL,
@@ -31,4 +30,4 @@ export const getQueryResult = (logsQuery: LogsQuery, query = '') => {
},
{},
);
-};
\ No newline at end of file
+};
diff --git a/src/components/ErrorBoundary/index.tsx b/src/components/ErrorBoundary/index.tsx
index 754fe66e..bf591e13 100644
--- a/src/components/ErrorBoundary/index.tsx
+++ b/src/components/ErrorBoundary/index.tsx
@@ -15,8 +15,6 @@ type ErrorHandlerFn = (error: Error, info: { componentStack: string }) => void;
const ErrorBoundary: FC = ({ children }) => {
const errorHandler: ErrorHandlerFn = (error, info) => {
// TODO: Send Errors to parseable maybe ?
-
-
};
return (
diff --git a/src/components/Header/SubHeader.tsx b/src/components/Header/SubHeader.tsx
index e29bd09d..046a7dc0 100644
--- a/src/components/Header/SubHeader.tsx
+++ b/src/components/Header/SubHeader.tsx
@@ -1,7 +1,7 @@
import useMountedState from '@/hooks/useMountedState';
import { Box, Breadcrumbs, Button, Menu, Text, TextInput, UnstyledButton, px } from '@mantine/core';
import { DateTimePicker } from '@mantine/dates';
-import { IconClock,IconRefresh, IconReload , IconRefreshOff, IconSearch } from '@tabler/icons-react';
+import { IconClock, IconRefresh, IconReload, IconRefreshOff, IconSearch } from '@tabler/icons-react';
import dayjs from 'dayjs';
import ms from 'ms';
import type { ChangeEvent, FC, KeyboardEvent } from 'react';
@@ -17,7 +17,6 @@ const SubHeader: FC = () => {
} = useHeaderContext();
const [streamName, setStreamName] = useMountedState(subLogQuery.get().streamName);
-
useEffect(() => {
const listener = subLogQuery.subscribe((state) => {
setStreamName(state.streamName);
@@ -30,23 +29,45 @@ const SubHeader: FC = () => {
-
-
+
+
- Streams
- {streamName}
- {useMatch("/:streamName/logs") ? "Logs" : "Query"}
+ Streams
+ {streamName}
+ {useMatch('/:streamName/stats') && Stats }
+
+ {useMatch('/:streamName/logs') && Logs }
+
+ {useMatch('/:streamName/query') && Query }
- {useMatch("/:streamName/logs") && }
- {useMatch("/:streamName/logs") && }
+ {useMatch('/:streamName/stats') && }
+
+ {useMatch('/:streamName/logs') && <>
+
+
+
+
+ >}
+
+ {useMatch('/:streamName/query') && <>
+
+
+ >}
-
-
@@ -58,7 +79,7 @@ const Search: FC = () => {
state: { subLogSearch },
} = useHeaderContext();
- const [searchValue, setSearchValue] = useMountedState("");
+ const [searchValue, setSearchValue] = useMountedState('');
const { classes } = useLogQueryStyles();
useEffect(() => {
@@ -108,7 +129,6 @@ const RefreshNow: FC = () => {
state: { subLogQuery, subLogSelectedTimeRange },
} = useHeaderContext();
-
const onRefresh = () => {
if (subLogSelectedTimeRange.get().includes('Past')) {
const now = dayjs();
@@ -123,9 +143,8 @@ const RefreshNow: FC = () => {
const { refreshNowBtn } = classes;
return (
-
-
+
);
};
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
index 457f66b5..a140d28f 100644
--- a/src/components/Header/index.tsx
+++ b/src/components/Header/index.tsx
@@ -7,14 +7,14 @@ import { FC, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useHeaderStyles } from './styles';
import SubHeader from './SubHeader';
-import {useHeaderContext} from '@/layouts/MainLayout/Context';
+import { useHeaderContext } from '@/layouts/MainLayout/Context';
import useMountedState from '@/hooks/useMountedState';
type HeaderProps = Omit;
const Header: FC = (props) => {
const { classes } = useHeaderStyles();
- const { container, logoContainer, burgerIcon ,navContainer, imageSty} = classes;
+ const { container, logoContainer, burgerIcon, navContainer, imageSty } = classes;
const {
state: { subNavbarTogle },
} = useHeaderContext();
@@ -35,17 +35,15 @@ const Header: FC = (props) => {
subNavbarTogle.set((state) => !state)}
- size={"sm"}
+ onClick={() => subNavbarTogle.set((state) => !state)}
+ size={'sm'}
/>
-
-
-
-
+
+
+
);
};
-
export default Header;
diff --git a/src/components/Header/styles.tsx b/src/components/Header/styles.tsx
index 1a0a853b..969a5a41 100644
--- a/src/components/Header/styles.tsx
+++ b/src/components/Header/styles.tsx
@@ -3,8 +3,7 @@ import { createStyles } from '@mantine/core';
export const useHeaderStyles = createStyles((theme) => {
const { colors, spacing, fontSizes } = theme;
- const { fontWeights} = theme.other;
-
+ const { fontWeights } = theme.other;
return {
container: {
@@ -15,7 +14,7 @@ export const useHeaderStyles = createStyles((theme) => {
fontFamily: theme.fontFamily,
fontSize: fontSizes.md,
fontWeight: fontWeights.normal,
- lineHeight: "normal",
+ lineHeight: 'normal',
height: HEADER_HEIGHT,
width: '100%',
},
@@ -23,25 +22,23 @@ export const useHeaderStyles = createStyles((theme) => {
display: 'flex',
alignItems: 'self-end',
width: NAVBAR_WIDTH,
- justifyContent: "space-between",
+ justifyContent: 'space-between',
// padding: spacing.md,
},
- imageSty:{
+ imageSty: {
marginLeft: spacing.md,
},
burgerIcon: {
- size:"24px",
+ size: '24px',
color: theme.colors.gray[7],
},
navContainer: {
width: `calc(100% - ${NAVBAR_WIDTH}px)`,
- justifyContent: "space-between",
+ justifyContent: 'space-between',
},
-
};
});
-
export const useLogQueryStyles = createStyles((theme) => {
const { spacing, radius, colors, fontSizes } = theme;
const { sizing, widths, fontWeights } = theme.other;
@@ -62,12 +59,12 @@ export const useLogQueryStyles = createStyles((theme) => {
color: colors.gray[6],
},
homeIcon: {
- size:"24px",
+ size: '24px',
strokeWidth: 1.0,
},
- activeBtn:{
+ activeBtn: {
color: colors.brandPrimary[0],
- textDecoration: "underline",
+ textDecoration: 'underline',
},
intervalBtn: {
@@ -86,9 +83,9 @@ export const useLogQueryStyles = createStyles((theme) => {
},
refreshNowBtn: {
background: colors.white[0],
- padding:0,
+ padding: 0,
marginRight: spacing.xs,
- width: "36px",
+ width: '36px',
color: theme.colors.gray[6],
border: `${sizing.px} ${colors.gray[2]} solid`,
'&:hover': {
@@ -207,4 +204,4 @@ export const useLogQueryStyles = createStyles((theme) => {
},
},
};
-});
\ No newline at end of file
+});
diff --git a/src/components/Mantine/theme.tsx b/src/components/Mantine/theme.tsx
index fd53aa8c..aeb5b53f 100644
--- a/src/components/Mantine/theme.tsx
+++ b/src/components/Mantine/theme.tsx
@@ -21,7 +21,7 @@ export const theme: MantineThemeOverride = {
white: ['#FFFFFF'],
brandPrimary: ['#545BEB', '#1F288E', '#1A237E', '#10143E'],
brandSecondary: ['#FC466B', '#F29C38', '#C27D2D'],
- gray: ['#F1F1F1', '#E0E0E0', '#D4D4D4', '#828282', '#4F4F4F', '#777777' , '#211F1F'],
+ gray: ['#F1F1F1', '#E0E0E0', '#D4D4D4', '#828282', '#4F4F4F', '#777777', '#211F1F'],
error: ['#8F0F27'],
dimmed: ['#868e96'],
},
@@ -108,22 +108,20 @@ export const theme: MantineThemeOverride = {
},
Table: {
styles: ({ defaultRadius: _defaultRadius, colors, fontSizes, other: { fontWeights } }) => {
-
return {
root: {
background: colors.white,
borderCollapse: 'separate',
borderSpacing: 0,
- padding:0,
- height: 20,
+ padding: 0,
+ height: 20,
'& tr th': {
background: theme.colors?.white,
- border:"none !important",
+ border: 'none !important',
overflow: 'hidden',
whiteSpace: 'nowrap',
textAlign: 'left',
padding: 0,
-
},
'& tr th .label': {
@@ -134,7 +132,6 @@ export const theme: MantineThemeOverride = {
height: '100%',
textAlign: 'left',
},
-
},
};
},
diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx
index ba8ad835..4224f120 100644
--- a/src/components/Navbar/index.tsx
+++ b/src/components/Navbar/index.tsx
@@ -1,12 +1,22 @@
import type { NavbarProps as MantineNavbarProps } from '@mantine/core';
import { Navbar as MantineNavbar, NavLink, Select, Anchor, Card, Box, Modal, Text, Image } from '@mantine/core';
-import { IconColumns , IconZoomCode,IconReportAnalytics , IconCheck, IconFileAlert, IconReload, IconHelpCircle, IconLogout, IconUser } from '@tabler/icons-react';
+import {
+ IconColumns,
+ IconZoomCode,
+ IconReportAnalytics,
+ IconCheck,
+ IconFileAlert,
+ IconReload,
+ IconHelpCircle,
+ IconLogout,
+ IconUser,
+} from '@tabler/icons-react';
import { FC, useEffect, useState } from 'react';
import docImage from '@/assets/images/doc.webp';
import githubLogo from '@/assets/images/github-logo.webp';
import slackLogo from '@/assets/images/slack-logo.webp';
import { useNavbarStyles } from './styles';
-import { useLocation, useParams } from "react-router-dom";
+import { useLocation, useParams } from 'react-router-dom';
import { useGetLogStreamList } from '@/hooks/useGetLogStreamList';
import { notifications } from '@mantine/notifications';
import { useNavigate } from 'react-router-dom';
@@ -16,11 +26,10 @@ import dayjs from 'dayjs';
import { useDisclosure, useLocalStorage } from '@mantine/hooks';
import { LOGIN_ROUTE } from '@/constants/routes';
-
const links = [
- { icon: IconColumns , label: 'Logs', pathname: "/logs" },
- { icon: IconZoomCode , label: 'Query', pathname: "/query" },
- { icon: IconReportAnalytics , label: 'Stats', pathname: "/metrics"}
+ { icon: IconColumns, label: 'Logs', pathname: '/logs' },
+ { icon: IconZoomCode, label: 'Query', pathname: '/query' },
+ { icon: IconReportAnalytics, label: 'Stats', pathname: '/stats' },
];
type NavbarProps = Omit;
@@ -29,13 +38,22 @@ const Navbar: FC = (props) => {
const [username] = useLocalStorage({ key: 'username', getInitialValueInEffect: false });
const navigate = useNavigate();
const { data: streams, loading, error, getData } = useGetLogStreamList();
- const [activeStream, setActiveStream] = useState("");
- const [searchValue, setSearchValue] = useState("");
+ const [activeStream, setActiveStream] = useState('');
+ const [searchValue, setSearchValue] = useState('');
const { classes } = useNavbarStyles();
- const [currentPage, setCurrentPage] = useState("/logs");
- const { container, linkBtnActive, linkBtn,
- selectStreambtn, streamsBtn, lowerContainer,
- actionBtn, helpTitle, helpDescription, userBtn } = classes;
+ const [currentPage, setCurrentPage] = useState('/logs');
+ const {
+ container,
+ linkBtnActive,
+ linkBtn,
+ selectStreambtn,
+ streamsBtn,
+ lowerContainer,
+ actionBtn,
+ helpTitle,
+ helpDescription,
+ userBtn,
+ } = classes;
const { streamName } = useParams();
const nav = useNavigate();
const [, , removeCredentials] = useLocalStorage({ key: 'credentials' });
@@ -45,7 +63,7 @@ const Navbar: FC = (props) => {
} = useHeaderContext();
const [isSubNavbarOpen, setIsSubNavbarOpen] = useMountedState(false);
const [opened, { close, open }] = useDisclosure();
- let location = useLocation()
+ let location = useLocation();
useEffect(() => {
const listener = subNavbarTogle.subscribe(setIsSubNavbarOpen);
@@ -54,7 +72,6 @@ const Navbar: FC = (props) => {
};
}, [subNavbarTogle.get()]);
-
const onSignOut = () => {
removeCredentials();
removeUsername();
@@ -66,7 +83,9 @@ const Navbar: FC = (props) => {
);
};
- const { state: { subLogQuery, subLogSelectedTimeRange, subLogSearch, subRefreshInterval } } = useHeaderContext();
+ const {
+ state: { subLogQuery, subLogSelectedTimeRange, subLogSearch, subRefreshInterval },
+ } = useHeaderContext();
useEffect(() => {
if (streamName) {
@@ -87,8 +106,7 @@ const Navbar: FC = (props) => {
subRefreshInterval.set(null);
setCurrentPage(location.pathname);
}
- }
- else if (streams && Boolean(streams.length)) {
+ } else if (streams && Boolean(streams.length)) {
navigate(`/${streams[0].name}/logs`);
}
}, [streams, location]);
@@ -109,8 +127,8 @@ const Navbar: FC = (props) => {
message: 'Streams will be loaded.',
autoClose: false,
withCloseButton: false,
- })
- };
+ });
+ }
if (streams && Boolean(streams.length)) {
notifications.update({
id: 'load-data',
@@ -131,26 +149,34 @@ const Navbar: FC = (props) => {
autoClose: 2000,
});
}
-
}, [streams, error, loading]);
-
return (
-
-
-
- } className={streamsBtn} />
+
+
+
+ }
+ className={streamsBtn}
+ />
handleChange(value || "")}
+ onChange={(value) => handleChange(value || '')}
nothingFound="No options"
value={activeStream}
searchValue={searchValue}
onSearchChange={(value) => setSearchValue(value)}
onDropdownClose={() => setSearchValue(activeStream)}
- onDropdownOpen={() => setSearchValue("")}
+ onDropdownOpen={() => setSearchValue('')}
data={streams?.map((stream) => ({ value: stream.name, label: stream.name })) ?? []}
searchable
required
@@ -162,19 +188,43 @@ const Navbar: FC = (props) => {
label={link.label}
icon={ }
sx={{ paddingLeft: 53 }}
- onClick={() => { navigate(`/${activeStream}${link.pathname}`); }}
+ onClick={() => {
+ navigate(`/${activeStream}${link.pathname}`);
+ }}
key={link.label}
- className={link.pathname ? window.location.pathname.includes(link.pathname) ? linkBtnActive : linkBtn : linkBtn}
+ className={
+ link.pathname ? (window.location.pathname.includes(link.pathname) ? linkBtnActive : linkBtn) : linkBtn
+ }
/>
- )
+ );
})}
{error && {error}
}
- {error && } component="button" onClick={getData} sx={{ paddingLeft: 0 }} />}
+ {error && (
+ }
+ component="button"
+ onClick={getData}
+ sx={{ paddingLeft: 0 }}
+ />
+ )}
} className={userBtn} component="a" />
- } className={actionBtn} component="a" onClick={open} />
- } className={actionBtn} component="a" onClick={onSignOut} />
+ }
+ className={actionBtn}
+ component="a"
+ onClick={open}
+ />
+ }
+ className={actionBtn}
+ component="a"
+ onClick={onSignOut}
+ />
Need any help?
@@ -189,7 +239,6 @@ const Navbar: FC = (props) => {
);
};
-
const helpResources = [
{
image: slackLogo,
@@ -215,8 +264,6 @@ type HelpCardProps = {
data: (typeof helpResources)[number];
};
-
-
const HelpCard: FC = (props) => {
const { data } = props;
@@ -236,4 +283,4 @@ const HelpCard: FC = (props) => {
);
};
-export default Navbar;
\ No newline at end of file
+export default Navbar;
diff --git a/src/components/Navbar/styles.tsx b/src/components/Navbar/styles.tsx
index 1a213bf2..e1ec5295 100644
--- a/src/components/Navbar/styles.tsx
+++ b/src/components/Navbar/styles.tsx
@@ -2,7 +2,7 @@ import { createStyles } from '@mantine/core';
import { heights, widths } from '../Mantine/sizing';
export const useNavbarStyles = createStyles((theme) => {
- const { colors, radius, fontSizes, shadows ,spacing} = theme;
+ const { colors, radius, fontSizes, shadows, spacing } = theme;
const { fontWeights, sizing } = theme.other;
const white = colors.white[0];
const sColor = colors.brandSecondary[0];
@@ -20,11 +20,12 @@ export const useNavbarStyles = createStyles((theme) => {
fontFamily: theme.fontFamily,
fontSize: fontSizes.md,
fontWeight: fontWeights.normal,
- lineHeight: "normal",
+ lineHeight: 'normal',
},
streamsBtn: {
- padding:"24px 24px 14px 24px", ":hover": { background: "white" },
- cursor: 'default',
+ padding: '24px 24px 14px 24px',
+ ':hover': { background: 'white' },
+ cursor: 'default',
color: theme.colors.gray[6],
},
linkBtnActive: {
@@ -42,9 +43,9 @@ export const useNavbarStyles = createStyles((theme) => {
},
selectStreambtn: {
'.mantine-Input-rightSection ': {
- "& svg": {
+ '& svg': {
stroke: colors.gray[2],
- }
+ },
},
'& input': {
border: `${sizing.px} ${colors.gray[2]} solid`,
@@ -52,34 +53,33 @@ export const useNavbarStyles = createStyles((theme) => {
},
'.mantine-Select-item[data-selected="true"]': {
background: pColor,
- "&:hover": { background: sColor, color: white }
+ '&:hover': { background: sColor, color: white },
},
'.mantine-Select-item': {
- "&:hover": { color: sColor }
+ '&:hover': { color: sColor },
},
- margin: "0 24px 0 24px"
-
- }
- ,lowerContainer: {
- marginBottom:"50px"
+ margin: '0 24px 0 24px',
+ },
+ lowerContainer: {
+ marginBottom: '50px',
},
actionBtn: {
- paddingLeft:"24px",
- height:"40px",
+ paddingLeft: '24px',
+ height: '40px',
color: theme.colors.gray[6],
'&:hover *': {
color: sColor,
- }
+ },
},
- userBtn: {
+ userBtn: {
cursor: 'default',
- paddingLeft:"24px",
- height:"40px",
+ paddingLeft: '24px',
+ height: '40px',
color: theme.colors.gray[6],
'&:hover ': {
background: white,
- }
+ },
},
helpTitle: {
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index 0ef34bf8..4b9909d1 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -3,3 +3,4 @@ export const LOGS_ROUTE = '/:streamName/logs';
export const LOGIN_ROUTE = '/login';
export const ALL_ROUTE = '*';
export const QUERY_ROUTE = '/:streamName/query';
+export const STATS_ROUTE = '/:streamName/stats';
diff --git a/src/hooks/useGetLogStreamAlert.ts b/src/hooks/useGetLogStreamAlert.ts
new file mode 100644
index 00000000..c4719022
--- /dev/null
+++ b/src/hooks/useGetLogStreamAlert.ts
@@ -0,0 +1,40 @@
+import { LogStreamSchemaData } from '@/@types/parseable/api/stream';
+import { getLogStreamAlerts } from '@/api/logStream';
+import { StatusCodes } from 'http-status-codes';
+import useMountedState from './useMountedState';
+
+export const useGetLogStreamAlert = () => {
+ const [data, setData] = useMountedState(null);
+ const [error, setError] = useMountedState(null);
+ const [loading, setLoading] = useMountedState(false);
+
+ const getLogAlert = async (streamName: string) => {
+ try {
+ setLoading(true);
+ setError(null);
+ const res = await getLogStreamAlerts(streamName);
+
+ switch (res.status) {
+ case StatusCodes.OK: {
+ const streams = res.data;
+
+ setData(streams);
+ break;
+ }
+ default: {
+ setError('Failed to get ALert');
+ }
+ }
+ } catch {
+ setError('Failed to get ALert');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const resetData = () => {
+ setData(null);
+ };
+
+ return { data, error, loading, getLogAlert, resetData };
+};
diff --git a/src/hooks/useGetLogStreamRetention.ts b/src/hooks/useGetLogStreamRetention.ts
new file mode 100644
index 00000000..99441585
--- /dev/null
+++ b/src/hooks/useGetLogStreamRetention.ts
@@ -0,0 +1,40 @@
+import { LogStreamRetention } from '@/@types/parseable/api/stream';
+import { getLogStreamRetention } from '@/api/logStream';
+import { StatusCodes } from 'http-status-codes';
+import useMountedState from './useMountedState';
+
+export const useGetLogStreamRetention = () => {
+ const [data, setData] = useMountedState(null);
+ const [error, setError] = useMountedState(null);
+ const [loading, setLoading] = useMountedState(false);
+
+ const getLogRetention = async (streamName: string) => {
+ try {
+ setLoading(true);
+ setError(null);
+ const res = await getLogStreamRetention(streamName);
+
+ switch (res.status) {
+ case StatusCodes.OK: {
+ const streams = res.data;
+
+ setData(streams);
+ break;
+ }
+ default: {
+ setError('Failed to get ALert');
+ }
+ }
+ } catch {
+ setError('Failed to get ALert');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const resetData = () => {
+ setData(null);
+ };
+
+ return { data, error, loading, getLogRetention, resetData };
+};
diff --git a/src/hooks/useGetLogStreamStat.ts b/src/hooks/useGetLogStreamStat.ts
new file mode 100644
index 00000000..1ef3c6c1
--- /dev/null
+++ b/src/hooks/useGetLogStreamStat.ts
@@ -0,0 +1,40 @@
+import { LogStreamStat } from '@/@types/parseable/api/stream';
+import { getLogStreamStats } from '@/api/logStream';
+import { StatusCodes } from 'http-status-codes';
+import useMountedState from './useMountedState';
+
+export const useGetLogStreamStat = () => {
+ const [data, setData] = useMountedState(null);
+ const [error, setError] = useMountedState(null);
+ const [loading, setLoading] = useMountedState(false);
+
+ const getLogStat = async (streamName: string) => {
+ try {
+ setLoading(true);
+ setError(null);
+ const res = await getLogStreamStats(streamName);
+
+ switch (res.status) {
+ case StatusCodes.OK: {
+ const streams = res.data;
+
+ setData(streams);
+ break;
+ }
+ default: {
+ setError('Failed to get ALert');
+ }
+ }
+ } catch {
+ setError('Failed to get ALert');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const resetData = () => {
+ setData(null);
+ };
+
+ return { data, error, loading, getLogStat, resetData };
+};
diff --git a/src/hooks/useQueryResult.ts b/src/hooks/useQueryResult.ts
index 2a9605f7..3e3c2b4b 100644
--- a/src/hooks/useQueryResult.ts
+++ b/src/hooks/useQueryResult.ts
@@ -1,53 +1,47 @@
-import { LogStreamData } from '@/@types/parseable/api/stream';
import { getQueryResult } from '@/api/query';
import { StatusCodes } from 'http-status-codes';
import useMountedState from './useMountedState';
import { LogsQuery } from '@/@types/parseable/api/query';
export const useQueryResult = () => {
- const [data, setData] = useMountedState<{
- data: LogStreamData;
- } | null>(null);
- const [error, setError] = useMountedState(null);
- const [loading, setLoading] = useMountedState(true);
-
- const getQueryData = async (logsQuery: LogsQuery, query = '') => {
- try {
- setLoading(true);
- setError(null);
-
- const [logsQueryRes] = await Promise.all([
- getQueryResult(logsQuery, query)
- ]);
-
- const data = logsQueryRes.data;
-
- if (logsQueryRes.status === StatusCodes.OK) {
-
- setData({ data });
- return;
- }
-
- if (
- typeof data === 'string' && data.includes('Stream is not initialized yet')
- ) {
- setData({
- data: [],
- });
- return;
- }
-
- setError(logsQueryRes.data);
- } catch(error: any) {
- setError(error.message);
- } finally {
- setLoading(false);
- }
- };
-
- const resetData = () => {
- setData(null);
- };
-
- return { data, error, loading, getQueryData, resetData };
+ const [data, setData] = useMountedState<{
+ data: any;
+ } | null>(null);
+ const [error, setError] = useMountedState(null);
+ const [loading, setLoading] = useMountedState(true);
+
+ const getQueryData = async (logsQuery: LogsQuery, query = '') => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const [logsQueryRes] = await Promise.all([getQueryResult(logsQuery, query)]);
+
+ const data = logsQueryRes.data;
+
+ if (logsQueryRes.status === StatusCodes.OK) {
+ setData({ data });
+ return;
+ }
+
+ if (typeof data === 'string' && data.includes('Stream is not initialized yet')) {
+ setData({
+ data: [],
+ });
+ return;
+ }
+
+ setError(logsQueryRes.data);
+ } catch (error: any) {
+ setError(error.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const resetData = () => {
+ setData(null);
+ };
+
+ return { data, error, loading, getQueryData, resetData };
};
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index 22c7d0bd..55052dd7 100644
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -3,8 +3,8 @@ import { Navigate, useLocation } from 'react-router-dom';
const Home: FC = () => {
const location = useLocation();
- const pathname = location.state?.from?.pathname ?? "/";
- return ;
+ const pathname = location.state?.from?.pathname ?? '/';
+ return ;
};
export default Home;
diff --git a/src/pages/Logs/Column.tsx b/src/pages/Logs/Column.tsx
index fdfc004b..321c68dd 100644
--- a/src/pages/Logs/Column.tsx
+++ b/src/pages/Logs/Column.tsx
@@ -64,7 +64,7 @@ const Column: FC = (props) => {
const filterActive = useMemo(() => Boolean(appliedFilter(columnName)?.length), [selectedFilters]);
const canApply = useMemo(() => !compare(selectedFilters, appliedFilter(columnName)), [selectedFilters]);
- function capitalizeFirstLetter(word:string) {
+ function capitalizeFirstLetter(word: string) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
const { classes, cx } = useTableColumnStyle();
@@ -160,8 +160,8 @@ const CheckboxRow: FC = (props) => {
style={{
whiteSpace: 'pre-wrap',
maxWidth: 250,
- color:"black",
- backgroundColor:"white"
+ color: 'black',
+ backgroundColor: 'white',
}}>
diff --git a/src/pages/Logs/Context.tsx b/src/pages/Logs/Context.tsx
index 12d6d5f7..dcdf2db1 100644
--- a/src/pages/Logs/Context.tsx
+++ b/src/pages/Logs/Context.tsx
@@ -1,4 +1,4 @@
-import type {Log } from '@/@types/parseable/api/query';
+import type { Log } from '@/@types/parseable/api/query';
import useSubscribeState, { SubData } from '@/hooks/useSubscribeState';
import type { FC } from 'react';
import { ReactNode, createContext, useContext } from 'react';
@@ -9,7 +9,6 @@ const { Provider } = Context;
export const LOG_QUERY_LIMITS = [30, 50, 100, 150, 200];
-
interface LogsPageContextState {
subLogStreamError: SubData
;
subViewLog: SubData;
diff --git a/src/pages/Logs/LogRow.tsx b/src/pages/Logs/LogRow.tsx
index b982bfce..598a8244 100644
--- a/src/pages/Logs/LogRow.tsx
+++ b/src/pages/Logs/LogRow.tsx
@@ -26,7 +26,7 @@ const LogRow: FC = (props) => {
};
const { classes } = useLogTableStyles();
- const { trStyle,trEvenStyle } = classes;
+ const { trStyle, trEvenStyle } = classes;
return (
@@ -38,7 +38,7 @@ const LogRow: FC = (props) => {
Hopefully there will be a plan to add a p_id filed internally
For now index is a better option for uniqueness, if you have a better way to handle this let us know
*/
- onShow(log)}>
+ onShow(log)}>
{logsSchema.map((logSchema, logSchemaIndex) => {
if (!isColumnActive(logSchema.name) || skipFields.includes(logSchema.name)) return null;
diff --git a/src/pages/Logs/LogTable.tsx b/src/pages/Logs/LogTable.tsx
index d9d16f40..09b1a314 100644
--- a/src/pages/Logs/LogTable.tsx
+++ b/src/pages/Logs/LogTable.tsx
@@ -26,8 +26,8 @@ const LogTable: FC = () => {
state: { subLogStreamError },
} = useLogsPageContext();
const {
- state: { subLogSearch ,subLogQuery , subRefreshInterval ,subLogSelectedTimeRange},
- }= useHeaderContext();
+ state: { subLogSearch, subLogQuery, subRefreshInterval, subLogSelectedTimeRange },
+ } = useHeaderContext();
const [refreshInterval, setRefreshInterval] = useMountedState(null);
const [logStreamError, setLogStreamError] = useMountedState(null);
@@ -119,19 +119,19 @@ const LogTable: FC = () => {
useEffect(() => {
if (subRefreshInterval.get()) {
- const interval = setInterval(() => {
- if(subLogSelectedTimeRange.get().includes('Past')){
- const now =dayjs();
- const timeDiff=subLogQuery.get().endTime.getTime()-subLogQuery.get().startTime.getTime();
- subLogQuery.set((state) => {
- state.startTime = now.subtract(timeDiff).toDate();
- state.endTime = now.toDate();
- });
- }
- }, subRefreshInterval.get() as number);
- return () => clearInterval(interval);
+ const interval = setInterval(() => {
+ if (subLogSelectedTimeRange.get().includes('Past')) {
+ const now = dayjs();
+ const timeDiff = subLogQuery.get().endTime.getTime() - subLogQuery.get().startTime.getTime();
+ subLogQuery.set((state) => {
+ state.startTime = now.subtract(timeDiff).toDate();
+ state.endTime = now.toDate();
+ });
+ }
+ }, subRefreshInterval.get() as number);
+ return () => clearInterval(interval);
}
- }, [refreshInterval]);
+ }, [refreshInterval]);
useEffect(() => {
const query = subLogQuery.get();
@@ -139,7 +139,7 @@ const LogTable: FC = () => {
if (query.streamName) {
if (logsSchema) {
resetStreamData();
- resetLogsData
+ resetLogsData;
}
getDataSchema(query.streamName);
getQueryData(query);
@@ -197,12 +197,16 @@ const LogTable: FC = () => {
/>
-
+
-
+
{(pageLogData?.totalPages || 0) > 1 && (
{
className={paginationRow}
/>
)}
-
) : (
diff --git a/src/pages/Logs/ViewLog.tsx b/src/pages/Logs/ViewLog.tsx
index cdc62c45..0a6bc929 100644
--- a/src/pages/Logs/ViewLog.tsx
+++ b/src/pages/Logs/ViewLog.tsx
@@ -106,9 +106,9 @@ const DataChip: FC = (props) => {
- {[...dataList].map((data,i) => {
+ {[...dataList].map((data, i) => {
return (
-
+
{data}
);
diff --git a/src/pages/Logs/index.tsx b/src/pages/Logs/index.tsx
index 7eab2a2b..5953dfcc 100644
--- a/src/pages/Logs/index.tsx
+++ b/src/pages/Logs/index.tsx
@@ -5,14 +5,12 @@ import LogTable from './LogTable';
import { useLogsStyles } from './styles';
import ViewLog from './ViewLog';
-
const Logs: FC = () => {
useDocumentTitle('Parseable | Dashboard');
const { classes } = useLogsStyles();
const { container } = classes;
-
return (
diff --git a/src/pages/Logs/styles.tsx b/src/pages/Logs/styles.tsx
index 7b306268..86209905 100644
--- a/src/pages/Logs/styles.tsx
+++ b/src/pages/Logs/styles.tsx
@@ -14,8 +14,8 @@ export const useLogsStyles = createStyles((theme) => {
});
export const useLogTableStyles = createStyles((theme) => {
- const { spacing, other, radius, shadows, colors ,fontSizes} = theme;
- const { heights, widths, fontWeights ,} = other;
+ const { spacing, other, radius, shadows, colors, fontSizes } = theme;
+ const { heights, widths, fontWeights } = other;
const pColor = colors.brandPrimary[0];
const sColor = colors.brandSecondary[0];
@@ -43,9 +43,9 @@ export const useLogTableStyles = createStyles((theme) => {
paginationRow: {
'.mantine-Pagination-control': {
- border: `solid 1px ${colors.gray[2]}`,
- fontSize: fontSizes.sm,
- '&:hover': {
+ border: `solid 1px ${colors.gray[2]}`,
+ fontSize: fontSizes.sm,
+ '&:hover': {
color: sColor,
},
'&:data-active=true': {
@@ -62,7 +62,7 @@ export const useLogTableStyles = createStyles((theme) => {
},
tableContainer: {
- position: 'relative'
+ position: 'relative',
},
tableStyle: {
@@ -94,11 +94,10 @@ export const useLogTableStyles = createStyles((theme) => {
height: heights[10],
textAlign: 'left',
verticalAlign: 'middle',
- border:"none !important",
-
+ border: 'none !important',
},
},
- trEvenStyle:{
+ trEvenStyle: {
cursor: 'pointer',
background: colors.gray[0],
@@ -110,8 +109,7 @@ export const useLogTableStyles = createStyles((theme) => {
height: heights[10],
textAlign: 'left',
verticalAlign: 'middle',
- border:"none !important",
-
+ border: 'none !important',
},
},
@@ -124,7 +122,7 @@ export const useLogTableStyles = createStyles((theme) => {
tdArrow: {
position: 'sticky',
right: 0,
- background:"inherit",
+ background: 'inherit',
boxShadow: shadows.sm,
'tr:hover &': {
@@ -160,9 +158,7 @@ export const useLogTableStyles = createStyles((theme) => {
height: heights.full,
},
- limitContainer: {
-
- },
+ limitContainer: {},
limitBtn: {
display: 'flex',
@@ -180,9 +176,7 @@ export const useLogTableStyles = createStyles((theme) => {
},
},
- limitBtnText: {
-
- },
+ limitBtnText: {},
limitActive: {
background: pColor,
@@ -239,7 +233,6 @@ export const useViewLogStyles = createStyles((theme) => {
};
});
-
export const useTableColumnStyle = createStyles((theme) => {
const { spacing, colors, fontSizes, other, primaryColor } = theme;
const { fontWeights, widths } = other;
@@ -252,7 +245,7 @@ export const useTableColumnStyle = createStyles((theme) => {
display: 'flex',
alignItems: 'center',
textAlign: 'left',
- height: "100%",
+ height: '100%',
'&:hover': {
background: colors.gray[1],
},
@@ -260,7 +253,7 @@ export const useTableColumnStyle = createStyles((theme) => {
labelIcon: {
color: colors.gray[5],
- marginLeft:spacing.xs
+ marginLeft: spacing.xs,
},
labelIconActive: {
diff --git a/src/pages/Query/Context.tsx b/src/pages/Query/Context.tsx
index 19a5e448..bad4b199 100644
--- a/src/pages/Query/Context.tsx
+++ b/src/pages/Query/Context.tsx
@@ -7,11 +7,10 @@ const Context = createContext({});
const { Provider } = Context;
-
-export const defaultQueryResult = ""
+export const defaultQueryResult = '';
interface QueryPageContextState {
- result: SubData;
+ result: SubData;
subSchemaToggle: SubData;
}
@@ -28,12 +27,12 @@ interface QueryPageProviderProps {
}
const QueryPageProvider: FC = ({ children }) => {
- const result = useSubscribeState(defaultQueryResult);
+ const result = useSubscribeState(defaultQueryResult);
const subSchemaToggle = useSubscribeState(false);
const state: QueryPageContextState = {
result,
- subSchemaToggle
+ subSchemaToggle,
};
const methods: QueryPageContextMethods = {};
diff --git a/src/pages/Query/ErrorMarker.tsx b/src/pages/Query/ErrorMarker.tsx
index ea7283f1..fa204774 100644
--- a/src/pages/Query/ErrorMarker.tsx
+++ b/src/pages/Query/ErrorMarker.tsx
@@ -20,43 +20,40 @@ export var ErrorMarker: any[] = [];
// });
// }
export function errChecker(code: string, streamName: string) {
+ let arr = code.split('\r\n');
+ ErrorMarker = [];
+ arr.map((wordsString: string, j: any) => {
+ let wordsArray = wordsString.split(' ');
+ wordsArray.map((word: string, i: any) => {
+ if (word.toLowerCase() === 'from' && wordsArray[i + 1]) {
+ if (wordsArray[i + 1] !== streamName && wordsArray[i + 1] !== `${streamName};`) {
+ ErrorMarker.push({
+ startLineNumber: j + 1,
+ endLineNumber: j + 1,
+ startColumn: wordsString.indexOf(wordsArray[i + 1]) + 1, //i+1,
+ endColumn: wordsString.indexOf(wordsArray[i + 1]) + wordsArray[i + 1].length + 1,
+ message: `Schema support available for ${streamName} `,
+ });
+ }
+ }
+ });
+ });
+ return;
- let arr = code.split('\r\n');
- ErrorMarker = [];
- arr.map((wordsString:string, j:any) => {
- let wordsArray=wordsString.split(' ');
- wordsArray.map((word:string, i:any) => {
- if (word.toLowerCase() === 'from' && wordsArray[i + 1]) {
- if(wordsArray[i+1]!==streamName && wordsArray[i+1]!==`${streamName};`){
- ErrorMarker.push({
- startLineNumber: j+1,
- endLineNumber: j+1,
- startColumn: wordsString.indexOf(wordsArray[i+1])+1, //i+1,
- endColumn: wordsString.indexOf(wordsArray[i+1]) + wordsArray[i+1].length+1,
- message: `Schema support available for ${streamName} `,
-
- });
- }
- }
- })
- });
- return;
+ // const blocksArray = GetCodeBlocks(code);
- // const blocksArray = GetCodeBlocks(code);
-
- // ErrorMarker = [];
- // for (var i = 0; i < blocksArray.length; ++i) {
- // const headLineArray = blocksArray[i].blockText.split(/\r?\n/)[0].split(" ");
- // if (keywords.indexOf(headLineArray[0]) < 0) {
- // ErrorMarker.push({
- // startLineNumber: blocksArray[i].blockStartLine,
- // endLineNumber: blocksArray[i].blockStartLine,
- // startColumn: 0,
- // endColumn: headLineArray[0].length,
- // message: "Expected one of GET/POST/PUT/DELETE/HEAD",
- // severity: "monaco.MarkerSeverity.Error",
- // });
- // }
- // }
-
+ // ErrorMarker = [];
+ // for (var i = 0; i < blocksArray.length; ++i) {
+ // const headLineArray = blocksArray[i].blockText.split(/\r?\n/)[0].split(" ");
+ // if (keywords.indexOf(headLineArray[0]) < 0) {
+ // ErrorMarker.push({
+ // startLineNumber: blocksArray[i].blockStartLine,
+ // endLineNumber: blocksArray[i].blockStartLine,
+ // startColumn: 0,
+ // endColumn: headLineArray[0].length,
+ // message: "Expected one of GET/POST/PUT/DELETE/HEAD",
+ // severity: "monaco.MarkerSeverity.Error",
+ // });
+ // }
+ // }
}
diff --git a/src/pages/Query/QueryCodeEditor.tsx b/src/pages/Query/QueryCodeEditor.tsx
index 754e7e91..3f8420c5 100644
--- a/src/pages/Query/QueryCodeEditor.tsx
+++ b/src/pages/Query/QueryCodeEditor.tsx
@@ -4,7 +4,7 @@ import { useQueryPageContext } from './Context';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
import { Box, Button, Text, px } from '@mantine/core';
import { useQueryResult } from '@/hooks/useQueryResult';
-import { ErrorMarker, errChecker } from "./ErrorMarker";
+import { ErrorMarker, errChecker } from './ErrorMarker';
import { notifications } from '@mantine/notifications';
import { IconPlayerPlayFilled, IconCheck, IconFileAlert, IconFileInfo } from '@tabler/icons-react';
import useMountedState from '@/hooks/useMountedState';
@@ -12,142 +12,141 @@ import { useQueryCodeEditorStyles } from './styles';
import dayjs from 'dayjs';
const QueryCodeEditor: FC = () => {
- const { state: { subLogQuery, subRefreshInterval, subLogSelectedTimeRange } } = useHeaderContext();
- const { state: { result, subSchemaToggle } } = useQueryPageContext();
+ const {
+ state: { subLogQuery, subRefreshInterval, subLogSelectedTimeRange },
+ } = useHeaderContext();
+ const {
+ state: { result, subSchemaToggle },
+ } = useQueryPageContext();
- const { data: queryResult, getQueryData, error, resetData } = useQueryResult();
- const editorRef = React.useRef();
- const monacoRef = React.useRef();
- const [isSchemaOpen, setIsSchemaOpen] = useMountedState(false);
- const [query, setQuery] = React.useState(`SELECT * FROM ${subLogQuery.get().streamName} LIMIT 100`);
- const [refreshInterval, setRefreshInterval] = useMountedState(null);
+ const { data: queryResult, getQueryData, error, resetData } = useQueryResult();
+ const editorRef = React.useRef();
+ const monacoRef = React.useRef();
+ const [isSchemaOpen, setIsSchemaOpen] = useMountedState(false);
+ const [query, setQuery] = React.useState(`SELECT * FROM ${subLogQuery.get().streamName} LIMIT 100`);
+ const [refreshInterval, setRefreshInterval] = useMountedState(null);
- const handleEditorChange = (code: any) => {
- setQuery(code);
- errChecker(code, subLogQuery.get().streamName);
- monacoRef.current?.editor.setModelMarkers(
- editorRef.current?.getModel(),
- "owner",
- ErrorMarker
- );
- };
+ const handleEditorChange = (code: any) => {
+ setQuery(code);
+ errChecker(code, subLogQuery.get().streamName);
+ monacoRef.current?.editor.setModelMarkers(editorRef.current?.getModel(), 'owner', ErrorMarker);
+ };
- useEffect(() => {
- if (subRefreshInterval.get()) {
- const interval = setInterval(() => {
- runQuery();
- }, subRefreshInterval.get() as number);
- return () => clearInterval(interval);
- }
- }, [refreshInterval, query]);
+ useEffect(() => {
+ if (subRefreshInterval.get()) {
+ const interval = setInterval(() => {
+ runQuery();
+ }, subRefreshInterval.get() as number);
+ return () => clearInterval(interval);
+ }
+ }, [refreshInterval, query]);
- useEffect(() => {
- const listener = subSchemaToggle.subscribe(setIsSchemaOpen);
- const refreshIntervalListener = subRefreshInterval.subscribe(setRefreshInterval);
- return () => {
- listener();
- refreshIntervalListener();
- };
- }, [subSchemaToggle.get()]);
+ useEffect(() => {
+ const listener = subSchemaToggle.subscribe(setIsSchemaOpen);
+ const refreshIntervalListener = subRefreshInterval.subscribe(setRefreshInterval);
+ return () => {
+ listener();
+ refreshIntervalListener();
+ };
+ }, [subSchemaToggle.get()]);
- useEffect(() => {
- if (subLogQuery.get().streamName) {
- setQuery(`SELECT * FROM ${subLogQuery.get().streamName} LIMIT 100;`);
- result.set("");
- }
- }, [subLogQuery.get().streamName]);
+ useEffect(() => {
+ if (subLogQuery.get().streamName) {
+ setQuery(`SELECT * FROM ${subLogQuery.get().streamName} LIMIT 100;`);
+ result.set('');
+ }
+ }, [subLogQuery.get().streamName]);
- function handleEditorDidMount(editor: any, monaco: any) {
- editorRef.current = editor;
- monacoRef.current = monaco;
- }
- const runQuery = () => {
- resetData();
- notifications.show({
- id: 'load-data',
- loading: true,
- color: '#545BEB',
- title: 'Running Query',
- message: 'Data will be loaded.',
- autoClose: false,
- withCloseButton: false,
- });
- if (subLogSelectedTimeRange.get().includes('Past')) {
- const now = dayjs();
- const timeDiff = subLogQuery.get().endTime.getTime() - subLogQuery.get().startTime.getTime();
- subLogQuery.set((state) => {
- state.startTime = now.subtract(timeDiff).toDate();
- state.endTime = now.toDate();
- });
- }
- const parsedQuery = query.replace(/(\r\n|\n|\r)/gm, "");
- getQueryData(subLogQuery.get(), parsedQuery);
+ function handleEditorDidMount(editor: any, monaco: any) {
+ editorRef.current = editor;
+ monacoRef.current = monaco;
+ }
+ const runQuery = () => {
+ resetData();
+ notifications.show({
+ id: 'load-data',
+ loading: true,
+ color: '#545BEB',
+ title: 'Running Query',
+ message: 'Data will be loaded.',
+ autoClose: false,
+ withCloseButton: false,
+ });
+ if (subLogSelectedTimeRange.get().includes('Past')) {
+ const now = dayjs();
+ const timeDiff = subLogQuery.get().endTime.getTime() - subLogQuery.get().startTime.getTime();
+ subLogQuery.set((state) => {
+ state.startTime = now.subtract(timeDiff).toDate();
+ state.endTime = now.toDate();
+ });
+ }
+ const parsedQuery = query.replace(/(\r\n|\n|\r)/gm, '');
+ getQueryData(subLogQuery.get(), parsedQuery);
+ };
+ useEffect(() => {
+ if (error) {
+ notifications.update({
+ id: 'load-data',
+ color: 'red',
+ title: 'Error Occured',
+ message: 'Error Occured, please check your query and try again',
+ icon: ,
+ autoClose: 2000,
+ });
+ result.set(error);
+ return;
+ }
+ if (queryResult) {
+ result.set(JSON.stringify(queryResult?.data, null, 2));
+ notifications.update({
+ id: 'load-data',
+ color: 'green',
+ title: 'Data was loaded',
+ message: 'Successfully Loaded!!',
+ icon: ,
+ autoClose: 1000,
+ });
+ return;
+ }
+ }, [queryResult, error]);
- }
- useEffect(() => {
- if (error) {
- notifications.update({
- id: 'load-data',
- color: 'red',
- title: 'Error Occured',
- message: 'Error Occured, please check your query and try again',
- icon: ,
- autoClose: 2000,
- });
- result.set(error);
- return;
- }
- if (queryResult) {
- result.set(JSON.stringify(queryResult?.data, null, 2));
- notifications.update({
- id: 'load-data',
- color: 'green',
- title: 'Data was loaded',
- message: 'Successfully Loaded!!',
- icon: ,
- autoClose: 1000,
- });
- return;
- }
+ const { classes } = useQueryCodeEditorStyles();
+ const { container, runQueryBtn, textContext, actionBtn } = classes;
- }, [queryResult, error]);
-
- const { classes } = useQueryCodeEditorStyles();
- const { container, runQueryBtn, textContext, actionBtn } = classes;
-
-
-
- return (
-
-
- Query
-
-
- subSchemaToggle.set(!isSchemaOpen)}>
-
-
-
-
-
-
-
- );
+ return (
+
+
+ Query
+
+ subSchemaToggle.set(!isSchemaOpen)}>
+
+
+
+
+
+
+
+
+
+
+
+ );
};
export default QueryCodeEditor;
diff --git a/src/pages/Query/QueryResultEditor.tsx b/src/pages/Query/QueryResultEditor.tsx
index b07f4a87..8ab12e29 100644
--- a/src/pages/Query/QueryResultEditor.tsx
+++ b/src/pages/Query/QueryResultEditor.tsx
@@ -3,91 +3,93 @@ import Editor from '@monaco-editor/react';
import { useQueryPageContext } from './Context';
import useMountedState from '@/hooks/useMountedState';
import { Box, Button, Text, px } from '@mantine/core';
-import { IconClipboard , IconSearch, IconCheck } from '@tabler/icons-react';
+import { IconClipboard, IconSearch, IconCheck } from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { useQueryResultEditorStyles } from './styles';
-
const QueryResultEditor: FC = () => {
- const { state: { result } } = useQueryPageContext();
- const [resultValue, setResultValue] = useMountedState(result.get());
- const editorRef = React.useRef();
- const monacoRef = React.useRef();
-
-
- useEffect(() => {
- const resultValueListener = result.subscribe(setResultValue);
-
- return () => {
- resultValueListener();
- }
+ const {
+ state: { result },
+ } = useQueryPageContext();
+ const [resultValue, setResultValue] = useMountedState(result.get());
+ const editorRef = React.useRef();
+ const monacoRef = React.useRef();
- }, [resultValue]);
+ useEffect(() => {
+ const resultValueListener = result.subscribe(setResultValue);
- const formatJSON = (jsonString: string) => {
- try {
- const jsonObject = JSON.parse(jsonString);
- return JSON.stringify(jsonObject, null, 2);
- } catch (e) {
- return jsonString;
- }
- }
- function handleEditorDidMount(editor: any, monaco: any) {
- editorRef.current = editor;
- monacoRef.current = monaco;
- }
+ return () => {
+ resultValueListener();
+ };
+ }, [resultValue]);
- const runFind = () => {
- editorRef.current?.getAction('actions.find').run();
- }
- const runCopy = () => {
- // Focus the editor.
- editorRef.current?.focus();
- // Get the current editor's content.
- const data = editorRef.current?.getModel()?.getValue();
- // Set the clipboard contents.
- navigator.clipboard.writeText(data || "");
+ const formatJSON = (jsonString: string) => {
+ try {
+ const jsonObject = JSON.parse(jsonString);
+ return JSON.stringify(jsonObject, null, 2);
+ } catch (e) {
+ return jsonString;
+ }
+ };
+ function handleEditorDidMount(editor: any, monaco: any) {
+ editorRef.current = editor;
+ monacoRef.current = monaco;
+ }
- notifications.show({
- color: 'green',
- icon: ,
- message: 'Copy Successfully',
- autoClose: 1000,
- })
- }
- const { classes } = useQueryResultEditorStyles();
- const { actionBtn, container, textContext } = classes;
- return (
+ const runFind = () => {
+ editorRef.current?.getAction('actions.find').run();
+ };
+ const runCopy = () => {
+ // Focus the editor.
+ editorRef.current?.focus();
+ // Get the current editor's content.
+ const data = editorRef.current?.getModel()?.getValue();
+ // Set the clipboard contents.
+ navigator.clipboard.writeText(data || '');
-
-
- Result
-
-
-
-
-
-
-
-
-
- );
+ notifications.show({
+ color: 'green',
+ icon: ,
+ message: 'Copy Successfully',
+ autoClose: 1000,
+ });
+ };
+ const { classes } = useQueryResultEditorStyles();
+ const { actionBtn, container, textContext } = classes;
+ return (
+
+
+ Result
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
};
-export default QueryResultEditor;
\ No newline at end of file
+export default QueryResultEditor;
diff --git a/src/pages/Query/QuerySchemaList.tsx b/src/pages/Query/QuerySchemaList.tsx
index 74ebdced..059be4db 100644
--- a/src/pages/Query/QuerySchemaList.tsx
+++ b/src/pages/Query/QuerySchemaList.tsx
@@ -2,14 +2,18 @@ import { FC, useEffect } from 'react';
import { useGetLogStreamSchema } from '@/hooks/useGetLogStreamSchema';
import { useHeaderContext } from '@/layouts/MainLayout/Context';
import { Table, Box, Title, Button, ScrollArea } from '@mantine/core';
-import { IconX } from '@tabler/icons-react';
-import { useQuerySchemaListStyles } from './styles';
+import { IconX } from '@tabler/icons-react';
+import { useQuerySchemaListStyles } from './styles';
import { useQueryPageContext } from './Context';
const QuerySchemaList: FC = () => {
const { data: querySchema, getDataSchema, resetData, loading, error: logStreamSchemaError } = useGetLogStreamSchema();
- const { state: { subLogQuery } } = useHeaderContext();
- const { state: { subSchemaToggle } } = useQueryPageContext();
+ const {
+ state: { subLogQuery },
+ } = useHeaderContext();
+ const {
+ state: { subSchemaToggle },
+ } = useQueryPageContext();
useEffect(() => {
if (subLogQuery.get().streamName) {
@@ -21,14 +25,14 @@ const QuerySchemaList: FC = () => {
}, [subLogQuery.get()]);
const renderList = querySchema?.fields.map((field, index) => {
- if(typeof field.data_type === "string")
- return (
-
- {field.name}
- {field.data_type}
-
- );
- else{
+ if (typeof field.data_type === 'string')
+ return (
+
+ {field.name}
+ {field.data_type}
+
+ );
+ else {
return (
{field.name}
@@ -38,29 +42,31 @@ const QuerySchemaList: FC = () => {
}
});
-const {classes} = useQuerySchemaListStyles();
- const {actionBtn ,container ,textContext ,theadSt ,tbodySt ,innercontainer,scrollAreaSt}=classes;
+ const { classes } = useQuerySchemaListStyles();
+ const { actionBtn, container, textContext, theadSt, tbodySt, innercontainer, scrollAreaSt } = classes;
return (
-
-
- Schema for {subLogQuery.get().streamName}
- subSchemaToggle.set((state)=>!state) }>
-
-
- {!(logStreamSchemaError) ? (
+
+
+ Schema for {subLogQuery.get().streamName}
+ subSchemaToggle.set((state) => !state)}>
+
+
+
+
+ {!logStreamSchemaError ? (
!loading && Boolean(querySchema) ? (
- (querySchema?.fields.length) ? (
-
-
-
-
- Feild
- Type
-
-
- {renderList}
-
+ querySchema?.fields.length ? (
+
+
+
+
+ Feild
+ Type
+
+
+ {renderList}
+
) : (
No Data
@@ -71,7 +77,6 @@ const {classes} = useQuerySchemaListStyles();
) : (
Error: {logStreamSchemaError}
)}
-
);
};
diff --git a/src/pages/Query/index.tsx b/src/pages/Query/index.tsx
index ec29c773..9adb1913 100644
--- a/src/pages/Query/index.tsx
+++ b/src/pages/Query/index.tsx
@@ -2,7 +2,7 @@ import { Box } from '@mantine/core';
import { useDocumentTitle } from '@mantine/hooks';
import { FC, useEffect } from 'react';
import { useQueryStyles } from './styles';
-import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
+import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import QueryCodeEditor from './QueryCodeEditor';
import QueryResultEditor from './QueryResultEditor';
import QuerySchemaList from './QuerySchemaList';
@@ -13,48 +13,50 @@ const Logs: FC = () => {
useDocumentTitle('Parseable | Query');
const { classes } = useQueryStyles();
- const { container,innerContainer,schemaContainer } = classes;
- const { state: { subSchemaToggle } } = useQueryPageContext();
+ const { container, innerContainer, schemaContainer } = classes;
+ const {
+ state: { subSchemaToggle },
+ } = useQueryPageContext();
const [isSchemaOpen, setIsSchemaOpen] = useMountedState(false);
useEffect(() => {
const listener = subSchemaToggle.subscribe(setIsSchemaOpen);
return () => {
- listener();
+ listener();
};
- }, [subSchemaToggle.get()]);
-
+ }, [subSchemaToggle.get()]);
return (
-
-
-
-
-
+
+
+
+
-
- …
-
-
-
-
-
-
+ …
+
+
+
+
+
+
-
-
+
+
-
);
};
diff --git a/src/pages/Query/styles.tsx b/src/pages/Query/styles.tsx
index f48c0b0c..0bf690f4 100644
--- a/src/pages/Query/styles.tsx
+++ b/src/pages/Query/styles.tsx
@@ -3,8 +3,8 @@ import { HEADER_HEIGHT, NAVBAR_WIDTH } from '@/constants/theme';
import { createStyles } from '@mantine/core';
export const useQueryStyles = createStyles((theme) => {
- const { colors ,shadows } = theme;
- const { heights, widths, } = theme.other;
+ const { colors, shadows } = theme;
+ const { heights, widths } = theme.other;
const sColor = colors.brandSecondary[0];
const pColor = colors.brandPrimary[0];
@@ -26,9 +26,9 @@ export const useQueryStyles = createStyles((theme) => {
},
runQueryBtn: {
background: pColor,
- color: "white",
- height: "40px",
- marginRight: "5px",
+ color: 'white',
+ height: '40px',
+ marginRight: '5px',
'&:hover': {
background: sColor,
},
@@ -37,13 +37,14 @@ export const useQueryStyles = createStyles((theme) => {
'&:hover': {
color: sColor,
},
- height: "25px", marginRight: "5px"
+ height: '25px',
+ marginRight: '5px',
},
- schemaContainer:{
+ schemaContainer: {
border: `${widths.px} ${colors.gray[1]} solid`,
boxShadow: shadows.sm,
- maxWidth: "500px"
- }
+ maxWidth: '500px',
+ },
};
});
@@ -55,110 +56,104 @@ export const useQueryCodeEditorStyles = createStyles((theme) => {
return {
container: {
- display: "flex",
+ display: 'flex',
borderBottom: `solid 1px ${colors.gray[2]}`,
- alignItems: "center",
- height: "55px",
- padding: `${spacing.xs} ${spacing.md}` ,
- width:"100%"
+ alignItems: 'center',
+ height: '55px',
+ padding: `${spacing.xs} ${spacing.md}`,
+ width: '100%',
},
runQueryBtn: {
color: pColor,
borderColor: colors.gray[2],
- height: "34px",
- width: "34px",
- padding: "0px",
+ height: '34px',
+ width: '34px',
+ padding: '0px',
'&:hover': {
color: sColor,
},
marginInlineEnd: spacing.xs,
},
actionBtn: {
-
'&:hover': {
color: sColor,
},
- height: "34px",
- width: "34px",
- padding: "0px",
+ height: '34px',
+ width: '34px',
+ padding: '0px',
marginInlineEnd: spacing.xs,
color: colors.gray[5],
- borderColor:colors.gray[2]
+ borderColor: colors.gray[2],
},
textContext: {
marginRight: spacing.md,
fontSize: fontSizes.md,
fontFamily: fontFamily,
fontWeight: fontWeights.semibold,
- }
-
+ },
};
});
-
export const useQueryResultEditorStyles = createStyles((theme) => {
- const { colors, spacing ,fontFamily,fontSizes} = theme;
+ const { colors, spacing, fontFamily, fontSizes } = theme;
const { fontWeights } = theme.other;
const sColor = colors.brandSecondary[0];
-
return {
container: {
- display: "flex",
- alignItems: "center",
+ display: 'flex',
+ alignItems: 'center',
borderBottom: `solid 1px ${colors.gray[2]}`,
- height: "55px",
- padding: `${spacing.xs} ${spacing.md}` ,
+ height: '55px',
+ padding: `${spacing.xs} ${spacing.md}`,
},
actionBtn: {
-
'&:hover': {
color: sColor,
},
- height: "34px",
- width: "34px",
- padding: "0px",
+ height: '34px',
+ width: '34px',
+ padding: '0px',
marginInlineEnd: spacing.xs,
color: colors.gray[5],
- borderColor:colors.gray[2]
+ borderColor: colors.gray[2],
},
textContext: {
marginRight: spacing.md,
fontSize: fontSizes.md,
fontFamily: fontFamily,
fontWeight: fontWeights.semibold,
- }
+ },
};
});
-
export const useQuerySchemaListStyles = createStyles((theme) => {
- const { colors ,spacing ,fontFamily,fontSizes} = theme;
- const {fontWeights } = theme.other;
+ const { colors, spacing, fontFamily, fontSizes } = theme;
+ const { fontWeights } = theme.other;
const sColor = colors.brandSecondary[0];
return {
container: {
- maxWidth: "500px",
- height: "100%",
- overflow: "auto",
+ maxWidth: '500px',
+ height: '100%',
+ overflow: 'auto',
},
innercontainer: {
- display: "flex",
- alignItems: "center",
+ display: 'flex',
+ alignItems: 'center',
borderBottom: `solid 1px ${colors.gray[2]}`,
- height: "55px",
- padding: `${spacing.xs} ${spacing.md}` ,
- justifyContent:"space-between"
- ,paddingRight:0,
+ height: '55px',
+ padding: `${spacing.xs} ${spacing.md}`,
+ justifyContent: 'space-between',
+ paddingRight: 0,
},
actionBtn: {
color: colors.gray[3],
- height: "34px",
- width: "34px",
- padding: "0px",
- borderColor:colors.gray[2],
+ height: '34px',
+ width: '34px',
+ padding: '0px',
+ borderColor: colors.gray[2],
'&:hover': {
color: sColor,
},
@@ -169,29 +164,29 @@ export const useQuerySchemaListStyles = createStyles((theme) => {
fontFamily: fontFamily,
fontWeight: fontWeights.semibold,
},
- theadSt:{
- "& tr>th":{
- backgroundColor:colors.gray[0],
+ theadSt: {
+ '& tr>th': {
+ backgroundColor: colors.gray[0],
fontSize: fontSizes.md,
fontFamily: fontFamily,
fontWeight: fontWeights.semibold,
- color:colors.gray[3],
- height: "34px"
- }
+ color: colors.gray[3],
+ height: '34px',
+ },
},
- tbodySt:{
- "& tr>td":{
+ tbodySt: {
+ '& tr>td': {
fontSize: fontSizes.md,
fontFamily: fontFamily,
- borderTop:`solid 1px ${colors.gray[2]} !important`,
- color:colors.gray[6],
- height: "34px"
- }
- },
- scrollAreaSt:{
- maxWidth: "500px",
- height: "calc(100% - 55px)",
- overflow: "auto",
- }
+ borderTop: `solid 1px ${colors.gray[2]} !important`,
+ color: colors.gray[6],
+ height: '34px',
+ },
+ },
+ scrollAreaSt: {
+ maxWidth: '500px',
+ height: 'calc(100% - 55px)',
+ overflow: 'auto',
+ },
};
-});
\ No newline at end of file
+});
diff --git a/src/pages/Stats/Alerts.tsx b/src/pages/Stats/Alerts.tsx
new file mode 100644
index 00000000..886bf688
--- /dev/null
+++ b/src/pages/Stats/Alerts.tsx
@@ -0,0 +1,66 @@
+import { useGetLogStreamAlert } from '@/hooks/useGetLogStreamAlert';
+import { useGetLogStreamRetention } from '@/hooks/useGetLogStreamRetention';
+import { useGetLogStreamStat } from '@/hooks/useGetLogStreamStat';
+import { useHeaderContext } from '@/layouts/MainLayout/Context';
+import { Box, Text } from '@mantine/core';
+import { useDocumentTitle } from '@mantine/hooks';
+import { FC, useEffect } from 'react';
+import { useAlertsStyles } from './styles';
+
+const Alerts: FC = () => {
+ useDocumentTitle('Parseable | Login');
+ const {
+ state: { subLogQuery },
+ } = useHeaderContext();
+ const { data, error, loading, getLogAlert, resetData } = useGetLogStreamAlert();
+ const {
+ data: data2,
+ error: error2,
+ loading: loading2,
+ getLogRetention: getLogRetention,
+ resetData: resetData2,
+ } = useGetLogStreamRetention();
+ const {
+ data: data3,
+ error: error3,
+ loading: loading3,
+ getLogStat: getLogStat,
+ resetData: resetData3,
+ } = useGetLogStreamStat();
+ useEffect(() => {
+ getLogAlert(subLogQuery.get().streamName);
+ getLogRetention(subLogQuery.get().streamName);
+ getLogStat(subLogQuery.get().streamName);
+ return () => {
+ resetData();
+ resetData2();
+ resetData3();
+ };
+ }, [subLogQuery]);
+ const { classes } = useAlertsStyles();
+ const {
+ container,
+ headContainer,
+ statusText,
+ statusTextResult,
+ genterateContiner,
+ genterateText,
+ genterateTextResult,
+ } = classes;
+ return (
+
+
+
+ Status: {'- Receiving'}
+
+
+
+ Genterated at :
+ {'07/07/2023 - 18:01:59'}
+
+
+
+ );
+};
+
+export default Alerts;
diff --git a/src/pages/Stats/Status.tsx b/src/pages/Stats/Status.tsx
new file mode 100644
index 00000000..73732c9b
--- /dev/null
+++ b/src/pages/Stats/Status.tsx
@@ -0,0 +1,261 @@
+import { useGetLogStreamRetention } from '@/hooks/useGetLogStreamRetention';
+import { useGetLogStreamStat } from '@/hooks/useGetLogStreamStat';
+import { FIXED_DURATIONS, useHeaderContext } from '@/layouts/MainLayout/Context';
+import { useDocumentTitle } from '@mantine/hooks';
+import { FC, useEffect } from 'react';
+import { useStatCardStyles, useStatusStyles } from './styles';
+import { Box, Text, ThemeIcon, Tooltip, px } from '@mantine/core';
+import dayjs from 'dayjs';
+import { IconBrandDeezer, IconInfoCircle, IconTimelineEventText } from '@tabler/icons-react';
+import { useQueryResult } from '@/hooks/useQueryResult';
+import useMountedState from '@/hooks/useMountedState';
+function convert(val: number) {
+ // Thousands, millions, billions etc..
+ let s = ['', ' K', ' M', ' B', ' T'];
+
+ // Dividing the value by 3.
+ let sNum = Math.floor(('' + val).length / 3);
+
+ // Calculating the precised value.
+ let sVal = parseFloat((sNum != 0 ? val / Math.pow(1000, sNum) : val).toPrecision(4));
+
+ if (sVal % 1 != 0) {
+ return sVal.toFixed(1) + s[sNum];
+ }
+
+ // Appending the letter to precised val.
+ return sVal + s[sNum];
+}
+function formatBytes(a: any, b = 1) {
+ if (!+a) return '0 Bytes';
+ const c = 0 > b ? 0 : b,
+ d = Math.floor(Math.log(a) / Math.log(1024));
+ return `${parseFloat((a / Math.pow(1024, d)).toFixed(c))} ${['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'][d]
+ }`;
+}
+
+const Status: FC = () => {
+ useDocumentTitle('Parseable | Login');
+ const [statusFIXEDDURATIONS, setStatusFIXEDDURATIONS] = useMountedState(0);
+ const [status, setStatus] = useMountedState("Loading....");
+ FIXED_DURATIONS
+ const {
+ state: { subLogQuery },
+ } = useHeaderContext();
+ const {
+ data: dataRetention,
+ error: errorRetention,
+ loading: loadingRetention,
+ getLogRetention: getLogRetention,
+ resetData: resetDataRetention,
+ } = useGetLogStreamRetention();
+ const { data: queryResult, getQueryData, error: errorQueryResult, resetData: resetqueryResult } = useQueryResult();
+
+ const {
+ data: dataStat,
+ error: errorStat,
+ loading: loadingStat,
+ getLogStat,
+ resetData: resetStat,
+ } = useGetLogStreamStat();
+ useEffect(() => {
+ getLogRetention(subLogQuery.get().streamName);
+ getLogStat(subLogQuery.get().streamName);
+ getStatus();
+ return () => {
+ resetDataRetention();
+ resetStat();
+ };
+ }, []);
+
+ useEffect(() => {
+ const logQueryListener = subLogQuery.subscribe((query) => {
+ if (query.streamName) {
+ if (dataRetention) {
+ resetDataRetention();
+ resetStat();
+ resetqueryResult();
+ setStatusFIXEDDURATIONS(0);
+
+ }
+ getStatus();
+ getLogRetention(subLogQuery.get().streamName);
+ getLogStat(subLogQuery.get().streamName);
+ }
+ });
+
+ return () => {
+ logQueryListener();
+ };
+ }, [dataRetention]);
+
+ const getStatus = async () => {
+ const now = dayjs();
+ const LogQuery =
+ {
+ streamName: subLogQuery.get().streamName,
+ startTime: now.subtract(FIXED_DURATIONS[statusFIXEDDURATIONS].milliseconds, 'milliseconds').toDate(),
+ endTime: now.toDate()
+ }
+ getQueryData(LogQuery, `SELECT count(*) FROM ${subLogQuery.get().streamName} ;`);
+ }
+
+ useEffect(() => {
+ if (queryResult?.data[0] && queryResult?.data[0]["COUNT(UInt8(1))"]) {
+ setStatus(`${queryResult?.data[0]["COUNT(UInt8(1))"]} events in ${FIXED_DURATIONS[statusFIXEDDURATIONS].name}`);
+ return;
+ }
+ if (errorQueryResult) {
+ setStatus(`Not Recieved any events in ${FIXED_DURATIONS[statusFIXEDDURATIONS-1].name}`);
+ return;
+ }
+ if(queryResult?.data[0]["COUNT(UInt8(1))"]===0){
+ setStatus("Loading...");
+ if(FIXED_DURATIONS.length-1>statusFIXEDDURATIONS){
+ setStatusFIXEDDURATIONS(statusFIXEDDURATIONS+1);
+ getStatus();
+ return;
+ }
+ else{
+ setStatus(`Not Recieved any events in ${FIXED_DURATIONS[statusFIXEDDURATIONS].name}`);
+ }
+ }
+ }, [queryResult, errorQueryResult]);
+ const { classes } = useStatusStyles();
+ const {
+ container,
+ headContainer,
+ statusText,
+ statusTextResult,
+ genterateContiner,
+ genterateText,
+ genterateTextResult,
+ StatsContainer,
+ } = classes;
+ return (
+
+
+
+ Status: {status}
+
+
+
+ Genterated at :
+
+ {!loadingStat
+ ? errorStat
+ ? 'ERROR'
+ : dataStat
+ ? dayjs(dataStat?.time).format('DD-MM-YY HH:mm')
+ : 'Not found'
+ : 'Loading'}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+type statCardProps = {
+ data: { Icon: any; title: string; description: string; value: string };
+};
+
+const StatCard: FC = (props) => {
+ const { data } = props;
+ const { classes } = useStatCardStyles();
+ 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
new file mode 100644
index 00000000..8bb3daaf
--- /dev/null
+++ b/src/pages/Stats/index.tsx
@@ -0,0 +1,22 @@
+import { Box } from '@mantine/core';
+import { useDocumentTitle } from '@mantine/hooks';
+import { FC } from 'react';
+import Status from './Status';
+import Alerts from './Alerts';
+import { useStatsStyles } from './styles';
+
+const Stats: FC = () => {
+ useDocumentTitle('Parseable | Stats');
+
+ const { classes } = useStatsStyles();
+ const { container } = classes;
+ return (
+
+
+
+ {/* */}
+
+ );
+};
+
+export default Stats;
diff --git a/src/pages/Stats/styles.tsx b/src/pages/Stats/styles.tsx
new file mode 100644
index 00000000..5d6ddeb4
--- /dev/null
+++ b/src/pages/Stats/styles.tsx
@@ -0,0 +1,445 @@
+import { HEADER_HEIGHT, NAVBAR_WIDTH } from '@/constants/theme';
+import { createStyles } from '@mantine/core';
+export const useStatsStyles = createStyles((theme) => {
+ const { widths } = theme.other;
+ return {
+ container: {
+ flex: 1,
+ width: `calc(${widths.full} - ${NAVBAR_WIDTH}px)`,
+ display: 'flex',
+ position: 'relative',
+ },
+ };
+});
+
+export const useStatusStyles = createStyles((theme) => {
+ const { colors, other, spacing, radius, shadows, primaryColor, fontSizes } = theme;
+
+ const { fontWeights, widths, heights } = other;
+
+ const pColor = colors[primaryColor][2];
+
+ const defaultRadius = radius[theme.defaultRadius as string];
+
+ return {
+ container: {
+ position: 'relative',
+ flex: 1,
+ maxHeight: `calc(${heights.screen} - ${HEADER_HEIGHT}px)`,
+ display: 'flex',
+ flexDirection: 'column',
+ overflow: 'hidden',
+ },
+ headContainer: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ padding: spacing.md,
+ height: '55px',
+ alignItems: 'center',
+ borderBottom: `${widths.px} ${colors.gray[1]} solid`,
+ },
+
+ statusText: {
+ fontSize: fontSizes.md,
+ fontWeight: fontWeights.semibold,
+ color: colors.gray[6],
+ },
+ statusTextResult: {
+ color: '#00CC14',
+ },
+ genterateContiner: {
+ marginRight: spacing.sm,
+ },
+ genterateText: {
+ fontSize: fontSizes.xs,
+ fontWeight: fontWeights.semibold,
+ color: colors.gray[6],
+ },
+ genterateTextResult: {
+ fontSize: fontSizes.xs,
+ color: colors.gray[6],
+ },
+ StatsContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ padding: spacing.md,
+ borderBottom: `${widths.px} ${colors.gray[1]} solid`,
+ justifyContent: 'space-between',
+ },
+ };
+});
+export const useStatCardStyles = createStyles((theme) => {
+ const { colors, other, spacing, radius, shadows, primaryColor, fontSizes } = theme;
+
+ const { fontWeights, widths, heights } = other;
+
+ const pColor = colors[primaryColor][0];
+
+ const defaultRadius = radius[theme.defaultRadius as string];
+
+ return {
+ statCard: {
+ border: `${widths.px} ${colors.gray[1]} solid`,
+ width: '18%',
+ borderRadius: defaultRadius,
+ textAlign: 'center',
+ },
+ statCardDescription: {
+ '& .mantine-Tooltip-tooltip': {
+ color: colors.white,
+ background: colors.black[0],
+ },
+ textAlign: 'right',
+ },
+ statCardDescriptionIcon: {
+ color: colors.gray[6],
+ margin: `${spacing.xs} ${spacing.xs} 0 0`,
+ },
+ statCardIcon: {
+ backgroundColor: '#e7eeff',
+ color: pColor,
+ marginTop: '-25px',
+ },
+ statCardTitleValue: {
+ fontSize: '30px',
+ fontWeight: fontWeights.bold,
+ color: pColor,
+ },
+ statCardTitle: {
+ fontSize: fontSizes.lg,
+ fontWeight: fontWeights.semibold,
+ color: colors.gray[6],
+ paddingBottom: spacing.md,
+ },
+ };
+});
+
+export const useAlertsStyles = createStyles((theme) => {
+ const { colors, other, spacing, radius, shadows, primaryColor, fontSizes } = theme;
+
+ const { fontWeights, widths, heights } = other;
+
+ const pColor = colors[primaryColor][2];
+
+ const defaultRadius = radius[theme.defaultRadius as string];
+
+ return {
+ container: {
+ position: 'relative',
+ flex: 1,
+ maxHeight: `calc(${heights.screen} - ${HEADER_HEIGHT}px)`,
+ display: 'flex',
+ flexDirection: 'column',
+ overflow: 'hidden',
+ },
+ headContainer: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ padding: spacing.md,
+ height: '55px',
+ alignItems: 'center',
+ borderBottom: `${widths.px} ${colors.gray[1]} solid`,
+ },
+
+ statusText: {
+ fontSize: fontSizes.md,
+ fontWeight: fontWeights.semibold,
+ color: colors.gray[6],
+ },
+ statusTextResult: {
+ color: '#00CC14',
+ },
+ genterateContiner: {
+ marginRight: spacing.sm,
+ },
+ genterateText: {
+ fontSize: fontSizes.xs,
+ fontWeight: fontWeights.semibold,
+ color: colors.gray[6],
+ },
+ genterateTextResult: {
+ fontSize: fontSizes.xs,
+ color: colors.gray[6],
+ },
+ };
+});
+
+export const useLogTableStyles = createStyles((theme) => {
+ const { spacing, other, radius, shadows, colors, fontSizes } = theme;
+ const { heights, widths, fontWeights } = other;
+ const pColor = colors.brandPrimary[0];
+ const sColor = colors.brandSecondary[0];
+
+ const defaultRadius = radius[theme.defaultRadius as string];
+
+ return {
+ container: {
+ position: 'relative',
+ flex: 1,
+ maxHeight: `calc(${heights.screen} - ${HEADER_HEIGHT}px)`,
+ display: 'flex',
+ flexDirection: 'column',
+ overflow: 'hidden',
+ // padding: px(spacing.sm),
+ },
+
+ innerContainer: {
+ position: 'relative',
+ flex: 1,
+ maxHeight: `calc(${heights.screen} - ${HEADER_HEIGHT}px)`,
+ display: 'flex',
+ flexDirection: 'column',
+ overflow: 'hidden',
+ },
+
+ paginationRow: {
+ '.mantine-Pagination-control': {
+ border: `solid 1px ${colors.gray[2]}`,
+ fontSize: fontSizes.sm,
+ '&:hover': {
+ color: sColor,
+ },
+ '&:data-active=true': {
+ color: pColor,
+ },
+ },
+ '.mantine-Pagination-control[data-active=true]': {
+ background: pColor,
+ '&:hover': {
+ backgroundColor: sColor,
+ color: colors.white[0],
+ },
+ },
+ },
+
+ tableContainer: {
+ position: 'relative',
+ },
+
+ tableStyle: {
+ whiteSpace: 'nowrap',
+ overflow: 'scroll',
+ width: '100%',
+ padding: 0,
+ },
+
+ theadStyle: {
+ position: 'sticky',
+ zIndex: 1,
+ top: 0,
+ '& th:last-of-type': {
+ position: 'sticky',
+ boxShadow: shadows.sm,
+ right: 0,
+ },
+ },
+
+ trStyle: {
+ cursor: 'pointer',
+ background: colors.white,
+ '&:hover': {
+ background: colors.gray[1],
+ },
+
+ '& td': {
+ height: heights[10],
+ textAlign: 'left',
+ verticalAlign: 'middle',
+ border: 'none !important',
+ },
+ },
+ trEvenStyle: {
+ cursor: 'pointer',
+ background: colors.gray[0],
+
+ '&:hover': {
+ background: colors.gray[1],
+ },
+
+ '& td': {
+ height: heights[10],
+ textAlign: 'left',
+ verticalAlign: 'middle',
+ border: 'none !important',
+ },
+ },
+
+ tdArrowContainer: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+
+ tdArrow: {
+ position: 'sticky',
+ right: 0,
+ background: 'inherit',
+ boxShadow: shadows.sm,
+
+ 'tr:hover &': {
+ background: colors.gray[1],
+ },
+ },
+
+ thColumnMenuBtn: {
+ width: widths[10],
+ height: heights[10],
+ },
+
+ thColumnMenuDropdown: {
+ maxHeight: heights[96],
+ overflowX: 'hidden',
+ overflowY: 'scroll',
+ },
+
+ footerContainer: {
+ padding: spacing.md,
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ background: colors.gray[0],
+ borderTop: `${widths.px} ${colors.gray[1]} solid`,
+ },
+
+ errorContainer: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: heights.full,
+ },
+
+ limitContainer: {},
+
+ limitBtn: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ cursor: 'pointer',
+ fontSize: fontSizes.sm,
+ background: colors.white[0],
+ padding: `0.2rem ${spacing.xs}`,
+ border: `${widths.px} ${colors.gray[1]} solid`,
+ borderRadius: defaultRadius,
+ '&:hover': {
+ background: colors.gray[1],
+ },
+ },
+
+ limitBtnText: {},
+
+ limitActive: {
+ background: pColor,
+ fontWeight: fontWeights.medium,
+ color: colors.white[0],
+
+ '&:hover': {
+ background: sColor,
+ },
+ },
+ limitOption: {
+ fontWeight: fontWeights.normal,
+
+ '&:hover': {
+ color: sColor,
+ },
+ },
+ };
+});
+
+export const useViewLogStyles = createStyles((theme) => {
+ const { spacing, other, colors, fontSizes } = theme;
+ const { fontWeights } = other;
+ const pColor = colors.brandPrimary[0];
+
+ return {
+ container: {
+ padding: spacing.lg,
+ },
+
+ headerContainer: {
+ background: pColor,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+
+ headerTimeStampTitle: {
+ color: colors.white,
+ fontSize: fontSizes.sm,
+ fontWeight: fontWeights.semibold,
+ },
+
+ headerTimeStamp: {
+ color: colors.white,
+ fontSize: fontSizes.sm,
+ fontWeight: fontWeights.medium,
+ },
+
+ dataChipContainer: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ };
+});
+
+export const useTableColumnStyle = createStyles((theme) => {
+ const { spacing, colors, fontSizes, other, primaryColor } = theme;
+ const { fontWeights, widths } = other;
+
+ const pColor = colors[primaryColor];
+
+ return {
+ labelBtn: {
+ width: widths.full,
+ display: 'flex',
+ alignItems: 'center',
+ textAlign: 'left',
+ height: '100%',
+ '&:hover': {
+ background: colors.gray[1],
+ },
+ },
+
+ labelIcon: {
+ color: colors.gray[5],
+ marginLeft: spacing.xs,
+ },
+
+ labelIconActive: {
+ color: pColor[0],
+ },
+
+ searchInputStyle: {
+ marginBottom: spacing.xs,
+ },
+
+ checkBoxStyle: {
+ height: 35,
+ paddingTop: spacing.xs,
+ paddingBottom: spacing.xxs,
+ fontWeight: fontWeights.medium,
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+
+ '& .mantine-Checkbox-label': {
+ fontSize: fontSizes.sm,
+ },
+
+ '&:hover': {
+ background: colors.gray[1],
+ },
+ },
+
+ applyBtn: {
+ marginTop: spacing.xs,
+ width: widths.full,
+ background: pColor[0],
+
+ '&:hover': {
+ background: pColor[1],
+ },
+ },
+ };
+});
diff --git a/src/routes/elements.tsx b/src/routes/elements.tsx
index 11a6ef9a..c82f301a 100644
--- a/src/routes/elements.tsx
+++ b/src/routes/elements.tsx
@@ -43,13 +43,20 @@ export const QueryElement: FC = () => {
);
};
-
-
export const MainLayoutElement: FC = () => {
return (
+
+
+
+ );
+};
+
+const Stats = lazy(() => import('@/pages/Stats'));
-
-
-
+export const StatsElement: FC = () => {
+ return (
+
+
+
);
-}
+};
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 5d3d4fa5..1b27492e 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -1,10 +1,10 @@
-import { ALL_ROUTE, HOME_ROUTE, LOGIN_ROUTE, LOGS_ROUTE, QUERY_ROUTE} from '@/constants/routes';
+import { ALL_ROUTE, HOME_ROUTE, LOGIN_ROUTE, LOGS_ROUTE, QUERY_ROUTE, STATS_ROUTE } from '@/constants/routes';
import FullPageLayout from '@/layouts/FullPageLayout';
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, QueryElement ,MainLayoutElement} from './elements';
+import { HomeElement, LoginElement, LogsElement, QueryElement, MainLayoutElement, StatsElement } from './elements';
const AppRouter: FC = () => {
return (
@@ -15,6 +15,7 @@ const AppRouter: FC = () => {
} />
} />
} />
+ } />
} />
diff --git a/tsconfig.json b/tsconfig.json
index 87aae91a..ce947166 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,28 +1,28 @@
{
- "compilerOptions": {
- "target": "ESNext",
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
- "module": "ESNext",
- "skipLibCheck": true,
- "baseUrl": ".",
- "paths": {
- "@/*": ["src/*"]
- },
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ },
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx",
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
- },
- "include": ["src"],
- "references": [{ "path": "./tsconfig.node.json" }]
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 42872c59..eca66688 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -1,10 +1,10 @@
{
- "compilerOptions": {
- "composite": true,
- "skipLibCheck": true,
- "module": "ESNext",
- "moduleResolution": "bundler",
- "allowSyntheticDefaultImports": true
- },
- "include": ["vite.config.ts"]
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
}