svelte-migrate: updated files
This commit is contained in:
parent
4d6149760d
commit
ff5a2eef65
@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed ui/build/* ui/build/css/* ui/build/surveys/* ui/build/_app/* ui/build/_app/immutable/* ui/build/_app/immutable/pages/* ui/build/_app/immutable/pages/surveys/* ui/build/_app/immutable/pages/surveys/_sid_/* ui/build/_app/immutable/pages/surveys/_sid_/responses/* ui/build/_app/immutable/pages/grades/* ui/build/_app/immutable/pages/works/* ui/build/_app/immutable/pages/works/_wid_/* ui/build/_app/immutable/pages/users/* ui/build/_app/immutable/pages/users/_uid_/* ui/build/_app/immutable/pages/users/_uid_/surveys/* ui/build/_app/immutable/chunks/* ui/build/_app/immutable/assets/* ui/build/img/* ui/build/works/*
|
||||
//go:embed all:ui/build
|
||||
var _assets embed.FS
|
||||
|
||||
var Assets http.FileSystem
|
||||
|
@ -48,7 +48,6 @@ func serveOrReverse(forced_url string) func(c *gin.Context) {
|
||||
}
|
||||
|
||||
func declareStaticRoutes(router *gin.Engine) {
|
||||
router.GET("/@fs/*_", serveOrReverse(""))
|
||||
router.GET("/", serveOrReverse(""))
|
||||
router.GET("/_app/*_", serveOrReverse(""))
|
||||
router.GET("/auth/", serveOrReverse("/"))
|
||||
@ -74,7 +73,7 @@ func declareStaticRoutes(router *gin.Engine) {
|
||||
router.GET("/.svelte-kit/*_", serveOrReverse(""))
|
||||
router.GET("/node_modules/*_", serveOrReverse(""))
|
||||
router.GET("/@vite/*_", serveOrReverse(""))
|
||||
router.GET("/__vite_ping", serveOrReverse(""))
|
||||
router.GET("/@fs/*_", serveOrReverse(""))
|
||||
router.GET("/src/*_", serveOrReverse(""))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
export async function handle({ event, resolve }) {
|
||||
const response = await resolve(event, {
|
||||
ssr: false,
|
||||
});
|
||||
return response;
|
||||
}
|
@ -8,14 +8,10 @@ function createUserStore() {
|
||||
set: (auth) => {
|
||||
update((m) => auth);
|
||||
},
|
||||
update: (res_auth, cb=null) => {
|
||||
update: (res_auth) => {
|
||||
if (res_auth.status === 200) {
|
||||
res_auth.json().then((auth) => {
|
||||
update((m) => (Object.assign(m?m:{}, auth)));
|
||||
|
||||
if (cb) {
|
||||
cb(my);
|
||||
}
|
||||
});
|
||||
} else if (res_auth.status >= 400 && res_auth.status < 500) {
|
||||
update((m) => (null));
|
||||
@ -24,4 +20,14 @@ function createUserStore() {
|
||||
};
|
||||
}
|
||||
|
||||
export async function refresh_auth() {
|
||||
const res = await fetch('api/auth', {headers: {'Accept': 'application/json'}})
|
||||
if (res.status == 200) {
|
||||
const auth = await res.json();
|
||||
user.set(auth);
|
||||
} else {
|
||||
user.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
export const user = createUserStore();
|
||||
|
16
ui/src/routes/+layout.js
Normal file
16
ui/src/routes/+layout.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { refresh_auth, user } from '$lib/stores/user';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
let refresh_interval_auth = null;
|
||||
|
||||
export async function load({ url }) {
|
||||
refresh_interval_auth = setInterval(refresh_auth, Math.floor(Math.random() * 200000) + 200000);
|
||||
refresh_auth();
|
||||
|
||||
const rroutes = url.pathname.split('/');
|
||||
|
||||
return {
|
||||
rroute: rroutes.length>1?rroutes[1]:'',
|
||||
};
|
||||
}
|
@ -1,51 +1,9 @@
|
||||
<script context="module">
|
||||
import { user } from '$lib/stores/user';
|
||||
let stop_refresh = false;
|
||||
|
||||
let refresh_interval_auth = null;
|
||||
async function refresh_auth(cb=null, interval=null) {
|
||||
if (refresh_interval_auth)
|
||||
clearInterval(refresh_interval_auth);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 200000) + 200000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_auth = setInterval(refresh_auth, interval);
|
||||
|
||||
const res = await fetch('api/auth', {headers: {'Accept': 'application/json'}})
|
||||
if (res.status == 200) {
|
||||
const auth = await res.json();
|
||||
user.set(auth);
|
||||
} else {
|
||||
user.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
export async function load({ props, stuff, url }) {
|
||||
refresh_auth();
|
||||
|
||||
const rroutes = url.pathname.split('/');
|
||||
|
||||
return {
|
||||
props: {
|
||||
...props,
|
||||
rroute: rroutes.length>1?rroutes[1]:'',
|
||||
},
|
||||
stuff: {
|
||||
...stuff,
|
||||
refresh_auth,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import AuthButton from '$lib/components/AuthButton.svelte';
|
||||
import Toaster from '$lib/components/Toaster.svelte';
|
||||
import { refresh_auth, user } from '$lib/stores/user';
|
||||
|
||||
export let rroute = '';
|
||||
export let data;
|
||||
|
||||
function switchAdminMode() {
|
||||
var tmp = $user.is_admin;
|
||||
@ -94,17 +52,17 @@
|
||||
</li>
|
||||
{/if}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" class:active={rroute === 'surveys'} href="surveys">
|
||||
<a class="nav-link" class:active={data.rroute === 'surveys'} href="surveys">
|
||||
Questionnaires
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" class:active={rroute === 'works'} href="works">
|
||||
<a class="nav-link" class:active={data.rroute === 'works'} href="works">
|
||||
Travaux
|
||||
</a>
|
||||
</li>
|
||||
{#if $user && $user.is_admin}
|
||||
<li class="nav-item"><a class="nav-link" class:active={rroute === 'users'} href="users">Étudiants</a></li>
|
||||
<li class="nav-item"><a class="nav-link" class:active={data.rroute === 'users'} href="users">Étudiants</a></li>
|
||||
{/if}
|
||||
<li class="nav-item"><a class="nav-link" href="virli" target="_self">VIRLI</a></li>
|
||||
</ul>
|
||||
@ -129,9 +87,9 @@
|
||||
<img class="rounded-circle" src="//photos.cri.epita.fr/square/{$user.login}" alt="Menu" style="margin: -0.75em 0; max-height: 2.5em; border: 2px solid white;">
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" class:active={rroute === 'keys'} href="keys">Clef PGP</a></li>
|
||||
<li><a class="dropdown-item" class:active={rroute === 'help'} href="help">Besoin d'aide ?</a></li>
|
||||
<li><a class="dropdown-item" class:active={rroute === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></li>
|
||||
<li><a class="dropdown-item" class:active={data.rroute === 'keys'} href="keys">Clef PGP</a></li>
|
||||
<li><a class="dropdown-item" class:active={data.rroute === 'help'} href="help">Besoin d'aide ?</a></li>
|
||||
<li><a class="dropdown-item" class:active={data.rroute === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<button class="dropdown-item" on:click={disconnectCurrentUser}>
|
||||
|
5
ui/src/routes/categories/[cid]/+page.js
Normal file
5
ui/src/routes/categories/[cid]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
cid: params.cid,
|
||||
};
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
cid: params.cid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -17,12 +5,10 @@
|
||||
import CategoryAdmin from '$lib/components/CategoryAdmin.svelte';
|
||||
import { Category, getCategory } from '$lib/categories';
|
||||
|
||||
export let cid;
|
||||
export let data;
|
||||
|
||||
let categoryP = null;
|
||||
$: {
|
||||
categoryP = getCategory(cid);
|
||||
}
|
||||
$: categoryP = getCategory(data.cid);
|
||||
</script>
|
||||
|
||||
{#await categoryP then category}
|
||||
|
5
ui/src/routes/grades/[promo]/+page.js
Normal file
5
ui/src/routes/grades/[promo]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
promo: params.promo,
|
||||
};
|
||||
}
|
@ -1,17 +1,7 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
promo: params.promo,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import StudentGrades from '$lib/components/StudentGrades.svelte';
|
||||
|
||||
export let promo;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<StudentGrades {promo} />
|
||||
<StudentGrades promo={data.promo} />
|
||||
|
7
ui/src/routes/results/+page.js
Normal file
7
ui/src/routes/results/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ url }) {
|
||||
return {
|
||||
secret: url.searchParams.get("secret"),
|
||||
idsurvey: url.searchParams.get("survey"),
|
||||
exportview_list: url.searchParams.get("graph_list")?false:true,
|
||||
};
|
||||
}
|
@ -1,26 +1,13 @@
|
||||
<script context="module">
|
||||
export async function load({ url }) {
|
||||
return {
|
||||
props: {
|
||||
secret: url.searchParams.get("secret"),
|
||||
idsurvey: url.searchParams.get("survey"),
|
||||
exportview_list: url.searchParams.get("graph_list")?false:true,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { getSharedQuestions } from '$lib/questions';
|
||||
import { getSharedSurvey } from '$lib/surveys';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||
|
||||
export let secret;
|
||||
export let idsurvey;
|
||||
export let data;
|
||||
|
||||
let surveyP = getSharedSurvey(idsurvey, secret);
|
||||
export let exportview_list = true;
|
||||
let surveyP = null;
|
||||
$: surveyP = getSharedSurvey(data.idsurvey, data.secret);
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
@ -32,7 +19,7 @@
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
|
||||
{#await getSharedQuestions(survey.id, secret)}
|
||||
{#await getSharedQuestions(survey.id, data.secret)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
@ -40,8 +27,8 @@
|
||||
{:then questions}
|
||||
{#each questions as question (question.id)}
|
||||
<h3>{question.title}</h3>
|
||||
{#if question.kind == "text" || (exportview_list && question.kind.indexOf("list") == 0)}
|
||||
{#await question.getResponses(secret) then responses}
|
||||
{#if question.kind == "text" || (data.exportview_list && question.kind.indexOf("list") == 0)}
|
||||
{#await question.getResponses(data.secret) then responses}
|
||||
{#each responses as response (response.id)}
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
@ -53,7 +40,7 @@
|
||||
{/each}
|
||||
{/await}
|
||||
{:else}
|
||||
<CorrectionPieChart {question} {secret} />
|
||||
<CorrectionPieChart {question} secret={data.secret} />
|
||||
{/if}
|
||||
<hr class="mb-3">
|
||||
{/each}
|
||||
|
9
ui/src/routes/surveys/[sid]/+layout.js
Normal file
9
ui/src/routes/surveys/[sid]/+layout.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params }) {
|
||||
const survey = getSurvey(params.sid);
|
||||
|
||||
return {
|
||||
survey,
|
||||
};
|
||||
}
|
@ -1,27 +1,8 @@
|
||||
<script context="module">
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
const survey = getSurvey(params.sid);
|
||||
|
||||
return {
|
||||
props: {
|
||||
survey,
|
||||
},
|
||||
stuff: {
|
||||
...stuff,
|
||||
survey,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export let survey;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await survey}
|
||||
{#await data.survey}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement du questionnaire …</span>
|
||||
|
5
ui/src/routes/surveys/[sid]/+page.js
Normal file
5
ui/src/routes/surveys/[sid]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return stuff;
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -19,49 +7,46 @@
|
||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||
import { getQuestions } from '$lib/questions';
|
||||
|
||||
export let surveyP;
|
||||
export let data;
|
||||
let survey = null;
|
||||
|
||||
$: {
|
||||
if (surveyP) {
|
||||
surveyP.then((survey) => {
|
||||
survey = data.survey;
|
||||
if (survey.direct && !$user.is_admin) {
|
||||
goto(`surveys/${survey.id}/live`);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let edit = false;
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
||||
{#if survey.direct}
|
||||
<a href="surveys/{survey.id}/live" class="btn btn-danger ms-1 float-end" title="Aller au direct"><i class="bi bi-film"></i></a>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#await getQuestions(survey.id)}
|
||||
{#await getQuestions(survey.id)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
{:then questions}
|
||||
<SurveyQuestions {survey} {questions} />
|
||||
{:catch error}
|
||||
{:catch error}
|
||||
<div class="row mt-5">
|
||||
<div class="d-none d-sm-block col-sm">
|
||||
<hr>
|
||||
@ -76,5 +61,4 @@
|
||||
<strong><a href="surveys/{survey.id}/live">Cliquez ici pour accéder au direct</a>.</strong> Il s'agit d'un questionnaire en direct, le questionnaire n'est pas accessible sur cette page.
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/await}
|
||||
|
8
ui/src/routes/surveys/[sid]/admin/+page.js
Normal file
8
ui/src/routes/surveys/[sid]/admin/+page.js
Normal file
@ -0,0 +1,8 @@
|
||||
export async function load({ parent, params }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
sid: params.sid,
|
||||
};
|
||||
}
|
@ -1,14 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
sid: params.sid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { user } from '$lib/stores/user';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
@ -21,28 +10,24 @@
|
||||
import { getQuestion, getQuestions, Question } from '$lib/questions';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export let surveyP;
|
||||
export let sid;
|
||||
export let data;
|
||||
let survey;
|
||||
let req_questions;
|
||||
|
||||
surveyP.then((s) => {
|
||||
survey = s;
|
||||
$: {
|
||||
survey = data.survey;
|
||||
updateQuestions();
|
||||
if (survey.direct !== null) {
|
||||
wsconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateSurvey() {
|
||||
surveyP = getSurvey(survey.id);
|
||||
surveyP.then((s) => {
|
||||
survey = s;
|
||||
async function updateSurvey() {
|
||||
survey = await getSurvey(survey.id);
|
||||
updateQuestions();
|
||||
if (survey.direct !== null) {
|
||||
wsconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateQuestions() {
|
||||
@ -162,7 +147,7 @@
|
||||
function wsconnect() {
|
||||
if (ws !== null) return;
|
||||
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws-admin`);
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${data.sid}/ws-admin`);
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws_up = true;
|
||||
@ -233,8 +218,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
{#if $user && $user.is_admin}
|
||||
<StartStopLiveSurvey
|
||||
{survey}
|
||||
class="ms-1 float-end"
|
||||
@ -242,8 +226,8 @@
|
||||
on:end={() => { if (confirm("Sûr ?")) ws.send('{"action":"end"}') }}
|
||||
/>
|
||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
@ -251,7 +235,7 @@
|
||||
Administration
|
||||
</small>
|
||||
{#if asks.length}
|
||||
<a href="surveys/{sid}/admin#questions_part">
|
||||
<a href="surveys/{data.sid}/admin#questions_part">
|
||||
<i class="bi bi-patch-question-fill text-danger"></i>
|
||||
</a>
|
||||
{/if}
|
||||
@ -270,14 +254,14 @@
|
||||
{survey}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if survey.direct === null}
|
||||
{#if survey.direct === null}
|
||||
<SurveyAdmin
|
||||
{survey}
|
||||
on:saved={updateSurvey}
|
||||
/>
|
||||
{:else}
|
||||
{:else}
|
||||
{#await req_questions}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
@ -368,7 +352,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
{#if responses[question.id]}
|
||||
<a href="surveys/{sid}/admin#q{question.id}_res">
|
||||
<a href="surveys/{data.sid}/admin#q{question.id}_res">
|
||||
{question.title}
|
||||
</a>
|
||||
{:else}
|
||||
@ -699,6 +683,4 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{/await}
|
||||
{/if}
|
||||
|
8
ui/src/routes/surveys/[sid]/live/+page.js
Normal file
8
ui/src/routes/surveys/[sid]/live/+page.js
Normal file
@ -0,0 +1,8 @@
|
||||
export async function load({ params, parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
sid: params.sid,
|
||||
};
|
||||
}
|
@ -1,14 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
sid: params.sid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
@ -18,11 +7,9 @@
|
||||
import QuestionForm from '$lib/components/QuestionForm.svelte';
|
||||
import { getQuestion } from '$lib/questions';
|
||||
|
||||
export let surveyP;
|
||||
export let sid;
|
||||
export let data;
|
||||
let survey;
|
||||
|
||||
surveyP.then((s) => survey = s);
|
||||
$: survey = data.survey;
|
||||
|
||||
let ws_up = false;
|
||||
let show_question = null;
|
||||
@ -75,7 +62,7 @@
|
||||
}
|
||||
});
|
||||
function wsconnect() {
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws`);
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${data.sid}/ws`);
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws_up = true;
|
||||
@ -183,23 +170,22 @@
|
||||
let corrections = null;
|
||||
</script>
|
||||
|
||||
{#await surveyP then unused}
|
||||
<div
|
||||
<div
|
||||
style={"transition: opacity 150ms ease-out; opacity: " + (displaySendInProgress?1:0)}
|
||||
class="ms-2 float-end"
|
||||
>
|
||||
>
|
||||
<div style="position: relative; left: 25%; top: 4px">
|
||||
<div style="position: absolute">
|
||||
<i class="bi bi-save"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
</div>
|
||||
{#if $user && $user.is_admin}
|
||||
</div>
|
||||
{#if $user && $user.is_admin}
|
||||
<a href="surveys/{survey.id}/admin" class="btn btn-primary ms-1 float-end" title="Aller à l'interface d'administration"><i class="bi bi-pencil"></i></a>
|
||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center mb-3 mb-md-4 mb-lg-5">
|
||||
{/if}
|
||||
<div class="d-flex align-items-center mb-3 mb-md-4 mb-lg-5">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
@ -211,9 +197,9 @@
|
||||
>
|
||||
{#if ws_up}Connecté{:else}Déconnecté{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form on:submit|preventDefault={sendValue}>
|
||||
<form on:submit|preventDefault={sendValue}>
|
||||
{#if show_question}
|
||||
{#await req_question}
|
||||
<div class="text-center">
|
||||
@ -298,5 +284,4 @@
|
||||
La session est terminée. <small class="text-muted">On se retrouve une prochaine fois…</small>
|
||||
</h2>
|
||||
{/if}
|
||||
</form>
|
||||
{/await}
|
||||
</form>
|
||||
|
7
ui/src/routes/surveys/[sid]/responses/+page.js
Normal file
7
ui/src/routes/surveys/[sid]/responses/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
};
|
||||
}
|
@ -1,32 +1,20 @@
|
||||
<script context="module">
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { user } from '$lib/stores/user';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
import StartStopLiveSurvey from '$lib/components/StartStopLiveSurvey.svelte';
|
||||
import SurveyAdmin from '$lib/components/SurveyAdmin.svelte';
|
||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||
import { getQuestions } from '$lib/questions';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export let surveyP;
|
||||
let usersP = null;
|
||||
surveyP.then((s) => {
|
||||
usersP = getUsers(s.promo, s.group);
|
||||
})
|
||||
export let data;
|
||||
let survey;
|
||||
let usersP;
|
||||
$: survey = data.survey;
|
||||
$: usersP = getUsers(data.survey.promo, data.survey.group);
|
||||
let edit = false;
|
||||
let exportview = false;
|
||||
let exportview_list = false;
|
||||
@ -41,8 +29,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<StartStopLiveSurvey
|
||||
{survey}
|
||||
@ -70,26 +57,26 @@
|
||||
on:click={() => { exportview_list = !exportview_list; } }
|
||||
><i class="bi bi-view-list"></i></button>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/{survey.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">{#if exportview}Réponses{:else}Corrections{/if}</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#await getQuestions(survey.id)}
|
||||
{#await getQuestions(survey.id)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
{:then questions}
|
||||
{#if !exportview}
|
||||
<div class="card mt-3 mb-5">
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
@ -156,7 +143,6 @@
|
||||
<hr class="mb-3">
|
||||
{/each}
|
||||
{/if}
|
||||
{/await}
|
||||
{/await}
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="shareModal">
|
||||
|
8
ui/src/routes/surveys/[sid]/responses/[rid]/+page.js
Normal file
8
ui/src/routes/surveys/[sid]/responses/[rid]/+page.js
Normal file
@ -0,0 +1,8 @@
|
||||
export async function load({ params, parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
rid: params.rid,
|
||||
};
|
||||
}
|
@ -1,16 +1,3 @@
|
||||
<script context="module">
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
rid: params.rid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Correction from '$lib/components/Correction.svelte';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
@ -20,8 +7,7 @@
|
||||
import { getCorrectionTemplates } from '$lib/correctionTemplates';
|
||||
import { getQuestion } from '$lib/questions';
|
||||
|
||||
export let surveyP;
|
||||
export let rid;
|
||||
export let data;
|
||||
|
||||
let showChart = false;
|
||||
let showResponses = false;
|
||||
@ -31,10 +17,11 @@
|
||||
|
||||
let child;
|
||||
let waitApply = false;
|
||||
let ctpls = getCorrectionTemplates(rid);
|
||||
let filter = "";
|
||||
|
||||
let ctpls;
|
||||
let cts = { };
|
||||
$: {
|
||||
ctpls = getCorrectionTemplates(data.rid);
|
||||
if (ctpls) {
|
||||
ctpls.then((ctpls) => {
|
||||
for (const tpl of ctpls) {
|
||||
cts[tpl.id] = { };
|
||||
@ -46,27 +33,31 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
cts = cts;
|
||||
});
|
||||
}
|
||||
}
|
||||
let filter = "";
|
||||
|
||||
let nodescription = false;
|
||||
let y = 0;
|
||||
$: nodescription = y > 10;
|
||||
|
||||
let survey = null;
|
||||
$: survey = data.survey;
|
||||
</script>
|
||||
|
||||
<svelte:window bind:scrollY={y} />
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#await getQuestion(rid)}
|
||||
{#await getQuestion(data.rid)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement de la question…</span>
|
||||
</div>
|
||||
{:then question}
|
||||
{:then question}
|
||||
{#await ctpls}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement de la question…</span>
|
||||
<span>Chargement des templates…</span>
|
||||
</div>
|
||||
{:then correctionTemplates}
|
||||
<div class="float-end">
|
||||
@ -177,7 +168,6 @@
|
||||
on:nb_responses={(v) => { nb_responses = v.detail; } }
|
||||
/>
|
||||
{/await}
|
||||
{/await}
|
||||
{/await}
|
||||
|
||||
<div class="mb-5" style="min-height: 60vh"></div>
|
||||
|
5
ui/src/routes/users/[uid]/+page.js
Normal file
5
ui/src/routes/users/[uid]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
uid: params.uid,
|
||||
};
|
||||
}
|
@ -1,13 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
uid: params.uid,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import UserKeys from '$lib/components/UserKeys.svelte';
|
||||
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
||||
@ -15,13 +5,13 @@
|
||||
import { getSurveys } from '$lib/surveys';
|
||||
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
||||
|
||||
export let uid;
|
||||
export let data;
|
||||
|
||||
let allPromos = false;
|
||||
|
||||
let myuser = null;
|
||||
let userP = null;
|
||||
$: userP = getUser(uid).then((u) => myuser = u)
|
||||
$: userP = getUser(data.uid).then((u) => myuser = u)
|
||||
|
||||
function impersonate() {
|
||||
fetch('api/auth/impersonate', {
|
||||
|
5
ui/src/routes/users/[uid]/surveys/+page.js
Normal file
5
ui/src/routes/users/[uid]/surveys/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
uid: params.uid,
|
||||
};
|
||||
}
|
@ -1,25 +1,15 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
uid: params.uid,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { getSurveys } from '$lib/surveys';
|
||||
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
||||
|
||||
export let uid;
|
||||
export let data;
|
||||
|
||||
let allPromos = false;
|
||||
</script>
|
||||
|
||||
{#await getUser(uid)}
|
||||
{#await getUser(data.uid)}
|
||||
<h2>
|
||||
Étudiant
|
||||
</h2>
|
||||
|
6
ui/src/routes/users/[uid]/surveys/[sid]/+page.js
Normal file
6
ui/src/routes/users/[uid]/surveys/[sid]/+page.js
Normal file
@ -0,0 +1,6 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
sid: params.sid,
|
||||
uid: params.uid,
|
||||
};
|
||||
}
|
@ -1,14 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
sid: params.sid,
|
||||
uid: params.uid,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||
@ -16,11 +5,10 @@
|
||||
import { getQuestions } from '$lib/questions';
|
||||
import { getUser } from '$lib/users';
|
||||
|
||||
export let sid;
|
||||
export let uid;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await getUser(uid)}
|
||||
{#await getUser(data.uid)}
|
||||
<h2>
|
||||
Étudiant
|
||||
</h2>
|
||||
@ -30,7 +18,7 @@
|
||||
Chargement des détails…
|
||||
</div>
|
||||
{:then student}
|
||||
{#await getSurvey(sid)}
|
||||
{#await getSurvey(data.sid)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement du questionnaire …</span>
|
||||
@ -50,7 +38,7 @@
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
<SurveyQuestions {survey} {questions} id_user={uid} />
|
||||
<SurveyQuestions {survey} {questions} id_user={data.uid} />
|
||||
{/await}
|
||||
{:catch error}
|
||||
<div class="text-center">
|
||||
|
9
ui/src/routes/works/[wid]/+layout.js
Normal file
9
ui/src/routes/works/[wid]/+layout.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params }) {
|
||||
const work = getWork(params.wid);
|
||||
|
||||
return {
|
||||
work,
|
||||
};
|
||||
}
|
@ -1,27 +1,8 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
const work = getWork(params.wid);
|
||||
|
||||
return {
|
||||
props: {
|
||||
work,
|
||||
},
|
||||
stuff: {
|
||||
...stuff,
|
||||
work,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export let work;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await work}
|
||||
{#await data.work}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement du rendu …</span>
|
||||
|
7
ui/src/routes/works/[wid]/+page.js
Normal file
7
ui/src/routes/works/[wid]/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
work: stuff.work,
|
||||
};
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
work: stuff.work,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -21,34 +9,33 @@
|
||||
import WorkRepository from '$lib/components/WorkRepository.svelte';
|
||||
import { getScore } from '$lib/users';
|
||||
|
||||
export let work = null;
|
||||
export let data;
|
||||
let edit = false;
|
||||
let my_submission = null;
|
||||
let warn_already_used = false;
|
||||
let w = null;
|
||||
|
||||
work.then((w) => {
|
||||
refresh_submission(w);
|
||||
})
|
||||
$: w = data.work;
|
||||
$: refresh_submission(data.work);
|
||||
|
||||
function refresh_submission(w) {
|
||||
my_submission = w.getSubmission();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await work then w}
|
||||
{#if $user && $user.is_admin}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<a class="btn btn-success ms-1 float-end" href="works/{w.id}/rendus" title="Voir les rendus"><i class="bi bi-files"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="works/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{w.title}
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" survey={w} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<WorkAdmin work={w} on:saved={() => edit = false} />
|
||||
|
||||
{#if w.description}
|
||||
@ -95,12 +82,12 @@
|
||||
</table>
|
||||
{/await}
|
||||
</div>
|
||||
{:else if (!$user || !$user.is_admin) && new Date(w.start_availability) > new Date()}
|
||||
{:else if (!$user || !$user.is_admin) && new Date(w.start_availability) > new Date()}
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-stopwatch-fill"></i>
|
||||
<strong>Ce travail n'est pas encore ouvert.</strong> Revenez plus tard !
|
||||
</div>
|
||||
{:else}
|
||||
{:else}
|
||||
<dl style="columns: 3">
|
||||
<dt>Date de début</dt>
|
||||
<dd><DateFormat date={new Date(w.start_availability)} dateStyle="medium" timeStyle="medium" /></dd>
|
||||
@ -226,7 +213,7 @@
|
||||
42sh$ git checkout renduY
|
||||
-- ou --
|
||||
42sh$ git checkout master
|
||||
</pre>
|
||||
</pre>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="alert alert-warning">
|
||||
@ -237,6 +224,5 @@
|
||||
Ce travail n'a pas de modalité de rendu.
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
<div class="mb-5"></div>
|
||||
|
7
ui/src/routes/works/[wid]/rendus/+page.js
Normal file
7
ui/src/routes/works/[wid]/rendus/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
work: stuff.work,
|
||||
};
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
work: stuff.work,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -23,11 +11,13 @@
|
||||
import { ToastsStore } from '$lib/stores/toasts';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export let work = null;
|
||||
export let data;
|
||||
let usersP = null;
|
||||
let show_dl_btn = { };
|
||||
let repositoriesP = { };
|
||||
work.then((w) => {
|
||||
let w = null;
|
||||
$: {
|
||||
w = data.work;
|
||||
usersP = getUsers(w.promo, w.group);
|
||||
usersP.then((users) => {
|
||||
nb_users = users.length;
|
||||
@ -36,7 +26,7 @@
|
||||
updateRepoUser(w.id, user.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateRepoUser(wid, userid) {
|
||||
repositoriesP[userid] = getRepositories(wid, userid);
|
||||
@ -50,17 +40,16 @@
|
||||
let search_repo_for = {repo: null, user: null};
|
||||
</script>
|
||||
|
||||
{#await work then w}
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="works/{w.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||
{w.title}
|
||||
<small class="text-muted">Rendus {Math.trunc(nb_rendus/nb_users*100)} % ({nb_rendus}/{nb_users})</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" survey={w} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#await usersP then users}
|
||||
{#await usersP then users}
|
||||
<table class="w-100 mb-5">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -143,9 +132,9 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
{/await}
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="pullModal">
|
||||
<div class="modal fade" tabindex="-1" id="pullModal">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" on:submit|preventDefault={() => {run_pull_for.modal.hide(); try { run_pull_for.repo.retrieveWork(run_pull_for.struct); updateRepoUser(w.id, run_pull_for.user.id); } catch(err) { ToastsStore.addToast({color: "danger", title: "Connexion impossible", msg: err}) };}}>
|
||||
<div class="modal-header">
|
||||
@ -171,9 +160,9 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="repoModal">
|
||||
<div class="modal fade" tabindex="-1" id="repoModal">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" on:submit|preventDefault={() => {search_repo_for.modal.hide(); try { search_repo_for.repo.retrieveWork(run_pull_for.struct); updateRepoUser(w.id, run_pull_for.user.id); } catch(err) { ToastsStore.addToast({color: "danger", title: "Connexion impossible", msg: err}) }; search_repo_for.user = null; }}>
|
||||
<div class="modal-header">
|
||||
@ -187,8 +176,7 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="logsModal">
|
||||
<div class="modal-dialog modal-xl">
|
||||
|
Reference in New Issue
Block a user