🚧 Maybe fix for overlapping text in old-school mode
This commit is contained in:
@@ -210,7 +210,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
d="m4.5 15.75 7.5-7.5 7.5 7.5"
|
d="m4.5 15.75 7.5-7.5 7.5 7.5"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
></path>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -240,7 +240,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
></path>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
{:else if question.type === QuizQuestionType.TEXT}
|
{:else if question.type === QuizQuestionType.TEXT}
|
||||||
|
<div>
|
||||||
<span
|
<span
|
||||||
class="fixed top-0 bg-red-500 h-8 transition-all"
|
class="fixed top-0 bg-red-500 h-8 transition-all"
|
||||||
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
|
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
|
||||||
@@ -263,6 +264,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
</BrownButton>
|
</BrownButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{:else if question.type === QuizQuestionType.RANGE}
|
{:else if question.type === QuizQuestionType.RANGE}
|
||||||
{#if solution === undefined}
|
{#if solution === undefined}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
if (!browser || user_data?.require_password === undefined) {
|
if (!browser || user_data?.require_password === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pw = require_password()
|
const pw = require_password();
|
||||||
const res = await fetch('/api/v1/users/2fa/require_password', {
|
const res = await fetch('/api/v1/users/2fa/require_password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -48,13 +48,20 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
};
|
};
|
||||||
|
|
||||||
const require_password = (): string => {
|
const require_password = (): string => {
|
||||||
return prompt("Please enter your password to continue")
|
return prompt('Please enter your password to continue');
|
||||||
}
|
};
|
||||||
|
|
||||||
const add_security_key = async () => {
|
const add_security_key = async () => {
|
||||||
const pw = require_password()
|
const pw = require_password();
|
||||||
const res1 = await fetch('/api/v1/users/webauthn/add_key_init', {method: "POST", body: JSON.stringify({password: pw}), headers: { 'Content-Type': 'application/json' }});
|
const res1 = await fetch('/api/v1/users/webauthn/add_key_init', {
|
||||||
if (res1.status === 401) {alert("Password probably wrong"); return}
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ password: pw }),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
if (res1.status === 401) {
|
||||||
|
alert('Password probably wrong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!res1.ok) {
|
if (!res1.ok) {
|
||||||
throw Error('Response not ok');
|
throw Error('Response not ok');
|
||||||
}
|
}
|
||||||
@@ -81,34 +88,62 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
};
|
};
|
||||||
|
|
||||||
const remove_security_key = async (key_id: number) => {
|
const remove_security_key = async (key_id: number) => {
|
||||||
const pw = require_password()
|
const pw = require_password();
|
||||||
const res = await fetch(`/api/v1/users/webauthn/key/${key_id}`, { method: 'DELETE', body: JSON.stringify({password: pw}), headers: { 'Content-Type': 'application/json' } });
|
const res = await fetch(`/api/v1/users/webauthn/key/${key_id}`, {
|
||||||
if (res.status === 401) {alert("Password probably wrong"); return}
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify({ password: pw }),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
alert('Password probably wrong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
data = get_data();
|
data = get_data();
|
||||||
};
|
};
|
||||||
|
|
||||||
const disable_totp = async () => {
|
const disable_totp = async () => {
|
||||||
const pw = require_password()
|
const pw = require_password();
|
||||||
const res = await fetch(`/api/v1/users/2fa/totp`, { method: 'DELETE', body: JSON.stringify({password: pw}), headers: { 'Content-Type': 'application/json' } });
|
const res = await fetch(`/api/v1/users/2fa/totp`, {
|
||||||
if (res.status === 401) {alert("Password probably wrong"); return}
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify({ password: pw }),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
alert('Password probably wrong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
data = get_data();
|
data = get_data();
|
||||||
};
|
};
|
||||||
|
|
||||||
const enable_totp = async () => {
|
const enable_totp = async () => {
|
||||||
const pw = require_password()
|
const pw = require_password();
|
||||||
const res = await fetch('/api/v1/users/2fa/totp', { method: 'POST', body: JSON.stringify({password: pw}), headers: { 'Content-Type': 'application/json' } });
|
const res = await fetch('/api/v1/users/2fa/totp', {
|
||||||
if (res.status === 401) {alert("Password probably wrong"); return}
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ password: pw }),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
alert('Password probably wrong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
data = get_data();
|
data = get_data();
|
||||||
totp_data = await res.json();
|
totp_data = await res.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
const get_backup_code = async () => {
|
const get_backup_code = async () => {
|
||||||
const pw = require_password()
|
const pw = require_password();
|
||||||
if (!confirm('If you continue, your old backup-code will be removed.')) {
|
if (!confirm('If you continue, your old backup-code will be removed.')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await fetch('/api/v1/users/2fa/backup_code', { method: 'POST', body: JSON.stringify({password: pw}), headers: { 'Content-Type': 'application/json' } });
|
const res = await fetch('/api/v1/users/2fa/backup_code', {
|
||||||
if (res.status === 401) {alert("Password probably wrong"); return}
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ password: pw }),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
alert('Password probably wrong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
backup_code = (await res.json()).code;
|
backup_code = (await res.json()).code;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -130,7 +165,12 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
</div>
|
</div>
|
||||||
<div class="h-full w-full">
|
<div class="h-full w-full">
|
||||||
<h2 class="text-center text-2xl">{$t('security_settings.activate_2fa')}</h2>
|
<h2 class="text-center text-2xl">{$t('security_settings.activate_2fa')}</h2>
|
||||||
<div class="flex h-full w-full justify-center flex-col" class:pointer-events-none={!totp_activated} class:grayscale={!totp_activated} class:opacity-50={!totp_activated}>
|
<div
|
||||||
|
class="flex h-full w-full justify-center flex-col"
|
||||||
|
class:pointer-events-none={!totp_activated}
|
||||||
|
class:grayscale={!totp_activated}
|
||||||
|
class:opacity-50={!totp_activated}
|
||||||
|
>
|
||||||
<div class="m-auto">
|
<div class="m-auto">
|
||||||
{#if user_data.require_password}
|
{#if user_data.require_password}
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
|
|||||||
|
|
||||||
SPDX-License-Identifier: MPL-2.0
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
<script lang='ts'>
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import '$lib/hljs.css';
|
import '$lib/hljs.css';
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>ClassQuiz/docs - Self-Host</title>
|
<title>ClassQuiz/docs - Self-Host</title>
|
||||||
<meta
|
<meta
|
||||||
name='description'
|
name="description"
|
||||||
content='How to self-host ClassQuiz, the open-source quiz-application'
|
content="How to self-host ClassQuiz, the open-source quiz-application"
|
||||||
/>
|
/>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<article
|
<article
|
||||||
class='prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-slate px-4 dark:prose-invert'
|
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-slate px-4 dark:prose-invert"
|
||||||
>
|
>
|
||||||
<h1>Self-Hosting</h1>
|
<h1>Self-Hosting</h1>
|
||||||
<p>Since ClassQuiz is open-source, it can also be self-hosted.</p>
|
<p>Since ClassQuiz is open-source, it can also be self-hosted.</p>
|
||||||
@@ -36,34 +36,36 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
<h3>Software</h3>
|
<h3>Software</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href='https://docker.com' target='_blank'>Docker</a> (<a
|
<a href="https://docker.com" target="_blank">Docker</a> (<a
|
||||||
href='https://docs.docker.com/compose/install/linux/'
|
href="https://docs.docker.com/compose/install/linux/"
|
||||||
target='_blank'>Compose</a
|
target="_blank">Compose</a
|
||||||
>)
|
>)
|
||||||
</li>
|
</li>
|
||||||
<li><a href='https://git-scm.com/' target='_blank'>Git</a></li>
|
<li><a href="https://git-scm.com/" target="_blank">Git</a></li>
|
||||||
<li>
|
<li>
|
||||||
A <a href='https://redis.com' target='_blank'>Redis</a>-Server
|
A <a href="https://redis.com" target="_blank">Redis</a>-Server
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>3rd-Parties</h3>
|
<h3>3rd-Parties</h3>
|
||||||
<h4>Optional</h4>
|
<h4>Optional</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href='https://sentry.io'>Sentry (Error-Logging)</a></li>
|
<li><a href="https://sentry.io">Sentry (Error-Logging)</a></li>
|
||||||
<li>
|
<li>
|
||||||
<a href='https://console.cloud.google.com/apis/dashboard'
|
<a href="https://console.cloud.google.com/apis/dashboard"
|
||||||
>Google-Credentials (Sign-In)</a
|
>Google-Credentials (Sign-In)</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li><a href='https://github.com/settings/developers'>GitHub-Credentials (Sign-In)</a></li>
|
<li><a href="https://github.com/settings/developers">GitHub-Credentials (Sign-In)</a></li>
|
||||||
<li><a href='https://hcaptcha.com'>hCaptcha</a> <b>OR</b> <a href='https://www.google.com/recaptcha/about/'>ReCaptcha</a></li>
|
<li>
|
||||||
|
<a href="https://hcaptcha.com">hCaptcha</a> <b>OR</b>
|
||||||
|
<a href="https://www.google.com/recaptcha/about/">ReCaptcha</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>Installation</h2>
|
<h2>Installation</h2>
|
||||||
<p>At first, clone the repo:</p>
|
<p>At first, clone the repo:</p>
|
||||||
|
|
||||||
<pre><code class='language-bash'
|
<pre><code class="language-bash"
|
||||||
>git clone https://github.com/mawoka-myblock/classquiz && cd ClassQuiz</code
|
>git clone https://github.com/mawoka-myblock/classquiz && cd ClassQuiz</code
|
||||||
></pre>
|
></pre>
|
||||||
<p>
|
<p>
|
||||||
@@ -93,7 +95,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
<h3>Storage Provider</h3>
|
<h3>Storage Provider</h3>
|
||||||
<p>
|
<p>
|
||||||
You'll have to set up a storage provider for some pictures (these getting imported from
|
You'll have to set up a storage provider for some pictures (these getting imported from
|
||||||
Kahoot!). For now, you can use <a href='https://min.io/' target='_blank'>Minio (S3)</a> or
|
Kahoot!). For now, you can use <a href="https://min.io/" target="_blank">Minio (S3)</a> or
|
||||||
the local filesystem. Please not that I'd recommend Minio for larger instances, since it can
|
the local filesystem. Please not that I'd recommend Minio for larger instances, since it can
|
||||||
be scaled and the media doesn't have to streamed through the (comparatively) slow ClassQuiz
|
be scaled and the media doesn't have to streamed through the (comparatively) slow ClassQuiz
|
||||||
server. Now, that you've decided on a storage backend, you can set the
|
server. Now, that you've decided on a storage backend, you can set the
|
||||||
@@ -124,7 +126,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
</p>
|
</p>
|
||||||
<h4>Google</h4>
|
<h4>Google</h4>
|
||||||
<p>
|
<p>
|
||||||
First, go to <a href='https://console.cloud.google.com/apis/dashboard'
|
First, go to <a href="https://console.cloud.google.com/apis/dashboard"
|
||||||
>console.cloud.google.com/apis/dashboard</a
|
>console.cloud.google.com/apis/dashboard</a
|
||||||
> and create a new project and select it. Then, go to the "OAuth consent screen" and set it up.
|
> and create a new project and select it. Then, go to the "OAuth consent screen" and set it up.
|
||||||
Next, go to the "Credentials"-tab and click on "Create Credentials" and create a new "OAuth Client
|
Next, go to the "Credentials"-tab and click on "Create Credentials" and create a new "OAuth Client
|
||||||
@@ -137,7 +139,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
|
|
||||||
<h4>GitHub</h4>
|
<h4>GitHub</h4>
|
||||||
<p>
|
<p>
|
||||||
First, go to <a href='https://github.com/settings/developers'
|
First, go to <a href="https://github.com/settings/developers"
|
||||||
>github.com/settings/developers</a
|
>github.com/settings/developers</a
|
||||||
> and create a "new OAuth App". The "Authorization callback URL" has the following schema:
|
> and create a "new OAuth App". The "Authorization callback URL" has the following schema:
|
||||||
</p>
|
</p>
|
||||||
@@ -147,19 +149,21 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
later, together with your client-id.
|
later, together with your client-id.
|
||||||
</p>
|
</p>
|
||||||
<h4>Custom OpenID auth</h4>
|
<h4>Custom OpenID auth</h4>
|
||||||
<p>A login using a custom OpenID provider is also possible. For that, adjust the settings in the docker-compose.yml
|
<p>
|
||||||
and configure the following scopes: "openid email profile". The follwoing redirect-url should be used:</p>
|
A login using a custom OpenID provider is also possible. For that, adjust the settings in
|
||||||
|
the docker-compose.yml and configure the following scopes: "openid email profile". The
|
||||||
|
follwoing redirect-url should be used:
|
||||||
|
</p>
|
||||||
<pre><code>https://[BASE_URL]/api/v1/users/oauth/custom/auth</code></pre>
|
<pre><code>https://[BASE_URL]/api/v1/users/oauth/custom/auth</code></pre>
|
||||||
You'll also need to tell the frontend by editing the <code>frontend/Dockerfile</code>. Add the following line at the
|
You'll also need to tell the frontend by editing the<code>frontend/Dockerfile</code>. Add the
|
||||||
top, where all the other ENV's are as well:
|
following line at the top, where all the other ENV's are as well:
|
||||||
<pre><code>ENV VITE_CUSTOM_OAUTH_NAME=[SOME_DISPLAY_NAME_FOR_THE_PROVIDER]</code></pre>
|
<pre><code>ENV VITE_CUSTOM_OAUTH_NAME=[SOME_DISPLAY_NAME_FOR_THE_PROVIDER]</code></pre>
|
||||||
|
|
||||||
|
|
||||||
<h3>Docker-Compose File</h3>
|
<h3>Docker-Compose File</h3>
|
||||||
Please go through the <code>docker-compose.yml</code> thoroughly and fill out all the details, which should be
|
Please go through the<code>docker-compose.yml</code> thoroughly and fill out all the details,
|
||||||
self-explanatory.
|
which should be self-explanatory.
|
||||||
<p>Run the following command to generate and set the secret up automatically</p>
|
<p>Run the following command to generate and set the secret up automatically</p>
|
||||||
<pre><code class='language-bash'
|
<pre><code class="language-bash"
|
||||||
>sed -i "s/TOP_SECRET/$(openssl rand -hex 32)/g" docker-compose.yml</code
|
>sed -i "s/TOP_SECRET/$(openssl rand -hex 32)/g" docker-compose.yml</code
|
||||||
></pre>
|
></pre>
|
||||||
<p>Now build and deploy:</p>
|
<p>Now build and deploy:</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user