diff --git a/webui/src/App.js b/webui/src/App.js index 9d8d242..0bc4916 100755 --- a/webui/src/App.js +++ b/webui/src/App.js @@ -10,8 +10,10 @@ import IndexPage from "./Components/Pages/IndexPage"; import GroupPage from "./Components/Pages/GroupPage"; import Loading from "./Components/Loading"; import LightPage from "./Components/Pages/LightPage"; +import AdminPage from "./Components/Pages/AdminPage"; export default function App() { + const [hasStarted, setHasStarted] = useState(false); const {isLoggedIn, isChecked, verify} = useAuth(); @@ -34,6 +36,7 @@ export default function App() { + )} diff --git a/webui/src/Components/Bridge.jsx b/webui/src/Components/Bridge.jsx new file mode 100644 index 0000000..cfff8fc --- /dev/null +++ b/webui/src/Components/Bridge.jsx @@ -0,0 +1,53 @@ +import React, {useState} from "react"; +import {Button, Card, CardBody, CardFooter, CardHeader} from "reactstrap"; +import useBridges from "../Hooks/bridge"; +import BridgeModal from "./Modals/BridgeModal"; +import useLights from "../Hooks/lights"; +import Loading from "./Loading"; +import BridgeLight from "./BridgeLight"; + +function Bridge({addr, driver, id, name}) { + const {forgetBridge, discoverLights} = useBridges(); + const {lightsByBridge} = useLights(); + const [modal, setModal] = useState(false); + + function edit() { + setModal(true); + } + + function unEdit() { + setModal(false); + } + + function forget() { + if (window.confirm(`Vil du virkelig glemme bruen "${name}"?`)) { + forgetBridge(id); + } + } + + function discover() { + discoverLights(id); + } + + const lights = lightsByBridge(id); + const hasLights = lights !== null; + + return ( + + {name} ({driver}, {addr}) + + {hasLights ? lights.map(l => ) : } + + + + {" "} + + {" "} + + + {modal && } + + ); +} + +export default Bridge; diff --git a/webui/src/Components/BridgeLight.jsx b/webui/src/Components/BridgeLight.jsx new file mode 100644 index 0000000..b001b7e --- /dev/null +++ b/webui/src/Components/BridgeLight.jsx @@ -0,0 +1,23 @@ +import React, {useState} from "react"; +import {Button, FormGroup} from "reactstrap"; +import useBridges from "../Hooks/bridge"; + +function BridgeLight({ id, bridgeId, name }) { + const [waiting, setWaiting] = useState(false); + const {forgetLight} = useBridges(); + + function forget() { + setWaiting(true); + forgetLight(bridgeId, id); + } + + return ( + + + {" "} + {name} + + ); +} + +export default BridgeLight; diff --git a/webui/src/Components/Bridges.jsx b/webui/src/Components/Bridges.jsx new file mode 100644 index 0000000..39888e0 --- /dev/null +++ b/webui/src/Components/Bridges.jsx @@ -0,0 +1,25 @@ +import React, {useState} from "react"; +import useBridges from "../Hooks/bridge"; +import Bridge from "./Bridge"; +import {Button, FormGroup} from "reactstrap"; +import BridgeModal from "./Modals/BridgeModal"; + +function Bridges() { + const [modal, setModal] = useState(false); + const {bridges} = useBridges(); + if (bridges === null) { + return <>; + } + + return ( +
+ {bridges.map(bridge => )} + + + + {modal && setModal(false)}/> } +
+ ); +} + +export default Bridges; diff --git a/webui/src/Components/Modals/BridgeModal.jsx b/webui/src/Components/Modals/BridgeModal.jsx new file mode 100644 index 0000000..d1a10d0 --- /dev/null +++ b/webui/src/Components/Modals/BridgeModal.jsx @@ -0,0 +1,94 @@ +import React, {useState} from "react"; +import {Button, Col, Form, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader} from "reactstrap"; +import useBridges from "../../Hooks/bridge"; +import Loading from "../Loading"; + +function BridgeModal({onCancel, bridge = null}) { + const {addBridge, editBridge} = useBridges(); + + const edit = bridge !== null; + + const [name, setName] = useState(edit ? bridge.name : ""); + const [driver, setDriver] = useState(edit ? bridge.driver : ""); + const [addr, setAddr] = useState(edit ? bridge.addr : ""); + + const [waiting, setWaiting] = useState(false); + + function onConfirm() { + if (edit) { + editBridge(bridge.id, name); + } else { + setWaiting(true); + addBridge(name, driver, addr, (result) => { + if (!result) { + alert("Noe gikk galt"); + } + + setWaiting(false); + onCancel(); + }); + } + } + + return ( + + {edit ? "Bruegenskaper" : "Ny bru"} + + {waiting ? ( + + ) : ( +
+ + + + setName(e.target.value)} + /> + + + + + + setDriver(e.target.value)} + disabled={edit} + /> + + + + + + setAddr(e.target.value)} + disabled={edit} + /> + + +
+ )} +
+ + + {" "} + + +
+ ); +} + +export default BridgeModal; diff --git a/webui/src/Components/Pages/AdminPage.jsx b/webui/src/Components/Pages/AdminPage.jsx new file mode 100644 index 0000000..8ebdf68 --- /dev/null +++ b/webui/src/Components/Pages/AdminPage.jsx @@ -0,0 +1,18 @@ +import React from "react"; +import Bridges from "../Bridges"; +import useAuth from "../../Hooks/auth"; + +function AdminPage() { + const {user} = useAuth(); + if (user.name === "Admin") { + return <>; + } + + return ( +
+ +
+ ); +} + +export default AdminPage; diff --git a/webui/src/Hooks/bridge.js b/webui/src/Hooks/bridge.js new file mode 100644 index 0000000..bbbc997 --- /dev/null +++ b/webui/src/Hooks/bridge.js @@ -0,0 +1,99 @@ +import {setGlobal, useGlobal} from "reactn"; +import {useEffect} from "react"; +import {fetchDelete, fetchGet, fetchPatch, fetchPost} from "../Helpers/fetcher"; +import useLights from "./lights"; + +setGlobal({ + bridges: null, +}); + +function useBridges() { + const [bridges, setBridges] = useGlobal("bridges"); + const {reloadLights} = useLights(); + + function reloadBridges() { + setBridges(null); + + fetchGet("/bridge/").then(({error, data}) => { + if (error !== null) { + console.error(error); + return; + } + + setBridges(data); + }); + } + + useEffect(() => { + if (bridges === null) { + reloadBridges(); + } + }, []); + + function bridge(id) { + if (bridges === null) { + return null; + } + + return bridges.find(b => b.id === id); + } + + function forgetBridge(id) { + fetchDelete(`/bridge/${id}`).then(({data, error}) => { + if (error !== null) { + console.error(error); + } + + reloadBridges(); + }); + } + + function forgetLight(bridgeId, lightId) { + fetchDelete(`/bridge/${bridgeId}/light/${lightId}`).then(({data, error}) => { + if (error !== null) { + console.error(error); + } + + reloadBridges(); + }); + } + + function addBridge(name, driver, addr, callback = null) { + fetchPost("/bridge/", {name, driver, addr}).then(({error}) => { + if (error !== null) { + console.error(error); + } + + if (callback !== null) { + callback(error === null); + } + + reloadBridges(); + }); + } + + function editBridge(id, name) { + fetchPatch(`/bridge/${id}`, {name}).then(({error}) => { + if (error !== null) { + console.error(error); + } + + reloadBridges(); + }); + } + + function discoverLights(id) { + fetchPost(`/bridge/${id}/discover`).then(({error}) => { + if (error !== null) { + console.error(error); + } + + reloadLights(); + reloadBridges(); + }); + } + + return {bridges, bridge, forgetBridge, addBridge, editBridge, forgetLight, discoverLights}; +} + +export default useBridges; diff --git a/webui/src/Hooks/lights.js b/webui/src/Hooks/lights.js new file mode 100644 index 0000000..cc9c343 --- /dev/null +++ b/webui/src/Hooks/lights.js @@ -0,0 +1,42 @@ +import {setGlobal, useGlobal} from "reactn"; +import {fetchGet} from "../Helpers/fetcher"; +import {useEffect} from "react"; + +setGlobal({ + lights: null, +}); + +function useLights() { + const [lights, setLights] = useGlobal("lights"); + + function reloadLights() { + setLights(null); + + fetchGet("/light/").then(({error, data}) => { + if (error !== null) { + console.error(error); + return; + } + + setLights(data); + }); + } + + useEffect(() => { + if (lights === null) { + reloadLights(); + } + }, []); + + function lightsByBridge(bridgeId) { + if (lights === null) { + return null; + } + + return lights.filter(light => light.bridgeId === bridgeId); + } + + return {lights, lightsByBridge, reloadLights}; +} + +export default useLights; \ No newline at end of file