mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 04:05:39 -04:00
fix(web): login error handling (#1322)
This commit is contained in:
parent
ba04b753de
commit
5fb3ea465f
6
web/src/app.d.ts
vendored
6
web/src/app.d.ts
vendored
@ -8,6 +8,12 @@ declare namespace App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// interface Platform {}
|
// 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
|
// 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 }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
return await resolve(event);
|
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 { goto } from '$app/navigation';
|
||||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
import { loginPageMessage } from '$lib/constants';
|
import { loginPageMessage } from '$lib/constants';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { api, oauth, OAuthConfigResponseDto } from '@api';
|
import { api, oauth, OAuthConfigResponseDto } from '@api';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
|
||||||
@ -9,7 +10,7 @@
|
|||||||
let email = '';
|
let email = '';
|
||||||
let password = '';
|
let password = '';
|
||||||
let oauthError: string;
|
let oauthError: string;
|
||||||
let oauthConfig: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: false };
|
let authConfig: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: false };
|
||||||
let loading = true;
|
let loading = true;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@ -30,17 +31,18 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await oauth.getConfig(window.location);
|
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)) {
|
if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) {
|
||||||
await goto('/auth/login?autoLaunch=0', { replaceState: true });
|
await goto('/auth/login?autoLaunch=0', { replaceState: true });
|
||||||
await goto(url);
|
await goto(url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error('Error [login-form] [oauth.generateConfig]', e);
|
authConfig.passwordLoginEnabled = true;
|
||||||
|
handleError(error, 'Unable to connect!');
|
||||||
}
|
}
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
@ -92,7 +94,7 @@
|
|||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#if oauthConfig.passwordLoginEnabled}
|
{#if authConfig.passwordLoginEnabled}
|
||||||
<form on:submit|preventDefault={login} autocomplete="off">
|
<form on:submit|preventDefault={login} autocomplete="off">
|
||||||
<div class="m-4 flex flex-col gap-2">
|
<div class="m-4 flex flex-col gap-2">
|
||||||
<label class="immich-form-label" for="email">Email</label>
|
<label class="immich-form-label" for="email">Email</label>
|
||||||
@ -133,26 +135,26 @@
|
|||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if oauthConfig.enabled}
|
{#if authConfig.enabled}
|
||||||
<div class="flex flex-col gap-4 px-4">
|
<div class="flex flex-col gap-4 px-4">
|
||||||
{#if oauthConfig.passwordLoginEnabled}
|
{#if authConfig.passwordLoginEnabled}
|
||||||
<hr />
|
<hr />
|
||||||
{/if}
|
{/if}
|
||||||
{#if oauthError}
|
{#if oauthError}
|
||||||
<p class="text-red-400">{oauthError}</p>
|
<p class="text-red-400">{oauthError}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<a href={oauthConfig.url} class="flex w-full">
|
<a href={authConfig.url} class="flex w-full">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={loading}
|
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"
|
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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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>
|
<p class="text-center dark:text-immich-dark-fg p-4">Login has been disabled.</p>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -90,5 +90,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
@ -6,8 +6,14 @@ import {
|
|||||||
|
|
||||||
export function handleError(error: unknown, message: string) {
|
export function handleError(error: unknown, message: string) {
|
||||||
console.error(`[handleError]: ${message}`, error);
|
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({
|
notificationController.show({
|
||||||
message: (error as ApiError)?.response?.data?.message || message,
|
message: serverMessage || message,
|
||||||
type: NotificationType.Error
|
type: NotificationType.Error
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,122 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="h-screen w-screen flex place-items-center place-content-center flex-col">
|
<div class="h-screen w-screen">
|
||||||
<div class="min-w-[500px] max-w-[95vw] bg-gray-300 rounded-2xl my-4 p-4">
|
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
||||||
<code class="text-xs text-red-500">Error code {$page.status}</code>
|
<div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-4">
|
||||||
<br />
|
<a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
|
||||||
<code class="text-sm">
|
<img src="/immich-logo.svg" alt="immich logo" height="35" width="35" />
|
||||||
{$page.error?.message}
|
<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
|
||||||
</code>
|
IMMICH
|
||||||
<br />
|
</h1>
|
||||||
<div class="mt-5">
|
</a>
|
||||||
<p class="text-sm font-medium">Verbose</p>
|
|
||||||
<pre class="text-xs">{JSON.stringify($page.error)}</pre>
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<a
|
<div
|
||||||
href="https://github.com/immich-app/immich/issues/new/choose"
|
class="fixed top-0 w-full h-full bg-black/50 flex place-items-center place-content-center overflow-hidden"
|
||||||
target="_blank"
|
>
|
||||||
rel="noopener noreferrer"
|
<div>
|
||||||
>
|
<div
|
||||||
<button
|
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"
|
||||||
class="px-5 py-2 rounded-lg text-sm mt-6 bg-immich-primary text-white hover:bg-immich-primary/75"
|
|
||||||
>Get help</button
|
|
||||||
>
|
>
|
||||||
</a>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{$page.data.meta?.title} - Immich</title>
|
<title>{$page.data.meta?.title || 'Web'} - Immich</title>
|
||||||
{#if $page.data.meta}
|
{#if $page.data.meta}
|
||||||
<meta name="description" content={$page.data.meta.description} />
|
<meta name="description" content={$page.data.meta.description} />
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user