Skip to content

Commit c531be0

Browse files
committed
feat: adds modal and right drawer as well as dropdown for debug level
1 parent 7000ae0 commit c531be0

8 files changed

Lines changed: 299 additions & 49 deletions

File tree

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,77 @@
1+
import { useState } from "react";
12
import { Col, Container, Row } from "react-bootstrap";
23
import ScrollToBottom from "react-scroll-to-bottom";
3-
import { Message } from "./DebugConsole";
4+
import { LogMessage } from "../../shared/types/LogMessage";
5+
import LogMessageDetailDrawer from "./LogMessageDetailDrawer";
46

57
const Content = ({ filteredItems }: ConsoleWindowProps) => {
8+
const [showDrawer, setShowDrawer] = useState(false);
9+
const [selectedItem, setSelectedItem] = useState<LogMessage>();
10+
11+
function clickItem(message: LogMessage) {
12+
setShowDrawer(true);
13+
setSelectedItem(message);
14+
}
15+
16+
function handleClose() {
17+
setShowDrawer(false);
18+
setSelectedItem(undefined);
19+
}
20+
621
return (
7-
<Container fluid>
8-
{filteredItems.map((message, index) => (
9-
<Row key={index}>
10-
<Col md={6}>{message.Timestamp}</Col>
11-
<Col md={3}>{message.Properties?.Key || "global"}</Col>
12-
<Col md={2}>{message.Level}</Col>
13-
<Col md={13}>{message.RenderedMessage}</Col>
14-
</Row>
15-
))}
16-
</Container>
22+
<>
23+
<Container fluid>
24+
{filteredItems.map((message, index) => (
25+
<Row
26+
key={index}
27+
onClick={() => clickItem(message)}
28+
className={selectedItem === message ? "bg-primary text-white cursor-pointer" : "cursor-pointer"}
29+
>
30+
<Col md={6}>{message.Timestamp}</Col>
31+
<Col md={3}>{message.Properties?.Key || "global"}</Col>
32+
<Col md={2}>{message.Level}</Col>
33+
<Col md={13} className="text-nowrap text-truncate">
34+
{message.RenderedMessage}
35+
</Col>
36+
</Row>
37+
))}
38+
</Container>
39+
40+
<LogMessageDetailDrawer
41+
show={showDrawer}
42+
message={selectedItem}
43+
handleClose={handleClose}
44+
/>
45+
</>
1746
);
1847
};
1948

2049
const ConsoleWindow = ({ filteredItems }: ConsoleWindowProps) => {
2150
return (
2251
<>
23-
<Container fluid>
24-
<Row className="fw-bold">
25-
<Col md={6}>Timestamp</Col>
26-
<Col md={3}>Key</Col>
27-
<Col md={2}>Level</Col>
28-
<Col md={13}>Message</Col>
29-
</Row>
52+
<Container fluid>
53+
<Row className="fw-bold">
54+
<Col md={6}>Timestamp</Col>
55+
<Col md={3}>Key</Col>
56+
<Col md={2}>Level</Col>
57+
<Col md={13}>Message</Col>
58+
</Row>
3059
</Container>
3160

3261
<ScrollToBottom
3362
className="overflow-auto flex-grow-1"
3463
followButtonClassName="btn btn-sm btn-outline-secondary"
3564
mode="bottom"
65+
initialScrollBehavior='auto'
3666
>
3767
<Content filteredItems={filteredItems} />
3868
</ScrollToBottom>
39-
</>
69+
</>
4070
);
4171
};
4272

4373
export default ConsoleWindow;
4474

4575
interface ConsoleWindowProps {
46-
filteredItems: Message[];
76+
filteredItems: LogMessage[];
4777
}

src/features/DebugConsole/DebugConsole.tsx

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@ import { useState } from "react";
22
import { Button, Form } from "react-bootstrap";
33
import { useDispatch } from "react-redux";
44
import ListFiltersHeader from "../../shared/ListFiltersHeader";
5+
import { LogMessage } from '../../shared/types/LogMessage';
56
import {
67
useGetDebugSessionMutation,
78
useGetDoNotLoadConfigOnNextBootQuery,
89
useSetDoNotLoadConfigOnNextBootMutation,
10+
useSetLoadConfigMutation,
911
useSetRestartMutation,
1012
useStopDebugSessionMutation,
1113
} from "../../store/apiSlice";
1214
import ConsoleWindow from "./ConsoleWindow";
1315
import { DebugFilters } from "./DebugFilters";
16+
import MinimumLogLevelDropdown from './MinimumLogLevelDropdown';
17+
import RestartConfirmModal from "./RestartConfirmModal";
1418
import { useFilteredMessages } from "./hooks/useFilteredMessages";
1519

1620
const DebugConsole = () => {
1721
//* HOOKS ***********************************************************/
1822
const [websocket, setWebsocket] = useState<WebSocket>();
19-
const [messages, setMessages] = useState<Message[]>([]);
23+
const [messages, setMessages] = useState<LogMessage[]>([]);
24+
const [showModal, setShowModal] = useState(false);
2025
const dispatch = useDispatch();
2126

2227
const { data: doNotLoadConfigOnNextBoot } =
@@ -26,6 +31,7 @@ const DebugConsole = () => {
2631
const [stopSession] = useStopDebugSessionMutation();
2732
const [setDoNotLoadConfig] = useSetDoNotLoadConfigOnNextBootMutation();
2833
const [restart] = useSetRestartMutation();
34+
const [loadConfig] = useSetLoadConfigMutation();
2935

3036
//* EFFECTS *********************************************************/
3137
const filteredItems = useFilteredMessages(messages);
@@ -55,14 +61,17 @@ const DebugConsole = () => {
5561
};
5662

5763
const clickRestart = () => {
58-
console.log("Restarting program");
64+
setShowModal(true);
65+
};
5966

60-
restart();
67+
const clickLoadConfig = () => {
68+
console.log("Loading config");
69+
loadConfig();
6170
};
6271

6372
const onMessage = (event: { data: string }) => {
6473
const data = JSON.parse(event.data);
65-
console.log(data);
74+
// console.log(data);
6675
setMessages((messages) => [...messages, data]);
6776
};
6877

@@ -72,45 +81,65 @@ const DebugConsole = () => {
7281
return (
7382
<>
7483
<div className="d-flex flex-column overflow-hidden h-100">
75-
<div className="d-flex align-items-center justify-content-start">
84+
<div className="d-flex align-items-center justify-content-start mb-2">
85+
<h2>Debug Console</h2>
86+
</div>
87+
<div className="d-flex align-items-center justify-content-start mb-2">
7688
<Button className="mx-1" variant="primary" size="sm" onClick={join}>
7789
Start Debug Session
7890
</Button>
7991
<Button className="mx-1" variant="primary" size="sm" onClick={stop}>
8092
Stop Debug Session
8193
</Button>
94+
<MinimumLogLevelDropdown />
8295
<Form.Check
83-
type="checkbox"
84-
className="mx-1"
85-
label="Do Not Load Config on Next Boot"
86-
name="doNotLoadConfig"
87-
id="doNotLoadConfig"
96+
type="checkbox"
97+
className="mx-1"
98+
label="Do Not Load Config on Next Boot"
99+
name="doNotLoadConfig"
100+
id="doNotLoadConfig"
88101
checked={doNotLoadConfigOnNextBoot?.doNotLoadConfigOnNextBoot}
89-
onChange={() => setDoNotLoadConfig(!doNotLoadConfigOnNextBoot?.doNotLoadConfigOnNextBoot)}
102+
onChange={() =>
103+
setDoNotLoadConfig(
104+
!doNotLoadConfigOnNextBoot?.doNotLoadConfigOnNextBoot
105+
)
106+
}
90107
/>
91-
<Button className="mx-1" variant="primary" size="sm" onClick={clickRestart}>
108+
<Button
109+
className="mx-1"
110+
variant="primary"
111+
size="sm"
112+
onClick={clickLoadConfig}
113+
disabled={!doNotLoadConfigOnNextBoot.doNotLoadConfigOnNextBoot}
114+
>
115+
Load Config
116+
</Button>
117+
<Button
118+
className="mx-1"
119+
variant="primary"
120+
size="sm"
121+
onClick={clickRestart}
122+
>
92123
Restart Program
93124
</Button>
94125
<span className="ps-2">Message Count: {messages.length}</span>
95126
</div>
96-
<div className="d-flex align-items-center justify-content-start">
97-
<h5>Debug Console</h5>
98-
</div>
99127
<ListFiltersHeader showSearch filters={<DebugFilters />} />
100-
<ConsoleWindow filteredItems={filteredItems} />
128+
<ConsoleWindow filteredItems={filteredItems}/>
101129
</div>
130+
131+
<RestartConfirmModal
132+
show={showModal}
133+
handleClose={() => setShowModal(false)}
134+
handleConfirm={() => {
135+
restart();
136+
setShowModal(false);
137+
}}
138+
/>
102139
</>
103140
);
104141
};
105142

106143
export default DebugConsole;
107144

108-
export interface Message {
109-
Timestamp: string;
110-
MessageTemplate: string;
111-
RenderedMessage: String;
112-
Level: string;
113-
Properties?: {
114-
Key: string;
115-
};
116-
}
145+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Offcanvas } from "react-bootstrap";
2+
import { LogMessage } from "../../shared/types/LogMessage";
3+
4+
const LogMessageDetailDrawer = ({
5+
show,
6+
message,
7+
handleClose,
8+
}: LogMessageDetailDrawerProps) => {
9+
if (!message) return null;
10+
11+
return (
12+
<Offcanvas
13+
show={show}
14+
onHide={handleClose}
15+
placement="end"
16+
backdrop={false}
17+
className="right-drawer shadow-sm border p-3"
18+
>
19+
<Offcanvas.Header closeButton>
20+
<Offcanvas.Title>Message Detail</Offcanvas.Title>
21+
</Offcanvas.Header>
22+
<Offcanvas.Body>
23+
<div>
24+
<h5>Timestamp</h5>
25+
{message.Timestamp}
26+
</div>
27+
<div>
28+
<h5>Rendered Message</h5>
29+
{message.RenderedMessage}
30+
</div>
31+
<div>
32+
<h5>Message Template</h5>
33+
{message.MessageTemplate}
34+
</div>
35+
<div>
36+
<h5>Properties</h5>
37+
<pre>{JSON.stringify(message.Properties, null, 2)}</pre>
38+
</div>
39+
</Offcanvas.Body>
40+
</Offcanvas>
41+
);
42+
};
43+
44+
export default LogMessageDetailDrawer;
45+
46+
interface LogMessageDetailDrawerProps {
47+
show: boolean;
48+
message: LogMessage | undefined;
49+
handleClose: () => void;
50+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Dropdown } from "react-bootstrap";
2+
import {
3+
useGetMinimumLogLevelQuery,
4+
useSetMinimumLogLevelMutation,
5+
} from "../../store/apiSlice";
6+
7+
const MinimumLogLevelDropdown = () => {
8+
const { data: currentLogLevel } = useGetMinimumLogLevelQuery();
9+
10+
const [setLogLevel] = useSetMinimumLogLevelMutation();
11+
12+
if (!currentLogLevel) return null;
13+
14+
return (
15+
<Dropdown>
16+
<Dropdown.Toggle
17+
variant="link"
18+
className="d-flex align-items-center gap-1 text-reset text-decoration-none p-0"
19+
>
20+
{currentLogLevel.minimumLevel}
21+
</Dropdown.Toggle>
22+
23+
<Dropdown.Menu className="shadow">
24+
<Dropdown.Item
25+
onClick={() => {
26+
setLogLevel("Information");
27+
}}
28+
>
29+
Information
30+
</Dropdown.Item>
31+
<Dropdown.Item
32+
onClick={() => {
33+
setLogLevel("Warning");
34+
}}
35+
>
36+
Warning
37+
</Dropdown.Item>
38+
<Dropdown.Item
39+
onClick={() => {
40+
setLogLevel("Error");
41+
}}
42+
>
43+
Error
44+
</Dropdown.Item>
45+
<Dropdown.Item
46+
onClick={() => {
47+
setLogLevel("Fatal");
48+
}}
49+
>
50+
Fatal
51+
</Dropdown.Item>
52+
<Dropdown.Item
53+
onClick={() => {
54+
setLogLevel("Debug");
55+
}}
56+
>
57+
Debug
58+
</Dropdown.Item>
59+
<Dropdown.Item
60+
onClick={() => {
61+
setLogLevel("Verbose");
62+
}}
63+
>
64+
Verbose
65+
</Dropdown.Item>
66+
</Dropdown.Menu>
67+
</Dropdown>
68+
);
69+
};
70+
71+
export default MinimumLogLevelDropdown;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Button, Modal } from 'react-bootstrap';
2+
3+
const RestartConfirmModal = ({show, handleClose, handleConfirm}: RestartConfirmModalProps) => {
4+
return (
5+
<Modal show={show} onHide={handleClose}>
6+
<Modal.Header closeButton>
7+
<Modal.Title>Restart Program</Modal.Title>
8+
</Modal.Header>
9+
<Modal.Body>Are you sure you want to restart the program?</Modal.Body>
10+
<Modal.Footer>
11+
<Button variant="light" onClick={handleClose}>
12+
Cancel
13+
</Button>
14+
<Button
15+
variant="danger"
16+
onClick={handleConfirm}
17+
>
18+
Restart
19+
</Button>
20+
</Modal.Footer>
21+
</Modal>
22+
23+
);
24+
}
25+
26+
export default RestartConfirmModal;
27+
28+
interface RestartConfirmModalProps {
29+
show: boolean;
30+
handleClose: () => void;
31+
handleConfirm: () => void;
32+
}

0 commit comments

Comments
 (0)