You've already forked microservices
mirror of
https://github.com/ebosas/microservices.git
synced 2025-06-18 22:17:48 +02:00
Create messages page
This commit is contained in:
@ -58,8 +58,16 @@ func insertToDB(d amqp.Delivery, c *pgx.Conn) bool {
|
|||||||
log.Fatalf("insert into database: %s", err)
|
log.Fatalf("insert into database: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternatively, send Cache messages from here
|
// An alternative query that returns the id of the inserted row.
|
||||||
// instead of server and backend services
|
// var id int64
|
||||||
|
// err = c.QueryRow(context.Background(), "insert into messages (message, created) values ($1, to_timestamp($2)) returning id", message.Text, message.Time/1000).Scan(&id)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("insert into database: %s", err)
|
||||||
|
// }
|
||||||
|
// fmt.Println(id)
|
||||||
|
|
||||||
|
// For cache, could send messages from here instead
|
||||||
|
// of doing it from the server and backend services.
|
||||||
// err = <Rabbit conn>.Publish(conf.Exchange, conf.KeyCache, d.Body)
|
// err = <Rabbit conn>.Publish(conf.Exchange, conf.KeyCache, d.Body)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -8,8 +8,20 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root" class="vh-100">
|
<div id="root" class="vh-100">
|
||||||
|
<nav class="navbar navbar-expand navbar-light bg-transparent">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/messages">Messages</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
<div class="container position-relative h-75">
|
<div class="container position-relative h-75">
|
||||||
<div class="position-absolute top-50 start-0 w-100 translate-middle-y">
|
<div class="position-absolute bottom-50 start-0 w-100 translate-middle-y">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-8 col-lg-6 mx-auto">
|
<div class="col col-md-8 col-lg-6 mx-auto">
|
||||||
<form>
|
<form>
|
||||||
@ -23,12 +35,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="notifications" class="position-absolute top-0 start-0 w-100">
|
<div id="notifications" class="position-absolute top-0 start-0 w-100">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-8 col-lg-6 mx-auto mt-5"></div>
|
<div class="col col-md-8 col-lg-6 mx-auto"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <script src="http://127.0.0.1:8000/index.js"></script> -->
|
<script src="http://127.0.0.1:8000/index.js"></script>
|
||||||
<script src="static/build/index.js"></script>
|
<!-- <script src="static/build/index.js"></script> -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -21,7 +21,7 @@ $input-btn-focus-width: 0rem;
|
|||||||
// @import "bootstrap/scss/images";
|
// @import "bootstrap/scss/images";
|
||||||
// @import "bootstrap/scss/code";
|
// @import "bootstrap/scss/code";
|
||||||
@import "bootstrap/scss/grid";
|
@import "bootstrap/scss/grid";
|
||||||
// @import "bootstrap/scss/tables";
|
@import "bootstrap/scss/tables";
|
||||||
@import "bootstrap/scss/forms";
|
@import "bootstrap/scss/forms";
|
||||||
@import "bootstrap/scss/buttons";
|
@import "bootstrap/scss/buttons";
|
||||||
// @import "bootstrap/scss/transitions";
|
// @import "bootstrap/scss/transitions";
|
||||||
@ -29,8 +29,8 @@ $input-btn-focus-width: 0rem;
|
|||||||
// @import "bootstrap/scss/button-group";
|
// @import "bootstrap/scss/button-group";
|
||||||
// @import "bootstrap/scss/input-group"; // Requires forms
|
// @import "bootstrap/scss/input-group"; // Requires forms
|
||||||
// @import "bootstrap/scss/custom-forms";
|
// @import "bootstrap/scss/custom-forms";
|
||||||
// @import "bootstrap/scss/nav";
|
@import "bootstrap/scss/nav";
|
||||||
// @import "bootstrap/scss/navbar"; // Requires nav
|
@import "bootstrap/scss/navbar"; // Requires nav
|
||||||
// @import "bootstrap/scss/card";
|
// @import "bootstrap/scss/card";
|
||||||
// @import "bootstrap/scss/breadcrumb";
|
// @import "bootstrap/scss/breadcrumb";
|
||||||
// @import "bootstrap/scss/pagination";
|
// @import "bootstrap/scss/pagination";
|
||||||
|
122
web/react/package-lock.json
generated
122
web/react/package-lock.json
generated
@ -4,11 +4,45 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.15.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
|
||||||
|
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"esbuild": {
|
"esbuild": {
|
||||||
"version": "0.12.5",
|
"version": "0.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.5.tgz",
|
||||||
"integrity": "sha512-vcuP53pA5XiwUU4FnlXM+2PnVjTfHGthM7uP1gtp+9yfheGvFFbq/KyuESThmtoHPUrfZH5JpxGVJIFDVD1Egw=="
|
"integrity": "sha512-vcuP53pA5XiwUU4FnlXM+2PnVjTfHGthM7uP1gtp+9yfheGvFFbq/KyuESThmtoHPUrfZH5JpxGVJIFDVD1Egw=="
|
||||||
},
|
},
|
||||||
|
"history": {
|
||||||
|
"version": "4.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||||
|
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"loose-envify": "^1.2.0",
|
||||||
|
"resolve-pathname": "^3.0.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0",
|
||||||
|
"value-equal": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isarray": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||||
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -22,11 +56,38 @@
|
|||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mini-create-react-context": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"tiny-warning": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||||
},
|
},
|
||||||
|
"path-to-regexp": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||||
|
"requires": {
|
||||||
|
"isarray": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prop-types": {
|
||||||
|
"version": "15.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
|
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
||||||
@ -46,6 +107,52 @@
|
|||||||
"scheduler": "^0.20.2"
|
"scheduler": "^0.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
},
|
||||||
|
"react-router": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.13",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"hoist-non-react-statics": "^3.1.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"mini-create-react-context": "^0.4.0",
|
||||||
|
"path-to-regexp": "^1.7.0",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-is": "^16.6.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.13",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-router": "5.2.1",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||||
|
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
|
||||||
|
},
|
||||||
|
"resolve-pathname": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
|
||||||
|
},
|
||||||
"scheduler": {
|
"scheduler": {
|
||||||
"version": "0.20.2",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
|
||||||
@ -54,6 +161,21 @@
|
|||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tiny-invariant": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
|
||||||
|
},
|
||||||
|
"tiny-warning": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||||
|
},
|
||||||
|
"value-equal": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.12.5",
|
"esbuild": "^0.12.5",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^17.0.2",
|
||||||
|
"react-router-dom": "^5.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
web/react/src/alert.jsx
Normal file
17
web/react/src/alert.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function Alert({alert}) {
|
||||||
|
const alertType = alert.source == "back" ? "success" : "primary";
|
||||||
|
const alertLabel = alert.source == "back" ? "Received" : "Sent";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={"alert alert-"+alertType}
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<em>{alertLabel}</em>: {alert.text}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Alert;
|
@ -1,141 +1,38 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import {
|
||||||
|
BrowserRouter as Router,
|
||||||
|
Switch,
|
||||||
|
Route,
|
||||||
|
NavLink
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
|
import Home from './home';
|
||||||
|
import Messages from './messages';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [alerts, setAlerts] = React.useState([]);
|
|
||||||
const ws = React.useRef(null);
|
|
||||||
const timeout = React.useRef(null);
|
|
||||||
|
|
||||||
const sendMessage = (message) => {
|
|
||||||
if (ws.current.readyState != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg = JSON.stringify({
|
|
||||||
text: message,
|
|
||||||
source: "front",
|
|
||||||
time: Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.current.send(msg);
|
|
||||||
addAlert(msg);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addAlert = (msg) => {
|
|
||||||
let item = JSON.parse(msg);
|
|
||||||
setAlerts([item, ...alerts.slice(0, 2)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets up a WebSocket connection
|
|
||||||
React.useEffect(() => {
|
|
||||||
ws.current = new WebSocket("ws://localhost:8080/ws");
|
|
||||||
ws.current.onopen = () => {
|
|
||||||
console.log('Connected');
|
|
||||||
// ws.current.send("Hello from the client!");
|
|
||||||
}
|
|
||||||
ws.current.onclose = () => console.log('Disconnected');
|
|
||||||
ws.current.onerror = () => console.log('Websocket error')
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
ws.current.close();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Sets a received message handler, only once.
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!ws.current) return;
|
|
||||||
|
|
||||||
ws.current.onmessage = e => {
|
|
||||||
const msg = e.data;
|
|
||||||
addAlert(msg);
|
|
||||||
}
|
|
||||||
}, [alerts]);
|
|
||||||
|
|
||||||
// Clears out alerts with a delay,
|
|
||||||
// resets after each message
|
|
||||||
React.useEffect(() => {
|
|
||||||
clearTimeout(timeout.current);
|
|
||||||
|
|
||||||
timeout.current = setTimeout(() => {setAlerts([])}, 20000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeout.current);
|
|
||||||
}
|
|
||||||
}, [alerts]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container position-relative h-75">
|
<Router>
|
||||||
<div className="position-absolute top-50 start-0 w-100 translate-middle-y">
|
<nav className="navbar navbar-expand navbar-light bg-transparent">
|
||||||
<div className="row">
|
<div className="container-fluid">
|
||||||
<div className="col col-md-8 col-lg-6 mx-auto">
|
<ul className="navbar-nav">
|
||||||
<Form sendMessage={sendMessage} />
|
<li className="nav-item">
|
||||||
|
<NavLink className="nav-link" to="/" activeClassName="active" exact={true}>Home</NavLink>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<NavLink className="nav-link" to="/messages" activeClassName="active">Messages</NavLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</div>
|
<Switch>
|
||||||
<div id="notifications" className="position-absolute top-0 start-0 w-100">
|
<Route path="/messages">
|
||||||
<div className="row">
|
<Messages />
|
||||||
<div className="col col-md-8 col-lg-6 mx-auto mt-5">
|
</Route>
|
||||||
{alerts.map((alert) => (
|
<Route path="/">
|
||||||
<Alert key={alert.time} alert={alert} />
|
<Home />
|
||||||
))}
|
</Route>
|
||||||
</div>
|
</Switch>
|
||||||
</div>
|
</Router>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Alert({alert}) {
|
|
||||||
const alertType = alert.source == "back" ? "success" : "primary";
|
|
||||||
const alertLabel = alert.source == "back" ? "Received" : "Sent";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={"alert alert-"+alertType}
|
|
||||||
role="alert"
|
|
||||||
>
|
|
||||||
<em>{alertLabel}</em>: {alert.text}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Form({sendMessage}) {
|
|
||||||
const [message, setMessage] = React.useState('');
|
|
||||||
|
|
||||||
const submitMessage = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!message) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendMessage(message)) {
|
|
||||||
setMessage('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={submitMessage}>
|
|
||||||
<div className="input-group">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="Enter message"
|
|
||||||
aria-label="Enter message"
|
|
||||||
aria-describedby="button-send"
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
id="button-send"
|
|
||||||
className="btn btn-success"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
42
web/react/src/form.jsx
Normal file
42
web/react/src/form.jsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function Form({sendMessage}) {
|
||||||
|
const [message, setMessage] = React.useState('');
|
||||||
|
|
||||||
|
const submitMessage = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendMessage(message)) {
|
||||||
|
setMessage('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={submitMessage}>
|
||||||
|
<div className="input-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Enter message"
|
||||||
|
aria-label="Enter message"
|
||||||
|
aria-describedby="button-send"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
id="button-send"
|
||||||
|
className="btn btn-success"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Form;
|
90
web/react/src/home.jsx
Normal file
90
web/react/src/home.jsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Form from './form';
|
||||||
|
import Alert from './alert';
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
const [alerts, setAlerts] = React.useState([]);
|
||||||
|
const ws = React.useRef(null);
|
||||||
|
const timeout = React.useRef(null);
|
||||||
|
|
||||||
|
const sendMessage = (message) => {
|
||||||
|
if (ws.current.readyState != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = JSON.stringify({
|
||||||
|
text: message,
|
||||||
|
source: "front",
|
||||||
|
time: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.current.send(msg);
|
||||||
|
addAlert(msg);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addAlert = (msg) => {
|
||||||
|
let item = JSON.parse(msg);
|
||||||
|
setAlerts([item, ...alerts.slice(0, 2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets up a WebSocket connection
|
||||||
|
React.useEffect(() => {
|
||||||
|
ws.current = new WebSocket("ws://" + window.location.host + "/ws");
|
||||||
|
ws.current.onopen = () => {
|
||||||
|
console.log('Connected');
|
||||||
|
// ws.current.send("Hello from the client!");
|
||||||
|
}
|
||||||
|
ws.current.onclose = () => console.log('Disconnected');
|
||||||
|
ws.current.onerror = () => console.log('Websocket error')
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ws.current.close();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Sets a received message handler, only once.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!ws.current) return;
|
||||||
|
|
||||||
|
ws.current.onmessage = e => {
|
||||||
|
const msg = e.data;
|
||||||
|
addAlert(msg);
|
||||||
|
}
|
||||||
|
}, [alerts]);
|
||||||
|
|
||||||
|
// Clears out alerts with a delay,
|
||||||
|
// resets after each message
|
||||||
|
React.useEffect(() => {
|
||||||
|
clearTimeout(timeout.current);
|
||||||
|
timeout.current = setTimeout(() => {setAlerts([])}, 20000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout.current);
|
||||||
|
}
|
||||||
|
}, [alerts]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container position-relative h-75">
|
||||||
|
<div className="position-absolute bottom-50 start-0 w-100 translate-middle-y">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col-md-8 col-lg-6 mx-auto">
|
||||||
|
<Form sendMessage={sendMessage} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="notifications" className="position-absolute top-0 start-0 w-100">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col-md-8 col-lg-6 mx-auto">
|
||||||
|
{alerts.map((alert) => (
|
||||||
|
<Alert key={alert.time} alert={alert} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
37
web/react/src/messages.jsx
Normal file
37
web/react/src/messages.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function Messages() {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<h3 className="my-4 ps-2">Recent messages (3/67)</h3>
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Time</th>
|
||||||
|
<th scope="col">Message</th>
|
||||||
|
<th scope="col">Source</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Mar 3, 2021</td>
|
||||||
|
<td>Hello there from the frontend</td>
|
||||||
|
<td>Front end</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Mar 3, 2021</td>
|
||||||
|
<td>Hello there from the back end</td>
|
||||||
|
<td>Back end</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Mar 3, 2021</td>
|
||||||
|
<td>This is good!</td>
|
||||||
|
<td>Front end</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Messages;
|
Reference in New Issue
Block a user