Merge pull request #22 from hubHarmony/newdetails

New Server details form
This commit is contained in:
Antoninop 2024-08-19 12:36:17 +02:00 committed by GitHub
commit d6709163ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 836 additions and 347 deletions

9
package-lock.json generated
View File

@ -19,6 +19,7 @@
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.24.0",
"react-toastify": "^10.0.5",
"sass": "^1.77.6",
@ -9754,6 +9755,14 @@
"react": ">=16.3.0"
}
},
"node_modules/react-icons": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
"integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@ -22,6 +22,7 @@
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.24.0",
"react-toastify": "^10.0.5",
"sass": "^1.77.6",

View File

@ -33,7 +33,8 @@ const App = () => {
<Routes>
<Route path="/login" element={user ? <Navigate to="/dashboard" /> : <LoginPage />} />
<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="/createServer" element={<Navigate />} />
<Route path="/server/:serverName/*" element={user ? <ServerDetails user={user} /> : <Navigate to="/login" />} />
<Route path="/" element={<Navigate to={user ? "/dashboard" : "/login"} />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>

View File

@ -10,26 +10,10 @@ import spigot from '../../assets/frameworks/spigot.png';
import paper from '../../assets/frameworks/paper.png';
const versions = {
vanilla: ['1.21', '1.20', '1.19'],
bukkit: [
'1.15.0', '1.15.1', '1.15.2', '1.15.3', '1.15.4', '1.15.5',
'1.16.0', '1.16.1', '1.16.2', '1.16.3', '1.16.4', '1.16.5',
'1.17.0', '1.17.1', '1.17.2', '1.17.3', '1.17.4', '1.17.5',
'1.18.1', '1.18.2',
'1.19.1', '1.19.2', '1.19.3', '1.19.4',
'1.20.1', '1.20.2', '1.20.4', '1.20.6'
],
spigot: ['1.21', '1.20'],
paper: [
'1.13.1', '1.13.2',
'1.14.1', '1.14.2', '1.14.3', '1.14.4',
'1.15.1', '1.15.2',
'1.16.1', '1.16.2', '1.16.3', '1.16.4', '1.16.5',
'1.17.1',
'1.18.1', '1.18.2',
'1.19.1', '1.19.2', '1.19.3', '1.19.4',
'1.20.1', '1.20.2', '1.20.4', '1.20.5', '1.20.6'
],
vanilla:[ "1.21.1", "1.21", "1.20.6", "1.20.5", "1.20.4", "1.20.2", "1.20.1", "1.19.4", "1.19.3", "1.19.2", "1.19.1", "1.18.2", "1.18.1", "1.17.1", "1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.15.2", "1.15.1", "1.14.4", "1.14.3", "1.14.2", "1.14.1", "1.13.2", "1.13.1", "1.12.2", "1.12.1", "1.11.2", "1.10.2", "1.9.4"],
bukkit: [ "1.20.6", "1.20.4", "1.20.2", "1.20.1", "1.19.4", "1.19.3", "1.19.2", "1.19.1", "1.18.2", "1.17.5", "1.17.4", "1.17.3", "1.17.2", "1.17.0", "1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.16.0", "1.15.5", "1.15.4", "1.15.3", "1.15.2", "1.15.1", "1.15.0", "1.14.5", "1.14.4", "1.14.3", "1.14.2", "1.14.1", "1.14.0", "1.13.5", "1.13.4", "1.13.3", "1.13.2", "1.13.1", "1.13.0", "1.12.5", "1.12.4", "1.12.3", "1.12.2", "1.12.1", "1.12.0", "1.11.5", "1.11.4", "1.11.3", "1.11.2", "1.11.1", "1.11.0", "1.10.5", "1.10.4", "1.10.3", "1.10.2", "1.10.1", "1.10.0", "1.9.5", "1.9.4", "1.9.3", "1.9.2", "1.9.1", "1.9.0", "1.8.5", "1.8.4", "1.8.3", "1.8.2", "1.8.1", "1.8.0", "1.7.5", "1.7.4", "1.7.3", "1.7.2", "1.7.1", "1.7.0", "1.6.5", "1.6.4", "1.6.3", "1.6.2", "1.6.1", "1.6.0", "1.5.5", "1.5.4", "1.5.3", "1.5.2", "1.5.1", "1.5.0", "1.4.5", "1.4.4", "1.4.3", "1.4.2", "1.4.1", "1.4.0", "1.3.5", "1.3.4", "1.3.3", "1.3.2", "1.3.1", "1.3.0", "1.2.5", "1.2.4", "1.2.3", "1.2.2", "1.2.1", "1.2.0", "1.1.5", "1.1.4", "1.1.3", "1.1.2", "1.1.1", "1.1.0", "1.0.5", "1.0.4", "1.0.3", "1.0.2", "1.0.1", "1.0.0"],
spigot: [ "1.21", "1.20.6", "1.20.4", "1.20.2", "1.20.1", "1.19.4", "1.19.3", "1.19.2", "1.19.1", "1.18.2", "1.18.1", "1.17.1", "1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.15.2", "1.15.1", "1.14.4", "1.14.3", "1.14.2", "1.14.1", "1.13.2", "1.13.1", "1.12.2", "1.12.1", "1.11.2", "1.11.1", "1.10.2", "1.9.4", "1.9.2", "1.8.8", "1.8.7", "1.8.6", "1.8.5", "1.8.4", "1.8.3", "1.7.10", "1.7.9", "1.7.8", "1.7.5", "1.7.2", "1.6.4", "1.6.2", "1.5.2", "1.5.1", "1.4.7", "1.4.6"],
paper: [ "1.21.1", "1.21", "1.20.6", "1.20.5", "1.20.4", "1.20.2", "1.20.1", "1.19.4", "1.19.3", "1.19.2", "1.19.1", "1.18.2", "1.18.1", "1.17.1", "1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.15.2", "1.15.1", "1.14.4", "1.14.3", "1.14.2", "1.14.1", "1.13.2", "1.13.1", "1.12.2", "1.12.1", "1.11.2", "1.10.2", "1.9.4"]
};
const CreateServer = ({ user, onCreateServer, onSubdomainUpdate, onCancel, noServers }) => {
@ -37,7 +21,7 @@ const CreateServer = ({ user, onCreateServer, onSubdomainUpdate, onCancel, noSer
const [subdomainInput, setSubdomainInput] = useState('');
const [serverName, setServerName] = useState('');
const [serverVersion, setServerVersion] = useState();
const [selectedFramework, setSelectedFramework] = useState("vanilla");
const [selectedFramework, setSelectedFramework] = useState("paper");
const loadSubdomain = async () => {
try {
@ -69,11 +53,13 @@ const CreateServer = ({ user, onCreateServer, onSubdomainUpdate, onCancel, noSer
const handleCreateServer = async () => {
try {
await onCreateServer(serverName, serverVersion, selectedFramework);
const frameworkToSend = selectedFramework === 'vanilla' ? 'paper' : selectedFramework;
await onCreateServer(serverName, serverVersion, frameworkToSend);
} catch (error) {
console.error('Error creating server:', error);
console.error('Error creating server:', error);
}
};
};
const validateInput = (input) => {
return input.replace(/[^a-zA-Z]/g, '');
@ -124,45 +110,56 @@ const CreateServer = ({ user, onCreateServer, onSubdomainUpdate, onCancel, noSer
<div className={styles.subtitle}>Sélection du framework</div>
<div className={styles.carreContainer}>
<img
className={`${styles.carre} ${selectedFramework === 'vanilla' ? styles.selected : ''}`}
src={vanilla}
alt="Vanilla Logo"
onClick={() => {
setSelectedFramework('vanilla');
setServerVersion('');
}}
/>
<img
className={`${styles.carre} ${selectedFramework === 'bukkit' ? styles.selected : ''}`}
src={bukkit}
alt="Bukkit Logo"
onClick={() => {
setSelectedFramework('bukkit');
setServerVersion('');
}}
/>
<img
className={`${styles.carre} ${selectedFramework === 'spigot' ? styles.selected : ''}`}
src={spigot}
alt="Spigot Logo"
onClick={() => {
setSelectedFramework('spigot');
setServerVersion('');
}}
/>
<img
className={`${styles.carre} ${selectedFramework === 'paper' ? styles.selected : ''}`}
src={paper}
alt="Paper Logo"
onClick={() => {
setSelectedFramework('paper');
setServerVersion('');
}}
/>
</div>
<div className={styles.carreWrapper}>
<img
className={`${styles.carre} ${selectedFramework === 'vanilla' ? styles.selected : ''}`}
src={vanilla}
alt="Vanilla Logo"
onClick={() => {
setSelectedFramework('vanilla');
setServerVersion('');
}}
/>
</div>
<div className={styles.carreWrapper}>
<img
className={`${styles.carre} ${selectedFramework === 'bukkit' ? styles.selected : ''}`}
src={bukkit}
alt="Bukkit Logo"
onClick={() => {
setSelectedFramework('bukkit');
setServerVersion('');
}}
/>
</div>
<div className={styles.subtitle}>Sélection de la version</div>
<div className={styles.carreWrapper}>
<img
className={`${styles.carre} ${selectedFramework === 'spigot' ? styles.selected : ''}`}
src={spigot}
alt="Spigot Logo"
onClick={() => {
setSelectedFramework('spigot');
setServerVersion('');
}}
/>
</div>
<div className={styles.carreWrapper}>
<img
className={`${styles.carre} ${styles.adviced} ${selectedFramework === 'paper' ? styles.selected : ''}`}
src={paper}
alt="Paper Logo"
onClick={() => {
setSelectedFramework('paper');
setServerVersion('');
}}
/>
<label className={styles.recommendationLabel}>Recommandé</label> {}
</div>
</div>
<div className={styles.subtitle}>Sélection de la version</div>
<select
className={styles.select}

View File

@ -139,7 +139,13 @@
justify-content: space-around;
align-items: center;
width: 100%;
height: 5rem;
height: 6rem;
}
.carreWrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.carre {
@ -160,6 +166,17 @@
transform: scale(1.2);
}
.adviced {
border: 2px solid violet;
}
.recommendationLabel {
margin-top: 0.5rem;
font-size: 1rem;
color: violet;
font-weight: 700;
text-align: center;
}

View File

@ -35,7 +35,6 @@ const Navbar = ({ user }) => {
}, []);
useEffect(() => {
console.log('User data:', user);
}, [user]);
return (

View File

@ -9,6 +9,7 @@ import PropTypes from "prop-types";
const ServerCard = ({ status, version, name, framework, onRunClick, onStopClick, onDeleteClick , countPlayers , maxPlayers}) => {
const getFrameworkSource = () => {
switch (framework) {
case "bukkit":
@ -50,7 +51,10 @@ const ServerCard = ({ status, version, name, framework, onRunClick, onStopClick,
};
return (
<Link to={`/server/${name}`} className={styles.serverCard}>
<Link
to={`/server/${name}/options`}
className={styles.serverCard}
state={{ status }}>
<div className={styles.header}>
<div className={styles.serverInfo}>
<img src={getFrameworkSource()} alt={`${name} Icon`} className={styles.frameworkIcon} />

View File

@ -88,9 +88,7 @@ const DashboardPage = ({ user }) => {
const handleCopyAddress = () => {
const address = `${subdomain}.servii.fr`;
navigator.clipboard.writeText(address)
.then(() => {
console.log('Address copied to clipboard');
})
};
if (loading) {

View File

@ -0,0 +1,133 @@
import { useEffect, useState, useRef } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { FaExclamationCircle } from 'react-icons/fa';
import styles from './ServerConsole.module.scss';
import serviiApi from "../../service/api.tsx";
import Loading from '../Loading/loading.jsx';
import PropTypes from "prop-types";
const ServerConsole = ({ user }) => {
const navigate = useNavigate();
const { serverName } = useParams();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [logs, setLogs] = useState('');
const [message, setMessage] = useState('');
const location = useLocation();
const status = location.state?.status;
const logsEndRef = useRef(null);
const scrollToBottom = () => {
logsEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
const fetchServerLogs = async () => {
setError(null);
try {
const response = await serviiApi.fetchLogs(serverName);
if (response.return_code === 200) {
let logString = response.message;
logString = logString.slice(1, -1);
logString = logString.replace(/\\n/g, '\n');
logString = logString.replace(/\\\"/g, '"');
logString = logString.replace(/, ?/g, '');
logString = logString.replace(/"{2,}/g, '');
logString = logString.replace(/'{2,}/g, '');
logString = logString.trim();
setLogs(logString);
} else {
setError(`Erreur lors de la récupération des logs: ${response.message}`);
}
} catch (err) {
setError(`Erreur: ${err.message}`);
} finally {
}
};
useEffect(() => {
fetchServerLogs();
}, [serverName]);
useEffect(() => {
scrollToBottom();
}, [logs]);
const handleSendMessage = async () => {
if (message.trim() === "") {
return;
}
try {
const response = await serviiApi.command(message, serverName);
setMessage('');
fetchServerLogs();
} catch (err) {
setError(`Erreur lors de l'envoi de la commande : ${err.message}`);
}
};
if (loading) {
return <Loading />;
}
if (error) {
return (
<div className={styles.serverDetailsContainer}>
<div className={styles.error}>
<h1>{error}</h1>
<button onClick={() => navigate('/dashboard')}>Retour au Dashboard</button>
</div>
</div>
);
}
return (
<div className={styles.container}>
<div className={styles.containerConsole}>
<div className={styles.header}>
Console {status ? '(En marche)' : '(Arrêté)'}
</div>
<div className={styles.logContainer}>
{status ? (
<>
<pre className={styles.logs}>
{logs}
</pre>
<div ref={logsEndRef} />
</>
) : (
<div className={styles.offline}>
<FaExclamationCircle style={{ marginRight: '.6rem' }} />
<span>Serveur hors ligne</span>
</div>
)}
</div>
<div className={styles.chatInputContainer}>
<input
type="text"
className={styles.chatInput}
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Écrire un message..."
/>
<button className={styles.sendButton} onClick={handleSendMessage}>
Envoyer
</button>
</div>
</div>
</div>
);
};
ServerConsole.propTypes = {
user: PropTypes.oneOfType([
PropTypes.shape({
uid: PropTypes.string.isRequired,
}),
]),
};
export default ServerConsole;

View File

@ -0,0 +1,99 @@
.container {
display: flex;
justify-content: center;
align-items: center;
margin-top: var(--navbar-height);
width: 100%;
overflow: hidden;
}
.containerConsole {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
background-color: #100D25;
width: 100%;
max-width: 50rem;
height: 38rem;
overflow: hidden;
border: .1rem solid #343947;
padding: 0;
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.5);
margin-bottom: 2rem;
}
.header {
display: flex;
justify-content: flex-start;
align-items: center;
background-color: #1D1836;
width: 100%;
height: 3rem;
font-size: 1rem;
font-weight: 500;
border-bottom: .1rem solid #343947;
padding-left: 1rem;
}
.logContainer {
width: 100%;
height: calc(100% - 6rem);
overflow-y: auto;
padding: 1rem;
box-sizing: border-box;
}
.logs {
font-family: 'Courier New', Courier, monospace;
font-size: 0.9rem;
color: #ffffff;
white-space: pre-wrap;
line-height: 1.5;
margin: 0;
}
.chatInputContainer {
display: flex;
width: 100%;
padding: 0.5rem;
background-color: #1D1836;
border-top: .1rem solid #343947;
border-left: .1rem solid #343947;
border-right: .1rem solid #343947;
}
.chatInput {
flex: 1;
padding: 0.5rem;
border: .1rem solid #343947;
background-color: #2C2A3E;
color: #ffffff;
font-size: 0.9rem;
box-sizing: border-box;
}
.sendButton {
margin-left: 0.5rem;
padding: 0.5rem 1rem;
background-color: #3E3B59;
color: #ffffff;
border: none;
cursor: pointer;
font-size: 0.9rem;
transition: background-color 0.3s;
}
.sendButton:hover {
background-color: #5A567E;
}
.offline {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
font-size: 1.5rem;
}

View File

@ -1,155 +1,101 @@
import { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useParams, useNavigate, Route, Routes, Link } from 'react-router-dom';
import Navbar from '../../components/navbar/Navbar';
import ServerProperties from '../ServerProperties/ServerProperties';
import ServerConsole from '../ServerConsole/ServerConsole';
// import ServerHistory from '../ServerHistory/ServerHistory';
// import ServerPlayers from '../ServerPlayers/ServerPlayers';
// import ServerWorlds from '../ServerWorlds/ServerWorlds';
// import ServerBackups from '../ServerBackups/ServerBackups';
// import ServerAccess from '../ServerAccess/ServerAccess';
import styles from './ServerDetails.module.scss';
import serviiApi from "../../service/api.tsx";
import Loading from '../Loading/loading';
import PropTypes from "prop-types";
import { FaServer, FaCogs, FaUserFriends, FaGlobe, FaHistory, FaClipboardList, FaSave, FaLock } from 'react-icons/fa';
import NotFoundPage from '../NotFoundPage/NotFoundPage';
import { NavLink } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
const ServerDetails = ({ user }) => {
const { serverName } = useParams();
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 validateInput = (input) => {
return input.replace(/[^a-zA-Z]/g, '');
};
const handleChange = (e) => {
const { name, value } = e.target;
const validatedValue = name === 'motd' ? validateInput(value) : value;
setServer({ ...server, [name]: validatedValue });
};
const handleSave = async () => {
try {
const props = [
['max-players', server.maxPlayers.toString()],
['motd', server.motd],
['difficulty', server.difficulty],
['enable-command-block', server.enableCommandBlock.toString()],
['gamemode', server.gamemode.toString()],
['hardcore', server.hardcore.toString()],
['online-mode', 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');
}
handleQuit();
};
const location = useLocation();
const status = location.state?.status;
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 className={styles.container}>
<div className={styles.sidebar}>
<div className={styles.userinfo}>
<div className={styles.user}>
<p>{user.displayName}</p>
</div>
</div>
<div className={styles.menu}>
<NavLink
to={`/server/${serverName}/options`}
className={({ isActive }) => isActive ? `${styles.menuItem} ${styles.active}` : styles.menuItem}>
<FaCogs /> Propriétés
</NavLink>
<NavLink
state={{ status }}
to={`/server/${serverName}/console`}
className={({ isActive }) => isActive ? `${styles.menuItem} ${styles.active}` : styles.menuItem}>
<FaClipboardList /> Console
</NavLink>
<NavLink
to={`/server/${serverName}/history`}
className={({ isActive }) => isActive ? `${styles.menuItem} ${styles.active}` : styles.menuItem}>
<FaHistory /> Historique
</NavLink>
<NavLink
to={`/server/${serverName}/players`}
className={({ isActive }) => isActive ? `${styles.menuItem} ${styles.active}` : styles.menuItem}>
<FaUserFriends /> Joueurs
</NavLink>
<NavLink
to={`/server/${serverName}/worlds`}
className={({ isActive }) => isActive ? `${styles.menuItem} ${styles.active}` : styles.menuItem}>
<FaGlobe /> Mondes
</NavLink>
<NavLink
to={`/server/${serverName}/backups`}
className={({ isActive }) => isActive ? `${styles.menuItem} ${styles.active}` : styles.menuItem}>
<FaSave /> Sauvegardes
</NavLink>
<NavLink
to={`/server/${serverName}/access`}
className={({ isActive }) => isActive ? `${styles.menuItem} ${styles.active}` : styles.menuItem}>
<FaLock /> Accès
</NavLink>
</div>
<button className={styles.BackButton} onClick={handleQuit}>Retour</button>
</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 className={styles.serverDetailsContainer}>
<Routes>
<Route path="options" element={<ServerProperties user={user} status={status} />} />
<Route path="console" element={<ServerConsole user={user} status={status} />} />
<Route path="history" element={<h2>Cette fonctionnalité sera prochainement disponible.</h2>} />
<Route path="players" element={<h2>Cette fonctionnalité sera prochainement disponible.</h2>} />
<Route path="worlds" element={<h2>Cette fonctionnalité sera prochainement disponible.</h2>} />
<Route path="backups" element={<h2>Cette fonctionnalité sera prochainement disponible.</h2>} />
<Route path="access" element={<h2>Cette fonctionnalité sera prochainement disponible.</h2>} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</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>
</>
);
};
@ -162,4 +108,5 @@ ServerDetails.propTypes = {
}),
]),
};
export default ServerDetails;

View File

@ -1,133 +1,114 @@
.container {
display: flex;
min-height: calc(100vh - var(--navbar-height));
}
.sidebar {
width: 20rem;
background-color: #100D25;
padding: 2rem 1rem;
padding-left: 0rem;
box-sizing: border-box;
position: fixed;
height: calc(100vh - var(--navbar-height));
top: var(--navbar-height);
color: white;
}
.menuItem {
display: flex;
align-items: center;
padding: 0.7rem 1rem ;
margin: .5rem 0rem;
padding-right: 0rem;
font-size: 1.5rem;
cursor: pointer;
color: #D1D5DB;
text-decoration: none;
width: 100%;
&.active {
background-color: #1D1836;
color: white;
}
}
.menuItem:hover {
color: white;
}
.user {
margin-bottom: 2rem;
}
.user p {
font-size: 1.25rem;
font-weight: bold;
}
.user small {
font-size: 0.875rem;
color: #9CA3AF;
}
.menu {
display: flex;
flex-direction: column;
}
.menuItem:hover {
color: white;
}
.menuItem svg {
margin-right: 1rem;
font-size: 1.25rem;
}
.BackButton {
margin-top: 1rem;
padding: 0.70rem 1.5rem;
background-color: #1D1836;
border: none;
color: white;
cursor: pointer;
width: 20rem;
text-align: center;
font-size: 1.25rem;
}
.serverDetailsContainer {
padding-top: var(--navbar-height);
margin-top: var(--navbar-height);
flex: 1;
margin-left: 20rem;
padding-top: 1rem;
background-color: var(--main-bg-color);
display: flex;
flex-direction: column;
align-items: center;
color: var(--text-color);
width: 100%;
box-sizing: border-box;
}
.details {
margin-top: 5rem;
padding: 3rem;
padding-top: 0;
background-color: var(--card-bg-color);
width: 100%;
max-width: 60rem;
border-radius: 1.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
box-sizing: border-box;
overflow-y: auto;
}
.formGroup {
margin-bottom: 2.5rem;
.navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: var(--navbar-height);
background-color: var(--navbar-bg-color);
z-index: 1000;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.userinfo {
display: flex;
align-items: start;
justify-content: center;
flex-direction: column;
}
.formGroup label {
font-weight: 600;
margin-bottom: 1rem;
font-size: 1.4rem;
color: var(--label-color);
}
.formGroup input,
.formGroup select {
width: 100%;
padding: 1.2rem;
height: auto;
background-color: var(--input-bg-color);
border: 1px solid var(--input-border-color);
border-radius: 0.75rem;
color: var(--text-color);
font-size: 1.2rem;
line-height: 1.5rem;
box-sizing: border-box;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.formGroup input:focus,
.formGroup select:focus {
border-color: var(--focus-border-color);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
outline: none;
}
/* Chrome- styles */
@supports (-webkit-appearance: none) {
.formGroup select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url('data:image/svg+xml;utf8,<svg fill="%23ddd" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/></svg>');
background-repeat: no-repeat;
background-position: right 1.2rem center;
background-size: 1rem;
padding-right: 3rem;
}
}
.saveButton,
.quitButton {
padding: 1.2rem 2.5rem;
color: var(--button-text-color);
border: none;
border-radius: 0.75rem;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
font-size: 1.2rem;
margin-top: 1rem;
}
.saveButton {
background-color: #05a771;
}
.saveButton:hover {
background-color: #05a77183;
}
.quitButton {
background-color: gray;
margin-left: 1rem;
}
.error {
color: var(--error-color);
font-size: 1.2rem;
}
.details h1 {
font-size: 2.5rem;
margin-bottom: 3rem;
color: var(--header-color);
}
@media (max-width: 768px) {
.details {
width: 90%;
padding: 2rem;
}
.formGroup input,
.formGroup select {
height: auto;
font-size: 1.1rem;
padding: 1rem;
}
.saveButton,
.quitButton {
padding: 1rem 2rem;
font-size: 1.1rem;
}
.details h1 {
font-size: 2rem;
margin-bottom: 2rem;
}
}
}

View File

@ -0,0 +1,156 @@
import { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import styles from './ServerProperties.module.scss';
import serviiApi from "../../service/api.tsx";
import Loading from '../Loading/loading';
import PropTypes from "prop-types";
const ServerProprieties = ({ 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 validateInput = (input) => {
return input.replace(/[^a-zA-Z]/g, '');
};
const handleChange = (e) => {
const { name, value } = e.target;
const validatedValue = name === 'motd' ? validateInput(value) : value;
setServer({ ...server, [name]: validatedValue });
};
const handleSave = async () => {
try {
const props = [
['max-players', server.maxPlayers.toString()],
['motd', server.motd],
['difficulty', server.difficulty],
['enable-command-block', server.enableCommandBlock.toString()],
['gamemode', server.gamemode.toString()],
['hardcore', server.hardcore.toString()],
['online-mode', 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');
}
};
if (loading) {
return <Loading />;
}
if (error) {
return (
<div className={styles.serverDetailsContainer}>
<div className={styles.error}>
<h1>{error}</h1>
<button onClick={() => navigate('/dashboard')}>Retour au Dashboard</button>
</div>
</div>
);
}
return (
<div className={styles.serverDetailsContainer}>
<div className={styles.details}>
<h1>Propriétés 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</button>
</div>
</div>
);
};
ServerProprieties.propTypes = {
user: PropTypes.oneOfType([
PropTypes.shape({
uid: PropTypes.string.isRequired,
displayName: PropTypes.string,
email: PropTypes.string,
}),
]),
};
export default ServerProprieties;

View File

@ -0,0 +1,133 @@
.serverDetailsContainer {
padding-top: var(--navbar-height);
background-color: var(--main-bg-color);
display: flex;
flex-direction: column;
align-items: center;
color: var(--text-color);
width: 100%;
box-sizing: border-box;
}
.details {
padding: 3rem;
padding-top: 0;
background-color: var(--card-bg-color);
width: 100%;
max-width: 60rem;
border-radius: 1.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
box-sizing: border-box;
}
.formGroup {
margin-bottom: 2.5rem;
display: flex;
flex-direction: column;
}
.formGroup label {
font-weight: 600;
margin-bottom: 1rem;
font-size: 1.4rem;
color: var(--label-color);
}
.formGroup input,
.formGroup select {
width: 100%;
padding: 1.2rem;
height: auto;
background-color: var(--input-bg-color);
border: 1px solid var(--input-border-color);
border-radius: 0.75rem;
color: var(--text-color);
font-size: 1.2rem;
line-height: 1.5rem;
box-sizing: border-box;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.formGroup input:focus,
.formGroup select:focus {
border-color: var(--focus-border-color);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
outline: none;
}
/* Chrome- styles */
@supports (-webkit-appearance: none) {
.formGroup select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url('data:image/svg+xml;utf8,<svg fill="%23ddd" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/></svg>');
background-repeat: no-repeat;
background-position: right 1.2rem center;
background-size: 1rem;
padding-right: 3rem;
}
}
.saveButton,
.quitButton {
padding: 1.2rem 2.5rem;
color: var(--button-text-color);
border: none;
border-radius: 0.75rem;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
font-size: 1.2rem;
margin-top: 1rem;
}
.saveButton {
background-color: #05a771;
}
.saveButton:hover {
background-color: #05a77183;
}
.quitButton {
background-color: gray;
margin-left: 1rem;
}
.error {
color: var(--error-color);
font-size: 1.2rem;
}
.details h1 {
font-size: 2.5rem;
margin-bottom: 3rem;
color: var(--header-color);
}
@media (max-width: 768px) {
.details {
width: 90%;
padding: 2rem;
}
.formGroup input,
.formGroup select {
height: auto;
font-size: 1.1rem;
padding: 1rem;
}
.saveButton,
.quitButton {
padding: 1rem 2rem;
font-size: 1.1rem;
}
.details h1 {
font-size: 2rem;
margin-bottom: 2rem;
}
}

View File

@ -47,8 +47,15 @@ enum serviiRequest {
serverStop = 'ServerStop',
updateProperty = 'UpdateProperties',
command = 'Command',
fetchLogs = 'FetchLogs',
}
const nonToastableCalls: string[] = [
serviiRequest.fetchServers,
serviiRequest.fetchLogs,
];
class serviiApi {
constructor() {}
@ -65,33 +72,35 @@ class serviiApi {
if (json.message === undefined) {
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" };
}
return { return_code: status, message: json };
}
let toastType: 'success' | 'error' | 'info';
let toastColor: string;
if (!nonToastableCalls.includes(endpoint)) {
if (status >= 200 && status < 300) {
toastType = 'success';
} else if (status >= 400 && status < 600) {
toastType = 'error';
} else {
toastType = 'info';
}
let toastType: 'success' | 'error' | 'info';
toast[toastType](json.message, {
position: "top-right",
autoClose: 3500,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
if (status >= 200 && status < 300) {
toastType = 'success';
} else if (status >= 400 && status < 600) {
toastType = 'error';
} else {
toastType = 'info';
}
toast[toastType](json.message, {
position: "top-right",
autoClose: 3500,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
return { return_code: status, message: json.message };
}
@ -114,6 +123,11 @@ class serviiApi {
return this.call(serviiRequest.fetchServers, payload);
}
public static async fetchLogs(name: string): Promise<ApiResponse> {
const payload: ServerRequest = { token: this.token(), name: name };
return this.call(serviiRequest.fetchLogs, payload);
}
public static async accountCreate(): Promise<ApiResponse> {
const payload: BaseRequest = { token: this.token() };
return this.call(serviiRequest.accountCreate, payload);
@ -150,7 +164,7 @@ class serviiApi {
name: name,
props: props,
};
return this.call(serviiRequest.updateProperty, payload); // Correct usage here
return this.call(serviiRequest.updateProperty, payload);
}
public static async command(command: string, name: string): Promise<ApiResponse> {