feat: update things

This commit is contained in:
Malopieds 2024-10-13 16:24:35 +02:00
parent de26f9f69d
commit f61d0a111a
Signed by: malopieds
GPG Key ID: 2E9430D75356529B
13 changed files with 640 additions and 27 deletions

100
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@heroicons/react": "^2.1.5",
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.1"
@ -1582,6 +1583,12 @@
"dev": true,
"license": "Python-2.0"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@ -1620,6 +1627,17 @@
"postcss": "^8.1.0"
}
},
"node_modules/axios": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -1813,6 +1831,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -1931,6 +1961,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -2337,6 +2376,26 @@
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
@ -2354,6 +2413,20 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@ -2795,6 +2868,27 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -3237,6 +3331,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -11,6 +11,7 @@
},
"dependencies": {
"@heroicons/react": "^2.1.5",
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.1"

View File

@ -5,6 +5,10 @@ import Navigation from "./component/Navigation/Navigation";
import Dashboard from "./pages/Dashboard";
import Practicals from "./pages/Practicals";
import Instances from "./pages/Instances";
import Practical from "./pages/Practical";
import LoginPage from "./pages/Login";
import PageTest from "./pages/PageTest";
import CreateTp from "./pages/admin/CreateTp";
function App() {
const [showNotif, setShowNotif] = useState(false);
@ -14,9 +18,11 @@ function App() {
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/tps" element={<Practicals />} />
<Route path="/tps/:id" element={<h1>id</h1>} />
<Route path="/tps/:id" element={<Practical />} />
<Route path="/instances" element={<Instances />} />
<Route path="/profile" element={<h1>Profile</h1>} />
<Route path="/profile" element={<PageTest />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/admin/tps" element={<CreateTp />} />
</Routes>
</Navigation>

View File

@ -15,7 +15,7 @@ export default function Card({ title, cards }: CardProps) {
))}
</div>
<div className="flex justify-end mt-auto pt-6">
<button className="btn btn-primary">View more</button>
<button className="btn btn-primary primary">View more</button>
</div>
</div>
);

View File

@ -42,7 +42,7 @@ const Navigation: React.FC<NavigationProps> = ({
}, [theme]);
return (
<div className="app">
<div className="app h-screen">
<div className="drawer">
<input
id="my-drawer"
@ -50,7 +50,7 @@ const Navigation: React.FC<NavigationProps> = ({
className="drawer-toggle"
checked={isDrawerOpen}
/>
<div className="drawer-content">
<div className="drawer-content h-full">
<div className="p-6 flex justify-between">
<div className="btn btn-ghost" onClick={toggleDrawer}>
<Bars3Icon className="size-6" />
@ -59,7 +59,6 @@ const Navigation: React.FC<NavigationProps> = ({
<input type="checkbox" onChange={handleToggle} />
<SunIcon className="size-6 swap-off" />
<MoonIcon className="size-6 swap-on" />
</label>
</div>
@ -100,7 +99,7 @@ const Navigation: React.FC<NavigationProps> = ({
</li>
<div className="divider" onClick={toggleDrawer}></div>
<li>
<Link to="logout" onClick={toggleDrawer}>
<Link to="login" onClick={toggleDrawer}>
Log Out
</Link>
</li>

View File

@ -4,11 +4,9 @@ import { Link } from "react-router-dom";
const Sidebar: React.FC = () => {
return (
<div className="drawer drawer-mobile">
{/* Drawer Toggle Button (for mobile) */}
<input id="sidebar-toggle" type="checkbox" className="drawer-toggle" />
<div className="drawer-content">
{/* Main content area */}
<label
htmlFor="sidebar-toggle"
className="btn btn-primary drawer-button lg:hidden"

View File

@ -1,16 +1,50 @@
import { useEffect, useState } from "react";
import axios from "axios";
import Card from "../component/Card/Card";
import { Link } from "react-router-dom";
function Instances() {
return (
<div className="flex flex-col items-center p-4 gap-5">
<h1 className="text-4xl font-bold mb-8">Instances</h1>
const [instances, setInstances] = useState([]);
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 w-[85%] h-full">
<Card title="Actives" cards={["Websocket Chat", "VM personnelle"]} />
<Card
title="Terminated"
cards={["Bot Discord", "Bienvenue a la Banquise"]}
/>
useEffect(() => {
axios.get("/api/instances").then((res) => {
setInstances(res.data);
});
}, []);
return (
<div className="container mx-auto py-10">
<h1 className="text-3xl font-bold mb-6 text-center">Your instances</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{instances.map((instance) => (
<div
key={instance.id}
className="card card-compact bg-base-200 shadow-lg p-4"
>
<div className="card-body">
<h2 className="card-title">{instance.name}</h2>
{instance.tp && (
<p className="text-lg">Linked to: {instance.tp.name}</p>
)}
<div className="card-actions justify-end">
<Link
to={`/instances/${instance.id}`}
className="btn btn-primary"
>
Learn More
</Link>
{instance.tp && (
<Link
to={`/tps/${instance.tp.id}`}
className="btn btn-primary"
>
See TP
</Link>
)}
</div>
</div>
</div>
))}
</div>
</div>
);

87
src/pages/Login.tsx Normal file
View File

@ -0,0 +1,87 @@
import axios from "axios";
import React, { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
const LoginPage: React.FC = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
const location = useLocation();
const { from } = location.state || { from: { pathname: "/" } }; // Default to home if no state
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData();
formData.append("j_username", username);
formData.append("j_password", password);
axios
.post("/j_security_check", formData, {
withCredentials: true,
})
.then((response) => {
if (response.status === 200) {
navigate(from);
}
})
.catch((response) => {
if (response.status === 401) {
setError("wrong_user_pwd");
}
});
};
return (
<div className="flex mt-20 justify-center items-center">
<div className="card w-96 bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">{"login"}</h2>
<form onSubmit={handleSubmit}>
<div className="form-control">
<label className="label">
<span className="label-text">{"username"}</span>
</label>
<input
type="text"
placeholder="Username"
name="j_username"
className={`input input-bordered ${error ? "input-error" : ""}`}
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-control mt-4">
<label className="label">
<span className="label-text">{"password"}</span>
</label>
<input
type="password"
placeholder="Password"
name="j_password"
className={`input input-bordered ${error ? "input-error" : ""}`}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{error && (
<>
<p className="pt-4 text-red-500">{error}</p>
</>
)}
<div className="form-control mt-4">
<button type="submit" className="btn btn-primary">
{"login"}
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default LoginPage;

90
src/pages/PageTest.tsx Normal file
View File

@ -0,0 +1,90 @@
import axios from "axios";
import { useState } from "react";
function PageTest() {
const [users, setUsers] = useState([
{
id: 1,
name: "Alice Smith",
email: "alice@example.com",
role: "Admin",
},
{
id: 2,
name: "Bob Johnson",
email: "bob@example.com",
role: "User",
},
{
id: 3,
name: "Charlie Brown",
email: "charlie@example.com",
role: "User",
},
// Add more user data here as needed
]);
const handleEditUser = (userId) => {
// Logic for editing the user (e.g., opening a modal)
alert(`Edit user with ID: ${userId}`);
};
const handleDeleteUser = (userId) => {
// Logic for deleting the user
const confirmDelete = window.confirm(
"Are you sure you want to delete this user?",
);
if (confirmDelete) {
setUsers(users.filter((user) => user.id !== userId));
}
};
return (
<div className="container mx-auto p-6">
<div className="bg-white shadow-lg rounded-lg p-6">
<h1 className="text-3xl font-bold text-blue-600 mb-6">Manage Users</h1>
{/* Users Table */}
<div className="overflow-x-auto">
<table className="table w-full">
<thead>
<tr>
<th className="p-4">#</th>
<th className="p-4">Name</th>
<th className="p-4">Email</th>
<th className="p-4">Role</th>
<th className="p-4">Actions</th>
</tr>
</thead>
<tbody>
{users.map((user, index) => (
<tr key={user.id} className="hover:bg-gray-100">
<td className="p-4">{index + 1}</td>
<td className="p-4">{user.name}</td>
<td className="p-4">{user.email}</td>
<td className="p-4">{user.role}</td>
<td className="p-4">
<button
className="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-400 mr-2"
onClick={() => handleEditUser(user.id)}
>
Edit
</button>
<button
className="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-400"
onClick={() => handleDeleteUser(user.id)}
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
export default PageTest;

98
src/pages/Practical.tsx Normal file
View File

@ -0,0 +1,98 @@
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import axios from "axios";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
function Practical() {
const { id } = useParams();
const [tp, setTp] = useState(null);
useEffect(() => {
console.log("akljsbcascb");
axios.get(`/api/tps/${id}`).then((res) => {
setTp(res.data);
});
}, [id]);
return (
<>
{tp && (
<>
<h1 className="text-3xl font-bold mb-2 ms-10">{tp.name}</h1>
<div className="px-6">
<div className="flex flex-col lg:flex-row">
<div className="lg:w-2/3 p-4">
<div className="w-full h-[80vh] bg-gray-200 rounded-lg overflow-hidden mb-4">
<iframe
src={tp.pdfLink}
className="w-full h-full"
title={`${tp.name} PDF`}
></iframe>
</div>
</div>
<div className="lg:w-1/3 px-4">
<div className="bg-base-200 p-6 rounded-lg shadow-md mt-4">
<h2 className="text-2xl font-semibold text-blue-600 mb-2">
Information
</h2>
<ul className="space-y-4">
<li>{tp.description}</li>
<li className="flex justify-between">
<span>Duration:</span>
<span className="font-medium">2 hours</span>
</li>
<li className="flex justify-between">
<span>Tools Used:</span>
<span className="font-medium">Python, SSH, Vim</span>
</li>
<li className="flex justify-between">
<span>Difficulty:</span>
<span className="font-medium">Beginner</span>
</li>
<li className="flex justify-between">
<span>Date:</span>
<span className="font-medium">Oct 23, 2024</span>
</li>
</ul>
</div>
<div className="bg-base-200 p-6 rounded-lg shadow-md mt-6">
<h2 className="text-2xl font-semibold text-blue-600 mb-2">
Resources
</h2>
<ul className="space-y-4">
<li className="flex justify-between">
<span>SSH:</span>
<span className="font-medium">{tp.ssh}</span>
</li>
<li className="flex justify-between">
<span>Password:</span>
<span className="font-medium">{tp.pwd}</span>
</li>
</ul>
</div>
<a
href={tp.pdfLink}
target="_blank"
className="card w-full bg-base-200 shadow-md hover:shadow-lg transition-shadow rounded-lg mt-6"
>
<div className="card-body flex-row justify-between items-center">
<div>
<div className="card-title text-lg font-bold">
Subject
</div>
<p className="text-base-content">Download</p>
</div>
<ArrowDownTrayIcon className="size-6" />
</div>
</a>
</div>
</div>
</div>
</>
)}
</>
);
}
export default Practical;

View File

@ -1,13 +1,37 @@
import Card from "../component/Card/Card";
import { useEffect, useState } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
function Practicals() {
return (
<div className="flex flex-col items-center p-4 gap-5">
<h1 className="text-4xl font-bold mb-8">TPs</h1>
const [tps, setTps] = useState([]);
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 w-[85%] h-full">
<Card title="En cours" cards={["Websocket Chat"]} />
<Card title="Fini" cards={["Bot Discord", "Bienvenue a la Banquise"]} />
useEffect(() => {
axios.get("/api/tps").then((res) => {
setTps(res.data);
});
}, []);
return (
<div className="container mx-auto py-10">
<h1 className="text-3xl font-bold mb-6 text-center">Your TPs</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{tps.map((practical) => (
<div
key={practical.id}
className="card card-compact bg-base-200 shadow-lg p-4"
>
<div className="card-body">
<h2 className="card-title">{practical.name}</h2>
<p>{practical.description}</p>
<p className="text-sm text-gray-500">Respo: {practical.respo}</p>
<div className="card-actions justify-end">
<Link to={`/tps/${practical.id}`} className="btn btn-primary">
Learn More
</Link>
</div>
</div>
</div>
))}
</div>
</div>
);

View File

@ -0,0 +1,159 @@
import axios from "axios";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
function CreateTp() {
const [practical, setPractical] = useState({
title: "",
description: "",
duration: "",
tools: "",
difficulty: "Intermediate",
date: "",
pdf: "",
});
const navigate = useNavigate();
const handleInputChange = (e) => {
const { name, value } = e.target;
setPractical({ ...practical, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Practical created :", practical);
axios.post("/api/tps", practical).then((res) => {
if (res.status === 200) {
navigate(`/tps/${res.data.id}`);
}
});
};
return (
<div className="container mx-auto p-6">
<div className="shadow-lg rounded-lg p-6">
<h1 className="text-3xl font-bold text-blue-600 mb-6">
Create Practical
</h1>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-gray-700 font-semibold mb-2">
Title:
</label>
<input
type="text"
name="title"
value={practical.title}
onChange={handleInputChange}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter practical title"
required
/>
</div>
<div>
<label className="block text-gray-700 font-semibold mb-2">
Description:
</label>
<textarea
name="description"
value={practical.description}
onChange={handleInputChange}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter practical description"
rows={4}
required
></textarea>
</div>
<div>
<label className="block text-gray-700 font-semibold mb-2">
PDF Link:
</label>
<input
type="text"
name="pdf"
value={practical.pdf}
onChange={handleInputChange}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter PDF link"
required
/>
</div>
<div>
<label className="block text-gray-700 font-semibold mb-2">
Duration (hours):
</label>
<input
type="number"
name="duration"
value={practical.duration}
onChange={handleInputChange}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter duration (in hours)"
min="1"
required
/>
</div>
<div>
<label className="block text-gray-700 font-semibold mb-2">
Tools Used:
</label>
<input
type="text"
name="tools"
value={practical.tools}
onChange={handleInputChange}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter tools used"
required
/>
</div>
<div>
<label className="block text-gray-700 font-semibold mb-2">
Difficulty:
</label>
<select
name="difficulty"
value={practical.difficulty}
onChange={handleInputChange}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="Beginner">Beginner</option>
<option value="Intermediate">Intermediate</option>
<option value="Advanced">Advanced</option>
</select>
</div>
<div>
<label className="block text-gray-700 font-semibold mb-2">
Date:
</label>
<input
type="date"
name="date"
value={practical.date}
onChange={handleInputChange}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div className="text-right">
<button
type="submit"
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Create Practical
</button>
</div>
</form>
</div>
</div>
);
}
export default CreateTp;

View File

@ -4,10 +4,27 @@ import daisyui from "daisyui";
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
extend: {
colors: {},
},
colors: {},
},
plugins: [daisyui],
daisyui: {
themes: ["light", "dark"],
// themes: ["light", "dark"],
themes: [
{
light: {
...require("daisyui/src/theming/themes")["light"],
primary: "#99d2fd",
test: "#76beee",
},
dark: {
...require("daisyui/src/theming/themes")["dark"],
primary: "#34a6fc",
test: "#1f5078",
},
},
],
},
};