Merge pull request #1 from Antoninop/dev

Dev
This commit is contained in:
Antoninop 2024-07-10 03:06:21 +02:00 committed by GitHub
commit 2b6ffe7ecf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 386 additions and 68 deletions

View File

@ -1,27 +1,20 @@
// src/App.js
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'; import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import LoginPage from './pages/LoginPage/LoginPage'; import LoginPage from './pages/LoginPage/LoginPage';
import DashboardPage from './pages/DashboardPage/DashboardPage'; import DashboardPage from './pages/DashboardPage/DashboardPage';
import ServerDetails from './pages/ServerDetails/ServerDetails';
import { auth } from './service/firebase'; import { auth } from './service/firebase';
import styles from './App.module.scss'; import styles from './App.module.scss';
import Loading from './pages/Loading/loading'; import Loading from './pages/Loading/loading';
import NotFoundPage from './pages/NotFoundPage/NotFoundPage';
const App = () => { const App = () => {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
const script = document.createElement('script');
script.src = 'https://static.cloudflareinsights.com/beacon.min.js';
script.async = true;
script.defer = true;
script.data = JSON.stringify({
token: '5cef39a7410e4a4e8ab3dfbe163b73d0',
});
const unsubscribe = auth.onAuthStateChanged((user) => { const unsubscribe = auth.onAuthStateChanged((user) => {
setUser(user); setUser(user);
setLoading(false); setLoading(false);
@ -40,7 +33,9 @@ const App = () => {
<Routes> <Routes>
<Route path="/login" element={user ? <Navigate to="/dashboard" /> : <LoginPage />} /> <Route path="/login" element={user ? <Navigate to="/dashboard" /> : <LoginPage />} />
<Route path="/dashboard" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} /> <Route path="/dashboard" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} />
<Route path="/server/:serverName" element={user ? <ServerDetails user={user} /> : <Navigate to="/login" />} />
<Route path="/" element={<Navigate to={user ? "/dashboard" : "/login"} />} /> <Route path="/" element={<Navigate to={user ? "/dashboard" : "/login"} />} />
<Route path="*" element={<NotFoundPage />} />
</Routes> </Routes>
<ToastContainer /> <ToastContainer />
</div> </div>

View File

@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom';
import styles from './serverCard.module.scss'; import styles from './serverCard.module.scss';
import paper from '../../assets/frameworks/paper_mc.png' import paper from '../../assets/frameworks/paper_mc.png';
import spigot from '../../assets/frameworks/spigot.png' import spigot from '../../assets/frameworks/spigot.png';
import bukkit from '../../assets/frameworks/bukkit.png' import bukkit from '../../assets/frameworks/bukkit.png';
import vanilla from '../../assets/frameworks/vanilla.png' import vanilla from '../../assets/frameworks/vanilla.png';
import delete_button from '../../assets/frameworks/delete.png' import delete_button from '../../assets/frameworks/delete.png';
const ServerCard = ({status, version, link, name, framework, onRunClick, onStopClick, onDeleteClick }) => { const ServerCard = ({ status, version, name, framework, onRunClick, onStopClick, onDeleteClick }) => {
const getFrameworkSource = () => { const getFrameworkSource = () => {
switch (framework) { switch (framework) {
@ -20,9 +21,10 @@ const ServerCard = ({status, version, link, name, framework, onRunClick, onStopC
default: default:
return vanilla; return vanilla;
} }
} };
const handleRun = async () => { const handleRun = async (event) => {
event.preventDefault();
try { try {
await onRunClick(name); await onRunClick(name);
} catch (error) { } catch (error) {
@ -30,7 +32,8 @@ const ServerCard = ({status, version, link, name, framework, onRunClick, onStopC
} }
}; };
const handleStop = async () => { const handleStop = async (event) => {
event.preventDefault();
try { try {
await onStopClick(name); await onStopClick(name);
} catch (error) { } catch (error) {
@ -38,7 +41,8 @@ const ServerCard = ({status, version, link, name, framework, onRunClick, onStopC
} }
}; };
const handleDelete = async () => { const handleDelete = async (event) => {
event.preventDefault();
try { try {
await onDeleteClick(name); await onDeleteClick(name);
} catch (error) { } catch (error) {
@ -47,25 +51,25 @@ const ServerCard = ({status, version, link, name, framework, onRunClick, onStopC
}; };
return ( return (
<a href={link} className={styles.serverCard}> <Link to={`/server/${name}`} className={styles.serverCard}>
<div className={styles.leftCard}> <div className={styles.leftCard}>
<div className={styles.status}> <div className={styles.status}>
<div className={styles.name}>{name}</div> <div className={styles.name}>{name}</div>
<img src={getFrameworkSource()} alt={`${name} Icon`} className={styles.frameworkIcon}/> <img src={getFrameworkSource()} alt={`${name} Icon`} className={styles.frameworkIcon} />
<div className="tooltip"></div> <div className="tooltip"></div>
</div> </div>
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
{!status && ( {status === "false" && (
<button className={styles.stoppedButton} onClick={handleRun}> Démarrer</button> <button className={styles.stoppedButton} onClick={handleRun}> Démarrer</button>
)} )}
{status && ( {status === true && (
<button className={styles.runningButton} onClick={handleStop}> Arrêter</button> <button className={styles.runningButton} onClick={handleStop}> Arrêter</button>
)} )}
</div> </div>
</div> </div>
<div className={styles.version}>Version: {version}</div> <div className={styles.version}>Version: {version}</div>
<button className={styles.runningButton} onClick={handleDelete}><img src={delete_button} className={styles.deleteButton}/></button> <button className={styles.runningButton} onClick={handleDelete}><img src={delete_button} className={styles.deleteButton} /></button>
</a> </Link>
); );
}; };

View File

@ -10,8 +10,11 @@
background-color: var(--card-bg-color); background-color: var(--card-bg-color);
color: var(--text-color); color: var(--text-color);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
cursor: pointer;
} }
.status { .status {
display: flex; display: flex;
align-items: center; align-items: center;
@ -46,6 +49,15 @@
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
margin-top: 1rem; margin-top: 1rem;
} }
.runningButton:hover{
background-color: #8d213ec1;
}
.stoppedButton:hover{
background-color: #008d5fc1;
}
.deleteButton{ .deleteButton{
width: 20px; width: 20px;
height: 20px; height: 20px;

View File

@ -1,4 +1,4 @@
/* index.css */ /* main.css */
html { html {
font-size: 12px; font-size: 12px;
font-family: 'Poppins', sans-serif; font-family: 'Poppins', sans-serif;
@ -18,6 +18,11 @@ html {
--main-bg-color: #050816; --main-bg-color: #050816;
--text-color: white; --text-color: white;
--text-color-black: black; --text-color-black: black;
--input-bg-color: #44475a;
--input-border-color: #6272a4;
--button-bg-color: #50fa7b;
--button-text-color: #282a36;
--button-bg-color-hover: #8be9fd;
} }
body { body {

View File

@ -85,6 +85,13 @@ const DashboardPage = ({ user }) => {
} }
}; };
const handleCopyAddress = () => {
const address = `${subdomain}.servii.fr`;
navigator.clipboard.writeText(address)
.then(() => {
})
};
if (loading) { if (loading) {
return ( return (
<div className={styles.dashboardContainer}> <div className={styles.dashboardContainer}>
@ -111,7 +118,10 @@ const DashboardPage = ({ user }) => {
) : ( ) : (
<div> <div>
<div className={styles.iptitle}> <div className={styles.iptitle}>
Adresse de connexion à vos serveurs : <span>{subdomain}.servii.fr</span> Adresse de connexion à vos serveurs :
<span onClick={handleCopyAddress}>
{" " + subdomain}.servii.fr
</span>
</div> </div>
<button <button
className={styles.btncreate} className={styles.btncreate}

View File

@ -1,7 +1,6 @@
html, body { html, body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: 'Poppins', system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: 'Poppins', system-ui, Avenir, Helvetica, Arial, sans-serif;
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
} }
@ -36,6 +35,7 @@ html, body {
.iptitle span { .iptitle span {
color: violet; color: violet;
cursor: pointer;
} }
.btncreate{ .btncreate{

View File

@ -0,0 +1,18 @@
// src/pages/NotFoundPage/NotFoundPage.jsx
import React from 'react';
import Navbar from '../../components/navbar/Navbar';
import styles from './NotFoundPage.module.scss';
const NotFoundPage = () => {
return (
<div className={styles.notFoundContainer}>
<Navbar />
<div className={styles.message}>
<h1>Page inconnue</h1>
<p>La page que vous recherchez n'existe pas.</p>
</div>
</div>
);
};
export default NotFoundPage;

View File

@ -0,0 +1,35 @@
// src/pages/NotFound/NotFound.module.scss
.notFoundContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
color:white;
}
.message {
margin-top: 50px;
}
.message h1 {
font-size: 3rem;
margin-bottom: 20px;
}
.message p {
font-size: 1.5rem;
margin-bottom: 20px;
}
.message a {
font-size: 1.2rem;
color: #007bff;
text-decoration: none;
}
.message a:hover {
text-decoration: underline;
}

View File

@ -0,0 +1,149 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import Navbar from '../../components/navbar/Navbar';
import styles from './ServerDetails.module.scss';
import serviiApi from "../../service/api.tsx";
import Loading from '../Loading/loading';
const ServerDetails = ({ user }) => {
const { serverName } = useParams();
const navigate = useNavigate();
const [server, setServer] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchServer = async () => {
try {
const ApiResponse = await serviiApi.fetchServers();
const data = ApiResponse.message;
const foundServer = data.find(server => server.name === serverName);
if (foundServer) {
setServer(foundServer);
} else {
setError('Server not found');
}
} catch (error) {
console.error('Error fetching server:', error);
setError('Error fetching server');
} finally {
setLoading(false);
}
};
fetchServer();
}, [serverName]);
const handleChange = (e) => {
const { name, value } = e.target;
setServer({ ...server, [name]: value });
};
const handleSave = async () => {
try {
const props = [
['max-players', server.maxPlayers.toString()],
['motd', server.motd],
['difficulty', server.difficulty],
['enableCommandBlock', server.enableCommandBlock.toString()],
['gamemode', server.gamemode],
['hardcore', server.hardcore.toString()],
['onlineMode', server.onlineMode.toString()],
['pvp', server.pvp.toString()]
];
await serviiApi.updateProperties(server.name, props);
} catch (error) {
console.error('Error updating server:', error);
alert('Error updating server');
}
};
const handleQuit = () => {
navigate('/dashboard');
}
if (loading) {
return <Loading />;
}
if (error) {
return (
<div className={styles.serverDetailsContainer}>
<Navbar user={user} />
<div className={styles.error}>
<h1>{error}</h1>
<button onClick={() => navigate('/dashboard')}>Retour au Dashboard</button>
</div>
</div>
);
}
return (
<div className={styles.serverDetailsContainer}>
<Navbar user={user} />
<div className={styles.details}>
<h1>Détails du serveur {server.name}</h1>
<div className={styles.formGroup}>
<label htmlFor="difficulty">Difficultée:</label>
<select id="difficulty" name="difficulty" value={server.difficulty} onChange={handleChange}>
<option value="easy">Facile</option>
<option value="normal">Normal</option>
<option value="hard">Difficile</option>
</select>
</div>
<div className={styles.formGroup}>
<label htmlFor="enableCommandBlock">Activation des commandes block:</label>
<select id="enableCommandBlock" name="enableCommandBlock" value={server.enableCommandBlock} onChange={handleChange}>
<option value="true">Activé</option>
<option value="false">Désactivé</option>
</select>
</div>
<div className={styles.formGroup}>
<label htmlFor="gamemode">Gamemode:</label>
<select id="gamemode" name="gamemode" value={server.gamemode} onChange={handleChange}>
<option value="survival">Survie</option>
<option value="creative">Créatif</option>
<option value="adventure">Aventure</option>
<option value="spectator">Spectateur</option>
</select>
</div>
<div className={styles.formGroup}>
<label htmlFor="hardcore">Hardcore:</label>
<select id="hardcore" name="hardcore" value={server.hardcore} onChange={handleChange}>
<option value="true">Activé</option>
<option value="false">Désactivé</option>
</select>
</div>
<div className={styles.formGroup}>
<label htmlFor="maxPlayers">Max de joueurs:</label>
<input type="number" id="maxPlayers" name="maxPlayers" value={server.maxPlayers} onChange={handleChange} />
</div>
<div className={styles.formGroup}>
<label htmlFor="motd">Description du serveur:</label>
<input type="text" id="motd" name="motd" value={server.motd} onChange={handleChange} />
</div>
<div className={styles.formGroup}>
<label htmlFor="onlineMode">Mode en ligne:</label>
<select id="onlineMode" name="onlineMode" value={server.onlineMode} onChange={handleChange}>
<option value="true">Activé</option>
<option value="false">Désactivé</option>
</select>
</div>
<div className={styles.formGroup}>
<label htmlFor="pvp">PVP:</label>
<select id="pvp" name="pvp" value={server.pvp} onChange={handleChange}>
<option value="true">Activé</option>
<option value="false">Désactivé</option>
</select>
</div>
<button className={styles.saveButton} onClick={handleSave}>Sauvegarder et quitter</button>
<button className={styles.quitButton} onClick={handleQuit}>Retour</button>
</div>
</div>
);
};
export default ServerDetails;

View File

@ -0,0 +1,83 @@
/* ServerDetails.module.scss */
.serverDetailsContainer {
padding-top: var(--navbar-height);
background-color: var(--main-bg-color);
display: flex;
flex-direction: column;
align-items: center;
color: var(--text-color);
}
.details {
margin-top: 5rem;
padding: 3rem;
padding-top: 0rem;
background-color: var(--card-bg-color);
width: 60rem;
border-radius: 1rem;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.formGroup {
margin-bottom: 2.5rem;
display: flex;
flex-direction: column;
}
.formGroup label {
font-weight: bold;
margin-bottom: 1.2rem;
font-size: 1.4rem;
}
.formGroup input,
.formGroup select {
width: calc(100% - 2px);
padding: 1.2rem;
height: 3.5rem;
background-color: var(--input-bg-color);
border: 1px solid var(--input-border-color);
border-radius: 0.5rem;
color: var(--text-color);
font-size: 1.2rem;
box-sizing: border-box;
}
.saveButton {
padding: 1.2rem 2.5rem;
background-color: var(--button-bg-color);
color: var(--button-text-color);
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
font-size: 1.2rem;
align-self: flex-start;
}
.quitButton{
padding: 1.2rem 2.5rem;
background-color: gray;
color: var(--button-text-color);
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
font-size: 1.2rem;
align-self: flex-start;
margin-left: 3rem;
}
.saveButton:hover {
background-color: var(--button-bg-hover-color);
}
.error {
color: red;
font-size: 1.2rem;
}
.details h1 {
font-size: 2.5rem;
margin-bottom: 3rem;
}

View File

@ -1,5 +1,4 @@
// src/services/serverService.ts import { getAuth } from 'firebase/auth';
import {getAuth} from 'firebase/auth';
const apiUrl: string = 'https://www.servii.fr/api'; const apiUrl: string = 'https://www.servii.fr/api';
@ -8,37 +7,30 @@ interface ApiResponse {
message: string; message: string;
} }
//fetchServers, accountCreate, accountDelete,
interface BaseRequest { interface BaseRequest {
token: string; token: string;
} }
//setSubdomain
interface SubdomainRequest { interface SubdomainRequest extends BaseRequest {
token: string;
subdomain: string; subdomain: string;
} }
//serverDelete, serverRun, serverStop
interface ServerRequest { interface ServerRequest extends BaseRequest {
token: string;
name: string; name: string;
} }
//serverCreate
interface ServerCreationRequest { interface ServerCreationRequest extends BaseRequest {
token: string;
name: string; name: string;
version: string; version: string;
framework: string; framework: string;
} }
//updateProperty
interface UpdatePropertyRequest { interface UpdatePropertiesRequest extends BaseRequest {
token: string;
name: string; name: string;
prop: string; props: [string, string][];
value: string;
} }
//command
interface CommandRequest { interface CommandRequest extends BaseRequest {
token: string;
command: string; command: string;
name: string; name: string;
} }
@ -52,14 +44,14 @@ enum serviiRequest {
accountDelete = 'AccountDelete', accountDelete = 'AccountDelete',
serverRun = 'ServerRun', serverRun = 'ServerRun',
serverStop = 'ServerStop', serverStop = 'ServerStop',
updateProperty = 'UpdateProperty', updateProperty = 'UpdateProperties',
command = 'Command', command = 'Command',
} }
class serviiApi { class serviiApi {
constructor() {} constructor() {}
private static async call<T extends BaseRequest>(endpoint: serviiRequest, body: T):Promise<ApiResponse>{ private static async call<T extends BaseRequest>(endpoint: serviiRequest, body: T): Promise<ApiResponse> {
const response = await fetch(`${apiUrl}/${endpoint}`, { const response = await fetch(`${apiUrl}/${endpoint}`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -67,62 +59,77 @@ class serviiApi {
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const status: number = response.status const status: number = response.status;
const json = await response.json(); const json = await response.json();
if (json.message === undefined) { if (json.message === undefined) {
if (!(endpoint === serviiRequest.fetchServers)){ if (!(endpoint === serviiRequest.fetchServers)) {
return {return_code: status, message: "Couldn't find an available API, we're sorry for the inconvenience."}; return { return_code: status, message: "Couldn't find an available API, we're sorry for the inconvenience." };
} }
return {return_code: status, message: json}; return { return_code: status, message: json };
} }
return {return_code: status, message: json.message}; return { return_code: status, message: json.message };
} }
private static token(): string { private static token(): string {
const currentUser = getAuth().currentUser; const currentUser = getAuth().currentUser;
if (!currentUser) {throw new Error('No user is currently logged in.');} if (!currentUser) {
throw new Error('No user is currently logged in.');
}
return currentUser.uid; return currentUser.uid;
} }
public static async setSubdomain(subdomain: string): Promise<ApiResponse> { public static async setSubdomain(subdomain: string): Promise<ApiResponse> {
const payload : SubdomainRequest = {token: this.token(), subdomain: subdomain} const payload: SubdomainRequest = { token: this.token(), subdomain: subdomain };
return this.call(serviiRequest.setSubdomain, payload); return this.call(serviiRequest.setSubdomain, payload);
} }
public static async fetchServers(): Promise<ApiResponse> { public static async fetchServers(): Promise<ApiResponse> {
const payload : BaseRequest = {token: this.token()} const payload: BaseRequest = { token: this.token() };
return this.call(serviiRequest.fetchServers, payload); return this.call(serviiRequest.fetchServers, payload);
} }
public static async accountCreate(): Promise<ApiResponse> { public static async accountCreate(): Promise<ApiResponse> {
const payload : BaseRequest = {token: this.token()} const payload: BaseRequest = { token: this.token() };
return this.call(serviiRequest.accountCreate, payload); return this.call(serviiRequest.accountCreate, payload);
} }
public static async serverCreate(name: string, version: string, framework: string): Promise<ApiResponse> { public static async serverCreate(name: string, version: string, framework: string): Promise<ApiResponse> {
const payload : ServerCreationRequest = {token: this.token(), name: name, version: version, framework: framework}; const payload: ServerCreationRequest = { token: this.token(), name: name, version: version, framework: framework };
return this.call(serviiRequest.serverCreate, payload); return this.call(serviiRequest.serverCreate, payload);
} }
public static async serverDelete(name: string): Promise<ApiResponse> { public static async serverDelete(name: string): Promise<ApiResponse> {
const payload : ServerRequest = {token: this.token(), name: name} const payload: ServerRequest = { token: this.token(), name: name };
return this.call(serviiRequest.serverDelete, payload); return this.call(serviiRequest.serverDelete, payload);
} }
public static async accountDelete(): Promise<ApiResponse> { public static async accountDelete(): Promise<ApiResponse> {
const payload : BaseRequest = {token: this.token()} const payload: BaseRequest = { token: this.token() };
return this.call(serviiRequest.accountDelete, payload); return this.call(serviiRequest.accountDelete, payload);
} }
public static async serverRun(name: string): Promise<ApiResponse> { public static async serverRun(name: string): Promise<ApiResponse> {
const payload : ServerRequest = {token: this.token(), name: name} const payload: ServerRequest = { token: this.token(), name: name };
return this.call(serviiRequest.serverRun, payload); return this.call(serviiRequest.serverRun, payload);
} }
public static async serverStop(name: string): Promise<ApiResponse> { public static async serverStop(name: string): Promise<ApiResponse> {
const payload : ServerRequest = {token: this.token(), name: name} const payload: ServerRequest = { token: this.token(), name: name };
return this.call(serviiRequest.serverStop, payload); return this.call(serviiRequest.serverStop, payload);
} }
public static async updateProperty(name: string, prop: string, value: string): Promise<ApiResponse> {
const payload : UpdatePropertyRequest = {token: this.token(), name: name, prop: prop, value: value} public static async updateProperties(name: string, props: [string, string][]): Promise<ApiResponse> {
return this.call(serviiRequest.updateProperty, payload); const payload: UpdatePropertiesRequest = {
token: this.token(),
name: name,
props: props,
};
return this.call(serviiRequest.updateProperty, payload); // Correct usage here
} }
public static async command(command: string, name: string): Promise<ApiResponse> { public static async command(command: string, name: string): Promise<ApiResponse> {
const payload : CommandRequest = {token: this.token(), command: command, name: name}; const payload: CommandRequest = { token: this.token(), command: command, name: name };
return this.call(serviiRequest.command, payload); return this.call(serviiRequest.command, payload);
} }
} }