Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
bind:value={setupPassword}
type="password"
placeholder="********"
autocomplete="current-password"
onkeydown={(e) => e.key === 'Enter' && enableTotp()}
/>
{#if verifyError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as Avatar from '#lib/components/ui/avatar/index.js';
import { Button } from '#lib/components/ui/button/index.js';
import * as Dialog from '#lib/components/ui/dialog/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import { Input } from '#lib/components/ui/input/index.js';
import { Label } from '#lib/components/ui/label/index.js';
import { Skeleton } from '#lib/components/ui/skeleton/index.js';
Expand Down Expand Up @@ -627,11 +628,16 @@
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-1.5">
<Label>Full Name</Label>
<Input bind:value={profileName} />
<Input bind:value={profileName} autocomplete="name" allowAutofill />
</div>
<div class="flex flex-col gap-1.5">
<Label>Email Address</Label>
<Input bind:value={profileEmail} type="email" />
<Input
bind:value={profileEmail}
type="email"
autocomplete="email"
allowAutofill
/>
{#if profileEmailChangePending}
<p class="text-xs text-muted-foreground">
Check {pendingProfileEmail} to verify this email change.
Expand Down Expand Up @@ -677,6 +683,7 @@
bind:value={currentPassword}
type={showPassword ? 'text' : 'password'}
placeholder="********"
autocomplete="current-password"
/>
<button
type="button"
Expand All @@ -694,11 +701,21 @@
{/if}
<div class="flex flex-col gap-1.5">
<Label>New Password</Label>
<Input bind:value={newPassword} type="password" placeholder="********" />
<Input
bind:value={newPassword}
type="password"
placeholder="********"
autocomplete="new-password"
/>
</div>
<div class="flex flex-col gap-1.5">
<Label>Confirm New Password</Label>
<Input bind:value={confirmPassword} type="password" placeholder="********" />
<Input
bind:value={confirmPassword}
type="password"
placeholder="********"
autocomplete="new-password"
/>
{#if confirmPassword && newPassword !== confirmPassword}
<p class="text-xs text-red-400">Passwords do not match.</p>
{/if}
Expand Down Expand Up @@ -914,25 +931,42 @@
<p class="py-2 text-center text-xs text-muted-foreground">No SSH keys added.</p>
{/if}

<div class="mt-3 flex flex-col gap-2 border-t border-border/50 pt-3">
<form
class="mt-3 flex flex-col gap-2 border-t border-border/50 pt-3"
aria-label="Add SSH key"
onsubmit={(e) => {
e.preventDefault();
addSshKey();
}}
>
<Label for="ssh-key-title" class="sr-only">Key name</Label>
<Input
id="ssh-key-title"
name="ssh_key[title]"
bind:value={newKeyName}
placeholder="Key name"
class="h-8 text-xs"
allowAutofill
onfocus={() => (newKeyNameFocused = true)}
onblur={() => (newKeyNameFocused = false)}
/>
<Label for="ssh-key-public-key" class="sr-only">Public key</Label>
<textarea
id="ssh-key-public-key"
name="ssh_key[public_key]"
bind:value={newKeyValue}
{...autofillAttributes(undefined, { allow: true })}
placeholder="ssh-ed25519 AAAA..."
rows="3"
spellcheck="false"
autocapitalize="off"
class="w-full resize-none border border-border bg-muted px-3 py-2 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
></textarea>
<Button
type="submit"
variant="outline"
size="sm"
class="w-fit gap-1.5 text-xs"
onclick={addSshKey}
disabled={!(newKeyName.trim() && newKeyValue.trim()) || sshKeyAdding}
>
{#if sshKeyAdding}
Expand All @@ -943,7 +977,7 @@
Add Key
{/if}
</Button>
</div>
</form>
</div>
</Tabs.Content>

Expand Down
43 changes: 43 additions & 0 deletions apps/dashboard/src/lib/components/ui/input/autofill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { FullAutoFill } from 'svelte/elements';

/** Autocomplete tokens for fields a password manager should fill. */
const credentialTokens = new Set([
'username',
'current-password',
'new-password',
'one-time-code',
'webauthn'
]);

/**
* Attributes that keep password managers away from a field, unless its
* `autocomplete` marks it as a sign-in credential or `allow` is set. Most
* dashboard fields name things (servers, volumes, networks, images), and
* 1Password, Bitwarden, LastPass and Dashlane otherwise offer to fill a
* person's name or a saved login into them.
*
* `allow` is for fields a password manager should fill but that have no
* matching `autocomplete` token, like SSH public keys
* (https://www.1password.dev/ssh/public-key-autofill).
*
* - 1Password: `data-1p-ignore` (https://www.1password.dev/web/compatible-website-design)
* - Bitwarden: `data-bwignore`
* - LastPass: `data-lpignore`
* - Dashlane: `data-form-type="other"`
*/
export function autofillAttributes(
autocomplete?: FullAutoFill | null,
{ allow = false }: { allow?: boolean } = {}
) {
const isCredential =
typeof autocomplete === 'string' &&
autocomplete.split(/\s+/).some((token) => credentialTokens.has(token));
if (allow || isCredential) return { autocomplete: autocomplete ?? undefined };
return {
autocomplete: autocomplete ?? ('off' as const),
'data-1p-ignore': '',
'data-bwignore': '',
'data-lpignore': 'true',
'data-form-type': 'other'
};
}
13 changes: 11 additions & 2 deletions apps/dashboard/src/lib/components/ui/input/input.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<script lang="ts">
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import { cn, type WithElementRef } from '#lib/utils.js';
import { autofillAttributes } from './autofill.js';

type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;

type Props = WithElementRef<
Omit<HTMLInputAttributes, 'type'> &
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
Omit<HTMLInputAttributes, 'type'> & {
/** Let password managers fill this field even without a credential `autocomplete` token. */
allowAutofill?: boolean;
} & ({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
>;

let {
Expand All @@ -16,8 +19,12 @@
files = $bindable(),
class: className,
'data-slot': dataSlot = 'input',
autocomplete,
allowAutofill = false,
...restProps
}: Props = $props();

const autofill = $derived(autofillAttributes(autocomplete, { allow: allowAutofill }));
</script>

{#if type === 'file'}
Expand All @@ -31,6 +38,7 @@
type="file"
bind:files
bind:value
{autocomplete}
{...restProps}
/>
{:else}
Expand All @@ -43,6 +51,7 @@
)}
{type}
bind:value
{...autofill}
{...restProps}
/>
{/if}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef, type WithoutChildren } from '#lib/utils.js';
import type { HTMLTextareaAttributes } from 'svelte/elements';
import { autofillAttributes } from '../input/autofill.js';

let {
ref = $bindable(null),
value = $bindable(),
class: className,
'data-slot': dataSlot = 'textarea',
autocomplete,
allowAutofill = false,
...restProps
}: WithoutChildren<WithElementRef<HTMLTextareaAttributes>> = $props();
}: WithoutChildren<WithElementRef<HTMLTextareaAttributes>> & {
/** Let password managers fill this field even without a credential `autocomplete` token. */
allowAutofill?: boolean;
} = $props();
</script>

<textarea
Expand All @@ -19,4 +25,5 @@
className
)}
bind:value
{...autofillAttributes(autocomplete, { allow: allowAutofill })}
{...restProps}></textarea>
2 changes: 2 additions & 0 deletions apps/dashboard/src/routes/(app)/admin/emails/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { onMount, untrack } from 'svelte';
import { Button } from '#lib/components/ui/button/index.js';
import * as Dialog from '#lib/components/ui/dialog/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import { Input } from '#lib/components/ui/input/index.js';
import { Label } from '#lib/components/ui/label/index.js';
import { confirmDestructive } from '#lib/confirm.svelte.js';
Expand Down Expand Up @@ -447,6 +448,7 @@
<span class="shrink-0 text-xs font-medium text-muted-foreground">Subject</span>
<input
bind:value={subject}
{...autofillAttributes()}
placeholder="Subject line"
disabled={sending}
aria-label="Subject"
Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/src/routes/(app)/admin/images/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Badge } from '#lib/components/ui/badge/index.js';
import { Button } from '#lib/components/ui/button/index.js';
import * as Dialog from '#lib/components/ui/dialog/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import { Input } from '#lib/components/ui/input/index.js';
import { Label } from '#lib/components/ui/label/index.js';
import { type AdminPageData, AdminState } from '#lib/state/admin.svelte.js';
Expand Down Expand Up @@ -416,6 +417,7 @@
<Label>Description</Label>
<textarea
bind:value={admin.imgDescription}
{...autofillAttributes()}
placeholder="Short description of this image..."
rows="2"
class="w-full resize-none border border-border bg-muted px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
Expand All @@ -431,6 +433,7 @@
>
<textarea
bind:value={admin.imgLogoSvg}
{...autofillAttributes()}
placeholder="<svg ...></svg>"
rows="3"
class="w-full resize-none border border-border bg-muted px-3 py-2 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Badge } from '#lib/components/ui/badge/index.js';
import { Button } from '#lib/components/ui/button/index.js';
import * as Dialog from '#lib/components/ui/dialog/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import { Input } from '#lib/components/ui/input/index.js';
import { Label } from '#lib/components/ui/label/index.js';
import * as Sheet from '#lib/components/ui/sheet/index.js';
Expand Down Expand Up @@ -260,6 +261,7 @@
/>
<input
bind:value={search}
{...autofillAttributes()}
placeholder="Search images..."
class="h-7 w-44 border border-border bg-muted pr-2 pl-7 text-xs text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { Badge } from '#lib/components/ui/badge/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import * as Sheet from '#lib/components/ui/sheet/index.js';
import { imageTypeColors } from '#lib/data/images.js';
import ChevronLeft from '~icons/lucide/chevron-left';
Expand Down Expand Up @@ -114,6 +115,7 @@
/>
<input
bind:value={search}
{...autofillAttributes()}
placeholder="Search images..."
class="h-7 w-44 border border-border bg-muted pr-2 pl-7 text-xs text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Icon from '#lib/components/icon.svelte';
import { Badge } from '#lib/components/ui/badge/index.js';
import { Button } from '#lib/components/ui/button/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import {
type ImageType,
imageTypeColors,
Expand Down Expand Up @@ -122,6 +123,7 @@
<div class="mt-3 flex items-center gap-2">
<input
bind:value={rebuildImageConfirm}
{...autofillAttributes()}
placeholder={selectedServer.id}
class="h-8 w-56 border border-border bg-muted px-3 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
/>
Expand Down Expand Up @@ -154,6 +156,7 @@
/>
<input
bind:value={imgSearch}
{...autofillAttributes()}
placeholder="Search images..."
class="h-7 w-44 border border-border bg-muted pr-2 pl-7 text-xs text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { Badge } from '#lib/components/ui/badge/index.js';
import { Button } from '#lib/components/ui/button/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import { getServerWithFallback } from '#lib/state/servers.svelte.js';
import X from '~icons/lucide/x';
import AlertTriangle from '~icons/nucleo/alert-triangle';
Expand Down Expand Up @@ -115,6 +116,7 @@
/><input
aria-label="Search logs"
bind:value={logSearch}
{...autofillAttributes()}
placeholder="Search..."
class="h-6 w-36 border border-border bg-muted pr-2 pl-6 text-[11px] text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import BillingSetupDialog from '#lib/components/billing-setup-dialog.svelte';
import { Button } from '#lib/components/ui/button/index.js';
import { autofillAttributes } from '#lib/components/ui/input/autofill.js';
import { Input } from '#lib/components/ui/input/index.js';
import * as Tabs from '#lib/components/ui/tabs/index.js';
import type { FeatureFlags } from '#lib/feature-flags.js';
Expand Down Expand Up @@ -541,6 +542,7 @@
<div class="relative">
<input
bind:value={imagesSearch}
{...autofillAttributes()}
placeholder="Search images..."
class="h-11 w-full border border-border bg-muted px-3 text-base text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none sm:h-8 sm:text-xs"
/>
Expand Down Expand Up @@ -796,6 +798,7 @@
id="new-vol-name"
name="newVolumeName"
bind:value={newVolumeName}
{...autofillAttributes()}
placeholder="volume-name"
class="h-10 w-full border border-border bg-background px-2 text-base text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none sm:h-7 sm:text-xs"
/>
Expand All @@ -812,6 +815,7 @@
type="number"
min="1"
bind:value={newVolumeSize}
{...autofillAttributes()}
class="h-10 w-full border border-border bg-background px-2 text-base text-foreground tabular-nums focus:border-ring focus:outline-none sm:h-7 sm:text-xs"
/>
</div>
Expand Down Expand Up @@ -1015,6 +1019,7 @@
name="serverPassword"
type={showServerPassword ? 'text' : 'password'}
bind:value={serverPassword}
{...autofillAttributes()}
class="h-11 min-w-0 flex-1 border border-border bg-muted px-3 font-mono text-base text-foreground placeholder:text-muted-foreground focus:border-red-500 focus:outline-none sm:h-9 sm:text-xs"
placeholder="Generated password"
/>
Expand Down
Loading
Loading