diff --git a/amqp_dashboard/frontend/.prettierrc.json b/amqp_dashboard/frontend/.prettierrc.json new file mode 100644 index 0000000..544138b --- /dev/null +++ b/amqp_dashboard/frontend/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/amqp_dashboard/frontend/src/App.css b/amqp_dashboard/frontend/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/amqp_dashboard/frontend/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/amqp_dashboard/frontend/src/App.test.tsx b/amqp_dashboard/frontend/src/App.test.tsx deleted file mode 100644 index d76787e..0000000 --- a/amqp_dashboard/frontend/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; -import App from "./App"; - -test("renders learn react link", () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/amqp_dashboard/frontend/src/App.tsx b/amqp_dashboard/frontend/src/App.tsx deleted file mode 100644 index 9cc63d9..0000000 --- a/amqp_dashboard/frontend/src/App.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import logo from "./logo.svg"; -import "./App.css"; - -function App() { - return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/amqp_dashboard/frontend/src/app.css b/amqp_dashboard/frontend/src/app.css new file mode 100644 index 0000000..96fa3f8 --- /dev/null +++ b/amqp_dashboard/frontend/src/app.css @@ -0,0 +1,17 @@ +html { + font-family: arial, sans-serif; + background-color: #282c34; + color: white; +} + +.app { + margin: 50px; +} + +table, +th, +td { + border: 1px solid white; + border-collapse: collapse; + padding: 10px; +} diff --git a/amqp_dashboard/frontend/src/app.tsx b/amqp_dashboard/frontend/src/app.tsx new file mode 100644 index 0000000..4d07d6a --- /dev/null +++ b/amqp_dashboard/frontend/src/app.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import DataPage from './components/data-page'; +import './app.css'; + +const IS_PROD = process.env.NODE_ENV === 'production'; + +const URL_PREFIX = IS_PROD ? '' : 'http://localhost:8080/'; + +const App = () => { + return ( +
+
+

AMQP Dashboard

+
+ +
+ ); +}; + +export default App; diff --git a/amqp_dashboard/frontend/src/components/data-page.tsx b/amqp_dashboard/frontend/src/components/data-page.tsx new file mode 100644 index 0000000..6d830da --- /dev/null +++ b/amqp_dashboard/frontend/src/components/data-page.tsx @@ -0,0 +1,87 @@ +import React, { FC, useCallback, useEffect, useState } from 'react'; +import Table from './table'; +import type { Data } from '../types'; + +const fetchData = async (prefix: string): Promise => { + const url = `${prefix}api/data`; + return fetch(url).then((res) => res.json()); +}; + +type Props = { + prefix: string; +}; + +const DataPage: FC = ({ prefix }) => { + const [data, setData] = useState(null); + + const refresh = useCallback(async () => { + const newData = await fetchData(prefix); + setData(newData); + }, [setData, prefix]); + + useEffect(() => { + const interval = setInterval(refresh, 1000); + + return () => clearInterval(interval); + }, [refresh]); + + return ( +
+
+

Connections

+ {data ? ( + [ + connection.id, + connection.peerAddr, + connection.channels.length, + ])} + /> + ) : ( +
Loading...
+ )} + +
+

Queues

+ {data ? ( +
[ + queue.id, + queue.name, + queue.durable ? 'Yes' : 'No', + ])} + /> + ) : ( +
Loading...
+ )} + +
+

Channels

+ {data ? ( +
+ connection.channels.map((channel) => ({ + ...channel, + connectionId: connection.id, + })) + ) + .flat() + .map((channel) => [ + channel.id, + channel.connectionId, + channel.number, + ])} + /> + ) : ( +
Loading...
+ )} + + + ); +}; + +export default DataPage; diff --git a/amqp_dashboard/frontend/src/components/table.tsx b/amqp_dashboard/frontend/src/components/table.tsx new file mode 100644 index 0000000..66a7e7d --- /dev/null +++ b/amqp_dashboard/frontend/src/components/table.tsx @@ -0,0 +1,31 @@ +import React, { FC } from 'react'; + +type Cell = string | number; + +type Row = ReadonlyArray; + +type Props = { + headers: ReadonlyArray; + rows: ReadonlyArray; +}; + +const Table: FC = ({ headers, rows }) => { + return ( +
+ + {headers.map((header) => ( + + ))} + + {rows.map((row) => ( + + {row.map((cell) => ( + + ))} + + ))} +
{header}
{cell}
+ ); +}; + +export default Table; diff --git a/amqp_dashboard/frontend/src/index.css b/amqp_dashboard/frontend/src/index.css index 4a1df4d..ec2585e 100644 --- a/amqp_dashboard/frontend/src/index.css +++ b/amqp_dashboard/frontend/src/index.css @@ -1,13 +1,13 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } diff --git a/amqp_dashboard/frontend/src/index.tsx b/amqp_dashboard/frontend/src/index.tsx index ad9cbbb..d6f53d1 100644 --- a/amqp_dashboard/frontend/src/index.tsx +++ b/amqp_dashboard/frontend/src/index.tsx @@ -1,17 +1,11 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import "./index.css"; -import App from "./App"; -import reportWebVitals from "./reportWebVitals"; +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './app'; ReactDOM.render( , - document.getElementById("root") + document.getElementById('root') ); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/amqp_dashboard/frontend/src/logo.svg b/amqp_dashboard/frontend/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/amqp_dashboard/frontend/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/amqp_dashboard/frontend/src/react-app-env.d.ts b/amqp_dashboard/frontend/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5..0000000 --- a/amqp_dashboard/frontend/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/amqp_dashboard/frontend/src/reportWebVitals.ts b/amqp_dashboard/frontend/src/reportWebVitals.ts deleted file mode 100644 index 5fa3583..0000000 --- a/amqp_dashboard/frontend/src/reportWebVitals.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReportHandler } from "web-vitals"; - -const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/amqp_dashboard/frontend/src/setupTests.ts b/amqp_dashboard/frontend/src/setupTests.ts deleted file mode 100644 index 1dd407a..0000000 --- a/amqp_dashboard/frontend/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom"; diff --git a/amqp_dashboard/frontend/src/types.ts b/amqp_dashboard/frontend/src/types.ts new file mode 100644 index 0000000..b052d40 --- /dev/null +++ b/amqp_dashboard/frontend/src/types.ts @@ -0,0 +1,21 @@ +export type Channel = { + id: string; + number: number; +}; + +export type Connection = { + id: string; + peerAddr: string; + channels: ReadonlyArray; +}; + +export type Queue = { + id: string; + name: string; + durable: boolean; +}; + +export type Data = { + connections: ReadonlyArray; + queues: ReadonlyArray; +};