Login: Passwort-Auge, aenderbare Serveradresse, laenger stehende Fehlermeldung
All checks were successful
Build APK / build-apk (push) Has been skipped
All checks were successful
Build APK / build-apk (push) Has been skipped
- Auge-Symbol im Passwortfeld. Haben alle anderen AWL-Apps seit 06.07.2026; NetDiag war die letzte ohne. Grund: bei falschem Tastaturlayout tippt man sich sonst blind fest. Der Typ wird imperativ am Element gesetzt, weil Svelte 5 ein dynamisches type-Attribut an einem Feld mit bind:value verbietet; type="button" am Schalter ist Pflicht, sonst schickt er das Formular ab. - Serveradresse laesst sich in den Einstellungen aendern. Bisher stand sie dort nur als Text — eine falsche Adresse liess sich nur durch Abmelden korrigieren. Ein Wechsel meldet bewusst ab, weil das Token nur beim bisherigen Server gilt. - Login-Fehler bleiben 8 s stehen statt 3 s und zeigen die Servermeldung im Wortlaut. Bei 429 steht dort die Wartezeit der Brute-Force-Bremse, bei 403 der Grund. Im Emulator (Pixel 6 / Android 14) gegen das Test-Dolibarr geprueft: Ein- und Ausblenden des Passworts, Anmeldung ueber awlauth, Auftragsliste, Einstellungen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3c95ff6b07
commit
4189448c56
2 changed files with 125 additions and 12 deletions
|
|
@ -2,7 +2,8 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import AppHeader from '$lib/components/AppHeader.svelte';
|
import AppHeader from '$lib/components/AppHeader.svelte';
|
||||||
import { auth } from '$lib/auth.svelte';
|
import { auth } from '$lib/auth.svelte';
|
||||||
import { getServerUrl } from '$lib/api';
|
import { getServerUrl, setServerUrl } from '$lib/api';
|
||||||
|
import { Capacitor } from '@capacitor/core';
|
||||||
import { sync } from '$lib/sync.svelte';
|
import { sync } from '$lib/sync.svelte';
|
||||||
import { toast } from '$lib/toast.svelte';
|
import { toast } from '$lib/toast.svelte';
|
||||||
import { APP_VERSION, checkForUpdate, installUpdate, type UpdateInfo } from '$lib/updater';
|
import { APP_VERSION, checkForUpdate, installUpdate, type UpdateInfo } from '$lib/updater';
|
||||||
|
|
@ -62,6 +63,33 @@
|
||||||
sync.stop();
|
sync.stop();
|
||||||
goto('/login/');
|
goto('/login/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Server-Adresse ändern ------------------------------------------- */
|
||||||
|
// Nur auf dem Gerät sinnvoll: im Browser läuft alles über den Vite-Proxy.
|
||||||
|
const canEditServer = Capacitor.isNativePlatform();
|
||||||
|
let editServer = $state(false);
|
||||||
|
let serverInput = $state(getServerUrl());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Neue Server-Adresse übernehmen. Das Token gilt nur beim alten Server,
|
||||||
|
* deshalb wird bewusst abgemeldet statt so zu tun, als liefe alles weiter.
|
||||||
|
*/
|
||||||
|
async function saveServer() {
|
||||||
|
const url = serverInput.trim();
|
||||||
|
if (!url) {
|
||||||
|
toast.show('Server-Adresse fehlt', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^https?:\/\//i.test(url)) {
|
||||||
|
toast.show('Adresse muss mit http:// oder https:// beginnen', 'error', 5000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setServerUrl(url);
|
||||||
|
await auth.logout();
|
||||||
|
sync.stop();
|
||||||
|
toast.show('Server geändert — bitte neu anmelden', 'info', 5000);
|
||||||
|
goto('/login/');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppHeader title="Einstellungen" back />
|
<AppHeader title="Einstellungen" back />
|
||||||
|
|
@ -113,9 +141,54 @@
|
||||||
<section class="rounded-lg bg-zinc-900 p-4">
|
<section class="rounded-lg bg-zinc-900 p-4">
|
||||||
<h2 class="mb-2 text-sm font-semibold text-zinc-300">Synchronisierung</h2>
|
<h2 class="mb-2 text-sm font-semibold text-zinc-300">Synchronisierung</h2>
|
||||||
<p class="text-sm text-zinc-300">{syncLabel}</p>
|
<p class="text-sm text-zinc-300">{syncLabel}</p>
|
||||||
<p class="mt-0.5 break-all text-xs text-zinc-500">
|
{#if editServer}
|
||||||
Server: {getServerUrl() || '(Browser-Proxy)'}
|
<div class="mt-2 flex flex-col gap-2">
|
||||||
</p>
|
<input
|
||||||
|
class="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm"
|
||||||
|
type="url"
|
||||||
|
inputmode="url"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
placeholder="https://dolibarr.example.de"
|
||||||
|
bind:value={serverInput}
|
||||||
|
/>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
class="flex-1 rounded bg-zinc-800 px-3 py-2 text-sm active:bg-zinc-700"
|
||||||
|
onclick={() => {
|
||||||
|
editServer = false;
|
||||||
|
serverInput = getServerUrl();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="flex-1 rounded bg-sky-700 px-3 py-2 text-sm font-semibold text-white active:bg-sky-800"
|
||||||
|
onclick={saveServer}
|
||||||
|
>
|
||||||
|
Speichern
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-zinc-500">
|
||||||
|
Ein Wechsel meldet ab — das Token gilt nur beim bisherigen Server.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="mt-0.5 break-all text-xs text-zinc-500">
|
||||||
|
Server: {getServerUrl() || '(Browser-Proxy)'}
|
||||||
|
{#if canEditServer}
|
||||||
|
<button
|
||||||
|
class="ml-1 text-sky-400 underline active:text-sky-300"
|
||||||
|
onclick={() => {
|
||||||
|
serverInput = getServerUrl();
|
||||||
|
editServer = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ändern
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
class="mt-3 w-full rounded bg-zinc-800 px-3 py-2 text-sm active:bg-zinc-700 disabled:opacity-50"
|
class="mt-3 w-full rounded bg-zinc-800 px-3 py-2 text-sm active:bg-zinc-700 disabled:opacity-50"
|
||||||
onclick={() => sync.syncManual()}
|
onclick={() => sync.syncManual()}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import { getServerUrl, setServerUrl, ApiError } from '$lib/api';
|
import { getServerUrl, setServerUrl, ApiError } from '$lib/api';
|
||||||
import { toast } from '$lib/toast.svelte';
|
import { toast } from '$lib/toast.svelte';
|
||||||
import { Capacitor } from '@capacitor/core';
|
import { Capacitor } from '@capacitor/core';
|
||||||
|
import { Eye, EyeOff } from 'lucide-svelte';
|
||||||
|
|
||||||
let server = $state(getServerUrl());
|
let server = $state(getServerUrl());
|
||||||
let loginName = $state('');
|
let loginName = $state('');
|
||||||
|
|
@ -14,6 +15,21 @@
|
||||||
// Server-URL nur auf dem Gerät nötig (im Browser läuft der Vite-Proxy)
|
// Server-URL nur auf dem Gerät nötig (im Browser läuft der Vite-Proxy)
|
||||||
const needsServer = Capacitor.isNativePlatform();
|
const needsServer = Capacitor.isNativePlatform();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passwort ein-/ausblenden.
|
||||||
|
*
|
||||||
|
* Der Typ wird imperativ am Element gesetzt: Svelte 5 verbietet ein
|
||||||
|
* dynamisches `type` an einem Feld mit `bind:value`. Grund für den Schalter:
|
||||||
|
* bei falschem Tastaturlayout tippt man sich sonst blind fest.
|
||||||
|
*/
|
||||||
|
let pwEl: HTMLInputElement;
|
||||||
|
let pwVisible = $state(false);
|
||||||
|
|
||||||
|
function togglePw() {
|
||||||
|
pwVisible = !pwVisible;
|
||||||
|
if (pwEl) pwEl.type = pwVisible ? 'text' : 'password';
|
||||||
|
}
|
||||||
|
|
||||||
async function submit(e: Event) {
|
async function submit(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
|
|
@ -28,7 +44,14 @@
|
||||||
toast.show('Angemeldet', 'success');
|
toast.show('Angemeldet', 'success');
|
||||||
goto('/auftraege/');
|
goto('/auftraege/');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.show(err instanceof ApiError ? err.message : 'Anmeldung fehlgeschlagen', 'error');
|
// Servermeldung im Wortlaut zeigen und stehen lassen — bei 429 steht dort
|
||||||
|
// die Wartezeit, bei 403 der Grund. Eine Meldung, die nach 3 s verschwindet,
|
||||||
|
// ist auf der Baustelle wertlos.
|
||||||
|
toast.show(
|
||||||
|
err instanceof ApiError ? err.message : 'Anmeldung fehlgeschlagen',
|
||||||
|
'error',
|
||||||
|
8000,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
busy = false;
|
busy = false;
|
||||||
}
|
}
|
||||||
|
|
@ -66,13 +89,30 @@
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1 text-sm">
|
<label class="flex flex-col gap-1 text-sm">
|
||||||
<span class="text-zinc-400">Passwort</span>
|
<span class="text-zinc-400">Passwort</span>
|
||||||
<input
|
<div class="relative">
|
||||||
class="rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2"
|
<input
|
||||||
type="password"
|
class="w-full rounded-lg border border-zinc-700 bg-zinc-800 py-2 pl-3 pr-12"
|
||||||
bind:value={password}
|
type="password"
|
||||||
autocomplete="current-password"
|
bind:this={pwEl}
|
||||||
required
|
bind:value={password}
|
||||||
/>
|
autocomplete="current-password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<!-- type="button" ist Pflicht: sonst schickt der Schalter das Formular ab -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute right-0 top-0 flex h-full w-12 items-center justify-center text-zinc-400 active:text-zinc-200"
|
||||||
|
onclick={togglePw}
|
||||||
|
aria-label={pwVisible ? 'Passwort verbergen' : 'Passwort anzeigen'}
|
||||||
|
aria-pressed={pwVisible}
|
||||||
|
>
|
||||||
|
{#if pwVisible}
|
||||||
|
<EyeOff size={20} />
|
||||||
|
{:else}
|
||||||
|
<Eye size={20} />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
class="mt-2 rounded-lg bg-sky-600 py-2.5 font-semibold text-white active:bg-sky-700 disabled:opacity-50"
|
class="mt-2 rounded-lg bg-sky-600 py-2.5 font-semibold text-white active:bg-sky-700 disabled:opacity-50"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue