Skip to content

Commit

Permalink
refactor log page
Browse files Browse the repository at this point in the history
  • Loading branch information
suchen-sci committed Oct 17, 2023
1 parent 4d482ad commit e4c7c20
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 62 deletions.
28 changes: 27 additions & 1 deletion src/apis/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ClusterType, getClusterMembers } from "./cluster";
import { ClusterType, getClusterMembers, getLogs } from "./cluster";
import useSWR, { SWRConfiguration } from 'swr'
import { getObjects } from "./object";
import React from "react";
Expand Down Expand Up @@ -53,4 +53,30 @@ export function useObjects(cluster: ClusterType, config: SWRConfiguration | unde
mutate,
isLoading,
}
}

export const getLogsSWRKey = (cluster: ClusterType, tail: number) => {
return `logs/${cluster.name}/${tail}`
}

export function useLogs(cluster: ClusterType, tail: number, config: SWRConfiguration | undefined = undefined) {
// call mutate() to refresh data
// the key is import here. useSWR will use the key to cache data.
// the name parameter here is not used, but to trick useSWR to refresh data when cluster changes.
const { data, error, isLoading, mutate } = useSWR(
getLogsSWRKey(cluster, tail),
(name: string) => {
return getLogs(cluster, tail)
}
)

React.useEffect(() => {
mutate()
}, [cluster, tail])
return {
logs: data,
error,
mutate,
isLoading
}
}
121 changes: 61 additions & 60 deletions src/app/(admin)/log/page.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,24 @@
"use client"

import React from "react"
import React, { Fragment } from "react"
import { useIntl } from "react-intl"

import { ClusterType, MemberType, getLogs } from "@/apis/cluster"
import clusterImage from '@/asserts/cluster.png'
import heartbeatSVG from '@/asserts/heartbeat.svg'
import nodeSVG from '@/asserts/node.svg'
import roleSVG from '@/asserts/role.svg'
import startSVG from '@/asserts/start.svg'
import { catchErrorMessage, isNullOrUndefined } from "@/common/utils"
import { isNullOrUndefined } from "@/common/utils"
import { useClusters } from "../../context"

import { useClusterMembers } from "@/apis/hooks"
import ErrorAlert from "@/components/ErrorAlert"
import YamlEditorDialog from "@/components/YamlEditorDialog"
import { Avatar, Button, Card, CardContent, CardHeader, Chip, CircularProgress, Grid, List, ListItem, ListItemText, Pagination, Paper, Stack, Typography, } from "@mui/material"
import yaml from 'js-yaml'
import moment from 'moment'
import Image from 'next/image'
import { useRouter } from "next/navigation"
import YamlViewer from "@/components/YamlViewer"
import { Box, CircularProgress, List, ListItem, ListItemText, Typography, } from "@mui/material"
import { SearchBarLayout, SearchText, SelectText, SwitchCluster } from "@/components/SearchBar"
import { useSnackbar } from "notistack"
import LoopIcon from '@mui/icons-material/Loop';
import Paginations from "@/components/Paginations"
import Paginations, { usePagination } from "@/components/Paginations"
import { useLogs } from "@/apis/hooks"
import ErrorAlert from "@/components/ErrorAlert"
import BlankPage from "@/components/BlankPage"

export default function Logs() {
const intl = useIntl()
const { currentCluster } = useClusters()
const { enqueueSnackbar } = useSnackbar()
const [search, setSearch] = React.useState("")

const [tail, setTail] = React.useState(logLimitOptions[0].value)
const [logs, setLogs] = React.useState([] as string[])
const filteredLogs = search === "" ? logs : logs.filter(l => l.includes(search))

const [page, setPage] = React.useState(1)
const [pageSize, setPageSize] = React.useState(50)
const pageCount = Math.ceil(filteredLogs.length / pageSize)
const [refresh, setRefresh] = React.useState(false)

const getCurrentLogs = () => {
let start = filteredLogs.length - page * pageSize
if (start < 0) {
start = 0
}
return filteredLogs.slice(start, start + pageSize).reverse()
}

const doRefresh = () => {
setRefresh(!refresh)
}

React.useEffect(() => {
setPage(1)
getLogs(currentCluster, tail).then(res => {
setLogs(res.split("\n"))
enqueueSnackbar(intl.formatMessage({ id: "app.log.getLogSuccess" }), { variant: 'success' })
}).catch(err => {
enqueueSnackbar(intl.formatMessage({ id: "app.log.getLogFailed" }, { error: catchErrorMessage(err) }), { variant: 'error' })
})
}, [tail, refresh])

React.useEffect(() => {
setPage(1)
}, [pageCount])
const { logs, error, isLoading, mutate } = useLogs(currentCluster, tail)

return (
<div>
Expand All @@ -86,14 +39,60 @@ export default function Logs() {
buttons={[
{
label: intl.formatMessage({ id: "app.log.refresh" }),
onClick: () => { doRefresh() },
onClick: () => { mutate() },
},
]}
/>
<LogContent logs={logs || ""} search={search} isLoading={isLoading} error={error} />
</div>
)
}

type LogContentProps = {
logs: string
isLoading: boolean
error: any
search: string
}

function LogContent(props: LogContentProps) {
const intl = useIntl()

const { logs, search, isLoading, error } = props
const logArray = logs ? logs.split("\n") : []
const filteredLogs = search === "" ? logArray : logArray.filter(l => l.includes(search))

const { page, setPage, pageSize, setPageSize, pageCount } = usePagination(filteredLogs.length, logPageSizeOptions[0])

const getCurrentLogs = () => {
let start = filteredLogs.length - page * pageSize
if (start < 0) {
start = 0
}
return filteredLogs.slice(start, start + pageSize).reverse()
}

if (isLoading) {
return (
<Box padding={'16px'}>
<CircularProgress />
</Box>
)
}

if (error) {
return <ErrorAlert error={error} expand={true} onClose={() => { }} />
}

if (filteredLogs.length === 0) {
return <BlankPage description={intl.formatMessage({ id: "app.general.noResult" })} />
}

return (
<Fragment>
<List>
{getCurrentLogs().map((l, index) => {
return (

<ListItem key={index}>
<ListItemText>
<Typography color={getLogColor(l)}>{l}</Typography>
Expand All @@ -110,10 +109,12 @@ export default function Logs() {
pageSizeOptions={[50, 100, 150, 200]}
onPageSizeChange={(pageSize) => { setPageSize(pageSize) }}
/>
</div>
</Fragment >
)
}

const logPageSizeOptions = [50, 100, 150, 200, 500]

const logLimitOptions = [
{ label: "500", value: 500 },
{ label: "1000", value: 1000 },
Expand All @@ -126,8 +127,8 @@ const logLimitOptions = [
const logColor = {
"INFO": "primary",
"ERROR": "error",
"WARN": "warning",
"DEBUG": "info",
"WARN": "primary",
"DEBUG": "primary",
} as {
[key: string]: string
}
Expand Down
19 changes: 18 additions & 1 deletion src/components/Paginations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Box, Button, MenuItem, Pagination, Stack, TextField } from "@mui/materi
import React from "react"
import { useIntl } from "react-intl"


export type PaginationsProps = {
pageCount: number
page: number
Expand Down Expand Up @@ -74,4 +73,22 @@ export default function Paginations(props: PaginationsProps) {
}
</Stack>
)
}

export function usePagination(length: number, initPageSize: number) {
const [page, setPage] = React.useState(1)
const [pageSize, setPageSize] = React.useState(initPageSize)
const pageCount = Math.ceil(length / pageSize)

React.useEffect(() => {
setPage(1)
}, [length, pageSize])

return {
page,
setPage,
pageSize,
setPageSize,
pageCount,
}
}

0 comments on commit e4c7c20

Please sign in to comment.