From 42dea977637fbd5a545efee864f49d731a5ab59f Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Thu, 16 Oct 2025 03:24:01 +0200 Subject: [PATCH 01/21] front --- src/component/Navigation/Navigation.tsx | 2 +- src/pages/Dashboard.tsx | 82 +++++++++++++++++-------- src/pages/Login.tsx | 2 +- src/type/Dashboard.ts | 9 +-- src/type/InstanceType.ts | 2 +- src/type/JiType.ts | 12 ++++ src/type/SujetType.ts | 7 +++ src/type/TpType.ts | 11 ---- 8 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 src/type/JiType.ts create mode 100644 src/type/SujetType.ts delete mode 100644 src/type/TpType.ts diff --git a/src/component/Navigation/Navigation.tsx b/src/component/Navigation/Navigation.tsx index 114416d..6487e90 100644 --- a/src/component/Navigation/Navigation.tsx +++ b/src/component/Navigation/Navigation.tsx @@ -41,7 +41,7 @@ const Navigation: React.FC = ({ }; useEffect(() => { - axios.get("/api/users/me").then((res) => { + axios.get("/api/user/me").then((res) => { if (res.data.username.trim() === "") { navigate("/login"); } diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index a3839a6..7a9cd86 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from "react"; import axios from "axios"; import { Link } from "react-router-dom"; -import { Tp } from "../type/TpType"; +import { Sujet } from "../type/SujetType"; +import { Ji } from "../type/JiType"; import { DashboardType } from "../type/Dashboard"; function Dashboard() { const [dashboard, setDashboard] = useState(null); - const username = localStorage.getItem("username"); + const username = localStorage.getItem("name"); useEffect(() => { axios.get("/api/dashboard").then((res) => { setDashboard(res.data); @@ -27,60 +28,61 @@ function Dashboard() { {dashboard && ( <> + { dashboard.jiRespo.length !== 0 && (

- Practicals + Immersions - Respo

-
- {dashboard.tps.map((tp: Tp) => ( +
+ {dashboard.jiRespo.map((jiRespo: Ji) => (
-

{tp.name}

-

{tp.description}

+

{jiRespo.name}

+

{jiRespo.description}

- - Learn More + + GO
))}
- {dashboard.tps.length === 0 && ( -

You have no tps

- )} +
+ )} + { dashboard.sujetRespo.length !== 0 && (
-

Instances

+

Sujets - Respo

- {dashboard.instances.map((instance) => ( + {dashboard.sujetRespo.map((sujet: Sujet) => (
-

{instance.name}

- {instance.tp && ( -

Linked to: {instance.tp.name}

+

{sujetRespo.name}

+ {sujetRespo && ( +

Linked to: {sujetRespo.name}

)}
{false && ( Learn More )} - {instance.tp && ( + {sujetRespo && ( See TP @@ -90,13 +92,41 @@ function Dashboard() {
))} - {dashboard.instances.length === 0 && ( -

You have no instances

- )}
-
+ )} +
+
+

+ Immersions - Activites +

+
+ {dashboard.jiParticipant.map((jiParticipant: Ji) => ( +
+
+

{jiParticipant.name}

+

{jiParticipant.description}

+
+ + Learn More + +
+
+
+ ))} +
+ {dashboard.jiParticipant.length === 0 && ( +
+

You are not registered on any activities.

+
+ )} +
+
+

Messages

You have no messages

diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 276a407..cc79e7c 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -24,7 +24,7 @@ const LoginPage: React.FC = () => { if (response.status === 200) { axios.get("/api/users/me").then((res) => { localStorage.setItem("username", res.data.username); - if (res.data.roles.includes("root")) { + if (res.data.roles.includes("ROOT")) { localStorage.setItem("root", "true"); } else { localStorage.removeItem("root"); diff --git a/src/type/Dashboard.ts b/src/type/Dashboard.ts index 18c3925..2abf5e0 100644 --- a/src/type/Dashboard.ts +++ b/src/type/Dashboard.ts @@ -1,7 +1,8 @@ -import { Instance } from "./InstanceType"; -import { Tp } from "./TpType"; +import { Ji } from "./JiType"; +import { Sujet } from "./SujetType"; export interface DashboardType { - tps: Tp[]; - instances: Instance[]; + jiRespo: Ji[]; + jiParticipant: Ji[]; + sujetRespo: Sujet[]; } diff --git a/src/type/InstanceType.ts b/src/type/InstanceType.ts index 2de6819..9e35263 100644 --- a/src/type/InstanceType.ts +++ b/src/type/InstanceType.ts @@ -1,4 +1,4 @@ -import { Tp } from "./TpType"; +import { Ji } from "./JiType"; import { User } from "./UserType"; export interface Instance { diff --git a/src/type/JiType.ts b/src/type/JiType.ts new file mode 100644 index 0000000..cc9de2c --- /dev/null +++ b/src/type/JiType.ts @@ -0,0 +1,12 @@ +import { Sujet } from "./SujetType"; +import { User } from "./UserType"; + +export interface Ji { + name: string; + description: string; + respos: User[]; + date: string; + site: Site; + participants: User[]; + //instances: Instances[]; +} diff --git a/src/type/SujetType.ts b/src/type/SujetType.ts new file mode 100644 index 0000000..0f404ad --- /dev/null +++ b/src/type/SujetType.ts @@ -0,0 +1,7 @@ +export interface Sujet { + id: number; + name: string; + description: string; + pdfLink: string; + respos: string; +} diff --git a/src/type/TpType.ts b/src/type/TpType.ts deleted file mode 100644 index 568d5f7..0000000 --- a/src/type/TpType.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface Tp { - id: number; - name: string; - description: string; - pdfLink: string; - respo: string; - date: string; - ssh: string; - port: string; - pwd: string; -} -- 2.47.2 From 31ffd227c70fe3b3c6fc7022633c515b5b2bc20f Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Thu, 16 Oct 2025 04:19:25 +0200 Subject: [PATCH 02/21] integration api immersion --- src/App.tsx | 8 ++--- src/component/Navigation/Navigation.tsx | 4 +-- src/pages/{Practical.tsx => Immersion.tsx} | 36 ++++++++++---------- src/pages/{Practicals.tsx => Immersions.tsx} | 26 +++++++------- 4 files changed, 37 insertions(+), 37 deletions(-) rename src/pages/{Practical.tsx => Immersion.tsx} (85%) rename src/pages/{Practicals.tsx => Immersions.tsx} (52%) diff --git a/src/App.tsx b/src/App.tsx index 6e8521e..8e6998a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,9 +2,9 @@ import "./App.css"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import Navigation from "./component/Navigation/Navigation"; import Dashboard from "./pages/Dashboard"; -import Practicals from "./pages/Practicals"; +import Immersions from "./pages/Immersions"; import Instances from "./pages/Instances"; -import Practical from "./pages/Practical"; +import Immersion from "./pages/Immersion"; import LoginPage from "./pages/Login"; import PageTest from "./pages/PageTest"; import CreateTp from "./pages/admin/CreateTp"; @@ -21,8 +21,8 @@ function App() { } /> - } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/src/component/Navigation/Navigation.tsx b/src/component/Navigation/Navigation.tsx index 6487e90..d7f4b01 100644 --- a/src/component/Navigation/Navigation.tsx +++ b/src/component/Navigation/Navigation.tsx @@ -183,8 +183,8 @@ const Navigation: React.FC = ({
  • - - TPs + + Immersions
  • diff --git a/src/pages/Practical.tsx b/src/pages/Immersion.tsx similarity index 85% rename from src/pages/Practical.tsx rename to src/pages/Immersion.tsx index aad2fc1..5e71f50 100644 --- a/src/pages/Practical.tsx +++ b/src/pages/Immersion.tsx @@ -3,11 +3,11 @@ import axios from "axios"; import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { toast } from "react-toastify"; -import { Tp } from "../type/TpType"; +import { Ji } from "../type/JiType"; -function Practical() { +function Immersion() { const { id } = useParams(); - const [tp, setTp] = useState(); + const [ji, setJi] = useState(); const copyText = (copy: string) => { navigator.clipboard.writeText(copy); @@ -18,24 +18,24 @@ function Practical() { }; useEffect(() => { - axios.get(`/api/tps/${id}`).then((res) => { - setTp(res.data); + axios.get(`/api/ji/${id}`).then((res) => { + setJi(res.data); }); }, [id]); return ( <> - {tp && ( + {ji && ( <> -

    {tp.name}

    +

    {ji.name}

    @@ -46,7 +46,7 @@ function Practical() { Information
      -
    • {tp.description}
    • +
    • {ji.description}
    • Duration: 2 hours @@ -74,9 +74,9 @@ function Practical() { SSH:
      copyText(tp.ssh)} + onClick={() => copyText(ji.ssh)} > - {tp.ssh} + {ji.ssh}
      @@ -86,9 +86,9 @@ function Practical() { Port:
      copyText(tp.port)} + onClick={() => copyText(ji.port)} > - {tp.port} + {ji.port}
      @@ -98,9 +98,9 @@ function Practical() { Password:
      copyText(tp.pwd)} + onClick={() => copyText(ji.pwd)} > - {tp.pwd} + {ji.pwd}
      @@ -109,7 +109,7 @@ function Practical() {
    @@ -132,4 +132,4 @@ function Practical() { ); } -export default Practical; +export default Immersion; diff --git a/src/pages/Practicals.tsx b/src/pages/Immersions.tsx similarity index 52% rename from src/pages/Practicals.tsx rename to src/pages/Immersions.tsx index 70a18e6..9de9644 100644 --- a/src/pages/Practicals.tsx +++ b/src/pages/Immersions.tsx @@ -1,32 +1,32 @@ import { useEffect, useState } from "react"; import axios from "axios"; import { Link } from "react-router-dom"; -import { Tp } from "../type/TpType"; +import { Ji } from "../type/JiType"; -function Practicals() { - const [tps, setTps] = useState([]); +function Immersions() { + const [jis, setJis] = useState([]); useEffect(() => { - axios.get("/api/tps").then((res) => { - setTps(res.data); + axios.get("/api/ji/listall").then((res) => { + setJis(res.data); }); }, []); return (
    -

    Your TPs

    +

    Journees d immersion - Activities

    - {tps.map((practical: Tp) => ( + {jis.map((ji: Ji) => (
    -

    {practical.name}

    -

    {practical.description}

    +

    {ji.name}

    +

    {ji.description}

    - - Learn More + + Open activity
    @@ -37,4 +37,4 @@ function Practicals() { ); } -export default Practicals; +export default Immersions; -- 2.47.2 From 8c04856614474d177ec8fc5140ed0e780ceb6530 Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Fri, 17 Oct 2025 00:45:48 +0200 Subject: [PATCH 03/21] still updating to new back --- src/App.tsx | 8 ++ src/component/Navigation/Navigation.tsx | 35 +++++++ src/pages/Immersion.tsx | 18 +--- src/pages/Login.tsx | 2 +- src/pages/Site.tsx | 88 +++++++++++++++++ src/pages/admin/CreateJi.tsx | 122 ++++++++++++++++++++++++ src/pages/admin/CreateSite.tsx | 106 ++++++++++++++++++++ src/pages/admin/ListJi.tsx | 75 +++++++++++++++ src/pages/admin/Sites.tsx | 75 +++++++++++++++ src/type/JiType.ts | 5 +- src/type/SiteType.ts | 9 ++ 11 files changed, 526 insertions(+), 17 deletions(-) create mode 100644 src/pages/Site.tsx create mode 100644 src/pages/admin/CreateJi.tsx create mode 100644 src/pages/admin/CreateSite.tsx create mode 100644 src/pages/admin/ListJi.tsx create mode 100644 src/pages/admin/Sites.tsx create mode 100644 src/type/SiteType.ts diff --git a/src/App.tsx b/src/App.tsx index 8e6998a..c049b34 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,11 +5,15 @@ import Dashboard from "./pages/Dashboard"; import Immersions from "./pages/Immersions"; import Instances from "./pages/Instances"; import Immersion from "./pages/Immersion"; +import Site from "./pages/Site"; import LoginPage from "./pages/Login"; import PageTest from "./pages/PageTest"; import CreateTp from "./pages/admin/CreateTp"; +import CreateSite from "./pages/admin/CreateSite"; +import CreateJi from "./pages/admin/CreateJi"; import BulkUsers from "./pages/admin/BulkCreateUser"; import Users from "./pages/admin/Users"; +import Sites from "./pages/admin/Sites"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import AdminPage from "./pages/admin/AdminPage"; @@ -23,13 +27,17 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> } /> } /> } /> + } /> + } /> } /> + } /> } /> diff --git a/src/component/Navigation/Navigation.tsx b/src/component/Navigation/Navigation.tsx index d7f4b01..37a1e94 100644 --- a/src/component/Navigation/Navigation.tsx +++ b/src/component/Navigation/Navigation.tsx @@ -147,6 +147,41 @@ const Navigation: React.FC = ({
  • +
    + + Create Site + +
    +
  • +
  • +
    + + Create new JDMI + +
    +
  • + +
  • +
    + + List Sites + +
    +
  • + +
  • {ji.name}
    -
    -
    - -
    -
    -

    - Information + {ji.name} - Information

    • {ji.description}
    • - Duration: - 2 hours + Description: + {ji.description}
    • Tools Used: @@ -61,7 +51,7 @@ function Immersion() {
    • Date: - Oct 23, 2024 + {ji.date}
    diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index cc79e7c..d589f36 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -22,7 +22,7 @@ const LoginPage: React.FC = () => { }) .then((response) => { if (response.status === 200) { - axios.get("/api/users/me").then((res) => { + axios.get("/api/user/me").then((res) => { localStorage.setItem("username", res.data.username); if (res.data.roles.includes("ROOT")) { localStorage.setItem("root", "true"); diff --git a/src/pages/Site.tsx b/src/pages/Site.tsx new file mode 100644 index 0000000..1e7d1c6 --- /dev/null +++ b/src/pages/Site.tsx @@ -0,0 +1,88 @@ +import { ArrowDownTrayIcon, ClipboardIcon } from "@heroicons/react/24/outline"; +import axios from "axios"; +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { toast } from "react-toastify"; +import { Site } from "../type/SiteType"; + +function Site() { + const { id } = useParams(); + const [site, setSite] = useState(); + + const copyText = (copy: string) => { + navigator.clipboard.writeText(copy); + toast.success("Copied!", { + draggable: true, + theme: localStorage.getItem("theme") || "dark", + }); + }; + + useEffect(() => { + axios.get(`/api/sites/${id}`).then((res) => { + setSite(res.data); + }); + }, [id]); + + return ( + <> + {site && ( + <> +

    {site.name}

    +
    + + )} + + ); +} + +export default Site; diff --git a/src/pages/admin/CreateJi.tsx b/src/pages/admin/CreateJi.tsx new file mode 100644 index 0000000..832ab05 --- /dev/null +++ b/src/pages/admin/CreateJi.tsx @@ -0,0 +1,122 @@ +import axios from "axios"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +function CreateJi() { + const [ji, setJi] = useState({ + name: "", + respo: "", + site_id: "", + date: "", + }); + const navigate = useNavigate(); + + const handleInputChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => { + const { name, value } = e.target; + setJi({ ...ji, [name]: value }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + // Créer les query parameters + const params = new URLSearchParams({ + name: ji.name, + respo: ji.respo, + site_id: ji.site_id, + date: ji.date, + }); + + axios.post(`/api/ji/create?${params.toString()}`).then((res) => { + if (res.status === 200) { + navigate(`/immersion/${res.data.id}`); + } + }); + }; + return ( +
    +
    +

    + Create Ji +

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    +
    + ); +} + +export default CreateJi; diff --git a/src/pages/admin/CreateSite.tsx b/src/pages/admin/CreateSite.tsx new file mode 100644 index 0000000..fe7c719 --- /dev/null +++ b/src/pages/admin/CreateSite.tsx @@ -0,0 +1,106 @@ +import axios from "axios"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +function CreateSite() { + const [site, setSite] = useState({ + name: "", + desc: "", + address: "", + }); + const navigate = useNavigate(); + + const handleInputChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => { + const { name, value } = e.target; + setSite({ ...site, [name]: value }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + // Créer les query parameters + const params = new URLSearchParams({ + name: site.name, + description: site.description, + address: site.address, + }); + + axios.post(`/api/sites?${params.toString()}`).then((res) => { + if (res.status === 200) { + navigate(`/site/${res.data.id}`); + } + }); + }; + return ( +
    +
    +

    + Create Site +

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    +
    + ); +} + +export default CreateSite; diff --git a/src/pages/admin/ListJi.tsx b/src/pages/admin/ListJi.tsx new file mode 100644 index 0000000..ed3decf --- /dev/null +++ b/src/pages/admin/ListJi.tsx @@ -0,0 +1,75 @@ +import axios from "axios"; +import { useEffect, useState } from "react"; +import { Ji } from "../../type/JiType"; + +function ListJI() { + const [jis, setJis] = useState([]); + const [reload, setReload] = useState(0); + + useEffect(() => { + axios.get("/api/ji/listall").then((res) => { + setJis(res.data); + }); + }, [reload]); + + const handleEditJi = (jiId: number) => { + alert(`Edit ji with ID: ${siteId}`); + }; + + /*const deleteJis = () => { + axios.delete("/api/jis").then(() => { + setReload(reload + 1); + }); + };*/ + + return ( +
    +
    +
    +

    + Manage Jis +

    + {/**/} +
    + + {/* Users Table */} +
    + + + + + + + + + + + + + {jis.map((site: Ji, index: number) => ( + + + + + + + + ))} + +
    #NameAddressRolesInstancesActions
    {index + 1}{ji.name}{ji.address}{ji.listJi} + +
    +
    +
    +
    + ); +} + +export default Jis; diff --git a/src/pages/admin/Sites.tsx b/src/pages/admin/Sites.tsx new file mode 100644 index 0000000..aa3fb1e --- /dev/null +++ b/src/pages/admin/Sites.tsx @@ -0,0 +1,75 @@ +import axios from "axios"; +import { useEffect, useState } from "react"; +import { Site } from "../../type/SiteType"; + +function Sites() { + const [sites, setSites] = useState([]); + const [reload, setReload] = useState(0); + + useEffect(() => { + axios.get("/api/sites").then((res) => { + setSites(res.data); + }); + }, [reload]); + + const handleEditSite = (siteId: number) => { + alert(`Edit site with ID: ${siteId}`); + }; + + /*const deleteSites = () => { + axios.delete("/api/sites").then(() => { + setReload(reload + 1); + }); + };*/ + + return ( +
    +
    +
    +

    + Manage Sites +

    + {/**/} +
    + + {/* Users Table */} +
    + + + + + + + + + + + + + {sites.map((site: Site, index: number) => ( + + + + + + + + ))} + +
    #NameAddressRolesInstancesActions
    {index + 1}{site.name}{site.address}{site.listJi} + +
    +
    +
    +
    + ); +} + +export default Sites; diff --git a/src/type/JiType.ts b/src/type/JiType.ts index cc9de2c..1387569 100644 --- a/src/type/JiType.ts +++ b/src/type/JiType.ts @@ -2,11 +2,12 @@ import { Sujet } from "./SujetType"; import { User } from "./UserType"; export interface Ji { + id: number, name: string; description: string; - respos: User[]; date: string; site: Site; - participants: User[]; + respos: User[]; + participants: User[] //instances: Instances[]; } diff --git a/src/type/SiteType.ts b/src/type/SiteType.ts new file mode 100644 index 0000000..9ca856e --- /dev/null +++ b/src/type/SiteType.ts @@ -0,0 +1,9 @@ +import { Ji } from "./JiType"; + +export interface Site { + id: number; + name: string; + description: string; + address: string; + listJi: List; +} -- 2.47.2 From 69955ac4336fc9c492bbe24b23c8098f7afb69ab Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Sat, 18 Oct 2025 02:32:23 +0200 Subject: [PATCH 04/21] dodo --- src/pages/admin/BulkCreateUser.tsx | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/pages/admin/BulkCreateUser.tsx b/src/pages/admin/BulkCreateUser.tsx index 420f6d8..4d575bf 100644 --- a/src/pages/admin/BulkCreateUser.tsx +++ b/src/pages/admin/BulkCreateUser.tsx @@ -2,22 +2,22 @@ import axios from "axios"; import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { BulkUserCreattionType } from "../../type/BulkUserCreattionType"; -import { Tp } from "../../type/TpType"; +import { Ji } from "../../type/TpType"; import { CSVParseType } from "../../type/CSVParseType"; function BulkUsers() { const [userData, setUserData] = useState([]); const [practical, setPractical] = useState("0"); const navigate = useNavigate(); - const [tps, setTps] = useState([]); + const [jis, setJis] = useState([]); const handlePracticalChange = (e: React.ChangeEvent) => { setPractical(e.target.value); }; useEffect(() => { - axios.get("/api/tps").then((res) => { - setTps(res.data); + axios.get("/api/ji/listall").then((res) => { + setJis(res.data); }); }, []); @@ -51,11 +51,21 @@ function BulkUsers() { const handleSubmit = () => { axios - .post("/api/users/jdmi", { + .post("/api/users", { users: userData.filter((user: CSVParseType) => user.name !== ""), - tpId: practical, - }) - .then(() => navigate(`/admin/users`)); + jiId: 1, // TODO: Unhardcode this + }).then((res) => { + if (res.status === 200) { + navigate(`/admin/users`); + } + else if (res.status === 202) { + alert(`Couldn't create some users`); + navigate(`/admin/users`); + } + else { + alert(`Couldn't create ANY users`); + } + }); }; return ( @@ -86,11 +96,6 @@ function BulkUsers() { {user.name} {user.email} - {user.password} - {user.instance_name} - {user.instance_ssh} - {user.instance_port} - {user.instance_pwd} ))} @@ -99,7 +104,7 @@ function BulkUsers() { )}
    - +
    -- 2.47.2 From a75b432aa4accf831a13ab6d4aa3854e601a8173 Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Sun, 19 Oct 2025 19:52:59 +0200 Subject: [PATCH 05/21] fix: login affichage, bulk user creation adjustement --- src/pages/Dashboard.tsx | 2 +- src/pages/Immersion.tsx | 40 +++++++++++++++++------------- src/pages/admin/BulkCreateUser.tsx | 13 ++++------ src/pages/admin/CreateJi.tsx | 6 ++--- src/type/InstanceType.ts | 6 +---- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 7a9cd86..64b8ccc 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -7,7 +7,7 @@ import { DashboardType } from "../type/Dashboard"; function Dashboard() { const [dashboard, setDashboard] = useState(null); - const username = localStorage.getItem("name"); + const username = localStorage.getItem("username"); useEffect(() => { axios.get("/api/dashboard").then((res) => { setDashboard(res.data); diff --git a/src/pages/Immersion.tsx b/src/pages/Immersion.tsx index e1a5701..1d40bde 100644 --- a/src/pages/Immersion.tsx +++ b/src/pages/Immersion.tsx @@ -4,11 +4,13 @@ import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { toast } from "react-toastify"; import { Ji } from "../type/JiType"; +import { Instance } from "../type/InstanceType"; function Immersion() { const { id } = useParams(); const [ji, setJi] = useState(); - + const [instance, setInstance] = useState(); + const username = localStorage.getItem("username"); const copyText = (copy: string) => { navigator.clipboard.writeText(copy); toast.success("Copied!", { @@ -22,6 +24,18 @@ function Immersion() { setJi(res.data); }); }, [id]); + + useEffect(() => { + axios.get(`/api/ji/${id}/instances`).then((res) => { + setInstance(res.data); + }); + }, [id]); + + useEffect(() => { + axios.get("/api/user/me").then((res) => { + localStorage.setItem("username", res.data.username); + }); + }, []); return ( <> @@ -41,44 +55,36 @@ function Immersion() { Description: {ji.description}
  • -
  • - Tools Used: - Python, SSH, Vim -
  • -
  • - Difficulty: - Beginner -
  • Date: {ji.date}
  • -
    + { instance &&

    - Resources + Your credentials :

    • SSH:
      copyText(ji.ssh)} + onClick={() => copyText("ssh -P {$instance.port} {$username}@la-banquise.fr")} > - {ji.ssh} + ssh -P {instance.port} {username}@la-banquise.fr
    • - Port: + Instance name:
      copyText(ji.port)} + onClick={() => copyText(instance.name)} > - {ji.port} + {instance.name}
      @@ -97,7 +103,7 @@ function Immersion() {
    -
    +
    }
    user.name !== ""), - jiId: 1, // TODO: Unhardcode this + jiId: 1, }).then((res) => { if (res.status === 200) { - navigate(`/admin/users`); + alert(`Users succesfully created !\n${res}`); } - else if (res.status === 202) { + if (res.status === 202) alert(`Couldn't create some users`); - navigate(`/admin/users`); - } - else { + if (res.status === 500) alert(`Couldn't create ANY users`); - } }); }; @@ -120,7 +117,7 @@ function BulkUsers() { diff --git a/src/pages/admin/CreateJi.tsx b/src/pages/admin/CreateJi.tsx index 832ab05..1dbf442 100644 --- a/src/pages/admin/CreateJi.tsx +++ b/src/pages/admin/CreateJi.tsx @@ -62,12 +62,12 @@ function CreateJi() {
    -
    - -
    - - -
    - -
    - -
    - -
    -
    - ); -} - -export default CreateSite; diff --git a/src/pages/admin/Sites.tsx b/src/pages/admin/Sites.tsx index aa3fb1e..e61dab0 100644 --- a/src/pages/admin/Sites.tsx +++ b/src/pages/admin/Sites.tsx @@ -1,10 +1,15 @@ import axios from "axios"; import { useEffect, useState } from "react"; -import { Site } from "../../type/SiteType"; function Sites() { const [sites, setSites] = useState([]); const [reload, setReload] = useState(0); + const [showCreateForm, setShowCreateForm] = useState(false); + const [site, setSite] = useState({ + name: "", + desc: "", + address: "", + }); useEffect(() => { axios.get("/api/sites").then((res) => { @@ -12,29 +17,113 @@ function Sites() { }); }, [reload]); - const handleEditSite = (siteId: number) => { + const handleEditSite = (siteId) => { alert(`Edit site with ID: ${siteId}`); }; - /*const deleteSites = () => { - axios.delete("/api/sites").then(() => { - setReload(reload + 1); + const handleInputChange = (e) => { + const { name, value } = e.target; + setSite({ ...site, [name]: value }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + const params = new URLSearchParams({ + name: site.name, + description: site.desc, + address: site.address, }); - };*/ + + axios.post(`/api/sites?${params.toString()}`).then((res) => { + if (res.status === 200) { + setSite({ name: "", desc: "", address: "" }); + setShowCreateForm(false); + setReload(reload + 1); + } + }); + }; return (
    -

    +

    Manage Sites

    - {/**/} +
    - {/* Users Table */} + {/* Create Site Form */} + {showCreateForm && ( +
    +

    + Create New Site +

    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    + )} + + {/* Sites Table */}
    @@ -43,12 +132,11 @@ function Sites() { - - {sites.map((site: Site, index: number) => ( + {sites.map((site, index) => ( @@ -56,7 +144,7 @@ function Sites() {
    Name Address RolesInstances Actions
    {index + 1} {site.name}{site.listJi} - - - - - ); -} - -export default CreateJi; diff --git a/src/pages/admin/Jis.tsx b/src/pages/admin/Jis.tsx new file mode 100644 index 0000000..ae06430 --- /dev/null +++ b/src/pages/admin/Jis.tsx @@ -0,0 +1,200 @@ +import axios from "axios"; +import { useEffect, useState } from "react"; +import { Ji } from "../../type/JiType"; + +function Jis() { + const [jis, setJis] = useState([]); + const [reload, setReload] = useState(0); + const [showCreateForm, setShowCreateForm] = useState(false); + const [newJi, setNewJi] = useState({ + name: "", + desc: "", + respo: "", + site_id: "", + date: "", + }); + + useEffect(() => { + axios.get("/api/ji/listall").then((res) => { + setJis(res.data); + }); + }, [reload]); + + const handleEditJi = (jiId: number) => { + alert(`Edit ji with ID: ${jiId}`); + }; + + const handleInputChange = ( + e: React.ChangeEvent + ) => { + const { name, value } = e.target; + setNewJi({ ...newJi, [name]: value }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + const params = new URLSearchParams({ + name: newJi.name, + respo: newJi.respo, + site_id: newJi.site_id, + date: newJi.date, + }); + + axios.post(`/api/ji/create?${params.toString()}`).then((res) => { + if (res.status === 200) { + setNewJi({ name: "", desc: "", respo: "", site_id: "", date: "" }); + setShowCreateForm(false); + setReload(reload + 1); + } + }); + }; + + return ( +
    +
    +
    +

    + Manage JIs +

    + +
    + + {/* Create JI Form */} + {showCreateForm && ( +
    +

    + Create New JI +

    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    + )} + + {/* JIs Table */} +
    + + + + + + + + + + + + + {jis.map((ji: Ji, index: number) => ( + + + + + + + + + ))} + +
    #NameResponsableSite IDDateActions
    {index + 1}{ji.name}{ji.respo}{ji.site_id}{ji.date} + +
    +
    +
    +
    + ); +} + +export default Jis; diff --git a/src/pages/admin/ListJi.tsx b/src/pages/admin/ListJi.tsx deleted file mode 100644 index ed3decf..0000000 --- a/src/pages/admin/ListJi.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import axios from "axios"; -import { useEffect, useState } from "react"; -import { Ji } from "../../type/JiType"; - -function ListJI() { - const [jis, setJis] = useState([]); - const [reload, setReload] = useState(0); - - useEffect(() => { - axios.get("/api/ji/listall").then((res) => { - setJis(res.data); - }); - }, [reload]); - - const handleEditJi = (jiId: number) => { - alert(`Edit ji with ID: ${siteId}`); - }; - - /*const deleteJis = () => { - axios.delete("/api/jis").then(() => { - setReload(reload + 1); - }); - };*/ - - return ( -
    -
    -
    -

    - Manage Jis -

    - {/**/} -
    - - {/* Users Table */} -
    - - - - - - - - - - - - - {jis.map((site: Ji, index: number) => ( - - - - - - - - ))} - -
    #NameAddressRolesInstancesActions
    {index + 1}{ji.name}{ji.address}{ji.listJi} - -
    -
    -
    -
    - ); -} - -export default Jis; -- 2.47.2 From 0a7e3fcc4479ec189128cb852b69f9b353e8fcd4 Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Mon, 20 Oct 2025 16:39:27 +0200 Subject: [PATCH 09/21] fix: updated navigation to match new ji path --- src/component/Navigation/Navigation.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/component/Navigation/Navigation.tsx b/src/component/Navigation/Navigation.tsx index 1bb260e..d56ca85 100644 --- a/src/component/Navigation/Navigation.tsx +++ b/src/component/Navigation/Navigation.tsx @@ -149,11 +149,11 @@ const Navigation: React.FC = ({
  • - Create new JDMI + JDMI
  • -- 2.47.2 From 3557050f8e27b84492423041c4908235d4b8f8be Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Mon, 20 Oct 2025 17:11:19 +0200 Subject: [PATCH 10/21] remaking Immersion page for admins --- src/pages/Immersion.tsx | 270 +++++++++++++++++++++++++++++++++------- 1 file changed, 228 insertions(+), 42 deletions(-) diff --git a/src/pages/Immersion.tsx b/src/pages/Immersion.tsx index 6c4b248..d2830be 100644 --- a/src/pages/Immersion.tsx +++ b/src/pages/Immersion.tsx @@ -10,7 +10,10 @@ function Immersion() { const { id } = useParams(); const [ji, setJi] = useState(); const [instance, setInstance] = useState(); + const [allInstances, setAllInstances] = useState([]); + const [instancesStatus, setInstancesStatus] = useState>({}); const username = localStorage.getItem("username"); + const copyText = (copy: string) => { navigator.clipboard.writeText(copy); toast.success("Copied!", { @@ -31,6 +34,21 @@ function Immersion() { }); }, [id]); + useEffect(() => { + axios.get(`/api/ji/${id}/all-instances`).then((res) => { + setAllInstances(res.data); + // Récupérer le status de chaque instance + res.data.forEach((inst: Instance) => { + axios.get(`/api/ji/${id}/container`).then((statusRes) => { + setInstancesStatus(prev => ({ + ...prev, + [inst.id]: statusRes.data + })); + }); + }); + }); + }, [id]); + useEffect(() => { axios.get("/api/user/me").then((res) => { localStorage.setItem("username", res.data.username); @@ -45,13 +63,75 @@ function Immersion() { const container_status = localStorage.getItem("container_status"); + const handleCreateContainers = () => { + axios.post(`/api/ji/${id}/containers`).then((res) => { + if (res.status === 200) { + toast.success("Containers created successfully!", { + draggable: true, + theme: localStorage.getItem("theme") || "dark", + }); + // Recharger les instances pour mettre à jour les status + axios.get(`/api/ji/${id}/all-instances`).then((res) => { + setAllInstances(res.data); + }); + } + }).catch((error) => { + toast.error("Failed to create containers", { + draggable: true, + theme: localStorage.getItem("theme") || "dark", + }); + }); + }; + + const handleStartAllContainers = () => { + axios.post(`/api/ji/${id}/container/start`).then((res) => { + if (res.status === 200) { + toast.success("All containers started successfully!", { + draggable: true, + theme: localStorage.getItem("theme") || "dark", + }); + // Recharger les instances pour mettre à jour les status + axios.get(`/api/ji/${id}/all-instances`).then((res) => { + setAllInstances(res.data); + }); + } + }).catch((error) => { + toast.error("Failed to start containers", { + draggable: true, + theme: localStorage.getItem("theme") || "dark", + }); + }); + }; + + const handleStopAllContainers = () => { + axios.post(`/api/ji/${id}/container/stop`).then((res) => { + if (res.status === 200) { + toast.success("All containers stopped successfully!", { + draggable: true, + theme: localStorage.getItem("theme") || "dark", + }); + // Recharger les instances pour mettre à jour les status + axios.get(`/api/ji/${id}/all-instances`).then((res) => { + setAllInstances(res.data); + }); + } + }).catch((error) => { + toast.error("Failed to stop containers", { + draggable: true, + theme: localStorage.getItem("theme") || "dark", + }); + }); + }; + return ( <> {ji && ( <>

    {ji.name}

    -
    + {/* Section supérieure : Info + iframe */} +
    + {/* Colonne gauche - Informations */}

    @@ -69,49 +149,53 @@ function Immersion() {

    - { instance &&
    -

    - Your credentials : -

    -
      -
    • - SSH: -
      copyText(`ssh -p ${instance.port} ${username}@la-banquise.fr`)} - > - ssh -p {instance.port} {username}@la-banquise.fr -
      - + + {instance && ( +
      +

      + Your credentials : +

      +
        +
      • + SSH: +
        copyText(`ssh -p ${instance.port} ${username}@la-banquise.fr`)} + > + ssh -p {instance.port} {username}@la-banquise.fr +
        + +
        -
      -
    • -
    • - Instance name: -
      - {instance.name} -
      -
    • -
    • - Password: -
      copyText(instance.pwd)} - > - {instance.pwd} -
      - +
    • +
    • + Instance name: +
      + {instance.name}
      -
    - -
  • - Status: -
    - {container_status} -
    -
  • - -
    } + +
  • + Password: +
    copyText(instance.pwd)} + > + {instance.pwd} +
    + +
    +
    +
  • +
  • + Status: +
    + {container_status} +
    +
  • + +
    + )} +
    + + {/* Colonne droite - iframe du sujet */} +
    +
    +

    + Subject +

    +