2026-10-08 02:01:58 +02:00
@ extends ( 'layouts.vitrine' )
@ section ( 'titre' , $classe -> nom )
@ section ( 'contenu' )
< div class = " mx-auto max-w-5xl px-5 py-14 lg:px-10 " >
< div class = " flex flex-wrap items-end justify-between gap-4 " >
< div >
< h1 class = " text-4xl font-extrabold tracking-tight " > {{ $classe -> nom }} </ h1 >
@ if ( $classe -> formation )
< p class = " mt-1 text-lg font-semibold " > {{ $classe -> formation }} </ p >
@ endif
</ div >
< p class = " font-mono text-sm font-semibold " > Code de classe : < span class = " rounded-lg border border-[#dadce0] bg-white px-2 py-1 " > {{ $classe -> code_classe }} </ span ></ p >
</ div >
@ if ( session ( 'classe_creee' ))
< p class = " mt-4 border border-[#e3e5e8] rounded-2xl bg-[#e6f4ea] p-4 font-semibold " > Classe créée . Ajoute maintenant tes élèves ci - dessous .</ p >
@ endif
2026-10-09 22:18:53 +02:00
@ unless ( $classe -> estArchivee ())
< section class = " mt-10 grid gap-6 rounded-2xl border border-[#e3e5e8] bg-white p-6 md:grid-cols-[1fr_auto] " aria - labelledby = " t-rejoindre " >
< div >
< h2 id = " t-rejoindre " class = " text-2xl font-extrabold " > Faire rejoindre tes élèves </ h2 >
< p class = " mt-2 font-semibold " > Projette ce code ou ce QR . Chaque élève va sur < span class = " font-mono " > {{ preg_replace ( '#^https?://#' , '' , route ( 'rejoindre' )) }} </ span > , tape le code et son prénom ; tu acceptes sa demande ici , et il reçoit à l ' écran son identifiant et son code secret . Aucun e - mail .</ p >
< p class = " mt-4 font-mono text-5xl font-bold tracking-[0.3em] " > {{ $classe -> code_classe }} </ p >
< h3 class = " mt-6 text-lg font-extrabold " > Demandes en attente ({{ $demandes -> count () }}) </ h3 >
@ if ( $demandes -> isEmpty ())
< p class = " mt-2 font-semibold text-[#5f6368] " > Aucune pour l ' instant . Cette liste se met à jour toute seule .</ p >
@ else
< ul class = " mt-2 grid gap-2 " >
@ foreach ( $demandes as $demande )
< li class = " flex flex-wrap items-center justify-between gap-3 rounded-xl border border-[#e3e5e8] p-3 " >
< span class = " font-extrabold " > {{ $demande -> prenom }} @ if ( $demande -> nom ) {{ $demande -> nom }} @ endif < span class = " font-mono text-sm font-semibold text-[#5f6368] " > · {{ $demande -> created_at -> diffForHumans () }} </ span ></ span >
< span class = " flex gap-2 " >
< form action = " { { route('mes-classes.demandes.accepter', [ $classe , $demande ]) }} " method = " post " >@ csrf < button type = " submit " class = " inline-flex h-10 items-center rounded-full bg-[#0b57d0] px-4 font-bold text-white " > Accepter </ button ></ form >
< form action = " { { route('mes-classes.demandes.refuser', [ $classe , $demande ]) }} " method = " post " >@ csrf < button type = " submit " class = " inline-flex h-10 items-center rounded-full border border-[#dadce0] px-4 font-bold " > Refuser </ button ></ form >
</ span >
</ li >
@ endforeach
</ ul >
@ endif
</ div >
< div class = " justify-self-center " > { !! \App\Support\QrCode :: svg ( route ( 'rejoindre' , [ 'code' => $classe -> code_classe ])) !! } </ div >
</ section >
@ endunless
2026-10-08 02:01:58 +02:00
@ if ( session ( 'fiches' ))
< section class = " mt-10 border border-[#e3e5e8] rounded-2xl bg-[#fef7e0] p-6 print:border-0 print:bg-white " aria - labelledby = " t-fiches " >
< div class = " flex flex-wrap items-center justify-between gap-3 print:hidden " >
< h2 id = " t-fiches " class = " text-2xl font-extrabold " >
Fiches à imprimer
@ if ( session ( 'nombre_importes' ))
< span class = " font-mono text-sm font-semibold " > ({{ session ( 'nombre_importes' ) }} élève ( s ) importé ( s )) </ span >
@ endif
</ h2 >
2026-10-09 10:39:50 +02:00
< button type = " button " data - imprimer class = " border border-[#dadce0] rounded-lg bg-black px-4 py-2 font-extrabold text-white " > Imprimer </ button >
2026-10-08 02:01:58 +02:00
</ div >
< p class = " mt-2 font-semibold print:hidden " > Le code secret ne sera plus jamais affiché après cette page : imprime ou note - le maintenant .</ p >
< div class = " mt-6 grid gap-4 sm:grid-cols-2 print:grid-cols-1 " >
@ foreach ( session ( 'fiches' ) as $fiche )
< div class = " flex items-center gap-4 border border-[#dadce0] rounded-2xl bg-white p-5 print:break-inside-avoid " >
< div class = " shrink-0 " > { !! \App\Support\QrCode :: svg ( route ( 'connexion.qr' , $fiche [ 'jeton_qr' ])) !! } </ div >
< div class = " min-w-0 " >
< p class = " text-lg font-extrabold " > {{ $fiche [ 'prenom' ] }} @ if ( $fiche [ 'nom' ]) {{ $fiche [ 'nom' ] }} @ endif </ p >
< p class = " mt-2 font-mono text-sm " > Identifiant < br >< span class = " text-lg font-bold " > {{ $fiche [ 'identifiant' ] }} </ span ></ p >
< p class = " mt-2 font-mono text-sm " > Code secret < br >< span class = " text-lg font-bold " > {{ $fiche [ 'code_secret' ] }} </ span ></ p >
</ div >
</ div >
@ endforeach
</ div >
</ section >
@ endif
< div class = " mt-10 grid gap-6 sm:grid-cols-2 " >
< section class = " border border-[#e3e5e8] rounded-2xl bg-white p-6 " aria - labelledby = " t-ajout " >
< h2 id = " t-ajout " class = " text-xl font-extrabold " > Ajouter un élève </ h2 >
@ error ( 'prenom' ) < p class = " mt-2 font-bold text-[#A63D2A] " > {{ $message }} </ p >@ enderror
< form action = " { { route('mes-classes.eleves.store', $classe ) }} " method = " post " class = " mt-4 grid gap-3 " >
@ csrf
< label class = " grid gap-1 font-bold " > Prénom < input name = " prenom " required class = " w-full rounded-xl border border-[#c4c7c5] bg-white px-3 py-2 font-semibold " ></ label >
< label class = " grid gap-1 font-bold " > Nom < span class = " font-medium " > ( facultatif , visible de toi seul ) </ span >< input name = " nom " class = " w-full rounded-xl border border-[#c4c7c5] bg-white px-3 py-2 font-semibold " ></ label >
< button type = " submit " class = " mt-2 inline-flex h-11 items-center justify-center rounded-full bg-[#0b57d0] px-5 font-bold text-white " > Ajouter </ button >
</ form >
</ section >
< section class = " border border-[#e3e5e8] rounded-2xl bg-white p-6 " aria - labelledby = " t-import " >
< h2 id = " t-import " class = " text-xl font-extrabold " > Importer une liste ( CSV ) </ h2 >
< p class = " mt-2 font-semibold " > Une ligne par élève : < code class = " font-mono " > prenom , nom </ code > ( le nom peut être vide ) .</ p >
@ error ( 'fichier' ) < p class = " mt-2 font-bold text-[#A63D2A] " > {{ $message }} </ p >@ enderror
< form action = " { { route('mes-classes.eleves.importer', $classe ) }} " method = " post " enctype = " multipart/form-data " class = " mt-4 grid gap-3 " >
@ csrf
< input type = " file " name = " fichier " accept = " .csv,text/csv " required class = " w-full rounded-xl border border-[#c4c7c5] bg-white px-3 py-2 font-semibold " >
< button type = " submit " class = " mt-2 inline-flex h-11 items-center justify-center rounded-full bg-[#0b57d0] px-5 font-bold text-white " > Importer </ button >
</ form >
</ section >
</ div >
< section class = " mt-10 border border-[#e3e5e8] rounded-2xl bg-white " aria - labelledby = " t-eleves " >
2026-10-09 22:18:53 +02:00
< div class = " flex flex-wrap items-center justify-between gap-3 p-6 pb-0 " >
< h2 id = " t-eleves " class = " text-2xl font-extrabold " > Élèves ({{ $classe -> eleves -> count () }}) </ h2 >
@ if ( $classe -> eleves -> isNotEmpty ())
< form id = " form-fiches " action = " { { route('mes-classes.fiches', $classe ) }} " method = " post " data - confirmer = " Générer de nouvelles fiches pour les élèves cochés ? Leurs anciens codes ne fonctionneront plus. " >
@ csrf
< button type = " submit " class = " inline-flex h-10 items-center rounded-full border border-[#0b57d0] px-4 font-bold text-[#0b57d0] " > Nouvelles fiches pour la sélection </ button >
</ form >
@ endif
</ div >
@ error ( 'eleves' ) < p class = " px-6 pt-2 font-semibold text-[#A63D2A] " > Coche au moins un élève .</ p >@ enderror
2026-10-08 02:01:58 +02:00
@ if ( $classe -> eleves -> isEmpty ())
< p class = " p-6 font-semibold " > Aucun élève pour l ' instant .</ p >
@ else
< div class = " overflow-x-auto " >
< table class = " mt-4 w-full min-w-[480px] text-left " >
2026-10-09 22:18:53 +02:00
< thead class = " bg-[#0b57d0] text-white " >< tr >< th class = " px-4 py-2 " >< input type = " checkbox " data - tout - cocher aria - label = " Tout sélectionner " ></ th >< th class = " px-4 py-2 " > Élève </ th >< th class = " px-4 py-2 font-mono text-xs " > Identifiant </ th >< th class = " px-4 py-2 " > Dernière connexion </ th >< th class = " px-4 py-2 " ></ th ></ tr ></ thead >
2026-10-08 02:01:58 +02:00
< tbody class = " divide-y divide-[#e3e5e8] " >
@ foreach ( $classe -> eleves as $eleve )
< tr >
2026-10-09 22:18:53 +02:00
< td class = " px-4 py-3 " >< input type = " checkbox " name = " eleves[] " value = " { { $eleve->id }} " form = " form-fiches " aria - label = " Sélectionner { { $eleve->prenom }} " ></ td >
2026-10-08 02:01:58 +02:00
< td class = " px-4 py-3 font-extrabold " > {{ $eleve -> nomAffiche () }} </ td >
< td class = " px-4 py-3 font-mono text-sm " > {{ $eleve -> identifiant }} </ td >
< td class = " px-4 py-3 font-semibold " > {{ $eleve -> derniere_connexion_a ? -> diffForHumans () ? ? 'jamais' }} </ td >
< td class = " px-4 py-3 " >
2026-10-09 10:39:50 +02:00
< form action = " { { route('mes-classes.eleves.regenererCode', [ $classe , $eleve ]) }} " method = " post " data - confirmer = " Régénérer le code de { { $eleve->prenom }} ? L’ ancien code ne fonctionnera plus. " >
2026-10-08 02:01:58 +02:00
@ csrf
< button type = " submit " class = " font-bold underline underline-offset-4 " > Régénérer le code </ button >
</ form >
</ td >
</ tr >
@ endforeach
</ tbody >
</ table >
</ div >
@ endif
</ section >
2026-10-09 21:07:25 +02:00
@ if ( $classe -> estArchivee ())
< section class = " mt-10 rounded-2xl border border-[#A63D2A] bg-[#fce8e6] p-6 " aria - labelledby = " t-archivee " >
< h2 id = " t-archivee " class = " text-2xl font-extrabold " > Classe archivée </ h2 >
< p class = " mt-2 font-semibold " >
Archivée le {{ $classe -> archivee_a -> timezone ( config ( 'app.fuseau_utilisateurs' )) -> format ( 'd/m/Y' ) }} .
La classe et toutes les données de ses élèves ( comptes , séances , résultats ) seront
< strong > supprimées définitivement le {{ $classe -> supprimeeLe () -> timezone ( config ( 'app.fuseau_utilisateurs' )) -> format ( 'd/m/Y' ) }} </ strong >.
</ p >
< form action = " { { route('mes-classes.desarchiver', $classe ) }} " method = " post " class = " mt-4 " >
@ csrf
< button type = " submit " class = " inline-flex h-11 items-center rounded-full bg-black px-5 font-bold text-white " > Désarchiver la classe </ button >
</ form >
</ section >
@ else
2026-10-08 02:01:58 +02:00
< section class = " mt-10 border border-[#e3e5e8] rounded-2xl bg-white p-6 " aria - labelledby = " t-seance " >
ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage
par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances
planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues
dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création
de stack ratée est supprimée et réessayée deux fois, puis la session passe
en échec, visible du prof.
Corrige aussi trois défauts des étapes précédentes : la console se coupait
à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas
rejoindre une séance, la déconnexion n'avait pas de jeton CSRF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:52:16 +02:00
< h2 id = " t-seance " class = " text-2xl font-extrabold " > Lancer ou planifier une séance </ h2 >
< p class = " mt-2 font-semibold " > Les élèves qui rejoignent patientent dans la salle d 'attente jusqu' à ce que tu les admettes . Si tu planifies la séance , les machines de toute la classe sont préparées 10 minutes avant : elles s ' ouvrent alors en quelques secondes .</ p >
@ if ( session ( 'seance_planifiee' ))
< p class = " mt-4 rounded-2xl border border-[#e3e5e8] bg-[#e6f4ea] p-4 font-semibold " > Séance planifiée .</ p >
@ endif
2026-10-08 02:01:58 +02:00
< form action = " { { route('mes-classes.seances.store', $classe ) }} " method = " post " class = " mt-4 flex flex-wrap items-end gap-3 " >
@ csrf
< label class = " grid gap-1 font-bold " > Lab < select name = " lab_id " required class = " rounded-xl border border-[#c4c7c5] bg-white px-3 py-2 font-semibold " >
@ foreach ( \App\Models\Lab :: orderBy ( 'titre' ) -> get () as $lab )
< option value = " { { $lab->id }} " > {{ $lab -> titre }} </ option >
@ endforeach
</ select ></ label >
ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage
par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances
planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues
dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création
de stack ratée est supprimée et réessayée deux fois, puis la session passe
en échec, visible du prof.
Corrige aussi trois défauts des étapes précédentes : la console se coupait
à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas
rejoindre une séance, la déconnexion n'avait pas de jeton CSRF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:52:16 +02:00
< label class = " grid gap-1 font-bold " > Date et heure ( facultatif ) < input type = " datetime-local " name = " prevue_a " value = " { { old('prevue_a') }} " class = " rounded-xl border border-[#c4c7c5] bg-white px-3 py-2 font-semibold " ></ label >
< button type = " submit " class = " inline-flex h-11 items-center justify-center rounded-full bg-black px-5 font-bold text-white " > Ouvrir ou planifier </ button >
2026-10-08 02:01:58 +02:00
</ form >
ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage
par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances
planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues
dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création
de stack ratée est supprimée et réessayée deux fois, puis la session passe
en échec, visible du prof.
Corrige aussi trois défauts des étapes précédentes : la console se coupait
à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas
rejoindre une séance, la déconnexion n'avait pas de jeton CSRF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:52:16 +02:00
@ error ( 'prevue_a' )
< p class = " mt-2 font-semibold text-[#A63D2A] " > {{ $message }} </ p >
@ enderror
@ php ( $planifiees = $classe -> seances () -> where ( 'statut' , \App\Models\Seance :: PLANIFIEE ) -> with ( 'lab' ) -> orderBy ( 'prevue_a' ) -> get ())
@ if ( $planifiees -> isNotEmpty ())
< h3 class = " mt-6 text-lg font-extrabold " > Séances planifiées </ h3 >
< ul class = " mt-2 grid gap-2 " >
@ foreach ( $planifiees as $planifiee )
< li class = " flex flex-wrap items-center justify-between gap-3 rounded-xl border border-[#e3e5e8] p-3 " >
< span >< span class = " font-mono font-bold " > {{ $planifiee -> prevue_a -> timezone ( config ( 'app.fuseau_utilisateurs' )) -> translatedFormat ( 'd/m/Y H:i' ) }} </ span > · < span class = " font-bold " > {{ $planifiee -> lab -> titre }} </ span ></ span >
< form action = " { { route('seances.ouvrir', $planifiee ) }} " method = " post " >
@ csrf
< button type = " submit " class = " font-bold underline underline-offset-4 " > Ouvrir la salle d ' attente </ button >
</ form >
</ li >
@ endforeach
</ ul >
@ endif
2026-10-08 02:01:58 +02:00
</ section >
2026-10-09 21:07:25 +02:00
< section class = " mt-10 rounded-2xl border border-[#e3e5e8] bg-white p-6 " aria - labelledby = " t-archiver " >
< h2 id = " t-archiver " class = " text-2xl font-extrabold " > Fin d ' année </ h2 >
< p class = " mt-2 font-semibold " >
Archiver la classe ferme ses séances . {{ \App\Models\Classe :: DELAI_PURGE_JOURS }} jours plus tard , la classe
et toutes les données de ses élèves sont supprimées automatiquement ( RGPD ) . Tu peux la désarchiver d ' ici là .
</ p >
@ error ( 'archivage' )
< p class = " mt-2 font-semibold text-[#A63D2A] " > {{ $message }} </ p >
@ enderror
< form action = " { { route('mes-classes.archiver', $classe ) }} " method = " post " class = " mt-4 "
data - confirmer = " Archiver { { $classe->nom }} ? Ses données seront supprimées dans { { \ App \ Models \ Classe::DELAI_PURGE_JOURS }} jours. " >
@ csrf
< button type = " submit " class = " inline-flex h-11 items-center rounded-full border border-[#A63D2A] px-5 font-bold text-[#A63D2A] " > Archiver la classe </ button >
</ form >
</ section >
@ endif
2026-10-08 02:01:58 +02:00
</ div >
2026-10-09 10:39:50 +02:00
< script nonce = " { { Vite::cspNonce() }} " >
2026-10-09 22:18:53 +02:00
document . querySelectorAll ( '[data-tout-cocher]' ) . forEach ( function ( c ) {
c . addEventListener ( 'change' , function () { document . querySelectorAll ( 'input[name="eleves[]"]' ) . forEach ( function ( x ) { x . checked = c . checked ; }); });
});
@ unless ( $classe -> estArchivee ())
( function () {
var vues = {{ $demandes -> count () }};
setInterval ( function () {
fetch ( '{{ route(' mes - classes . demandes . nombre ', $classe) }}' , { headers : { 'Accept' : 'application/json' } })
. then ( function ( r ) { return r . json (); })
. then ( function ( d ) {
var occupe = document . activeElement && / INPUT | TEXTAREA | SELECT /. test ( document . activeElement . tagName );
if ( d . en_attente !== vues && ! occupe ) location . reload ();
}) . catch ( function () {});
}, 4000 );
})();
@ endunless
2026-10-09 10:39:50 +02:00
document . querySelectorAll ( '[data-imprimer]' ) . forEach ( function ( b ) { b . addEventListener ( 'click' , function () { window . print (); }); });
document . querySelectorAll ( 'form[data-confirmer]' ) . forEach ( function ( f ) {
f . addEventListener ( 'submit' , function ( e ) { if ( ! window . confirm ( f . dataset . confirmer )) e . preventDefault (); });
});
</ script >
2026-10-08 02:01:58 +02:00
@ endsection