diff --git a/Backend/API/handlers/newDepartment.go b/Backend/API/handlers/newDepartment.go index b438e91533c3d21c343b2e0680c852b4861ef974..133db262938daee02aadea96fb12918a8e5c5411 100644 --- a/Backend/API/handlers/newDepartment.go +++ b/Backend/API/handlers/newDepartment.go @@ -101,15 +101,8 @@ func NewDepartment(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } - //get the building id - building, err := other.GetBuildingIdByName(data.BuildingName, CompID) - if err != nil { - log.Println(err.Error()) - w.WriteHeader(http.StatusBadRequest) - return - } - depId, err := other.GetDepartmentIdByName(data.DepartmentName1, building) + depId, err := other.GetDepartmentIdByName(data.DepartmentName1, CompID) if err != nil { log.Println(err.Error()) w.WriteHeader(http.StatusBadRequest) @@ -167,6 +160,7 @@ func NewDepartment(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } + //get the building id depId, err := other.GetDepartmentIdByName(data.DepartmentName, CompID) if err != nil { @@ -174,6 +168,7 @@ func NewDepartment(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) return } + //calls function to add building err = other.DeleteDepartment(depId) if err != nil { diff --git a/Backend/API/handlers/newGateway.go b/Backend/API/handlers/newGateway.go index b920cc4a61e76c9f38d0700d7bc3e9be6ae3f76d..5a16b13cfe1120fa7f4b9d7e7b2a23b5bcad9a45 100644 --- a/Backend/API/handlers/newGateway.go +++ b/Backend/API/handlers/newGateway.go @@ -11,7 +11,7 @@ import ( func AddGateway(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Headers", "Content-Type") w.Header().Set("Access-Control-Allow-Origin", ""+other.WebsiteURL) - w.Header().Set("Access-Control-Allow-Methods", "POST, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Methods", "POST, DELETE, PUT, OPTIONS") if r.Method == http.MethodPost { decoder := json.NewDecoder(r.Body) //creates an anonymous struct to reduce clutter in structs file @@ -49,6 +49,60 @@ func AddGateway(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } + } else if r.Method == http.MethodPut { + decoder := json.NewDecoder(r.Body) + //creates an anonymous struct to reduce clutter in structs file + data := struct { + Name string `json:"name"` + OldName string `json:"oldName"` + Eui string `json:"eui"` + OldEui string `json:"oldEui"` + Token string `json:"sessionToken"` + }{} + err := decoder.Decode(&data) + if err != nil { + println(err.Error()) + return + } + valid, err := other.IsTokenStillActive(data.Token) + if !valid { + log.Println("Session token expired or not valid!") + w.WriteHeader(http.StatusUnauthorized) + if err != nil { + log.Println(err.Error()) + } + return + } + //verify that the user making the request is allowed + permissionLevel, err := other.GetPermissionFromToken(data.Token) + if err != nil { + log.Println(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + if permissionLevel != 0 { + w.WriteHeader(http.StatusUnauthorized) + return + } + UserID, err := other.GetUserIDFromToken(data.Token) + if err != nil { + log.Println(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + CompID, err := other.GetCompanyFromUserID(UserID) + if err != nil { + log.Println(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + //calls function to edit building + err = other.EditGateway(data.Eui, data.OldEui, data.Name, data.OldName, CompID) + if err != nil { + println(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } } else if r.Method == http.MethodDelete { decoder := json.NewDecoder(r.Body) //creates an anonymous struct to reduce clutter in structs file @@ -71,20 +125,19 @@ func AddGateway(w http.ResponseWriter, r *http.Request) { } return } - - err = other.DeleteGateway(data.Eui) + err = other.DoNewRequest(nil, other.CHIRP_URL+"api/gateways/"+data.Eui, http.MethodDelete) if err != nil { log.Println(err.Error()) w.WriteHeader(http.StatusInternalServerError) return } - - err = other.DoNewRequest(nil, other.CHIRP_URL+"api/gateways/"+data.Eui, http.MethodDelete) + err = other.DeleteGateway(data.Eui) if err != nil { log.Println(err.Error()) w.WriteHeader(http.StatusInternalServerError) return } + } else if r.Method == http.MethodOptions { } else { diff --git a/Backend/API/other/sqlqueries.go b/Backend/API/other/sqlqueries.go index cfc8265d54c6497c142633e89469212bd69d4404..87575eafe1e7cd2b66dc6651bf606ed53ca34ebd 100644 --- a/Backend/API/other/sqlqueries.go +++ b/Backend/API/other/sqlqueries.go @@ -134,6 +134,10 @@ func DeleteBuilding(id int) error { if err != nil { return err } + _, err = DB.Exec("DELETE FROM sensorData WHERE eui=?", eui) + if err != nil { + return err + } } _, err = DB.Exec("DELETE FROM machine WHERE building_id=?", id) @@ -193,9 +197,9 @@ func DeleteDepartment(id int) error { return nil } -func GetDepartmentIdByName(name string, buildId int) (int, error) { +func GetDepartmentIdByName(name string, compId int) (int, error) { var id = 0 - row := DB.QueryRow("SELECT id FROM department WHERE name=? AND building_id=?", name, buildId) + row := DB.QueryRow("SELECT id FROM department WHERE name=? AND building_id IN (SELECT id FROM building WHERE company_id=?)", name, compId) err := row.Scan(&id) if err != nil { return 0, err @@ -478,8 +482,8 @@ func AddGateway(token string, eui string, name string) error { } return nil } -func EditGateway(newEui string, oldEui string) error { - _, err := DB.Exec("UPDATE gateway SET eui=? where eui=?", newEui, oldEui) +func EditGateway(newEui string, oldEui string, newName string, oldName string, compId int) error { + _, err := DB.Exec("UPDATE gateway SET eui=?, name=? where eui=? AND name=? AND company_id=?", newEui, newName, oldEui, oldName, compId) if err != nil { return err } diff --git a/Frontend/power-tracker/src/components/manageBuildDep.tsx b/Frontend/power-tracker/src/components/manageBuildDep.tsx index e2731cb1261c8acf7e565fed4d72298da9a32c6c..b46872d88a59b89d597e4318534869e148ab894b 100644 --- a/Frontend/power-tracker/src/components/manageBuildDep.tsx +++ b/Frontend/power-tracker/src/components/manageBuildDep.tsx @@ -197,6 +197,7 @@ function BuildDepSubpage () { <div> {/* Building Name */} <div className="buildingbar"> + <Separator orientation='vertical' className="mr-[5px]"/> <AccordionTrigger> <div className='flex flex-row'> <Factory className="mx-[20px]" /> @@ -205,14 +206,38 @@ function BuildDepSubpage () { </AccordionTrigger> <Separator orientation='vertical' className="mx-[5px]"/> <div className="h-[100%] flex items-center justify-around"> - <Button className="h-[30px] w-[60px]" variant="outline">Edit</Button> - <Button className="h-[30px] w-[60px]" variant="outline">Delete</Button> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" onClick={()=>{setBuildingNameInput(buildDep.building.name)}}>Edit</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Edit {buildDep.building.name}</AlertDialogHeader> + <form onSubmit={(e)=>{e.preventDefault();editBuilding(buildDep.building.name, buildingNameInput); setBuildingNameInput("")}}> + <Input className="mb-[20px]" type="text" placeholder="Buidling Name" value={buildingNameInput} onChange={(event) => setBuildingNameInput(event.target.value)} required/> + <AlertDialogAction type='submit'>Confirm</AlertDialogAction> + <AlertDialogCancel onClick={()=>{setBuildingNameInput("")}}>Cancel</AlertDialogCancel> + </form> + </AlertDialogContent> + </AlertDialog> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" >Delete</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Are you sure about deleting {buildDep.building.name} and all its machines and departments?</AlertDialogHeader> + <AlertDialogAction onClick={()=>deleteBuilding(buildDep.building.name)}>DELETE</AlertDialogAction> + <AlertDialogCancel>Cancel</AlertDialogCancel> + </AlertDialogContent> + </AlertDialog> + </div> + <Separator orientation='vertical' className="ml-[5px]"/> </div> {/* In building dropdown */} <AccordionContent> {/* Add department button */} <div className="depbar"> + <Separator orientation='vertical' className="mr-[5px]"/> <AlertDialog> <AlertDialogTrigger asChild> <Button className="w-[60px] h-[30px] mb-[10px] ml-[20px] mt-[10px]" variant="default" size="icon" onClick={() => { @@ -225,8 +250,17 @@ function BuildDepSubpage () { <Plus className="h-[18px]" /> </Button> </AlertDialogTrigger> - <AlertDialogContent> {/* NEEDS FUNCTIONALITY */} - <form> + <AlertDialogContent> + <form onSubmit={(e) => { + e.preventDefault(); + console.log(departmentNameInput) + if (departmentNameInput !== "") { + addDepartment(buildDep.building.name, departmentNameInput); + setDepartmentNameInput(""); + setBuildingNameInputDP(""); + setDepartmentBool(!departmentBool); + } + }}> <AlertDialogHeader> <h4 className="scroll-m-20 text-xl font-semibold tracking-tight"> Add Department to: {buildDep.building.name} @@ -234,7 +268,7 @@ function BuildDepSubpage () { </AlertDialogHeader> <div className="flex items-center space-x-2"> <Label htmlFor='addDepName'>Name</Label> - <Input id="addDepName" className="my-[20px] w-[200px]" required/> + <Input id="addDepName" className="my-[20px] w-[200px]" required value={departmentNameInput} onChange={(event) => setDepartmentNameInput(event.target.value)}/> </div> <AlertDialogFooter> <AlertDialogAction> @@ -264,12 +298,14 @@ function BuildDepSubpage () { <div/> <div/> <div/> <Separator orientation='vertical' className="mx-[5px]"/> <div/> + <Separator orientation='vertical' className="ml-[5px]"/> </div> {/* Department list */} <ul> {buildDep.departments.map((department, index1) => ( <li key={index1}> <div className="depbar"> + <Separator orientation='vertical' className="mr-[5px]"/> <p className="h-[35px] flex leading-7 items-start items-center"> <Warehouse className="h-[20px] mr-[20px] ml-[40px]" /> <h5 className="scroll-m-20 text-lg tracking-tight">{department.name}</h5> @@ -279,11 +315,33 @@ function BuildDepSubpage () { </div> <div/> <div className="h-[100%] flex items-center justify-around"> - <Button className="h-[30px] w-[60px]" variant="outline">Edit</Button> - <Button className="h-[30px] w-[60px]" variant="outline">Delete</Button> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" onClick={()=>{setDepartmentNameInput(department.name)}}>Edit</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Edit {department.name}</AlertDialogHeader> + <form onSubmit={(e)=>{e.preventDefault();editDepartment(department.name, departmentNameInput,buildDep.building.name);setDepartmentNameInput("")}}> + <Input className="mb-[20px]" type="text" placeholder="Buidling Name" value={departmentNameInput} onChange={(event) => setDepartmentNameInput(event.target.value)} required/> + <AlertDialogAction type='submit'>Confirm</AlertDialogAction> + <AlertDialogCancel onClick={()=>{setDepartmentNameInput("")}}>Cancel</AlertDialogCancel> + </form> + </AlertDialogContent> + </AlertDialog> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" >Delete</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Are you sure about deleting {department.name}? This will remove it from alle machines that has this department.</AlertDialogHeader> + <AlertDialogAction onClick={()=>deleteDepartment(department.name)}>DELETE</AlertDialogAction> + <AlertDialogCancel>Cancel</AlertDialogCancel> + </AlertDialogContent> + </AlertDialog> </div> <Separator orientation='vertical' className="mx-[5px]"/> <div></div> + <Separator orientation='vertical' className="ml-[5px]"/> </div> </li> ))} @@ -315,8 +373,12 @@ function BuildDepSubpage () { </button> </form> )}*/} - <div className="h-[10px] grid grid-cols-[1fr_auto_130px]"> - <Separator orientation='vertical' className="mx-[5px] col-start-2"/> + <div className="h-[10px] grid grid-cols-[auto_1fr_auto_130px_auto]"> + <Separator orientation='vertical' className="mr-[5px]"/> + <div/> + <Separator orientation='vertical' className="mx-[5px]"/> + <div/> + <Separator orientation='vertical' className="ml-[5px]"/> </div> <Separator/> </AccordionContent> @@ -363,8 +425,7 @@ const addBuilding = (e: React.FormEvent<HTMLFormElement>, buildingName: string) }) } -const addDepartment = (e: React.FormEvent<HTMLFormElement>, buildingName: string, departmentName: string) => { - e.preventDefault() +const addDepartment = ( buildingName: string, departmentName: string) => { var token: string = "" var tokenBool = sessionStorage.getItem("TOKEN") @@ -439,8 +500,8 @@ const editDepartment= ( oldDepartmentName: string, newDepartmentName: string, bu }) } -const deleteBuilding = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>, name: string) => { - e.preventDefault() +const deleteBuilding = ( name: string) => { + var token: string = "" var tokenBool = sessionStorage.getItem("TOKEN") @@ -464,8 +525,8 @@ const deleteBuilding = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>, name console.log(error) }) } -const deleteDepartment= (e: React.MouseEvent<HTMLButtonElement, MouseEvent>, name: string) => { - e.preventDefault() +const deleteDepartment= ( name: string) => { + var token: string = "" var tokenBool = sessionStorage.getItem("TOKEN") diff --git a/Frontend/power-tracker/src/components/manageGateway.tsx b/Frontend/power-tracker/src/components/manageGateway.tsx index 5efeabe5a9e670d3f5c5acccd3881c5caf198458..41f9e62e9d171436ba06a00c1898be39ba39297b 100644 --- a/Frontend/power-tracker/src/components/manageGateway.tsx +++ b/Frontend/power-tracker/src/components/manageGateway.tsx @@ -3,6 +3,36 @@ import { Button } from "@/components/ui/button"; import "../pages/login.css" import axios from 'axios'; import { redirect } from 'react-router-dom'; +import { Input } from "@/components/ui/input" +import { Separator } from "@/components/ui/separator" +import { Plus } from 'lucide-react'; +import { Router } from 'lucide-react'; +import { Textarea } from "@/components/ui/textarea" +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" const EgressAPI = import.meta.env.VITE_EAPI_URL const IngressAPI = import.meta.env.VITE_IAPI_URL @@ -29,46 +59,111 @@ function ManageGateways() { }, [setGatewayData]); return ( - <div> - <h1>GATEWAYS</h1> - <input type="text" className="outlined-input" value={search} onChange={(event) => setSearch(event.target.value) } placeholder="Search.."/> - <ul> - {gatewayData.gateway.map((gates, index) => ( - gates.eui_gate.includes(search.toLowerCase()) || gates.name.includes(search.toLowerCase()) ? - <li key={index}> - {gates.eui_gate} {gates.name} - </li>:null - ))} - </ul> - <h1 id="header">Add new gateway</h1> - <form onSubmit={(e) => {addGateway(e, euiInput, nameInput); setEuiInput(""); setNameInput("");}}> - <p>EUI</p> - <input type="text" id="uname" value={euiInput} onChange={(event) => setEuiInput(event.target.value)} required/> - <p>Name</p> - <input type="text" id="name" value={nameInput} onChange={(event) => setNameInput(event.target.value)} required/> - <br/> <br/> - <Button type='submit' onClick={() => { - setGatewayData(prevState => ({ - ...prevState, - gateway: [ - ...prevState.gateway, - { - eui_gate: euiInput, - name: nameInput - } - ] - })); - }}>Submit gateway! - </Button> - </form> - </div> + <ResizablePanelGroup className='flex h-[100%]' direction="horizontal"> + <ResizablePanel minSize={72.5}> + {/* Title and search bar */} + <div className="w-[100%] h-[40px] flex justify-between content-center"> + <h1 className="scroll-m-20 text-2xl font-semibold tracking-tight">Buildings and Departments</h1> + <Input + className="h-[30px] w-[200px] mr-[10px]" + type="text" + value={search} + onChange={(event) => setSearch(event.target.value) } + placeholder="Search.." + /> + </div> + + <Separator className="mb-[10px]" /> + + {/* New building button */} + <AlertDialog> + <AlertDialogTrigger> + <Button className="w-[60px] h-[30px] mb-[10px] ml-[10px]" variant="default" size="icon" > + <Router className="h-[20px]" /> + <Plus className="h-[18px]" /> + </Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader><strong>Add Gateway:</strong></AlertDialogHeader> + <form onSubmit={(e) => {addGateway(euiInput, nameInput); setEuiInput(""); setNameInput(""); e.preventDefault()}}> + <label>EUI</label> + <Input className="mb-[20px]" type="text" value={euiInput} onChange={(event) => setEuiInput(event.target.value)} required/> + <label>Name</label> + <Input className="mb-[20px]" type="text" value={nameInput} onChange={(event) => setNameInput(event.target.value)} required/> + <br/> <br/> + <AlertDialogFooter> + <AlertDialogAction type='submit'>Submit gateway!</AlertDialogAction> + <AlertDialogCancel>Cancel</AlertDialogCancel> + </AlertDialogFooter> + </form> + </AlertDialogContent> + </AlertDialog> + + <Table className=''> + <TableCaption>Gateways</TableCaption> + <TableHeader> + <TableRow> + <TableHead>Name</TableHead> + <TableHead>ID</TableHead> + <TableHead></TableHead> + <TableHead className="text-right"></TableHead> + </TableRow> + </TableHeader> + <TableBody> + {gatewayData.gateway.map((gates, index) => ( + <TableRow> + <TableCell className="min-w-[200px] max-w-[300px] scroll-m-20 text-xl font-semibold tracking-tight flex"> + <Router className="mx-[20px]" /> + <h4 className="scroll-m-20 text-xl font-semibold tracking-tight">{gates.name}</h4> + </TableCell> + <TableCell className="w-[400px]">{gates.eui_gate}</TableCell> + <TableCell className="w-[1fr] flex justify-end"> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" onClick={()=>{setEuiInput(gates.eui_gate); setNameInput(gates.name)}}>Edit</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Edit {gates.eui_gate} - {gates.name}</AlertDialogHeader> + <form onSubmit={(e)=>{e.preventDefault();editGateway(euiInput,gates.eui_gate, nameInput, gates.name); setEuiInput(""); setNameInput("");}}> + <label>EUI</label> + <Input className="mb-[20px]" type="text" placeholder="EUI" value={euiInput} onChange={(event) => setEuiInput(event.target.value)} required/> + <label>Name</label> + <Input className="mb-[20px]" type="text" placeholder="Name" value={nameInput} onChange={(event) => setNameInput(event.target.value)} required/> + <AlertDialogAction type='submit'>Confirm</AlertDialogAction> + <AlertDialogCancel onClick={()=>{setEuiInput(""); setNameInput("");}}>Cancel</AlertDialogCancel> + </form> + </AlertDialogContent> + </AlertDialog> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" >Delete</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Are you sure about deleting {gates.eui_gate} - {gates.name}?</AlertDialogHeader> + <AlertDialogAction onClick={()=>deleteGateway(gates.eui_gate)}>DELETE</AlertDialogAction> + <AlertDialogCancel>Cancel</AlertDialogCancel> + </AlertDialogContent> + </AlertDialog> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + + </ResizablePanel> + + <ResizableHandle withHandle className="mx-[10px]" /> + + <ResizablePanel defaultSize={72.5} className="w-[400px]"> + + </ResizablePanel> + </ResizablePanelGroup> ) } -const addGateway = (e: React.FormEvent<HTMLFormElement>, euiI: string, nameI: string) => { - e.preventDefault() - +const addGateway = ( euiI: string, nameI: string) => { + var token: string = "" var tokenBool = sessionStorage.getItem("TOKEN") if (tokenBool == null) { @@ -91,8 +186,35 @@ const addGateway = (e: React.FormEvent<HTMLFormElement>, euiI: string, nameI: st }) } -const deleteProcess = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>, eui: string) => { - e.preventDefault() +const editGateway= ( eui: string, oldEui: string, name: string, oldName: string) => { + + + var token: string = "" + var tokenBool = sessionStorage.getItem("TOKEN") + if (tokenBool == null) { + redirect('/') + } else { + token = tokenBool + } + console.log('URL To call: ' + IngressAPI + 'token used' + token) + axios.put( + IngressAPI + '/add-gateway', + { + name: name, + oldName: oldName, + eui: eui, + oldEui: oldEui, + sessionToken: token, + }) + .then((res)=>{ + console.log(res.data) + }).catch((error) => { + console.log(error) + }) +} + +const deleteGateway = ( eui: string) => { + var token: string = "" var tokenBool = sessionStorage.getItem("TOKEN") if (tokenBool == null) { diff --git a/Frontend/power-tracker/src/components/manageProcesses.tsx b/Frontend/power-tracker/src/components/manageProcesses.tsx index 65e47839c2e05af0b6be21339f14c8a922c840e3..7352d50b483efdfad64b08dd95e8f95512a20bec 100644 --- a/Frontend/power-tracker/src/components/manageProcesses.tsx +++ b/Frontend/power-tracker/src/components/manageProcesses.tsx @@ -2,17 +2,28 @@ import { useEffect, useState } from 'react' import { Button } from "@/components/ui/button"; import axios from 'axios'; import { redirect } from 'react-router-dom'; +import { Waypoints } from 'lucide-react'; +import { Separator } from "@/components/ui/separator" +import { Plus } from 'lucide-react'; +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, + AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog" - +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" const EgressAPI = import.meta.env.VITE_EAPI_URL const IngressAPI = import.meta.env.VITE_IAPI_URL @@ -112,117 +123,158 @@ const ManageProcesses= () => { }, [processDropDownId]); return ( - <div> - <h1>Processes:</h1> - <input type="text" className="outlined-input" value={search} onChange={(event) => setSearch(event.target.value) } placeholder="Search.."/> - <ul> - {processData.process.map((process, index) => ( - process.name.toLowerCase().includes(search.toLowerCase()) ? - <li key={index}> - {editIndex === index ? ( - <form onSubmit={(_) => { handleSave(); processData.process[index].name = text; processData.process[index].description = desc; }} className="form-container"> - <input - type="text" - value={text} - onChange={(e) => setText(e.target.value)} - onFocus={() => setTextFocus(true)} - onBlur={() => { setTextFocus(false) }} - autoFocus={nameOrNot} - required - /> - <input - type="text" - value={desc} - onChange={(e) => setDesc(e.target.value)} - onFocus={() => setDescFocus(true)} - onBlur={() => { setDescFocus(false) }} - autoFocus={!nameOrNot} - required - /> - <Button type='submit' onClick={() => {}}>Save</Button> - <Button onClick={() => handleCancelEdit()}>Cancel</Button> - </form> - ) : ( - <div> - <span onClick={() => handleEdit(index, process.name, process.description, true)} title="Edit process"> - {process.name} - </span> - <span onClick={() => handleEdit(index, process.name, process.description, false)} title="Edit process"> - {" " + process.description} - </span> + <div className="flex h-[100%]"> + <div className='w-[1100px]'> + {/* Title and search bar */} + <div className="w-[100%] h-[40px] flex justify-between content-center"> + <h1 className="scroll-m-20 text-2xl font-semibold tracking-tight">Processes</h1> + <Input + className="h-[30px] w-[200px]" + type="text" + value={search} + onChange={(event) => setSearch(event.target.value) } + placeholder="Search.." + /> + </div> + + <Separator className="mb-[10px]" /> + + {/* New process button */} + <AlertDialog> + <AlertDialogTrigger> + <Button className="w-[60px] h-[30px] mb-[10px] ml-[10px]" variant="default" size="icon" > + <Waypoints className="h-[20px]" /> + <Plus className="h-[18px]" /> + </Button> + </AlertDialogTrigger> + <AlertDialogContent> + <form onSubmit={(e) => {addProcess(e, nameInput, descriptionInput); }}> + <Input className="mb-[10px]" type="text" id="uname" value={nameInput} onChange={(event) => setNameInput(event.target.value)} placeholder="Process Name" required/> + <Textarea id="description" value={descriptionInput} onChange={(event) => setDescriptionInput(event.target.value)} placeholder="Description" required/> + <br/><br/> + <AlertDialogFooter> + <AlertDialogAction> + <Button type='submit' onClick={() => { + setProcessData(prevState => ({ + ...prevState, + process: [ + ...prevState.process, + { + id: 0, + name: nameInput, + description: descriptionInput + } + ] + })); + }}>Submit Process!</Button> + </AlertDialogAction> + <AlertDialogCancel>Cancel</AlertDialogCancel> + </AlertDialogFooter> + </form> + </AlertDialogContent> + </AlertDialog> + + <Separator /> + + <ul> + {processData.process.map((process, index) => ( + <li> + <div className="buildingbar flex items-center"> + <Separator orientation='vertical' className="mr-[5px]"/> + <div className='flex flex-row'> + <Waypoints className="mx-[20px] mt-[2px]" /> + <strong><h4 className="scroll-m-20 text-xl font-semibold tracking-tight">{process.name}</h4></strong> + </div> + <Separator orientation='vertical' className="mx-[5px]"/> + <div className="h-[100%] flex items-center justify-around"> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" onClick={()=>{setText(process.name); setDesc(process.description)}}>Edit</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Edit {process.name}</AlertDialogHeader> + <form onSubmit={(e)=>{e.preventDefault();editProcess(process.name, text ,process.description,desc); setText(""); setDesc("");}}> + <label>Process Name</label> + <Input className="mb-[20px]" type="text" placeholder="Process Name" value={text} onChange={(event) => setText(event.target.value)} required/> + <label>Description</label> + <Input className="mb-[20px]" type="text" placeholder="Description" value={desc} onChange={(event) => setDesc(event.target.value)} required/> + <AlertDialogAction type='submit'>Confirm</AlertDialogAction> + <AlertDialogCancel onClick={()=>{setText(""); setDesc("");}}>Cancel</AlertDialogCancel> + </form> + </AlertDialogContent> + </AlertDialog> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button className="h-[30px] w-[60px]" variant="outline" >Delete</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader>Are you sure about deleting {process.name}?</AlertDialogHeader> + <AlertDialogAction onClick={()=>deleteProcess(process.name, process.description)}>DELETE</AlertDialogAction> + <AlertDialogCancel>Cancel</AlertDialogCancel> + </AlertDialogContent> + </AlertDialog> </div> - )} - </li>:null - ))} - </ul> - <h1 id="header">Add process</h1> - <form onSubmit={(e) => {addProcess(e, nameInput, descriptionInput); }}> - <input type="text" id="uname" value={nameInput} onChange={(event) => setNameInput(event.target.value)} placeholder="Process Name" required/> - <textarea id="description" value={descriptionInput} onChange={(event) => setDescriptionInput(event.target.value)} placeholder="Description" required/> - <br/><br/> - <Button type='submit' onClick={() => { - setProcessData(prevState => ({ - ...prevState, - process: [ - ...prevState.process, - { - id: 0, - name: nameInput, - description: descriptionInput - } - ] - })); - }}>Submit Process!</Button> - </form> - <div className="center"> - <AlertDialog> - <AlertDialogTrigger asChild> - <Button size="sm" variant="outline" onClick={()=>{}}>Add machine to process</Button> - </AlertDialogTrigger> - <AlertDialogContent> - <AlertDialogHeader> - <AlertDialogTitle>Add machine to process</AlertDialogTitle> - <label>Process</label> - <select onChange={(e) => { const selectedIndex = e.target.selectedIndex; setProcessDropDown(e.target.value); setProcessDropDownId(processData.process[selectedIndex-1].id)}} value={processDropDown}> - <option value={""}>Select an option</option> - {processData.process.map((pro, index) => ( - pro.name !== "" ? ( - <option key={index} value={pro.name}> - {pro.name} - </option> - ) : null - ))} - </select> - {processDropDown !== "" && ( - <div> - {processMachineData.processMachine.map((element, index) => ( - <div key={index}> - <input - type="checkbox" - checked={element.added} - onChange={() => { - const updatedProcessMachineData = [...processMachineData.processMachine]; - updatedProcessMachineData[index].added = !element.added; - setProcessMachineData({ - ...processMachineData, - processMachine: updatedProcessMachineData - }); - }} - /> - {element.machineEUI} {element.machineName} - <br/> - </div> + <Separator orientation='vertical' className="ml-[5px]"/> + </div> + <Separator /> + </li> + ))} + </ul> + + <div className="center"> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button size="sm" variant="outline" onClick={()=>{}}>Add machine to process</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>Add machine to process</AlertDialogTitle> + <label>Process</label> + <select onChange={(e) => { const selectedIndex = e.target.selectedIndex; setProcessDropDown(e.target.value); setProcessDropDownId(processData.process[selectedIndex-1].id)}} value={processDropDown}> + <option value={""}>Select an option</option> + {processData.process.map((pro, index) => ( + pro.name !== "" ? ( + <option key={index} value={pro.name}> + {pro.name} + </option> + ) : null ))} - </div> - )} - </AlertDialogHeader> - <AlertDialogFooter> - <AlertDialogCancel>Cancel</AlertDialogCancel> - <AlertDialogAction type='submit' onClick={()=>{handleSave2(processMachineData, processMachineData2, processDropDownId); console.log(processDropDownId)}}>Save</AlertDialogAction> - </AlertDialogFooter> - </AlertDialogContent> - </AlertDialog> + </select> + {processDropDown !== "" && ( + <div> + {processMachineData.processMachine.map((element, index) => ( + <div key={index}> + <input + type="checkbox" + checked={element.added} + onChange={() => { + const updatedProcessMachineData = [...processMachineData.processMachine]; + updatedProcessMachineData[index].added = !element.added; + setProcessMachineData({ + ...processMachineData, + processMachine: updatedProcessMachineData + }); + }} + /> + {element.machineEUI} {element.machineName} + <br/> + </div> + ))} + </div> + )} + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel>Cancel</AlertDialogCancel> + <AlertDialogAction type='submit' onClick={()=>{handleSave2(processMachineData, processMachineData2, processDropDownId); console.log(processDropDownId)}}>Save</AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + </div> </div> + <Separator className='mx-10' orientation="vertical"/> {/* Needs parent container to be set to 100% height for it to work as it takes on the height of parent container */} + <div className="w-[400px]"> + + </div> </div> ); }; @@ -402,8 +454,8 @@ const editProcess = ( oldName: string, newName: string, oldDescription: string, console.log(error) }) } -const deleteProcess = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>, name: string, description:string) => { - e.preventDefault() +const deleteProcess = (name: string, description:string) => { + var token: string = "" var tokenBool = sessionStorage.getItem("TOKEN") @@ -454,7 +506,6 @@ async function fetchDataProcess(): Promise<Processes[]> { }).catch((error) => { console.log(error) }) - console.log(processArrray) return processArrray } diff --git a/Frontend/power-tracker/src/components/ui/textarea.tsx b/Frontend/power-tracker/src/components/ui/textarea.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9f9a6dc56193b728feda6d9b1a3d9302b8880f0b --- /dev/null +++ b/Frontend/power-tracker/src/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface TextareaProps + extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} + +const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( + ({ className, ...props }, ref) => { + return ( + <textarea + className={cn( + "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + className + )} + ref={ref} + {...props} + /> + ) + } +) +Textarea.displayName = "Textarea" + +export { Textarea } diff --git a/Frontend/power-tracker/src/index.css b/Frontend/power-tracker/src/index.css index 15c98ef5201dd4af3f61076b4c09b78b77e9a0d7..354173d1f0ff57c3b6f149cc2edd31ff2145174b 100644 --- a/Frontend/power-tracker/src/index.css +++ b/Frontend/power-tracker/src/index.css @@ -101,14 +101,14 @@ main{ .buildingbar{ display: grid; - grid-template-columns: 1fr auto 130px; + grid-template-columns: auto 1fr auto 130px auto; grid-template-rows: 40px; height: 100%; } .depbar{ display: grid; - grid-template-columns: 180px 1fr 60px 130px auto 130px; + grid-template-columns: auto 180px 1fr 60px 130px auto 130px auto; grid-template-rows: 40px; height: 100%; } diff --git a/Set-up-guide.md b/Set-up-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..c7068f5df29421d1e6bb1877d8f0c93d29d31627 --- /dev/null +++ b/Set-up-guide.md @@ -0,0 +1,59 @@ +# Gateway setup +This setup uses the LoRaWAN gateway "WisGate Edge Lite 2", but other similar gateways should use similar settings. + +## Opening the box +When opening the box there should be four items, the gateway itself, an antenne, a power cable and an instruction pamphlet. + +Firstly, screw the antenne into the gateway, and plug in the power to the gateway. It should light up. + +## Configuring internet + +Then open up a laptop and connect to the network created by the gateway, named RAK7268_\<some numbers>. + + + +Then open up a web browser and enter the browser adress shown in the image above. Log into the gateway with the username root, and password root. You might be prompted to set a password, if so, set a fitting password. + +You may also be prompted to set a region, set the appropriate region, as shown on the picture below. Click save changes in the bottom right corner afterwards. + + +If you connect an ethernet port directly in the back you can skip to the section "configure packet forwarder" + +After logging in you should be in the dashboard, clicking on where the red box below shows should enter into the internet section of the gateway. + + + + +Clicking the arrow in the red box, then settings, as shown on the image, should take you to a page. + + + +When opening the page, click enable (1), then after saving changes, click scan (2) and then lastly find the internet connection in the red box (3). Enter network credentials and click save changes. You can test if it works by trying to use the internet while connected to the gateway-network. + + + +## Configure packet forwarder + +Clicking on configuration settings (1), then select packet forwarder (2), then scrolling down a bit, and open "choose from the available protocols" (3). + + + +Scrolling down you should see a site looking somewhat like the picture below. Make sure "Semtech UDP GWMP protocol is selected, and then enter the ip adress "129.241.150.176" into the selection bar under "server adress", like on the image. Make sure the port up/down in 1700. Remember to click "save changes" after. + + + +## Website configuration + +Now all that remains is to add the gateway on the website. Open up the website on the link [https://powertracker.public.skyhigh.iik.ntnu.no](https://powertracker.public.skyhigh.iik.ntnu.no) and log in with your given credentials. + +Clicking gateways and system (1), then gateways(2) and lastly add new gateway (3). + + + + + +The EUI can be found back on the gateway page. Click the button circled in red in the image below, and then clicking "overivew", also circled in red. You can then see the EUI there, copy it into the field "EUI" on the image above. Then give the gateway a name, and click "submit gateway". + + + +After this the gateway should have been added, and you should be able to see it under the gateways section. Now the gateway is configured and ready to go. No more setup is needed. Sensors added through the website should start sending their data. \ No newline at end of file diff --git a/images/EUI.png b/images/EUI.png new file mode 100644 index 0000000000000000000000000000000000000000..154acef32a5a83900b8504769f36fac6c2bb6b0f Binary files /dev/null and b/images/EUI.png differ diff --git a/images/add-gateway.png b/images/add-gateway.png new file mode 100644 index 0000000000000000000000000000000000000000..cd9ec73912ffca2e82f26fff99abc06b90d2ed26 Binary files /dev/null and b/images/add-gateway.png differ diff --git a/images/configuration.png b/images/configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..18758140b6de36ebaf6e6542e17735a991df6f04 Binary files /dev/null and b/images/configuration.png differ diff --git a/images/gateway_note.jpg b/images/gateway_note.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5dc033de9d27758db4e80bdeaeed78743e2e7096 Binary files /dev/null and b/images/gateway_note.jpg differ diff --git a/images/internet_sec.png b/images/internet_sec.png new file mode 100644 index 0000000000000000000000000000000000000000..a4ea80db3be7c29f17e2b682aea36bd915365ff8 Binary files /dev/null and b/images/internet_sec.png differ diff --git a/images/network-settings.png b/images/network-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..255e6ec498d9760cb1797ab1845b018d2b96727c Binary files /dev/null and b/images/network-settings.png differ diff --git a/images/region.png b/images/region.png new file mode 100644 index 0000000000000000000000000000000000000000..bd3e359e65b3f82ccdf94f923328946c928c4cf3 Binary files /dev/null and b/images/region.png differ diff --git a/images/udp-forwarder.png b/images/udp-forwarder.png new file mode 100644 index 0000000000000000000000000000000000000000..3de3439386d8818180075ed97b5e9339fb4a76b8 Binary files /dev/null and b/images/udp-forwarder.png differ diff --git a/images/website-log.png b/images/website-log.png new file mode 100644 index 0000000000000000000000000000000000000000..22cd7a13c235c005446d390791d1d9706ceb4c18 Binary files /dev/null and b/images/website-log.png differ diff --git a/images/wifi-settings.png b/images/wifi-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..537f13a368ad60ca2f842b0f698e726993b53ac3 Binary files /dev/null and b/images/wifi-settings.png differ