diff --git a/oxibadge/src/lib/components/ActionButton.svelte b/oxibadge/src/lib/components/ActionButton.svelte index 1095db9..10fbcfd 100644 --- a/oxibadge/src/lib/components/ActionButton.svelte +++ b/oxibadge/src/lib/components/ActionButton.svelte @@ -3,18 +3,20 @@ let { primary = false, + disabled = false, onclick, icon, children }: { primary?: boolean; + disabled?: boolean; onclick: () => void; icon?: Snippet; children: Snippet; } = $props(); - @@ -37,7 +39,7 @@ transition: background 0.15s; } - .btn:hover { + .btn:hover:not(:disabled) { background: #1e1e1e; color: #ffffff; } @@ -47,6 +49,11 @@ outline-offset: 2px; } + .btn:disabled { + opacity: 0.5; + cursor: progress; + } + .btn.primary { padding: 0 18px; border: none; @@ -58,12 +65,12 @@ transform 0.1s; } - .btn.primary:hover { + .btn.primary:hover:not(:disabled) { background: var(--ox-accent); filter: brightness(1.08); } - .btn.primary:active { + .btn.primary:active:not(:disabled) { transform: translateY(1px); } diff --git a/oxibadge/src/lib/components/settings/ReaderCard.svelte b/oxibadge/src/lib/components/settings/ReaderCard.svelte index 3050191..18d7a9a 100644 --- a/oxibadge/src/lib/components/settings/ReaderCard.svelte +++ b/oxibadge/src/lib/components/settings/ReaderCard.svelte @@ -10,7 +10,7 @@ import ActionButton from '../ActionButton.svelte'; import PillButton from '../PillButton.svelte'; import EmployeesDrawer from './EmployeesDrawer.svelte'; - import { IPV4 } from './logic'; + import { isValidHost } from './logic'; let { host, port, onsave }: { host: string; port: number; onsave: (host: string, port: number) => void } = $props(); @@ -19,6 +19,7 @@ let draftHost = $state(''); let draftPort = $state(0); let ipError = $state(false); + let confirming = $state(false); let status = $state<'' | 'testing' | 'ok'>(''); @@ -79,20 +80,25 @@ async function confirmEdit() { const v = draftHost.trim(); - if (!IPV4.test(v)) { + if (!isValidHost(v)) { ipError = true; return; } const nextPort = draftPort || 5005; - editing = false; - status = ''; + confirming = true; clearError(); try { + await invoke('test_connection', { host: v, port: nextPort }); const settings = await invoke('load_settings'); await invoke('save_settings', { settings: { ...settings, readers: [{ host: v, port: nextPort }] } }); + editing = false; + status = 'ok'; onsave(v, nextPort); } catch (err) { + status = ''; setError(err); + } finally { + confirming = false; } } @@ -116,7 +122,7 @@ } function onKey(e: KeyboardEvent) { - if (e.key === 'Escape' && editing) cancelEdit(); + if (e.key === 'Escape' && editing && !confirming) cancelEdit(); } @@ -194,7 +200,6 @@ id="dev-ip" label={$_('settings.ip_label')} bind:value={draftHost} - inputmode="decimal" placeholder="192.168.1.100" err={ipError} oninput={() => (ipError = false)} @@ -212,8 +217,17 @@ />
- {$_('settings.cancel')} - {$_('settings.confirm')} + {$_('settings.cancel')} + + {#snippet icon()} + {#if confirming} + + {/if} + {/snippet} + {confirming ? $_('settings.status_testing') : $_('settings.confirm')} +
{/if} diff --git a/oxibadge/src/lib/components/settings/logic.ts b/oxibadge/src/lib/components/settings/logic.ts index c4f8824..1b4a465 100644 --- a/oxibadge/src/lib/components/settings/logic.ts +++ b/oxibadge/src/lib/components/settings/logic.ts @@ -3,7 +3,15 @@ import type { Shifts } from '$lib/shifts'; export type { Shifts }; -export const IPV4 = /^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/; +const IPV4 = /^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/; + +// A DNS label: alphanumeric, hyphens allowed in the middle, 1-63 chars. +const LABEL = '[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?'; +const HOSTNAME = new RegExp(`^${LABEL}(?:\\.${LABEL})*$`); + +/** Whether `v` is a usable time clock address: an IPv4 address, a bare + * hostname (e.g. "timbratore") or a FQDN (e.g. "timbratore.lan"). */ +export const isValidHost = (v: string): boolean => IPV4.test(v) || HOSTNAME.test(v); /** The 4 time fields of `Shifts` — everything but `round_step_min`, which isn't a time. */ export type ShiftTimeKey = 'in1' | 'out1' | 'in2' | 'out2'; diff --git a/oxibadge/src/lib/locales/en.json b/oxibadge/src/lib/locales/en.json index b80d0a6..e1571bc 100644 --- a/oxibadge/src/lib/locales/en.json +++ b/oxibadge/src/lib/locales/en.json @@ -2,8 +2,8 @@ "loading": "Loading...", "onboarding": { "title": "Welcome!", - "subtitle": "Connect Oxibadge to your time clock by entering its IP address and port.", - "host_label": "IP address", + "subtitle": "Connect Oxibadge to your time clock by entering its address and port.", + "host_label": "Address", "port_label": "Port", "hint": "You can find the address in the time clock's network settings.", "button": { @@ -136,9 +136,9 @@ "status_idle": "Not verified", "edit_address": "Edit address", "test_connection": "Test connection", - "ip_label": "IP address", + "ip_label": "Address", "port_label": "Port", - "ip_invalid": "Invalid format, e.g. 192.168.1.100", + "ip_invalid": "Invalid format, e.g. 192.168.1.100 or timbratore.lan", "cancel": "Cancel", "confirm": "Confirm", "shifts_title": "Expected shifts", diff --git a/oxibadge/src/lib/locales/it.json b/oxibadge/src/lib/locales/it.json index 2417705..a78d921 100644 --- a/oxibadge/src/lib/locales/it.json +++ b/oxibadge/src/lib/locales/it.json @@ -2,8 +2,8 @@ "loading": "Caricamento...", "onboarding": { "title": "Benvenuto!", - "subtitle": "Collega Oxibadge al tuo timbratore inserendo il suo indirizzo IP e la porta.", - "host_label": "Indirizzo IP", + "subtitle": "Collega Oxibadge al tuo timbratore inserendo il suo indirizzo e la porta.", + "host_label": "Indirizzo", "port_label": "Porta", "hint": "Trovi l'indirizzo nelle impostazioni di rete del timbratore.", "button": { @@ -136,9 +136,9 @@ "status_idle": "Non verificato", "edit_address": "Modifica indirizzo", "test_connection": "Testa connessione", - "ip_label": "Indirizzo IP", + "ip_label": "Indirizzo", "port_label": "Porta", - "ip_invalid": "Formato non valido, es. 192.168.1.100", + "ip_invalid": "Formato non valido, es. 192.168.1.100 o timbratore.lan", "cancel": "Annulla", "confirm": "Conferma", "shifts_title": "Turni previsti",