main
anulax1225 ago%!(EXTRA string=2 months)
parent d522f986e7
commit 5f00d6188c
  1. 2
      Servers/ArkServer/Dockerfile
  2. 0
      Servers/ArkServer/pre-run.sh
  3. 11
      app/Docker/Container.php
  4. 2
      app/Http/Controllers/Auth/AuthenticatedSessionController.php
  5. 2
      app/Http/Controllers/Auth/ConfirmablePasswordController.php
  6. 2
      app/Http/Controllers/Auth/EmailVerificationNotificationController.php
  7. 2
      app/Http/Controllers/Auth/EmailVerificationPromptController.php
  8. 2
      app/Http/Controllers/Auth/RegisteredUserController.php
  9. 4
      app/Http/Controllers/Auth/VerifyEmailController.php
  10. 111
      app/Http/Controllers/ServerController.php
  11. 25
      app/Http/Middleware/ResourceRules.php
  12. 41
      app/Jobs/InitServer.php
  13. 2
      app/Models/Server.php
  14. 2
      app/Models/User.php
  15. 5
      database/migrations/2025_02_14_231846_create_server_statuses_table.php
  16. 1
      database/migrations/2025_02_14_234835_create_servers_table.php
  17. 4
      public/icons/lock.svg
  18. 2
      public/icons/logout.svg
  19. BIN
      public/img/banner.gif
  20. BIN
      public/img/banner.webp
  21. 13
      resources/css/app.css
  22. 6
      resources/js/Layouts/AuthenticatedLayout.vue
  23. 57
      resources/js/Layouts/Layout.vue
  24. 129
      resources/js/Pages/Auth/Login.vue
  25. 24
      resources/js/Pages/Home.vue
  26. 9
      resources/js/Pages/Server/Create.vue
  27. 4
      resources/js/Pages/Server/Index.vue
  28. 44
      resources/js/Pages/Server/Paritals/ServerElement.vue
  29. 128
      resources/js/Pages/Server/Show.vue
  30. 8
      routes/auth.php
  31. 12
      routes/console.php
  32. 21
      routes/web.php
  33. 2
      tests/Feature/Auth/AuthenticationTest.php
  34. 2
      tests/Feature/Auth/EmailVerificationTest.php
  35. 2
      tests/Feature/Auth/RegistrationTest.php

@ -26,7 +26,5 @@ RUN sudo -u ark -s
RUN chmod +x ./run.sh
RUN ulimit -n 100000
RUN /usr/games/steamcmd +force_install_dir /app +login anonymous +app_update 376030 validate +exit
#RUN chmod +x ./ShooterGame/Binaries/Linux/ShooterGameServer
ENTRYPOINT [ "./run.sh" ]

@ -31,7 +31,7 @@ public function start()
{
try {
$response = await($this->browser->post(Docker::endpoint("/containers/" . $this->id . "/start"), [
"Content-Type" => "text/plain"
"Content-Type" => "text/plain"
]));
return json_decode($response->getBody());
} catch (Exception $e) {
@ -39,9 +39,8 @@ public function start()
}
}
public function restart($context)
public function restart()
{
$context = $context ?? [ "onSuccess" => function() {}, "onError" => function() {}];
try {
$response = await($this->browser->post(
Docker::endpoint("/containers/" . $this->id . "/restart"),
@ -53,9 +52,8 @@ public function restart($context)
}
}
public function stop($context)
public function stop()
{
$context = $context ?? [ "onSuccess" => function() {}, "onError" => function() {}];
try {
$response = await($this->browser->post(
Docker::endpoint("/containers/" . $this->id . "/stop"),
@ -67,9 +65,8 @@ public function stop($context)
}
}
public function kill($context)
public function kill()
{
$context = $context ?? [ "onSuccess" => function() {}, "onError" => function() {}];
try {
$response = await($this->browser->post(
Docker::endpoint("/containers/" . $this->id . "/kill"),

@ -33,7 +33,7 @@ public function store(LoginRequest $request): RedirectResponse
$request->session()->regenerate();
return redirect()->intended(route('dashboard', absolute: false));
return redirect()->intended(route('home', absolute: false));
}
/**

@ -36,6 +36,6 @@ public function store(Request $request): RedirectResponse
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(route('dashboard', absolute: false));
return redirect()->intended(route('home', absolute: false));
}
}

@ -14,7 +14,7 @@ class EmailVerificationNotificationController extends Controller
public function store(Request $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false));
return redirect()->intended(route('home', absolute: false));
}
$request->user()->sendEmailVerificationNotification();

@ -16,7 +16,7 @@ class EmailVerificationPromptController extends Controller
public function __invoke(Request $request): RedirectResponse|Response
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(route('dashboard', absolute: false))
? redirect()->intended(route('home', absolute: false))
: Inertia::render('Auth/VerifyEmail', ['status' => session('status')]);
}
}

@ -46,6 +46,6 @@ public function store(Request $request): RedirectResponse
Auth::login($user);
return redirect(route('dashboard', absolute: false));
return redirect(route('home', absolute: false));
}
}

@ -15,13 +15,13 @@ class VerifyEmailController extends Controller
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
return redirect()->intended(route('home', absolute: false).'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
return redirect()->intended(route('home', absolute: false).'?verified=1');
}
}

@ -10,6 +10,7 @@
use App\Models\ExposedPort;
use App\Models\Server;
use App\Models\Service;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
@ -17,13 +18,48 @@
class ServerController extends Controller
{
public function start(Request $request)
{
$server = Server::where("uuid", $request->id)->first();
if($server->status_id >= 3) {
try {
$container = new Container($server->container);
$container->start();
$server->update([
"status_id" => 1,
"start" => Carbon::now()
]);
return redirect()->back()->withErrors([ "message" => "Serveur lancé avec succès. Attendez quelque seconds avant de vous connectez." ]);
} catch(Exception $e) { return redirect()->back()->withErrors([ "error" => "Impossible de démarré le serveur." ]); }
} else { return redirect()->back()->withErrors([ "error" => "Serveur déjà ou entrain de démarré." ]); }
}
public function stop(Request $request)
{
$server = Server::where("uuid", $request->id)->first();
if($server->status_id < 3) {
try {
$server->update([
"status_id" => 3,
"end" => Carbon::now()
]);
$container = new Container($server->container);
$container->stop();
return redirect()->back()->withErrors([ "message" => "Serveur arrêté avec succès. Merci de votre visite." ]);
} catch(Exception $e) { return redirect()->back()->withErrors([ "error" => "Impossible de d'arrêter le serveur." ]); }
} else { return redirect()->back()->withErrors([ "error" => "Serveur déjà ou entrain de s'arrêter." ]); }
}
/**
* Display a listing of the resource.
*/
public function index()
{
return Inertia::render("Server/Index", [
"servers" => Server::all()->jsonSerialize(),
"banner" => "/img/banner.gif",
"servers" => (Auth::user()->admin ? Server::orderBy("start", "DESC")->orderBy("end", "DESC")->get() : Server::where("user_id", Auth::user()->id)->get())->jsonSerialize(),
]);
}
@ -33,6 +69,7 @@ public function index()
public function create()
{
return Inertia::render('Server/Create', [
"banner" => "/img/banner.jpg",
"services" => Service::all()
]);
}
@ -45,9 +82,14 @@ public function store(Request $request)
$request->validate([
"name" => "required|string",
"service" => "required|string",
"launch" => "nullable"
]);
$user = Auth::user();
if(!$user->admin && count($user->server) >= 2) return redirect()->back()->withErrors(["error" => "Vous avez déjà trop de servers. Veillez en supprimer un!"]);
$service = Service::where("uuid", $request->service)->firstOrFail();
$service = Service::where("uuid", $request->service)->first();
if(!$service) return redirect()->back()->withErrors(["error" => "Impossible de trouver ce service!"]);
$server = Server::create([
"uuid" => Str::uuid(),
"name" => preg_replace('/[^a-zA-Z0-9_.-]/', '', $request->name),
@ -55,8 +97,27 @@ public function store(Request $request)
"status_id" => 3,
"user_id" => Auth::user()->id
]);
dispatch(new InitServer($server));
return redirect(route("servers.index"));
$config = config($server->service->config);
$ports = explode("|", $server->service->ports);
for ($i = 0; $i < count($ports); $i++) {
$port = ExposedPort::where("usable", true)->first();
if(!$port) {
$server->exposedPorts()->update([ "usable" => true ]);
$server->exposedPorts()->detach();
$server->delete();
return redirect()->back()->withErrors(["error" => "Aucune place libre pour votre server, réessayer plus tard."]);
}
$server->exposedPorts()->attach($port->id);
$port->update(["usable" => false]);
$config["ExposedPorts"][$ports[$i] . "/" . $server->service->protocol] = (object)[];
$config["HostConfig"]["PortBindings"][$ports[$i] . "/" . $server->service->protocol] = [[ "HostPort" => "" . $port->number ]];
}
dispatch(new InitServer($server, $config, $request->launch ? true : false));
return redirect(route("servers.index"))->withErrors([ "message" => "Serveur créer avec succès" . ($request->launch ? " et devrait se lancer d'une minute à l'autre." : ".") ]);
}
/**
@ -65,32 +126,52 @@ public function store(Request $request)
public function show(Request $request)
{
$server = Server::where("uuid", $request->id)->firstOrFail();
return Inertia::render("Server/Show", [
"server" => $server
"banner" => "/img/banner.webp",
"server" => $server->jsonSerialize()
]);
}
/**
* Show the form for editing the specified resource.
* Update the specified resource in storage.
*/
public function edit(string $id)
public function update(Request $request)
{
//
$request->validate([
"name" => "required|string|max:25",
]);
$server = Server::where("uuid", $request->id)->first();
$server->update([
"name" => preg_replace('/[^a-zA-Z0-9_.-]/', '', $request->name)
]);
return redirect()->back()->withErrors(["message" => "Nom changé avec succès."]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
public function makePublic(Request $request)
{
//
$server = Server::where("uuid", $request->id)->first();
$server->update([
"public" => !$server->public,
]);
return redirect()->back()->withErrors(["message" => $server->public ? "Server rendu publiquement accéssible." : "Server rendu privé."]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
public function destroy(Request $request)
{
//
$server = Server::where("uuid", $request->id)->first();
try {
$container = new Container($server->container);
$data = $container->inspect()->State;
if($data->Running || $data->Paused || $data->Restarting) $container->kill();
} catch(Exception) { return redirect()->back()->withErrors(["error" => "Problème pour éteindre le serveur. Réessayer!"]); }
$server->exposedPorts()->update([ "usable" => true ]);
$server->exposedPorts()->detach();
$server->delete();
return redirect(route("servers.index"))->withErrors(["message" => "Server supprimer avec succès."]);
}
}

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use App\Models\Server;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class ResourceRules
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$server = Server::where("uuid", $request->id)->first();
if(!$server) return redirect(route("servers.index"))->withErrors(["error" => "Impossible de trouver ce server!"]);
if(Auth::user()->id == $server->id || Auth::user()->admin) return $next($request);
return redirect(route("servers.index"))->withErrors(["error" => "Vous n'avez d'autorisation d'access a se serveur!"]);
}
}

@ -15,12 +15,16 @@ class InitServer implements ShouldQueue
use Queueable;
protected $server;
protected $config;
protected $launch;
/**
* Create a new job instance.
*/
public function __construct($server)
public function __construct($server, $config, $launch)
{
$this->server = $server;
$this->launch = $launch;
$this->config = $config;
$server->update(["status_id" => 2]);
}
@ -30,24 +34,22 @@ public function __construct($server)
public function handle(): void
{
try {
$config = config($this->server->service->config);
$ports = explode("|", $this->server->service->ports);
for ($i = 0; $i < count($ports); $i++) {
$port = ExposedPort::where("usable", true)->first();
if(!$port) throw new Exception("All ports are used, please try later.");
$this->server->exposedPorts()->attach($port->id);
$port->update(["usable" => false]);
$config["ExposedPorts"][$ports[$i] . "/" . $this->server->service->protocol] = (object)[];
$config["HostConfig"]["PortBindings"][$ports[$i] . "/" . $this->server->service->protocol] = [[ "HostPort" => "" . $port->number ]];
$container = Container::create($this->server->name . Str::random(5), $this->config);
if($this->launch) {
$container->start();
$this->server->update([
"container" => $container->getId(),
"status_id" => 1,
"start" => now(),
"end" => now()
]);
} else {
$this->server->update([
"container" => $container->getId(),
"status_id" => 3,
"end" => now()
]);
}
Log::info(json_encode($config, JSON_UNESCAPED_SLASHES));
$container = Container::create($this->server->name . Str::random(5), $config);
$container->start();
$this->server->update([
"container" => $container->getId(),
"status_id" => 1,
"start" => now()
]);
}catch(Exception $e) {
$this->fail($e);
}
@ -57,9 +59,8 @@ public function fail($e): void
{
$this->server->update([
"status_id" => 4,
"end" => now()
]);
$this->server->exposedPorts()->detach();
Log::info($e->getResponse()->getBody());
Log::info(((object)$e)->getResponse()->getBody());
}
}

@ -12,6 +12,7 @@ class Server extends Model
"container",
"start",
"end",
"public",
"service_id",
"user_id",
"status_id",
@ -24,6 +25,7 @@ public function jsonSerialize(): mixed
'name' => $this->name,
"start" => $this->start,
"end" => $this->end,
"public" => $this->public,
"service" => $this->service,
"user" => $this->user,
"status" => $this->status,

@ -21,6 +21,8 @@ class User extends Authenticatable
'name',
'email',
'password',
'email_verified_at',
'admin'
];
public function servers()

@ -34,6 +34,11 @@ public function up(): void
"message" => "",
]);
Status::create([
"title" => "Stopping",
"message" => "",
]);
Status::create([
"title" => "Failed",
"message" => "",

@ -16,6 +16,7 @@ public function up(): void
$table->uuid("uuid");
$table->string("container", 511)->nullable();
$table->string("name");
$table->boolean("public")->default(false);
$table->dateTime("start")->nullable();
$table->dateTime("end")->nullable();
$table->unsignedBigInteger("service_id");

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 14.5V16.5M7 10.0288C7.47142 10 8.05259 10 8.8 10H15.2C15.9474 10 16.5286 10 17 10.0288M7 10.0288C6.41168 10.0647 5.99429 10.1455 5.63803 10.327C5.07354 10.6146 4.6146 11.0735 4.32698 11.638C4 12.2798 4 13.1198 4 14.8V16.2C4 17.8802 4 18.7202 4.32698 19.362C4.6146 19.9265 5.07354 20.3854 5.63803 20.673C6.27976 21 7.11984 21 8.8 21H15.2C16.8802 21 17.7202 21 18.362 20.673C18.9265 20.3854 19.3854 19.9265 19.673 19.362C20 18.7202 20 17.8802 20 16.2V14.8C20 13.1198 20 12.2798 19.673 11.638C19.3854 11.0735 18.9265 10.6146 18.362 10.327C18.0057 10.1455 17.5883 10.0647 17 10.0288M7 10.0288V8C7 5.23858 9.23858 3 12 3C14.7614 3 17 5.23858 17 8V10.0288" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 974 B

@ -0,0 +1,2 @@
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 3H7C5.89543 3 5 3.89543 5 5V19C5 20.1046 5.89543 21 7 21H15" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M19 12L15 8M19 12L15 16M19 12H9" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

@ -49,4 +49,17 @@ .glow-on-hover {
.glow-on-hover:hover {
transform: scale(1.05);
box-shadow: 0 0 2px 2px rgba(51, 51, 51, 0.8);
}
@keyframes grow {
from {
width: 0%;
}
to {
width: 100%;
}
}
.grow {
animation: grow 6s linear forwards;
}

@ -22,7 +22,7 @@ const showingNavigationDropdown = ref(false);
<div class="flex">
<!-- Logo -->
<div class="flex shrink-0 items-center">
<Link :href="route('dashboard')">
<Link :href="route('home')">
<ApplicationLogo
class="block h-9 w-auto fill-current text-textColor-800"
/>
@ -34,7 +34,7 @@ const showingNavigationDropdown = ref(false);
class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex"
>
<NavLink
:href="route('dashboard')"
:href="route('home')"
:active="route().current('dashboard')"
>
Dashboard
@ -141,7 +141,7 @@ const showingNavigationDropdown = ref(false);
>
<div class="space-y-1 pb-3 pt-2">
<ResponsiveNavLink
:href="route('dashboard')"
:href="route('home')"
:active="route().current('dashboard')"
>
Dashboard

@ -1,6 +1,33 @@
<script setup>
import { Link } from '@inertiajs/vue3';
import { Link, usePage } from '@inertiajs/vue3';
import { onMounted, onUpdated, ref, watch } from 'vue';
const user = usePage().props.auth.user;
const banner = usePage().props.banner;
const message = ref(usePage().props.errors.message);
const error = ref(usePage().props.errors.error);
onUpdated(() => {
if(message.value != usePage().props.errors.message) {
message.value = usePage().props.errors.message;
setTimeout(() => {
usePage().props.errors.message = null;
message.value = null;
}, 6000);
}
if(error.value != usePage().props.errors.error) {
error.value = usePage().props.errors.error;
setTimeout(() => {
usePage().props.errors.error = null;
error.value = null;
}, 6000);
}
});
message.value = usePage().props.errors.message;
error.value = usePage().props.errors.error;
usePage().props.errors.message = null;
usePage().props.errors.error = null;
</script>
<template>
@ -10,17 +37,21 @@ import { Link } from '@inertiajs/vue3';
<Link :href="route('home')" class="mr-2"><img src="/img/logo.png" class="h-[6.8rem]"></Link>
<Link :href="route('home')" class="mr-5">Home</Link>
<Link :href="route('servers.create')" class="mr-5">Spawn server</Link>
<Link :href="'/servers'">Management</Link>
<Link v-if="user" :href="'/servers'">Management</Link>
</div>
<div class="flex text-lg text-textColor-300 items-center font-medium">
<div v-if="!user" class="flex text-lg text-textColor-300 items-center font-medium">
<Link :href="route('login')" class=""><img src="/icons/user.svg" class="h-8 invert"></Link>
</div>
<div v-else class="flex text-lg text-textColor-300 items-center font-medium">
<Link :href="route('logout')"><img src="/icons/logout.svg" class="h-8 invert"></Link>
</div>
</div>
</nav>
<main class="w-full max-h-full overflow-y-auto text-white">
<div class="w-full h-80 flex items-center justify-between overflow-hidden">
<img src="/img/banner.avif" class="w-full pb-72">
<div v-if="banner" class="w-full h-80 flex items-center justify-between overflow-hidden">
<img :src="banner" class="w-full pb-20">
</div>
<div v-else class="pt-20"></div>
<slot />
</main>
<!-- Footer -->
@ -32,4 +63,20 @@ import { Link } from '@inertiajs/vue3';
<footer class="bg-gray-800 text-center py-6">
<p class="text-gray-400">&copy; 2024 Hosting - Tous droits réservés</p>
</footer>
<div v-if="message" class="fixed bottom-0 left-0 right-0 h-16 bg-green-600">
<div class="relative w-full h-full flex items-center justify-center">
<div class="absolute top-0 bottom-0 right-0 left-0">
<div class="h-full bg-green-500 grow"></div>
</div>
<p class="text-white text-2xl font-bold z-10">{{ message }}</p>
</div>
</div>
<div v-if="error" class="fixed bottom-0 left-0 right-0 h-16 bg-red-600">
<div class="relative w-full h-full flex items-center justify-center">
<div class="absolute top-0 bottom-0 right-0 left-0">
<div class="h-full bg-red-500 grow"></div>
</div>
<p class="text-white text-2xl font-bold z-10">{{ error }}</p>
</div>
</div>
</template>

@ -1,6 +1,6 @@
<script setup>
import Checkbox from '@/Components/Checkbox.vue';
import GuestLayout from '@/Layouts/GuestLayout.vue';
import Layout from '@/Layouts/Layout.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
@ -30,81 +30,64 @@ const submit = () => {
</script>
<template>
<GuestLayout>
<Head title="Log in" />
<div v-if="status" class="mb-4 text-sm font-medium text-green-600">
{{ status }}
</div>
<form @submit.prevent="submit">
<div>
<InputLabel for="email" value="Email" />
<TextInput
id="email"
type="email"
class="mt-1 block w-full"
v-model="form.email"
required
autofocus
autocomplete="username"
/>
<Layout>
<div class="w-full h-screen flex items-center justify-center">
<div class="w-full max-w-md bg-gray-800 p-8 rounded-lg shadow-lg fade-in">
<!-- Boutons de bascule -->
<!-- <div class="flex justify-end mb-6">
<Link class="rounded-md text-sm text-textColor-600 underline hover:text-textColor-900
focus:outline-none">Inscription</Link>
</div> -->
<!-- Formulaire de Connexion -->
<form @submit.prevent="submit" class=" space-y-4 my-2 pt-1">
<div class="flex items-center rounded-lg">
<img src="/icons/user.svg" alt="User" class="w-6 h-6 mr-1">
<TextInput
id="email"
type="email"
class="block w-full bg-transparent focus:outline-none"
v-model="form.email"
required
autofocus
autocomplete="username"
/>
</div>
<InputError class="mt-2" :message="form.errors.email" />
</div>
<div class="mt-4">
<InputLabel for="password" value="Password" />
<TextInput
id="password"
type="password"
class="mt-1 block w-full"
v-model="form.password"
required
autocomplete="current-password"
/>
<div class="flex items-center">
<img src="/icons/lock.svg" alt="Lock" class="w-6 h-6 mr-1">
<TextInput
id="password"
type="password"
class="mt-1 block w-full bg-transparent focus:outline-none"
v-model="form.password"
required
autocomplete="current-password"
/>
</div>
<InputError class="mt-2" :message="form.errors.password" />
</div>
<div class="mt-4 block">
<label class="flex items-center">
<Checkbox name="remember" v-model:checked="form.remember" />
<span class="ms-2 text-sm text-textColor-600"
>Remember me</span
<button class="w-full bg-green-500 hover:bg-green-600 text-white py-3 rounded-lg shadow-md transition-all">
Se connecter
</button>
<div class="mt-4 flex items-center justify-between">
<label class="flex items-center">
<Checkbox name="remember" v-model:checked="form.remember" />
<span class="ms-2 text-sm text-textColor-600"
>Remember me</span
>
</label>
<Link
v-if="canResetPassword"
:href="route('password.request')"
class="rounded-md text-sm text-textColor-600 underline hover:text-textColor-900
focus:outline-none"
>
</label>
</div>
<div class="flex justify-between items-center mt-4">
<Link
v-if="canResetPassword"
:href="route('register')"
class="rounded-md text-sm text-textColor-600 underline hover:text-textColor-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
New account
</Link>
<div class="flex items-center justify-end">
<Link
v-if="canResetPassword"
:href="route('password.request')"
class="rounded-md text-sm text-textColor-600 underline hover:text-textColor-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Forgot your password?
</Link>
<PrimaryButton
class="ms-4"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
Log in
</PrimaryButton>
</div>
</div>
</form>
</GuestLayout>
Forgot your password?
</Link>
</div>
</form>
</div>
</div>
</Layout>
</template>

@ -1,10 +1,15 @@
<script setup>
import { Head, Link } from '@inertiajs/vue3';
import Layout from '@/Layouts/Layout.vue';
import ServerElement from './Server/Paritals/ServerElement.vue';
const props = defineProps({
services: {
type: Object,
type: Array,
default: []
},
servers: {
type: Array,
default: []
}
});
@ -20,6 +25,13 @@ const props = defineProps({
</h1>
<p class="text-gray-300 max-w-3xl mx-auto mt-4 fade-in">Un service gratuit permettant d'héberger facilement vos serveurs dans des conteneurs Docker sécurisés et performants.</p>
</header>
<p class="text-center text-2xl font-bold mb-4 fade-in">Nos Services</p>
<section class="flex justify-center mx-auto mb-10 slide-up">
<div v-for="service in props.services" class="relative flex items-center justify-center h-72 max-w-52 overflow-x-hidden bg-black w-full rounded-lg mx-5">
<img :src="service.image" class="object-cover h-full">
<!-- <p class="absolute bottom-0 right-0 left-0 p-1 text-center bg-gray-950/60 font-extrabold text-gray-300 text-2xl">{{ service.name }}</p> -->
</div>
</section>
<section class="max-w-4xl mx-auto bg-gray-800 p-6 rounded-lg shadow-lg slide-up">
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
@ -66,14 +78,8 @@ const props = defineProps({
<img src="/icons/arrow-icon.svg" alt="Créer" class="w-5 h-5 rotate-180 invert mr-2 object-cover"> Créer un serveur
</Link>
</section>
<p class="text-center text-2xl font-bold mb-4 fade-in">Nos Services</p>
<section class="flex justify-center mx-auto mb-10 slide-up">
<div v-for="service in props.services" class="relative flex items-center justify-center h-72 max-w-52 overflow-x-hidden bg-black w-full rounded-lg mx-5">
<img :src="service.image" class="object-cover h-full">
<!-- <p class="absolute bottom-0 right-0 left-0 p-1 text-center bg-gray-950/60 font-extrabold text-gray-300 text-2xl">{{ service.name }}</p> -->
</div>
<section class="max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-6 my-10">
<ServerElement v-for="server in props.servers" :server="server" :editable="false"/>
</section>
</Layout>
</template>

@ -18,7 +18,7 @@ const props = defineProps({
const form = useForm({
name: "",
service: props.services[0].uuid,
port: "",
launch: false,
});
const formatPort = computed(() => {
@ -49,15 +49,18 @@ console.log();
<TextInput v-model="form.name" type="text" class="w-full p-3 rounded bg-gray-700 text-white" placeholder="Server name"></TextInput>
<InputError :message="form.errors.name" class="text-left mt-1"></InputError>
</div>
<div class="mb-10">
<div class="mb-4">
<InputLabel class="block text-left text-gray-300 mb-2" for="service">Service</InputLabel>
<select @change="e => form.service = e.target.value"
class="w-full p-3 rounded bg-gray-700 text-white border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option v-for="service in props.services" :value="service.uuid">{{ service.name }}</option>
</select>
</div>
<InputError :message="form.errors.service" class="text-left mt-1"></InputError>
<div class="flex items-center mb-6">
<p class="text-textColor-700 mr-3">Lancer maintenant</p>
<input type="checkbox" class="rounded bg-gray-500" @click="() => form.launch = !form.launch">
</div>
<button type="submit" class="w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg shadow-md scale-hover">Créer le Serveur</button>
</form>
</section>

@ -1,6 +1,6 @@
<script setup>
import Layout from '@/Layouts/Layout.vue';
import { Head } from '@inertiajs/vue3';
import { Head, useForm } from '@inertiajs/vue3';
import ServerElement from './Paritals/ServerElement.vue';
const props = defineProps({
@ -18,7 +18,7 @@ const props = defineProps({
<!-- En-tête -->
<header class="max-w-6xl mx-auto my-10 text-center">
<h1 class="text-3xl font-bold animate-fade-in">Liste des Serveurs</h1>
<p class="text-gray-400 mt-2">Gérez vos serveurs Minecraft, Ark, Rust et plus encore.</p>
<p class="text-gray-400 mt-2">Gérez vos serveurs Minecraft, Ark et plus encore (Minecraft).</p>
</header>
<!-- Liste des serveurs -->

@ -1,13 +1,34 @@
<script setup>
import { Link } from '@inertiajs/vue3';
import { Link, useForm } from '@inertiajs/vue3';
const props = defineProps({
server: {
type: Object,
required: true
},
editable: {
type: Boolean,
default: true
}
})
});
if(props.editable) {
}
const form = useForm();
const start = (uuid) => {
form.post(route("servers.start", uuid), {
onSuccess: () => {
vm.$forceUpdate()
}
});
}
const stop = (uuid) => {
form.post(route("servers.stop", uuid));
}
</script>
<template>
@ -24,24 +45,27 @@ const props = defineProps({
<div class="mt-4 space-y-2 text-sm">
<p><span class="font-medium">Type </span> {{ props.server.service.name }}</p>
<p><span class="font-medium">Dernier démarrage </span> {{ props.server.start ? props.server.start : "aucun" }}</p>
<p v-if="props.editable" ><span class="font-medium">Dernier démarrage </span> {{ props.server.start ? props.server.start : "aucun" }}</p>
<p class="flex gap-2">
<span class="font-medium">Lien :</span>
<p class="text-green-400 hover:underline">
{{ props.server.ports.length ? "hosting.anulax.ch:" + props.server.ports[0] : "Indisponible" }}
{{ props.server.ports.length && props.server.status.id < 3 ? "hosting.anulax.ch:" + props.server.ports[0] : "Indisponible" }}
</p>
</p>
</div>
<!-- Actions -->
<div class="mt-4 flex gap-2 w-full justify-end">
<a href="" class="bg-green-500 hover:bg-green-600 text-white font-bold p-2 rounded-lg shadow-lg flex items-center gap-2">
<div v-if="props.editable" class="mt-4 flex gap-2 w-full justify-end">
<div v-if="props.server.status.id >= 3" @click="start(props.server.uuid)" class="bg-green-500 hover:bg-green-600 text-white f
glow-on-hover font-bold p-2 rounded-lg shadow-lg flex items-center gap-2">
<img src="/icons/start-icon.svg" alt="Start" class="w-5 h-5 invert">
</a>
<a href="#" class="bg-red-500 hover:bg-red-600 text-white font-bold p-2 rounded-lg shadow-lg flex items-center gap-2">
</div>
<div v-else @click="stop(props.server.uuid)" class="bg-red-500 hover:bg-red-600 text-white
glow-on-hover font-bold p-2 rounded-lg shadow-lg flex items-center gap-2">
<img src="/icons/stop-icon.svg" alt="Stop" class="w-5 h-5 invert">
</a>
<Link :href="route('servers.show', props.server.uuid)" class="bg-blue-500 hover:bg-blue-600 text-white font-bold p-2 rounded-lg shadow-lg flex items-center gap-2">
</div>
<Link :href="route('servers.show', props.server.uuid)" class="bg-blue-500 hover:bg-blue-600 text-white
glow-on-hover font-bold p-2 rounded-lg shadow-lg flex items-center gap-2">
<img src="/icons/details-icon.svg" alt="Détails" class="w-5 h-5 invert">
</Link>
</div>

@ -1,7 +1,46 @@
<script setup>
import InputError from '@/Components/InputError.vue';
import TextInput from '@/Components/TextInput.vue';
import Layout from '@/Layouts/Layout.vue';
import { Head } from '@inertiajs/vue3';
import { Head, useForm } from '@inertiajs/vue3';
import { ref } from 'vue';
const props = defineProps({
server: {
type: Object,
required: true
}
});
const isFocus = ref(false);
const form = useForm();
const editForm = useForm({ name: props.server.name });
const start = (uuid) => {
form.post(route("servers.start", uuid));
}
const stop = (uuid) => {
form.post(route("servers.stop", uuid));
}
const del = (uuid) => {
if(prompt("Entrer le nom du server en confirmation.") == props.server.name) form.delete(route("servers.destroy", uuid));
}
const pub = (uuid) => {
if(props.server.public) form.post(route("servers.public", uuid));
else if (prompt("Entrer le nom du server en confirmation.") == props.server.name) form.post(route("servers.public", uuid));
}
const edit = () => {
console.log(editForm.name);
editForm.post(route("servers.update", props.server.uuid));
}
const copie = () => {
navigator.clipboard.writeText('hosting.anulax.ch:' + props.server.ports[0]).then(() => alert("lien de connection copié!"))
}
</script>
@ -11,7 +50,7 @@ import { Head } from '@inertiajs/vue3';
<!-- En-tête -->
<header class="my-10 text-center">
<h1 class="text-4xl font-bold flex items-center justify-center gap-2 fade-in">
Dashboard - Détails du Serveur
Dashboard du Serveur
</h1>
<p class="text-gray-300 mt-4 fade-in">
Gérez et visualisez les informations clés de votre serveur en un coup d'œil.
@ -19,32 +58,36 @@ import { Head } from '@inertiajs/vue3';
</header>
<!-- Cartes d'information -->
<main class="max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-6 mb-10 slide-up">
<main class="max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-6 mb-6 slide-up">
<!-- Carte : Informations Générales -->
<div class="bg-gray-800 rounded-lg shadow-lg p-6">
<h2 class="text-2xl font-semibold border-b border-gray-700 pb-2 mb-4 flex items-center gap-2">
<img src="/icons/server-icon.svg" alt="Général" class="w-6 h-6 rotate-on-hover glow-on-hover invert">
<img src="/icons/server-icon.svg" alt="Général" class="w-6 h-6 rotate-on-hover invert">
Informations Générales
</h2>
<div class="space-y-3 text-sm">
<div class="flex justify-between">
<span class="font-medium">Nom</span>
<span class="text-green-400 font-bold">Mon Serveur Minecraft</span>
<span class="text-green-400 font-bold">{{ props.server.name }}</span>
</div>
<div class="flex justify-between">
<span class="font-medium">Type de jeu</span>
<span>Minecraft</span>
<span>{{ props.server.service.name }}</span>
</div>
<div class="flex justify-between">
<span class="font-medium">Public</span>
<span>{{ props.server.public ? "Oui" : "Non" }}</span>
</div>
<div class="flex flex-col gap-2">
<div class="flex justify-between items-center mb-1">
<div class="flex items-center gap-2">
<span class="font-medium">Lien d'accès</span>
<span class="font-medium">Lien de connection</span>
</div>
<div class="flex items-center gap-2">
<a href="http://hosting.anulax.ch:25005" class="text-green-400 hover:underline">
hosting.anulax.ch:25005
</a>
<button class="bg-blue-500 hover:bg-blue-600 text-white font-bold p-1 rounded-lg shadow-lg glow-on-hover flex items-center">
<p class="text-green-400 hover:underline">
{{ props.server.ports.length && props.server.status.id < 3 ? "hosting.anulax.ch:" + props.server.ports[0] : "Indisponible" }}
</p>
<button v-show="props.server.ports.length && props.server.status.id < 3" @click="copie" class="bg-blue-500 hover:bg-blue-600 text-white font-bold p-1 rounded-lg shadow-lg glow-on-hover flex items-center">
<img src="/icons/copy-icon.svg" alt="Copier" class="w-5 h-5 invert">
</button>
</div>
@ -57,57 +100,56 @@ import { Head } from '@inertiajs/vue3';
<!-- Carte : Activité -->
<div class="bg-gray-800 rounded-lg shadow-lg p-6">
<h2 class="text-2xl font-semibold border-b border-gray-700 pb-2 mb-4 flex items-center gap-2">
<img src="/icons/stats-icon.svg" alt="Activité" class="w-6 h-6 rotate-on-hover glow-on-hover invert">
Activité
<img src="/icons/stats-icon.svg" alt="Activité" class="w-6 h-6 rotate-on-hover invert">Activité
</h2>
<div class="space-y-3 text-sm mb-4">
<div class="flex justify-between">
<span class="font-medium">Statut</span>
<span class="text-green-400 font-bold">Running</span>
<span class="text-green-400 font-bold">{{ props.server.status.title }}</span>
</div>
<div class="flex justify-between">
<span class="font-medium">Dernier lancement :</span>
<span class="text-green-400 font-bold">2024-02-15 10:00</span>
<span class="font-medium">Dernier lancement</span>
<span class="text-green-400 font-bold">{{ props.server.start ? props.server.start : "Aucun" }}</span>
</div>
<div class="flex justify-between">
<span class="font-medium">Dernier arrêt :</span>
<span class="text-red-400 font-bold">2024-02-15 09:00</span>
<span class="font-medium">Dernier arrêt </span>
<span class="text-red-400 font-bold">{{ props.server.end ? props.server.end : "Aucun" }}</span>
</div>
</div>
<div class="flex gap-3">
<a href="#" class="bg-green-500 hover:bg-green-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
<div @click="start(props.server.uuid)" v-if="props.server.status.id >= 3" class="bg-green-500 hover:bg-green-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
<img src="/icons/start-icon.svg" alt="Start" class="w-5 h-5 invert">
</a>
<a href="#" class="bg-red-500 hover:bg-red-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
</div>
<div @click="stop(props.server.uuid)" v-else class="bg-red-500 hover:bg-red-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
<img src="/icons/stop-icon.svg" alt="Stop" class="w-5 h-5 invert">
</a>
</div>
</div>
</div>
<!-- Carte : Hardware -->
<div class="bg-gray-800 rounded-lg shadow-lg p-6">
<h2 class="text-2xl font-semibold border-b border-gray-700 pb-2 mb-4 flex items-center gap-2">
<img src="/icons/hardware-icon.svg" alt="Hardware" class="w-6 h-6 rotate-on-hover glow-on-hover invert">
Hardware
<img src="/icons/hardware-icon.svg" alt="Hardware" class="w-6 h-6 rotate-on-hover invert">
Hardware(fake)
</h2>
<div class="space-y-3 text-sm">
<div class="flex justify-between">
<span class="font-medium">CPU :</span>
<span>Intel Xeon E5</span>
<span class="font-medium">CPUs</span>
<span>1</span>
</div>
<div class="flex justify-between">
<span class="font-medium">RAM :</span>
<span>16GB</span>
<span class="font-medium">Memory</span>
<span>4GB</span>
</div>
<div class="flex justify-between">
<span class="font-medium">Stockage :</span>
<span>10GB SSD</span>
<span class="font-medium">Stockage</span>
<span>10GB</span>
</div>
</div>
</div>
</main>
<!-- Section Actions en haut -->
<section class="max-w-6xl mx-auto mb-6 fade-in">
<section class="max-w-6xl mx-auto mb-10 fade-in">
<div class="flex justify-between items-center bg-gray-800 p-4 rounded-lg shadow-lg">
<div class="flex gap-3">
<button class="bg-blue-500 hover:bg-blue-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center">
@ -115,16 +157,28 @@ import { Head } from '@inertiajs/vue3';
</button>
</div>
<!-- Actions de gestion -->
<div class="flex gap-3">
<a href="#" class="bg-blue-500 hover:bg-blue-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
<div class="flex gap-3 items-center">
<form v-show="isFocus" @submit.prevent="edit">
<TextInput
v-model="editForm.name"
type="text"
class="w-full px-3 py-1 rounded bg-gray-700 text-white"
/>
<button type="submit"></button>
<InputError :message="form.errors.name" class="text-left mt-1"></InputError>
</form>
<button @click="isFocus = !isFocus" class="bg-blue-500 hover:bg-blue-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
<img src="/icons/edit-icon.svg" alt="Edit" class="w-5 h-5 invert">
</a>
<button class="bg-red-500 hover:bg-red-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center">
</button>
<button v-if="props.server.public" @click="pub(props.server.uuid)" class="bg-blue-500 hover:bg-blue-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center">
<img src="/icons/lock.svg" alt="Rendre Privé" class="w-5 h-5 invert">
</button>
<button v-else @click="pub(props.server.uuid)" class="bg-red-500 hover:bg-red-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center">
<img src="/icons/public-icon.svg" alt="Rendre public" class="w-5 h-5 invert">
</button>
<a href="#" class="bg-red-500 hover:bg-red-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
<button @click="del(props.server.uuid)" class="bg-red-500 hover:bg-red-600 text-white font-bold p-2 rounded-lg shadow-lg glow-on-hover flex items-center gap-2">
<img src="/icons/delete-icon.svg" alt="Delete" class="w-5 h-5 invert">
</a>
</button>
</div>
</div>
</section>

@ -12,10 +12,10 @@
use Illuminate\Support\Facades\Route;
Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');
// Route::get('register', [RegisteredUserController::class, 'create'])
// ->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
// Route::post('register', [RegisteredUserController::class, 'store']);
Route::get('login', [AuthenticatedSessionController::class, 'create'])
->name('login');
@ -54,6 +54,6 @@
Route::put('password', [PasswordController::class, 'update'])->name('password.update');
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
Route::get('logout', [AuthenticatedSessionController::class, 'destroy'])
->name('logout');
});

@ -1,6 +1,8 @@
<?php
use App\Docker\Container;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
@ -9,6 +11,16 @@
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
Artisan::command('create:user {name} {email} {password} {admin}', function ($name, $email, $password, $admin) {
User::create([
"name" => $name,
"email" => $email,
"password" => bcrypt($password),
"admin" => $admin,
"email_verified_at" => Carbon::now()
]);
})->purpose('Display an inspiring quote');
Artisan::command('exec {id}', function ($id) {
try{
$container = new Container($id);

@ -3,6 +3,8 @@
use App\Docker\Container;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\ServerController;
use App\Http\Middleware\ResourceRules;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
@ -18,7 +20,9 @@
Route::get('/', function () {
return Inertia::render('Home', [
"services" => Service::all()
"banner" => "/img/banner.avif",
"services" => Service::all(),
"servers" => Server::where("public", true)->get()->jsonSerialize()
]);
})->name("home");
@ -27,15 +31,20 @@
return Inertia::render('Info', [ "config" => $config ]);
})->name("docker.info");
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware(['auth'])->group(function () {
Route::get("/servers/create", [ServerController::class, 'create'])->name("servers.create");
Route::post("/servers", [ServerController::class, 'store'])->name("servers.store");
Route::get("/servers", [ServerController::class, 'index'])->name("servers.index");
Route::get("/servers/{id}", [ServerController::class, 'show'])->name("servers.show");
Route::middleware(ResourceRules::class)->group(function() {
Route::get("/servers/{id}", [ServerController::class, 'show'])->name("servers.show");
Route::post("/servers/{id}/update", [ServerController::class, 'update'])->name("servers.update");
Route::post("/servers/{id}/public", [ServerController::class, 'makePublic'])->name("servers.public");
Route::delete("/servers/{id}", [ServerController::class, 'destroy'])->name("servers.destroy");
Route::post("/servers/{id}/start", [ServerController::class, 'start'])->name("servers.start");
Route::post("/servers/{id}/stop", [ServerController::class, 'stop'])->name("servers.stop");
});
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');

@ -27,7 +27,7 @@ public function test_users_can_authenticate_using_the_login_screen(): void
]);
$this->assertAuthenticated();
$response->assertRedirect(route('dashboard', absolute: false));
$response->assertRedirect(route('home', absolute: false));
}
public function test_users_can_not_authenticate_with_invalid_password(): void

@ -38,7 +38,7 @@ public function test_email_can_be_verified(): void
Event::assertDispatched(Verified::class);
$this->assertTrue($user->fresh()->hasVerifiedEmail());
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
$response->assertRedirect(route('home', absolute: false).'?verified=1');
}
public function test_email_is_not_verified_with_invalid_hash(): void

@ -26,6 +26,6 @@ public function test_new_users_can_register(): void
]);
$this->assertAuthenticated();
$response->assertRedirect(route('dashboard', absolute: false));
$response->assertRedirect(route('home', absolute: false));
}
}

Loading…
Cancel
Save