diff --git a/database/migration/deploy/esp.sql b/database/migration/deploy/esp.sql index caba1b8..3a719c8 100644 --- a/database/migration/deploy/esp.sql +++ b/database/migration/deploy/esp.sql @@ -1,9 +1,79 @@ +-- Deploy climat-guardian:esp to pg + BEGIN; -create table esp -( - name character varying(20) not null, - ip character varying(15) NOT NULL -); + create table api.esp ( + id serial primary key, + name character varying(20) not null, + ip character varying(15) NOT NULL unique, + x integer default 0, + y integer default 0 + ); + grant all on api.esp to web_user; + grant usage, select on sequence api.esp_id_seq to web_user; + + -- Change api.data to use a foreign key instead of a string + alter table api.data add column esp_id integer; + insert into api.esp (name, ip) select distinct 'unnamed', ip from api.data; + update api.data set esp_id = esp.id from api.esp esp where api.data.ip = esp.ip; + alter table api.data drop column ip; + alter table api.data add constraint data_esp_fk foreign key (esp_id) references api.esp(id); + + -- Create a view to get the data with the esp name and ip + create view api.data_view as + select temperature, humidity, timestamp, ip, name + from api.data + join api.esp as e on e.id = data.esp_id; + grant select on api.data_view to web_user; + + -- Replace the function to insert data to match the new data structure + drop function api.insert_data; + create function api.insert_data( + temperature real, + humidity real, + ip varchar(15), + unix_timestamp bigint + ) + returns void as $$ + declare + ip_address varchar(15):= ip; + begin + -- if the ip said in the token is not the same as the one in the request, then this means that the user is not who he pretends to be and we should throw an error + if (current_setting('request.jwt.claims', true)::json->>'ip' != ip_address) then + raise exception insufficient_privilege + using hint = 'You are not who you pretend to be'; + end if; + -- insert the data + insert into api.data("temperature", "humidity", "timestamp", "esp_id") values (temperature, humidity, to_timestamp(unix_timestamp), (select id from api.esp where ip_address = esp.ip)); + end $$ language plpgsql; + grant insert on api.data to esp32; + + -- Replace the function to get the averaged data to match the new data structure + drop function api.avg_date; + create function api.avg_date( + delta varchar + ) + returns table( + avg_temperature double precision, + avg_humidity double precision, + date timestamp, + ip character varying(15), + name character varying(20), + count bigint + ) as $$ + begin + return query select + avg(temperature) as avg_temperature, + avg(humidity) as avg_humidity, + date_trunc(delta, timestamp) as date, + esp.ip, + esp.name, + count(*) as count + from api.data + join api.esp on data.esp_id = esp.id + group by date, esp.id + order by date; + end; + $$ language plpgsql; COMMIT; diff --git a/database/migration/revert/esp.sql b/database/migration/revert/esp.sql index c807937..7529016 100644 --- a/database/migration/revert/esp.sql +++ b/database/migration/revert/esp.sql @@ -1,5 +1,60 @@ +-- Revert climat-guardian:esp from pg + BEGIN; -drop table esp; + -- drop the view + drop view api.data_view; + + -- set the data table back to the old state + alter table api.data add column ip varchar(15); + update api.data data set ip = esp.ip from api.esp esp where data.esp_id = esp.id; + alter table api.data drop column esp_id; + + -- drop the esp table + drop table api.esp; + + -- use the old functions bback + drop function api.insert_data; + create function api.insert_data( + temperature real, + humidity real, + ip varchar(15), + unix_timestamp bigint + ) + returns void as $$ + begin + -- if the ip said in the token is not the same as the one in the request, then this means that the user is not who he pretends to be and we should throw an error + if (current_setting('request.jwt.claims', true)::json->>'ip' != ip) then + raise exception insufficient_privilege + using hint = 'You are not who you pretend to be'; + end if; + -- insert the data + insert into api.data("temperature", "humidity", "ip", "timestamp") values (temperature, humidity, ip, to_timestamp(unix_timestamp)); + end $$ language plpgsql; + grant insert on api.data to esp32; + + drop function api.avg_date; + create function api.avg_date( + delta varchar + ) + returns table( + avg_temperature double precision, + avg_humidity double precision, + date timestamp, + ip character varying(15), + count bigint + ) as $$ + begin + return query select + avg(temperature) as avg_temperature, + avg(humidity) as avg_humidity, + date_trunc(delta, timestamp) as date, + data.ip, + count(*) as count + from api.data + group by date, data.ip + order by date; + end; + $$ language plpgsql; -COMMIT; \ No newline at end of file +COMMIT; diff --git a/database/migration/sqitch.plan b/database/migration/sqitch.plan index 1d8dfd7..41f8afc 100644 --- a/database/migration/sqitch.plan +++ b/database/migration/sqitch.plan @@ -6,3 +6,4 @@ postgrest 2024-06-07T14:50:40Z Nils # create schema and roles for roles 2024-06-07T18:32:02Z Nils # create the role used by the user to distinguish users and esp data 2024-06-07T20:09:18Z Nils # Add table to store data user 2024-06-07T20:47:39Z Nils # create a user table to store every user and password +esp 2024-06-26T08:20:01Z dylan # create a esp table to stove every esp data diff --git a/database/migration/verify/esp.sql b/database/migration/verify/esp.sql index d72b2b7..a287630 100644 --- a/database/migration/verify/esp.sql +++ b/database/migration/verify/esp.sql @@ -1,8 +1,24 @@ +-- Verify climat-guardian:esp on pg + BEGIN; -select * from esp -INSERT INTO esp (name, ip) VALUES ('ESP1', '192.168.1.10'); -SELECT ip FROM esp WHERE name = 'ESP1'; + -- test esp table + select * from api.esp; + INSERT INTO api.esp (name, ip) VALUES ('ESP1', '192.168.1.10'); + UPDATE api.esp SET (x,y) = (4,5) WHERE name = 'ESP1'; + SELECT ip FROM api.esp WHERE name = 'ESP1'; + + -- test data table + select * from api.data; + INSERT INTO api.data (temperature, humidity, timestamp, esp_id) VALUES (25.5, 50.5, '2021-01-01 00:00:00', (SELECT id FROM api.esp WHERE name = 'ESP1')); + + -- test data_view and avg_date + select * from api.data_view where name = 'ESP1'; + select * from api.avg_date('day') where name = 'ESP1'; + -- test insert_data + delete from api.data where esp_id = (SELECT id FROM api.esp WHERE name = 'ESP1'); + select api.insert_data(25.5, 50.5, '192.168.1.10', 1609459200); + select * from api.data where esp_id = (SELECT id FROM api.esp WHERE name = 'ESP1'); -COMMIT; \ No newline at end of file +ROLLBACK; diff --git a/nextjs-interface/src/app/dashboard/esp/[espName]/page.tsx b/nextjs-interface/src/app/dashboard/esp/[espName]/page.tsx index 9d1bf63..433fcd0 100644 --- a/nextjs-interface/src/app/dashboard/esp/[espName]/page.tsx +++ b/nextjs-interface/src/app/dashboard/esp/[espName]/page.tsx @@ -1,21 +1,16 @@ -// import components "use client"; +// import charts import { PieChartHumidity } from "@/app/ui/dashboard/PieChartHumidity"; import { ChartElement } from "@/app/ui/dashboard/ChartElement"; import { PieChartTemperature } from "@/app/ui/dashboard/PieChartTemperature"; import { DateRangeElement } from "@/app/ui/dashboard/DateRangeElement"; - +import findIpByName, { useFetchData, useLastData } from "@/lib/data"; import { endOfMonth, format, startOfMonth } from "date-fns"; import { DateRange } from "react-day-picker"; -import findIpByName, { useFetchData, useLastData } from "@/lib/data"; - import React from "react"; -import { useParams } from "react-router"; - -export default function Page() { - const params = useParams<{ espName: string }>(); +export default function Page({ params }: { params: any }) { const [date, setDate] = React.useState(() => { const now = new Date(); return { @@ -23,11 +18,12 @@ export default function Page() { to: endOfMonth(now), }; }); + const from = date?.from ? format(date.from, "yyyy-MM-dd") : ""; const to = date?.to ? format(date.to, "yyyy-MM-dd") : ""; - const precision = "day"; + const precision = "minute"; - const ip = findIpByName(params.espName || "Loading"); + const ip = findIpByName(params.espName); const allData = useFetchData(precision, ip, from, to); const temperature = useLastData("temperature", ip); const humidity = useLastData("humidity", ip); @@ -37,7 +33,6 @@ export default function Page() {

{params.espName}

-
diff --git a/nextjs-interface/src/app/dashboard/page.tsx b/nextjs-interface/src/app/dashboard/page.tsx index c4e5f2a..65cac2a 100644 --- a/nextjs-interface/src/app/dashboard/page.tsx +++ b/nextjs-interface/src/app/dashboard/page.tsx @@ -1,17 +1,10 @@ "use client"; // import components import DataCircle from "@/app/ui/dashboard/DataCircle"; - -const ESPList = [ - { name: "ESP N°1", ip: "172.16.5.178" }, - { name: "ESP N°2", ip: "172.16.4.100" }, - { name: "ESP N°3", ip: "172.16.5.178" }, - { name: "ESP N°4", ip: "172.16.4.100" }, - { name: "ESP N°5", ip: "172.16.5.178" }, - { name: "ESP N°6", ip: "172.16.4.100" }, -]; +import { useAllEsp } from "@/lib/data"; export default function Page() { + const ESPList = useAllEsp(); return ( <>
diff --git a/nextjs-interface/src/app/ui/dashboard/EspLinksElement.tsx b/nextjs-interface/src/app/ui/dashboard/EspLinksElement.tsx index a3164f0..5a0c668 100644 --- a/nextjs-interface/src/app/ui/dashboard/EspLinksElement.tsx +++ b/nextjs-interface/src/app/ui/dashboard/EspLinksElement.tsx @@ -15,25 +15,41 @@ import { import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "recharts"; +import { getToken } from "@/lib/context"; +import { useAllEsp } from "@/lib/data"; export default function EspLinksElement() { const pathname = usePathname(); - const [links, setLinks] = useState([ - { name: "chasseron", ip: "172.16.4.100" }, - { name: "pleiades", ip: "172.16.5.178" }, - ]); - - const [newLink, setNewLink] = useState({ name: "", ip: "" }); + const links = useAllEsp(); + const [newLink, setNewLink] = useState({ name: "", ip: ""}); const handleInputChange = (e: any) => { setNewLink({ ...newLink, [e.target.id]: e.target.value }); }; - const handleSubmit = (e: any) => { + const handleSubmit = async (e: { preventDefault: () => void }) => { e.preventDefault(); - setLinks([...links, newLink]); - setNewLink({ name: "", ip: "" }); + const url = `/postgrest/esp`; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${getToken()}`, + }, + body: JSON.stringify(newLink), + }); + + if (!response.ok) { + Error("Erreur lors de l'envoi des données à l'API"); + } + + setNewLink({ name: "", ip: "" }); + } catch (e) { + console.error("Une erreur s'est produite :", e); + } }; return ( diff --git a/nextjs-interface/src/app/ui/dashboard/espLinks.tsx b/nextjs-interface/src/app/ui/dashboard/espLinks.tsx deleted file mode 100644 index d0f3809..0000000 --- a/nextjs-interface/src/app/ui/dashboard/espLinks.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export const links = [ - { name: "chasseron", ip: "172.16.5.178" }, - { name: "pleiades", ip: "172.16.5.178" }, -]; diff --git a/nextjs-interface/src/app/ui/login/LoginElement.tsx b/nextjs-interface/src/app/ui/login/LoginElement.tsx index 8f58faa..67d5cf8 100644 --- a/nextjs-interface/src/app/ui/login/LoginElement.tsx +++ b/nextjs-interface/src/app/ui/login/LoginElement.tsx @@ -12,7 +12,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; -import { SampleContext } from "@/lib/context"; export function LoginElement() { const [username, setUsername] = useState(""); @@ -23,7 +22,7 @@ export function LoginElement() { e.preventDefault(); await fetch( - `${SampleContext.urlLogin}/login.php?username=${username}&password=${password}`, + `/php/login.php?username=${username}&password=${password}`, { headers: { Accept: "application/json", @@ -39,7 +38,7 @@ export function LoginElement() { if (reponse.token) { localStorage.setItem("token", reponse.token); localStorage.setItem("username", username); - window.location.replace(`${SampleContext.urlCurrent}/dashboard`); + window.location.replace(`/dashboard`); } }) .catch((e) => { diff --git a/nextjs-interface/src/app/ui/plan/AddPointElement.tsx b/nextjs-interface/src/app/ui/plan/AddPointElement.tsx deleted file mode 100644 index 1bb96ac..0000000 --- a/nextjs-interface/src/app/ui/plan/AddPointElement.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import React from "react"; - -export function AddPointElement({ - newName, - newIp, - setNewName, - setNewIp, - cx, - cy, - setOpen, - addEsp, - open, -}: { - newName: string; - newIp: string; - setNewName: React.Dispatch>; - setNewIp: React.Dispatch>; - cx: number; - cy: number; - setOpen: React.Dispatch>; - addEsp: () => void; - open: boolean; -}) { - return ( - - - - - -
-
-

X: {Number(cx.toFixed(2))}

-

Y: {Number(cy.toFixed(2))}

-
-
- setNewName(e.target.value)} - /> - setNewIp(e.target.value)} - /> -
- - - -
-
-
- ); -} diff --git a/nextjs-interface/src/app/ui/plan/Plan.tsx b/nextjs-interface/src/app/ui/plan/Plan.tsx index 3c26001..5fb3c0c 100644 --- a/nextjs-interface/src/app/ui/plan/Plan.tsx +++ b/nextjs-interface/src/app/ui/plan/Plan.tsx @@ -1,68 +1,33 @@ "use client"; import React, { useState } from "react"; import { EspMap } from "@/app/ui/plan/espMap"; -import { AddPointElement } from "@/app/ui/plan/AddPointElement"; +import {useAllEsp} from "@/lib/data"; export default function Plan() { const [hoveredCircle, setHoveredCircle] = useState(""); - const [newName, setNewName] = useState(""); - const [newIp, setNewIp] = useState(""); - const [cx, setCx] = useState(0); - const [cy, setCy] = useState(0); + const [newX, setNewX] = useState(0); + const [newY, setNewY] = useState(0); const [open, setOpen] = useState(false); const mouseClick = (circle: string) => { setHoveredCircle(circle); }; - const [esp, setEsp] = useState< - { cx: number; cy: number; ip: string; name: string }[] - >([ - { cx: 78, cy: 80, ip: "172.16.4.100", name: "Chasseron" }, - { cx: 16, cy: 59, ip: "172.16.5.178", name: "Argentine" }, - { cx: 82, cy: 42, ip: "182.250.231.113", name: "Jungfrau" }, - { cx: 51, cy: 42, ip: "182.250.231.114", name: "Pleiades" }, - ]); + const esp = useAllEsp() const getPosition = (event: React.MouseEvent) => { const rect = event.currentTarget.getBoundingClientRect(); - setCx(((event.clientX - rect.left) / rect.width) * 100); - setCy(((event.clientY - rect.top) / rect.height) * 100); + setNewX(((event.clientX - rect.left) / rect.width) * 100); + setNewY(((event.clientY - rect.top) / rect.height) * 100); }; - const addEsp = () => { - if (newName.trim() !== "" && newIp.trim() !== "") { - const newCircle = { - cx, - cy, - ip: newIp, - name: newName, - }; - setEsp([...esp, newCircle]); - setNewName(""); - setNewIp(""); - setOpen(false); - } - }; const deleteEsp = (ip: string) => { const updatedEsp = esp.filter((circle) => circle.ip !== ip); - setEsp(updatedEsp); }; return (
- - {esp.map(({ cx, cy, ip, name }) => ( + {esp.map(({ x, y, ip, name }) => ( ([]); useEffect(() => { - const url = `${SampleContext.urlData}/rpc/avg_date?delta=${precision}&ip=eq.${ip}&and=(date.gte.${from},date.lt.${to})`; + const url = `/postgrest/rpc/avg_date?delta=${precision}&ip=eq.${ip}&and=(date.gte.${from},date.lt.${to})`; fetch(url, { headers: { Authorization: `Bearer ${getToken()}` } }) .then((response) => response.json()) .then((apiData: avgData[]) => { setData(apiData); + console.log(apiData); }) .catch((e) => { console.error("Une erreur s'est produite :", e); @@ -28,7 +28,7 @@ export function useLastData(type: string, ip: string) { const [value, setValue] = useState(undefined); useEffect(() => { - const url = `${SampleContext.urlData}/data?limit=1&order=timestamp.desc&ip=eq.${ip}`; + const url = `/postgrest/data_view?limit=1&order=timestamp.desc&ip=eq.${ip}`; fetch(url, { headers: { Authorization: `Bearer ${getToken()}` } }) .then((response) => response.json()) .then((apiData: data[]) => { @@ -45,7 +45,38 @@ export function useLastData(type: string, ip: string) { return value; } -export default function findIpByName(name: string) { - const link = links.find((link: { name: string }) => link.name === name); - return link ? link.ip : "IP non trouvée"; +export const useAllEsp = () => { + const [esp, setEsp] = useState([]); + + useEffect(() => { + const url = `/postgrest/esp`; + fetch(url, { headers: { Authorization: `Bearer ${getToken()}` } }) + .then((response) => response.json()) + .then((apiEsp: esp[]) => { + setEsp(apiEsp); + console.log(apiEsp); + }) + .catch((e) => { + console.error("Une erreur s'est produite :", e); + }); + }, []); + return esp; +}; + +export default function useFindIpByName(name: string) { + const [ip, setIp] = useState(""); + + useEffect(() => { + const url = `/postgrest/esp?select=ip&name=eq.${name}`; + fetch(url, { headers: { Authorization: `Bearer ${getToken()}` } }) + .then((response) => response.json()) + .then((apiIp: esp[]) => { + console.log(apiIp[0].ip); + setIp(apiIp[0].ip); + }) + .catch((e) => { + console.error("Une erreur s'est produite :", e); + }); + }, [name]); + return ip; }