fix(web): login error handling (#1322)
This commit is contained in:
parent
ba04b753de
commit
5fb3ea465f
7 changed files with 154 additions and 35 deletions
6
web/src/app.d.ts
vendored
6
web/src/app.d.ts
vendored
|
@ -8,6 +8,12 @@ declare namespace App {
|
|||
}
|
||||
|
||||
// interface Platform {}
|
||||
|
||||
interface Error {
|
||||
message: string;
|
||||
stack?: string;
|
||||
code?: string;
|
||||
}
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/questions/63814432/typescript-typing-of-non-standard-window-event-in-svelte
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
import type { Handle } from '@sveltejs/kit';
|
||||
import type { Handle, HandleServerError } from '@sveltejs/kit';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
return await resolve(event);
|
||||
};
|
||||
|
||||
export const handleError: HandleServerError = async ({ error }) => {
|
||||
const httpError = error as AxiosError;
|
||||
return {
|
||||
message: httpError?.message || 'Hmm, not sure about that. Check the logs or open a ticket?',
|
||||
stack: httpError?.stack,
|
||||
code: httpError.code || '500'
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||
import { loginPageMessage } from '$lib/constants';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { api, oauth, OAuthConfigResponseDto } from '@api';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
|
@ -9,7 +10,7 @@
|
|||
let email = '';
|
||||
let password = '';
|
||||
let oauthError: string;
|
||||
let oauthConfig: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: false };
|
||||
let authConfig: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: false };
|
||||
let loading = true;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
@ -30,17 +31,18 @@
|
|||
|
||||
try {
|
||||
const { data } = await oauth.getConfig(window.location);
|
||||
oauthConfig = data;
|
||||
authConfig = data;
|
||||
|
||||
const { enabled, url, autoLaunch } = oauthConfig;
|
||||
const { enabled, url, autoLaunch } = authConfig;
|
||||
|
||||
if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) {
|
||||
await goto('/auth/login?autoLaunch=0', { replaceState: true });
|
||||
await goto(url);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error [login-form] [oauth.generateConfig]', e);
|
||||
} catch (error) {
|
||||
authConfig.passwordLoginEnabled = true;
|
||||
handleError(error, 'Unable to connect!');
|
||||
}
|
||||
|
||||
loading = false;
|
||||
|
@ -92,7 +94,7 @@
|
|||
<LoadingSpinner />
|
||||
</div>
|
||||
{:else}
|
||||
{#if oauthConfig.passwordLoginEnabled}
|
||||
{#if authConfig.passwordLoginEnabled}
|
||||
<form on:submit|preventDefault={login} autocomplete="off">
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="email">Email</label>
|
||||
|
@ -133,26 +135,26 @@
|
|||
</form>
|
||||
{/if}
|
||||
|
||||
{#if oauthConfig.enabled}
|
||||
{#if authConfig.enabled}
|
||||
<div class="flex flex-col gap-4 px-4">
|
||||
{#if oauthConfig.passwordLoginEnabled}
|
||||
{#if authConfig.passwordLoginEnabled}
|
||||
<hr />
|
||||
{/if}
|
||||
{#if oauthError}
|
||||
<p class="text-red-400">{oauthError}</p>
|
||||
{/if}
|
||||
<a href={oauthConfig.url} class="flex w-full">
|
||||
<a href={authConfig.url} class="flex w-full">
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading}
|
||||
class="bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray dark:hover:bg-immich-dark-primary/80 hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full font-semibold"
|
||||
>{oauthConfig.buttonText || 'Login with OAuth'}</button
|
||||
>{authConfig.buttonText || 'Login with OAuth'}</button
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !oauthConfig.enabled && !oauthConfig.passwordLoginEnabled}
|
||||
{#if !authConfig.enabled && !authConfig.passwordLoginEnabled}
|
||||
<p class="text-center dark:text-immich-dark-fg p-4">Login has been disabled.</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -90,5 +90,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-sm pl-[28px] pr-[16px]" data-testid="message">{@html notificationInfo.message}</p>
|
||||
<p class="whitespace-pre text-sm pl-[28px] pr-[16px]" data-testid="message">
|
||||
{@html notificationInfo.message}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -6,8 +6,14 @@ import {
|
|||
|
||||
export function handleError(error: unknown, message: string) {
|
||||
console.error(`[handleError]: ${message}`, error);
|
||||
|
||||
let serverMessage = (error as ApiError)?.response?.data?.message;
|
||||
if (serverMessage) {
|
||||
serverMessage = `${String(serverMessage).slice(0, 50)}\n<i>(Immich Server Error)<i>`;
|
||||
}
|
||||
|
||||
notificationController.show({
|
||||
message: (error as ApiError)?.response?.data?.message || message,
|
||||
message: serverMessage || message,
|
||||
type: NotificationType.Error
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,29 +1,122 @@
|
|||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import Message from 'svelte-material-icons/Message.svelte';
|
||||
import PartyPopper from 'svelte-material-icons/PartyPopper.svelte';
|
||||
import CodeTags from 'svelte-material-icons/CodeTags.svelte';
|
||||
import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
|
||||
const handleCopy = async () => {
|
||||
//
|
||||
const error = $page.error || null;
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(`${error.message} - ${error.code}\n${error.stack}`);
|
||||
notificationController.show({
|
||||
type: NotificationType.Info,
|
||||
message: 'Copied error to clipboard'
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to copy to clipboard');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="h-screen w-screen flex place-items-center place-content-center flex-col">
|
||||
<div class="min-w-[500px] max-w-[95vw] bg-gray-300 rounded-2xl my-4 p-4">
|
||||
<code class="text-xs text-red-500">Error code {$page.status}</code>
|
||||
<br />
|
||||
<code class="text-sm">
|
||||
{$page.error?.message}
|
||||
</code>
|
||||
<br />
|
||||
<div class="mt-5">
|
||||
<p class="text-sm font-medium">Verbose</p>
|
||||
<pre class="text-xs">{JSON.stringify($page.error)}</pre>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://github.com/immich-app/immich/issues/new/choose"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<button
|
||||
class="px-5 py-2 rounded-lg text-sm mt-6 bg-immich-primary text-white hover:bg-immich-primary/75"
|
||||
>Get help</button
|
||||
>
|
||||
<div class="h-screen w-screen">
|
||||
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
||||
<div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-4">
|
||||
<a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
|
||||
<img src="/immich-logo.svg" alt="immich logo" height="35" width="35" />
|
||||
<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
|
||||
IMMICH
|
||||
</h1>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div
|
||||
class="fixed top-0 w-full h-full bg-black/50 flex place-items-center place-content-center overflow-hidden"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray shadow-sm w-[500px] max-w-[95vw] rounded-3xl dark:text-immich-dark-fg"
|
||||
>
|
||||
<div>
|
||||
<div class="flex items-center justify-between gap-4 px-4 py-4">
|
||||
<h1 class="text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||
🚨 Error - Something went wrong
|
||||
</h1>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
on:click={() => handleCopy()}
|
||||
class="transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-3 py-2 text-white rounded-full shadow-md text-sm"
|
||||
>
|
||||
<ContentCopy size={24} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="p-4 max-h-[75vh] min-h-[300px] overflow-y-auto immich-scrollbar pb-4 gap-4">
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<p class="text-red-500">{$page.error?.message} - {$page.error?.code}</p>
|
||||
{#if $page.error?.stack}
|
||||
<label for="stacktrace">Stacktrace</label>
|
||||
<pre id="stacktrace" class="text-xs">{$page.error?.stack || 'No stack'}</pre>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="flex justify-around place-items-center place-content-center">
|
||||
<!-- href="https://github.com/immich-app/immich/issues/new" -->
|
||||
<a
|
||||
href="https://discord.com/invite/D8JsnBEuKb"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex justify-center grow basis-0 p-4"
|
||||
>
|
||||
<button class="flex flex-col gap-2 place-items-center place-content-center">
|
||||
<Message size={24} />
|
||||
<p class="text-sm">Get Help</p>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/immich-app/immich/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex justify-center grow basis-0 p-4"
|
||||
>
|
||||
<button class="flex flex-col gap-2 place-items-center place-content-center">
|
||||
<PartyPopper size={24} />
|
||||
<p class="text-sm">Read Changelog</p>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://immich.app/docs/guides/docker-help"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex justify-center grow basis-0 p-4"
|
||||
>
|
||||
<button class="flex flex-col gap-2 place-items-center place-content-center">
|
||||
<CodeTags size={24} />
|
||||
<p class="text-sm">Check Logs</p>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$page.data.meta?.title} - Immich</title>
|
||||
<title>{$page.data.meta?.title || 'Web'} - Immich</title>
|
||||
{#if $page.data.meta}
|
||||
<meta name="description" content={$page.data.meta.description} />
|
||||
|
||||
|
|
Loading…
Reference in a new issue