Paillasse/portail/resources/views/seances/attente-eleve.blade.php
Adam Belkacemi ec4eae3744 ajoute la visio de classe : Jitsi auto-hébergé, jetons du portail, prof seul modérateur
Jitsi (docker-jitsi-meet, version figée) derrière Nginx sur meet.syllabuslab.fr,
accès par jeton JWT signé par le portail, modération du micro, mains levées
synchronisées avec l'application. Pare-feu : UDP 10000 et pont interne
br-visio (accords d'Adam). Jicofo n'authentifie plus lui-même : il donnait
le rôle de propriétaire aux élèves. Capacité ramenée à 20 VM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:35:35 +02:00

120 lines
7.3 KiB
PHP

@extends('layouts.vitrine')
@section('titre', "Salle d'attente")
@php($etats = \App\Models\LabSession::class)
@section('contenu')
<div class="mx-auto max-w-5xl px-5 py-14 lg:px-10">
<p class="font-mono text-sm font-semibold">{{ $seance->lab->titre }}</p>
@if ($seance->visioOuverte())
<p class="mt-3"><a href="{{ route('seances.visio.eleve', $seance) }}" class="inline-flex h-11 items-center rounded-full bg-[#0b57d0] px-5 font-bold text-white">Rejoindre la visio de la classe</a></p>
@endif
@if ($labSession->etat === $etats::EN_ATTENTE)
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">En attente que le prof t'admette…</h1>
<p class="mt-4 font-semibold">Tes machines s'ouvriront ici dès qu'il t'aura admis.</p>
</div>
@elseif ($labSession->etat === $etats::ADMIS)
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">Préparation de tes VM…</h1>
<p class="mt-4 font-semibold">Ça prend généralement moins d'une minute.</p>
</div>
@elseif ($labSession->etat === $etats::ECHEC)
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">Tes machines n'ont pas pu être préparées</h1>
<p class="mt-4 font-semibold">Ton prof le voit et peut relancer. Cette page se mettra à jour toute seule.</p>
</div>
@elseif ($labSession->etat === $etats::ACTIF && $labSession->machines->isNotEmpty())
<h1 class="mt-2 text-3xl font-extrabold tracking-tight">Tes machines</h1>
<div class="mt-4 flex gap-2" role="tablist">
@foreach ($labSession->machines as $i => $machine)
<button type="button" data-onglet="{{ $machine->id }}" class="onglet-machine rounded-lg border border-[#dadce0] px-4 py-2 font-mono font-bold {{ $i === 0 ? 'bg-[#0b57d0] text-white' : 'bg-white' }}">{{ $machine->nom }}</button>
@endforeach
</div>
@foreach ($labSession->machines as $i => $machine)
<div data-panneau="{{ $machine->id }}" @if ($i !== 0) hidden @endif class="mt-2 h-[480px] overflow-hidden rounded-2xl border border-[#e3e5e8]" style="background:#13233A">
<div id="terminal-{{ $machine->id }}" class="h-full"></div>
</div>
@endforeach
<section class="mt-6 rounded-2xl border border-[#e3e5e8] bg-white p-6" aria-labelledby="t-verification">
<div class="flex flex-wrap items-center justify-between gap-3">
<h2 id="t-verification" class="text-2xl font-extrabold">Vérification</h2>
<form action="{{ route('lab-sessions.corriger', $labSession) }}" method="post" id="form-verifier">
@csrf
<button type="submit" class="inline-flex h-11 items-center rounded-full bg-black px-5 font-bold text-white">Vérifier mon travail</button>
</form>
</div>
@if (session('correction_indisponible'))
<p class="mt-4 rounded-xl bg-[#fce8e6] p-3 font-semibold">La vérification est indisponible pour le moment. Réessaie dans une minute.</p>
@endif
@if ($correction = $labSession->derniereCorrection)
<p class="mt-4 text-lg font-extrabold">
{{ $correction->reussi ? 'Réussi' : 'Pas encore réussi' }}
<span class="font-mono">· {{ $correction->note }}/{{ $correction->note_max }}</span>
</p>
<ul class="mt-3 grid gap-2">
@foreach ($correction->details as $detail)
<li class="rounded-xl border border-[#e3e5e8] p-3">
<span class="font-mono font-bold">{{ $detail['reussi'] ? '✓' : '✗' }}</span>
<span class="font-bold">{{ $detail['titre'] }}</span>
<span class="font-mono text-sm">({{ $detail['points'] }} pt{{ $detail['points'] > 1 ? 's' : '' }})</span>
@if ($detail['message'])
<p class="mt-1 font-semibold text-[#A63D2A]">{{ $detail['message'] }}</p>
@endif
</li>
@endforeach
</ul>
@endif
</section>
@vite('resources/js/console.js')
<script nonce="{{ Vite::cspNonce() }}" src="{{ asset('js/console-reelle.js') }}"></script>
<script nonce="{{ Vite::cspNonce() }}">
// Après les modules Vite (différés) : window.Terminal est défini.
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.onglet-machine').forEach(function (bouton) {
bouton.addEventListener('click', function () {
var id = bouton.dataset.onglet;
document.querySelectorAll('.onglet-machine').forEach(function (b) { b.classList.toggle('bg-[#0b57d0]', b === bouton); b.classList.toggle('text-white', b === bouton); b.classList.toggle('bg-white', b !== bouton); });
document.querySelectorAll('[data-panneau]').forEach(function (p) { p.hidden = p.dataset.panneau !== id; });
});
});
document.getElementById('form-verifier').addEventListener('submit', function (e) {
var bouton = e.target.querySelector('button');
bouton.disabled = true;
bouton.textContent = 'Vérification en cours…';
});
@foreach ($labSession->machines as $machine)
fetch('{{ route('console.eleve', [$labSession, $machine]) }}', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
}).then(function (r) { return r.json(); }).then(function (d) {
window.SyllabusConsoleReelle.mount(document.getElementById('terminal-{{ $machine->id }}'), { jeton: d.jeton, lectureSeule: false, reveil: true });
});
@endforeach
});
</script>
@else
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">Cette séance est terminée pour toi</h1>
<p class="mt-4 font-semibold">Tes machines ont été supprimées.</p>
</div>
@endif
</div>
@if (in_array($labSession->etat, [$etats::EN_ATTENTE, $etats::ADMIS, $etats::ECHEC], true))
<script nonce="{{ Vite::cspNonce() }}">
(function () {
var etatAffiche = @json($labSession->etat);
setInterval(function () {
fetch('{{ route('seances.eleve.etat', $seance) }}', { headers: { 'Accept': 'application/json' } })
.then(function (r) { return r.json(); })
.then(function (d) { if (d.etat !== etatAffiche) location.reload(); })
.catch(function () {});
}, 1000);
})();
</script>
@endif
@endsection