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 (