Skip to content

Commit

Permalink
Merge pull request #113 from mahmednabil109/add_logs_table
Browse files Browse the repository at this point in the history
Add logs table ui
  • Loading branch information
mimir-d authored Aug 3, 2022
2 parents 1d0a68a + 48e5802 commit cd42c60
Show file tree
Hide file tree
Showing 13 changed files with 4,749 additions and 881 deletions.
4,535 changes: 3,749 additions & 786 deletions cmds/admin_server/server/static.go

Large diffs are not rendered by default.

791 changes: 706 additions & 85 deletions cmds/admin_server/ui/package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions cmds/admin_server/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@babel/preset-typescript": "^7.18.6",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/superagent": "^4.1.15",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"html-webpack-plugin": "^5.5.0",
Expand All @@ -31,12 +32,14 @@
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"rsuite": "^5.16.1",
"superagent": "^8.0.0"
},
"prettier": {
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 4,
"useTabs": false
}
}
}
39 changes: 39 additions & 0 deletions cmds/admin_server/ui/src/api/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import superagent from 'superagent';

// TODO: remove the hardcoded levels
// logLevels is the possible levels for logs
export const Levels = ['panic', 'fatal', 'error', 'warning', 'info', 'debug'];

// Log defines the expected log entry returned from the api
export interface Log {
job_id: number;
log_data: string;
log_level: string;
date: string;
}

// Query defines all the possible filters to form a query
export interface Query {
job_id?: number;
text?: string;
log_level?: string;
start_date?: string;
end_date?: string;
page: number;
page_size: number;
}

// Result defines the structure of the api response to a query
export interface Result {
logs: Log[] | null;
count: number;
page: number;
page_size: number;
}

// getLogs returns Result that contains logs fetched according to the Query
export async function getLogs(query: Query): Promise<Result> {
let result: superagent.Response = await superagent.get('/log').query(query);

return result.body;
}
5 changes: 5 additions & 0 deletions cmds/admin_server/ui/src/app.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
9 changes: 4 additions & 5 deletions cmds/admin_server/ui/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import '../styles/app.scss';
import './app.scss';
import SearchLogs from './search_logs/search_logs';

function App() {
return <h1>Context is not Contest</h1>;
export default function App() {
return <SearchLogs />;
}

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { Cell, CellProps } from 'rsuite-table';

export default function DateCell({ rowData, dataKey, ...props }: CellProps) {
return (
<Cell {...props}>
<p>{dataKey && new Date(rowData[dataKey]).toLocaleString()}</p>
</Cell>
);
}
14 changes: 14 additions & 0 deletions cmds/admin_server/ui/src/search_logs/log_table/log_table.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.log-table {
.log-table__search-btn {
display: block !important;
margin: auto;
}

.log-table__cell {
padding: 2px;
}

.log-table__pagination {
padding: 2px;
}
}
125 changes: 125 additions & 0 deletions cmds/admin_server/ui/src/search_logs/log_table/log_table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useState } from 'react';
import { Table, Pagination, Button, useToaster, Message } from 'rsuite';
import { Column, Cell, HeaderCell } from 'rsuite-table';
import { StandardProps } from 'rsuite-table/lib/@types/common';
import { getLogs, Log, Result } from '../../api/logs';
import { TypeAttributes } from 'rsuite/esm/@types/common';
import DateCell from './date_cell/date_cell';
import 'rsuite/dist/rsuite.min.css';
import './log_table.scss';

export interface LogTableProps extends StandardProps {
logLevels?: string;
queryText?: string;
jobID?: number;
startDate?: Date;
endDate?: Date;
}

export default function LogTable({
logLevels,
queryText,
jobID,
startDate,
endDate,
}: LogTableProps) {
const [loading, setLoading] = useState<boolean>(false);
const [logs, setLogs] = useState<Log[] | null>([]);
const [count, setCount] = useState<number>(0);
const [page, setPage] = useState<number>(0);
const [limit, setLimit] = useState<number>(20);

const toaster = useToaster();
const pageSizes = [20, 50, 100];

const showMsg = (type: TypeAttributes.Status, message: string) => {
toaster.push(
<Message showIcon type={type}>
{message}
</Message>,
{ placement: 'topEnd' }
);
};
const updateLogsTable = async (page: number, limit: number) => {
setLoading(true);
try {
let result: Result = await getLogs({
job_id: jobID ?? undefined,
text: queryText,
page: page,
page_size: limit,
log_level: logLevels,
start_date: startDate?.toJSON(),
end_date: endDate?.toJSON(),
});

setLogs(result.logs);
setCount(result.count);
setPage(result.page);
setLimit(result.page_size);
} catch (err) {
console.log(err);
showMsg('error', err?.message);
}
setLoading(false);
};

return (
<div className="log-table">
<div>
<Button
className="log-table__search-btn"
color="green"
appearance="primary"
onClick={() => updateLogsTable(0, limit)}
>
Search
</Button>
</div>
<Table
loading={loading}
height={700}
data={logs ?? []}
wordWrap="break-word"
rowHeight={30}
>
<Column width={80} align="center" fixed>
<HeaderCell>JobID</HeaderCell>
<Cell className="log-table__cell" dataKey="job_id" />
</Column>
<Column width={250} align="center" fixed>
<HeaderCell>Date</HeaderCell>
<DateCell className="log-table__cell" dataKey="date" />
</Column>
<Column width={80} align="center" fixed>
<HeaderCell>Level</HeaderCell>
<Cell className="log-table__cell" dataKey="log_level" />
</Column>
<Column width={600} align="left" flexGrow={1}>
<HeaderCell>Data</HeaderCell>
<Cell className="log-table__cell" dataKey="log_data" />
</Column>
</Table>
<div>
<Pagination
prev
next
first
last
ellipsis
boundaryLinks
className="log-table__pagination"
maxButtons={5}
size="xs"
layout={['total', '-', 'limit', '|', 'pager', 'skip']}
total={count}
limitOptions={pageSizes}
limit={limit}
activePage={page + 1}
onChangePage={(page) => updateLogsTable(page - 1, limit)}
onChangeLimit={(limit) => updateLogsTable(0, limit)}
/>
</div>
</div>
);
}
17 changes: 17 additions & 0 deletions cmds/admin_server/ui/src/search_logs/search_logs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.logs-search {
padding: 1.3em;

.logs-search__input-group {
display: flex;
align-items: center;
padding: 0.3em;

p {
width: 9em;
}

.filter-input {
width: 30em;
}
}
}
73 changes: 73 additions & 0 deletions cmds/admin_server/ui/src/search_logs/search_logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useState, useEffect, useMemo, useRef } from 'react';
import { Input, DateRangePicker, TagPicker, InputNumber } from 'rsuite';
import LogTable from './log_table/log_table';
import { Levels } from '../api/logs';
import './search_logs.scss';

export default function SearchLogs() {
const [queryText, setQueryText] = useState<string>('');
const [jobID, setJobID] = useState<number | null>(null);
const [logLevels, setLogLevels] = useState<string[]>([]);
const [dateRange, setDateRange] = useState<[Date, Date] | null>(null);

const levelTags = useMemo(
() => Levels.map((l) => ({ key: l, label: l })),
Levels
);

return (
<div className="logs-search">
<div className="logs-search__input-group">
<p> Search: </p>
<Input
className="filter-input"
placeholder="log data"
value={queryText}
onChange={setQueryText}
/>
</div>
<div className="logs-search__input-group">
<p> Job ID: </p>
<InputNumber
className="filter-input"
placeholder="Job ID"
min={0}
value={jobID ?? ''}
onChange={(id) =>
setJobID(id === '' ? null : parseInt(String(id)))
}
/>
</div>
<div className="logs-search__input-group">
<p>Date Range:</p>
<DateRangePicker
className="filter-input"
format="yyyy-MM-dd HH:mm:ss"
value={dateRange}
onChange={setDateRange}
/>
</div>
<div className="logs-search__input-group">
<p>Levels:</p>
<TagPicker
className="filter-input"
data={levelTags}
labelKey="label"
valueKey="key"
value={logLevels}
onChange={setLogLevels}
cleanable={false}
/>
</div>
<LogTable
queryText={queryText}
jobID={jobID ?? undefined}
logLevels={
logLevels.length > 0 ? logLevels.join(',') : undefined
}
startDate={dateRange ? dateRange[0] : undefined}
endDate={dateRange ? dateRange[1] : undefined}
/>
</div>
);
}
3 changes: 0 additions & 3 deletions cmds/admin_server/ui/styles/app.scss

This file was deleted.

2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ services:
# then we run admin_server from the cmds
dockerfile: docker/contest/Dockerfile
command: bash -c "cd /go/src/github.com/linuxboot/contest/cmds/admin_server/ && go run . -port 8000 -dbURI 'mongodb://mongostorage:27017'"
ports:
- 8000:8000
healthcheck:
test: curl --fail -X GET http://localhost:8000/status || exit 1
interval: 5s
Expand Down

0 comments on commit cd42c60

Please sign in to comment.