mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-23 13:51:31 +00:00
feat(ui): add password visibility toggle to all password inputs
This commit is contained in:
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import PasswordInput from "@/components/ui/PasswordInput.vue";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
@@ -502,7 +503,7 @@ onMounted(loadUsers);
|
||||
{{ t("userAdmin.allowLogin") }}
|
||||
</label>
|
||||
<label class="block text-xs font-medium">{{ t("connection.password") }}</label>
|
||||
<Input v-model="createPassword" type="password" />
|
||||
<PasswordInput v-model="createPassword" />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="createDialogOpen = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
@@ -518,7 +519,7 @@ onMounted(loadUsers);
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("userAdmin.changePassword") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input v-model="newPassword" type="password" :placeholder="t('userAdmin.newPassword')" />
|
||||
<PasswordInput v-model="newPassword" :placeholder="t('userAdmin.newPassword')" />
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="passwordDialogOpen = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="!newPassword" @click="previewPasswordChange">{{ t("userAdmin.previewSql") }}</Button>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import PasswordInput from "@/components/ui/PasswordInput.vue";
|
||||
import { Lock, Loader2, ShieldCheck } from "@lucide/vue";
|
||||
import AppLogo from "@/components/icons/AppLogo.vue";
|
||||
|
||||
@@ -70,11 +71,11 @@ async function submit() {
|
||||
</div>
|
||||
<div class="relative">
|
||||
<Lock class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input v-model="password" type="password" :placeholder="setupMode ? t('auth.newPassword') : t('auth.enterPassword')" class="pl-10 h-11" autocomplete="off" autofocus />
|
||||
<PasswordInput v-model="password" :placeholder="setupMode ? t('auth.newPassword') : t('auth.enterPassword')" inputClass="pl-10 h-11" autocomplete="off" autofocus />
|
||||
</div>
|
||||
<div v-if="setupMode" class="relative">
|
||||
<Lock class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input v-model="confirmPassword" type="password" :placeholder="t('auth.confirmPassword')" class="pl-10 h-11" autocomplete="off" />
|
||||
<PasswordInput v-model="confirmPassword" :placeholder="t('auth.confirmPassword')" inputClass="pl-10 h-11" autocomplete="off" />
|
||||
</div>
|
||||
<p v-if="error" class="text-sm text-destructive text-center">{{ error }}</p>
|
||||
<Button type="submit" class="w-full h-11 text-sm font-medium" :disabled="loading || !password || (setupMode && !confirmPassword)">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useI18n } from "vue-i18n";
|
||||
import { Lock } from "@lucide/vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import PasswordInput from "@/components/ui/PasswordInput.vue";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
|
||||
@@ -76,12 +77,12 @@ const displayError = computed(() => error.value || props.externalError || "");
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ t("configExport.passphrase") }}</Label>
|
||||
<Input v-model="passphrase" type="password" :placeholder="t('configExport.passphrasePlaceholder')" @keydown.enter="mode === 'import' ? confirm() : undefined" />
|
||||
<PasswordInput v-model="passphrase" :placeholder="t('configExport.passphrasePlaceholder')" @keydown.enter="mode === 'import' ? confirm() : undefined" />
|
||||
</div>
|
||||
|
||||
<div v-if="mode === 'export'" class="grid gap-2">
|
||||
<Label>{{ t("configExport.passphraseConfirm") }}</Label>
|
||||
<Input v-model="passphraseConfirm" type="password" :placeholder="t('configExport.passphraseConfirmPlaceholder')" @keydown.enter="confirm" />
|
||||
<PasswordInput v-model="passphraseConfirm" :placeholder="t('configExport.passphraseConfirmPlaceholder')" @keydown.enter="confirm" />
|
||||
</div>
|
||||
|
||||
<p v-if="displayError" class="text-sm text-destructive">{{ displayError }}</p>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import PasswordInput from "@/components/ui/PasswordInput.vue";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
@@ -2257,7 +2258,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" />
|
||||
<PasswordInput v-model="form.password" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label class="text-right mt-2">{{ t("connection.jdbcDriverPaths") }}</Label>
|
||||
@@ -2378,7 +2379,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}{{ form.db_type === "access" ? "(可选)" : "" }}</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" />
|
||||
<PasswordInput v-model="form.password" class="col-span-3" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -2424,7 +2425,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.redisSentinelPassword") }}</Label>
|
||||
<Input v-model="form.redis_sentinel_password" type="password" class="col-span-3" />
|
||||
<PasswordInput v-model="form.redis_sentinel_password" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.redisSentinelTls") }}</Label>
|
||||
@@ -2451,7 +2452,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" :placeholder="t('connection.databasePlaceholder')" />
|
||||
<PasswordInput v-model="form.password" class="col-span-3" :placeholder="t('connection.databasePlaceholder')" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.redisKeySeparator") }}</Label>
|
||||
@@ -2486,7 +2487,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" />
|
||||
<PasswordInput v-model="form.password" class="col-span-3" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2528,7 +2529,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" />
|
||||
<PasswordInput v-model="form.password" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.defaultDatabase") }}</Label>
|
||||
@@ -2578,7 +2579,7 @@ function openExternalUrl(url: string) {
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">Auth Token</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" placeholder="eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..." />
|
||||
<PasswordInput v-model="form.password" class="col-span-3" placeholder="eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..." />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
@@ -2612,7 +2613,7 @@ function openExternalUrl(url: string) {
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" />
|
||||
<PasswordInput v-model="form.password" class="col-span-3" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
@@ -3061,7 +3062,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.sshPassword") }}</Label>
|
||||
<Input v-model="selectedSshLayer.password" type="password" class="col-span-3" :placeholder="t('connection.sshPasswordPlaceholder')" :disabled="selectedSshLayer.enabled === false" />
|
||||
<PasswordInput v-model="selectedSshLayer.password" class="col-span-3" :placeholder="t('connection.sshPasswordPlaceholder')" :disabled="selectedSshLayer.enabled === false" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.sshKeyPath") }}</Label>
|
||||
@@ -3079,7 +3080,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.sshKeyPassphrase") }}</Label>
|
||||
<Input v-model="selectedSshLayer.key_passphrase" type="password" class="col-span-3" :placeholder="t('connection.sshKeyPassphrasePlaceholder')" :disabled="selectedSshLayer.enabled === false" />
|
||||
<PasswordInput v-model="selectedSshLayer.key_passphrase" class="col-span-3" :placeholder="t('connection.sshKeyPassphrasePlaceholder')" :disabled="selectedSshLayer.enabled === false" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<span />
|
||||
@@ -3124,7 +3125,7 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.proxyPassword") }}</Label>
|
||||
<Input v-model="selectedProxyLayer.password" type="password" class="col-span-3" :placeholder="t('connection.proxyPasswordPlaceholder')" :disabled="selectedProxyLayer.enabled === false" />
|
||||
<PasswordInput v-model="selectedProxyLayer.password" class="col-span-3" :placeholder="t('connection.proxyPasswordPlaceholder')" :disabled="selectedProxyLayer.enabled === false" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import PasswordInput from "@/components/ui/PasswordInput.vue";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { SearchableSelect } from "@/components/ui/searchable-select";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
@@ -2055,7 +2056,7 @@ watch(
|
||||
<div class="space-y-2">
|
||||
<Label for="webdav-password">{{ t("settings.syncPassword") }}</Label>
|
||||
<div class="relative">
|
||||
<Input id="webdav-password" v-model="webdavPassword" type="password" :placeholder="webdavHasSavedPassword ? '••••••••' : t('settings.syncPasswordPlaceholder')" :disabled="webdavHasSavedPassword" autocomplete="current-password" />
|
||||
<PasswordInput id="webdav-password" v-model="webdavPassword" :placeholder="webdavHasSavedPassword ? '••••••••' : t('settings.syncPasswordPlaceholder')" :disabled="webdavHasSavedPassword" autocomplete="current-password" />
|
||||
<Button
|
||||
v-if="webdavHasSavedPassword"
|
||||
variant="ghost"
|
||||
@@ -2109,7 +2110,7 @@ watch(
|
||||
</div>
|
||||
<div v-if="webdavSyncSecrets" class="space-y-2">
|
||||
<Label for="webdav-secrets-passphrase">{{ t("settings.syncSecretsPassphrase") }}</Label>
|
||||
<Input id="webdav-secrets-passphrase" v-model="webdavSecretsPassphrase" type="password" autocomplete="new-password" />
|
||||
<PasswordInput id="webdav-secrets-passphrase" v-model="webdavSecretsPassphrase" autocomplete="new-password" />
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.syncSecretsPassphraseDescription") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2121,7 +2122,7 @@ watch(
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.provider") }}</Label>
|
||||
<Select :model-value="aiEditProvider" @update:model-value="(v: any) => aiSelectProvider(v)">
|
||||
<SelectTrigger class="col-span-2 h-8 text-xs">
|
||||
<SelectTrigger class="col-span-2" inputClass="h-8 text-xs">
|
||||
<SelectValue>
|
||||
<span class="flex items-center gap-2">
|
||||
<AiProviderLogo :provider="selectedAiProviderPreset.provider" :label="selectedAiProviderPreset.label" :icon-slug="selectedAiProviderPreset.iconSlug" />
|
||||
@@ -2143,7 +2144,7 @@ watch(
|
||||
<div v-if="aiSupportsAuthMethod" class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">Authentication</Label>
|
||||
<Select v-model="aiEditAuthMethod">
|
||||
<SelectTrigger class="col-span-2 h-8 text-xs">
|
||||
<SelectTrigger class="col-span-2" inputClass="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -2155,7 +2156,7 @@ watch(
|
||||
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ aiCredentialLabel }}</Label>
|
||||
<Input v-model="aiEditApiKey" type="password" autocomplete="off" class="col-span-2 h-8 text-xs" :placeholder="aiCredentialPlaceholder" />
|
||||
<PasswordInput v-model="aiEditApiKey" autocomplete="off" class="col-span-2" inputClass="h-8 text-xs" :placeholder="aiCredentialPlaceholder" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-start gap-3">
|
||||
@@ -2243,7 +2244,7 @@ watch(
|
||||
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.proxyUrl") }}</Label>
|
||||
<Input v-model="aiEditProxyUrl" autocomplete="off" class="col-span-2 h-8 text-xs" placeholder="socks5://127.0.0.1:7890" :disabled="!aiEditProxyEnabled" />
|
||||
<Input v-model="aiEditProxyUrl" autocomplete="off" class="col-span-2" inputClass="h-8 text-xs" placeholder="socks5://127.0.0.1:7890" :disabled="!aiEditProxyEnabled" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -2401,9 +2402,9 @@ watch(
|
||||
<div class="space-y-3">
|
||||
<Label class="text-base">{{ t("auth.changePassword") }}</Label>
|
||||
<p class="text-sm text-muted-foreground">{{ t("auth.changePasswordDescription") }}</p>
|
||||
<Input v-model="oldPassword" type="password" :placeholder="t('auth.oldPassword')" class="h-9" autocomplete="off" />
|
||||
<Input v-model="newPassword" type="password" :placeholder="t('auth.newPassword')" class="h-9" autocomplete="off" />
|
||||
<Input v-model="confirmNewPassword" type="password" :placeholder="t('auth.confirmPassword')" class="h-9" autocomplete="off" />
|
||||
<PasswordInput v-model="oldPassword" :placeholder="t('auth.oldPassword')" inputClass="h-9" autocomplete="off" />
|
||||
<PasswordInput v-model="newPassword" :placeholder="t('auth.newPassword')" inputClass="h-9" autocomplete="off" />
|
||||
<PasswordInput v-model="confirmNewPassword" :placeholder="t('auth.confirmPassword')" inputClass="h-9" autocomplete="off" />
|
||||
<p v-if="passwordMessage" class="text-xs" :class="passwordError ? 'text-destructive' : 'text-green-500'">
|
||||
{{ passwordMessage }}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { Eye, EyeOff } from "@lucide/vue";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
const model = defineModel<string>();
|
||||
|
||||
const props = defineProps<{
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
class?: string;
|
||||
inputClass?: string;
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="props.class" class="relative">
|
||||
<Input v-model="model" :type="visible ? 'text' : 'password'" :placeholder="placeholder" :disabled="disabled" :class="props.inputClass" class="pr-8" v-bind="$attrs" />
|
||||
<button type="button" :disabled="disabled" class="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground disabled:pointer-events-none" tabindex="-1" @click="visible = !visible">
|
||||
<Eye v-if="!visible" class="size-3.5" />
|
||||
<EyeOff v-else class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user