Skip to content

Commit cd42c60

Browse files
authored
Merge pull request #113 from mahmednabil109/add_logs_table
Add logs table ui
2 parents 1d0a68a + 48e5802 commit cd42c60

File tree

13 files changed

+4749
-881
lines changed

13 files changed

+4749
-881
lines changed

cmds/admin_server/server/static.go

+3,749-786
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmds/admin_server/ui/package-lock.json

+706-85
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmds/admin_server/ui/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@babel/preset-typescript": "^7.18.6",
1818
"@types/react": "^18.0.15",
1919
"@types/react-dom": "^18.0.6",
20+
"@types/superagent": "^4.1.15",
2021
"babel-loader": "^8.2.5",
2122
"css-loader": "^6.7.1",
2223
"html-webpack-plugin": "^5.5.0",
@@ -31,12 +32,14 @@
3132
},
3233
"dependencies": {
3334
"react": "^18.2.0",
34-
"react-dom": "^18.2.0"
35+
"react-dom": "^18.2.0",
36+
"rsuite": "^5.16.1",
37+
"superagent": "^8.0.0"
3538
},
3639
"prettier": {
3740
"singleQuote": true,
3841
"trailingComma": "es5",
3942
"tabWidth": 4,
4043
"useTabs": false
4144
}
42-
}
45+
}

cmds/admin_server/ui/src/api/logs.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import superagent from 'superagent';
2+
3+
// TODO: remove the hardcoded levels
4+
// logLevels is the possible levels for logs
5+
export const Levels = ['panic', 'fatal', 'error', 'warning', 'info', 'debug'];
6+
7+
// Log defines the expected log entry returned from the api
8+
export interface Log {
9+
job_id: number;
10+
log_data: string;
11+
log_level: string;
12+
date: string;
13+
}
14+
15+
// Query defines all the possible filters to form a query
16+
export interface Query {
17+
job_id?: number;
18+
text?: string;
19+
log_level?: string;
20+
start_date?: string;
21+
end_date?: string;
22+
page: number;
23+
page_size: number;
24+
}
25+
26+
// Result defines the structure of the api response to a query
27+
export interface Result {
28+
logs: Log[] | null;
29+
count: number;
30+
page: number;
31+
page_size: number;
32+
}
33+
34+
// getLogs returns Result that contains logs fetched according to the Query
35+
export async function getLogs(query: Query): Promise<Result> {
36+
let result: superagent.Response = await superagent.get('/log').query(query);
37+
38+
return result.body;
39+
}

cmds/admin_server/ui/src/app.scss

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
* {
2+
margin: 0;
3+
padding: 0;
4+
box-sizing: border-box;
5+
}

cmds/admin_server/ui/src/app.tsx

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react';
2-
import '../styles/app.scss';
2+
import './app.scss';
3+
import SearchLogs from './search_logs/search_logs';
34

4-
function App() {
5-
return <h1>Context is not Contest</h1>;
5+
export default function App() {
6+
return <SearchLogs />;
67
}
7-
8-
export default App;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import { Cell, CellProps } from 'rsuite-table';
3+
4+
export default function DateCell({ rowData, dataKey, ...props }: CellProps) {
5+
return (
6+
<Cell {...props}>
7+
<p>{dataKey && new Date(rowData[dataKey]).toLocaleString()}</p>
8+
</Cell>
9+
);
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.log-table {
2+
.log-table__search-btn {
3+
display: block !important;
4+
margin: auto;
5+
}
6+
7+
.log-table__cell {
8+
padding: 2px;
9+
}
10+
11+
.log-table__pagination {
12+
padding: 2px;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { useState } from 'react';
2+
import { Table, Pagination, Button, useToaster, Message } from 'rsuite';
3+
import { Column, Cell, HeaderCell } from 'rsuite-table';
4+
import { StandardProps } from 'rsuite-table/lib/@types/common';
5+
import { getLogs, Log, Result } from '../../api/logs';
6+
import { TypeAttributes } from 'rsuite/esm/@types/common';
7+
import DateCell from './date_cell/date_cell';
8+
import 'rsuite/dist/rsuite.min.css';
9+
import './log_table.scss';
10+
11+
export interface LogTableProps extends StandardProps {
12+
logLevels?: string;
13+
queryText?: string;
14+
jobID?: number;
15+
startDate?: Date;
16+
endDate?: Date;
17+
}
18+
19+
export default function LogTable({
20+
logLevels,
21+
queryText,
22+
jobID,
23+
startDate,
24+
endDate,
25+
}: LogTableProps) {
26+
const [loading, setLoading] = useState<boolean>(false);
27+
const [logs, setLogs] = useState<Log[] | null>([]);
28+
const [count, setCount] = useState<number>(0);
29+
const [page, setPage] = useState<number>(0);
30+
const [limit, setLimit] = useState<number>(20);
31+
32+
const toaster = useToaster();
33+
const pageSizes = [20, 50, 100];
34+
35+
const showMsg = (type: TypeAttributes.Status, message: string) => {
36+
toaster.push(
37+
<Message showIcon type={type}>
38+
{message}
39+
</Message>,
40+
{ placement: 'topEnd' }
41+
);
42+
};
43+
const updateLogsTable = async (page: number, limit: number) => {
44+
setLoading(true);
45+
try {
46+
let result: Result = await getLogs({
47+
job_id: jobID ?? undefined,
48+
text: queryText,
49+
page: page,
50+
page_size: limit,
51+
log_level: logLevels,
52+
start_date: startDate?.toJSON(),
53+
end_date: endDate?.toJSON(),
54+
});
55+
56+
setLogs(result.logs);
57+
setCount(result.count);
58+
setPage(result.page);
59+
setLimit(result.page_size);
60+
} catch (err) {
61+
console.log(err);
62+
showMsg('error', err?.message);
63+
}
64+
setLoading(false);
65+
};
66+
67+
return (
68+
<div className="log-table">
69+
<div>
70+
<Button
71+
className="log-table__search-btn"
72+
color="green"
73+
appearance="primary"
74+
onClick={() => updateLogsTable(0, limit)}
75+
>
76+
Search
77+
</Button>
78+
</div>
79+
<Table
80+
loading={loading}
81+
height={700}
82+
data={logs ?? []}
83+
wordWrap="break-word"
84+
rowHeight={30}
85+
>
86+
<Column width={80} align="center" fixed>
87+
<HeaderCell>JobID</HeaderCell>
88+
<Cell className="log-table__cell" dataKey="job_id" />
89+
</Column>
90+
<Column width={250} align="center" fixed>
91+
<HeaderCell>Date</HeaderCell>
92+
<DateCell className="log-table__cell" dataKey="date" />
93+
</Column>
94+
<Column width={80} align="center" fixed>
95+
<HeaderCell>Level</HeaderCell>
96+
<Cell className="log-table__cell" dataKey="log_level" />
97+
</Column>
98+
<Column width={600} align="left" flexGrow={1}>
99+
<HeaderCell>Data</HeaderCell>
100+
<Cell className="log-table__cell" dataKey="log_data" />
101+
</Column>
102+
</Table>
103+
<div>
104+
<Pagination
105+
prev
106+
next
107+
first
108+
last
109+
ellipsis
110+
boundaryLinks
111+
className="log-table__pagination"
112+
maxButtons={5}
113+
size="xs"
114+
layout={['total', '-', 'limit', '|', 'pager', 'skip']}
115+
total={count}
116+
limitOptions={pageSizes}
117+
limit={limit}
118+
activePage={page + 1}
119+
onChangePage={(page) => updateLogsTable(page - 1, limit)}
120+
onChangeLimit={(limit) => updateLogsTable(0, limit)}
121+
/>
122+
</div>
123+
</div>
124+
);
125+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.logs-search {
2+
padding: 1.3em;
3+
4+
.logs-search__input-group {
5+
display: flex;
6+
align-items: center;
7+
padding: 0.3em;
8+
9+
p {
10+
width: 9em;
11+
}
12+
13+
.filter-input {
14+
width: 30em;
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React, { useState, useEffect, useMemo, useRef } from 'react';
2+
import { Input, DateRangePicker, TagPicker, InputNumber } from 'rsuite';
3+
import LogTable from './log_table/log_table';
4+
import { Levels } from '../api/logs';
5+
import './search_logs.scss';
6+
7+
export default function SearchLogs() {
8+
const [queryText, setQueryText] = useState<string>('');
9+
const [jobID, setJobID] = useState<number | null>(null);
10+
const [logLevels, setLogLevels] = useState<string[]>([]);
11+
const [dateRange, setDateRange] = useState<[Date, Date] | null>(null);
12+
13+
const levelTags = useMemo(
14+
() => Levels.map((l) => ({ key: l, label: l })),
15+
Levels
16+
);
17+
18+
return (
19+
<div className="logs-search">
20+
<div className="logs-search__input-group">
21+
<p> Search: </p>
22+
<Input
23+
className="filter-input"
24+
placeholder="log data"
25+
value={queryText}
26+
onChange={setQueryText}
27+
/>
28+
</div>
29+
<div className="logs-search__input-group">
30+
<p> Job ID: </p>
31+
<InputNumber
32+
className="filter-input"
33+
placeholder="Job ID"
34+
min={0}
35+
value={jobID ?? ''}
36+
onChange={(id) =>
37+
setJobID(id === '' ? null : parseInt(String(id)))
38+
}
39+
/>
40+
</div>
41+
<div className="logs-search__input-group">
42+
<p>Date Range:</p>
43+
<DateRangePicker
44+
className="filter-input"
45+
format="yyyy-MM-dd HH:mm:ss"
46+
value={dateRange}
47+
onChange={setDateRange}
48+
/>
49+
</div>
50+
<div className="logs-search__input-group">
51+
<p>Levels:</p>
52+
<TagPicker
53+
className="filter-input"
54+
data={levelTags}
55+
labelKey="label"
56+
valueKey="key"
57+
value={logLevels}
58+
onChange={setLogLevels}
59+
cleanable={false}
60+
/>
61+
</div>
62+
<LogTable
63+
queryText={queryText}
64+
jobID={jobID ?? undefined}
65+
logLevels={
66+
logLevels.length > 0 ? logLevels.join(',') : undefined
67+
}
68+
startDate={dateRange ? dateRange[0] : undefined}
69+
endDate={dateRange ? dateRange[1] : undefined}
70+
/>
71+
</div>
72+
);
73+
}

cmds/admin_server/ui/styles/app.scss

-3
This file was deleted.

docker-compose.yml

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ services:
4040
# then we run admin_server from the cmds
4141
dockerfile: docker/contest/Dockerfile
4242
command: bash -c "cd /go/src/github.com/linuxboot/contest/cmds/admin_server/ && go run . -port 8000 -dbURI 'mongodb://mongostorage:27017'"
43+
ports:
44+
- 8000:8000
4345
healthcheck:
4446
test: curl --fail -X GET http://localhost:8000/status || exit 1
4547
interval: 5s

0 commit comments

Comments
 (0)