mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-06-13 11:30:29 +08:00
feat: improve address credential connections
This commit is contained in:
@@ -105,6 +105,8 @@ const getOpenSettings = async (message, notification) => {
|
||||
enableWebhook: res["enableWebhook"] || false,
|
||||
isS3Enabled: res["isS3Enabled"] || false,
|
||||
enableAddressPassword: res["enableAddressPassword"] || false,
|
||||
enableAgentEmailInfo: res["enableAgentEmailInfo"] || false,
|
||||
smtpImapProxyConfig: res["smtpImapProxyConfig"] || openSettings.value.smtpImapProxyConfig,
|
||||
statusUrl: res["statusUrl"] || "",
|
||||
enableGlobalTurnstileCheck: res["enableGlobalTurnstileCheck"] || false,
|
||||
});
|
||||
|
||||
322
frontend/src/components/AddressCredentialModal.vue
Normal file
322
frontend/src/components/AddressCredentialModal.vue
Normal file
@@ -0,0 +1,322 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useScopedI18n } from '@/i18n/app'
|
||||
|
||||
import { useGlobalState } from '../store'
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
jwt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
addressPassword: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:show'])
|
||||
|
||||
const { openSettings, auth } = useGlobalState()
|
||||
const { locale, t } = useScopedI18n('components.AddressCredentialModal')
|
||||
const message = useMessage()
|
||||
|
||||
const modalShow = computed({
|
||||
get: () => props.show,
|
||||
set: (value) => emit('update:show', value),
|
||||
})
|
||||
|
||||
const configuredApiBaseUrl = import.meta.env.VITE_API_BASE || ''
|
||||
const frontendBaseUrl = computed(() => window.location.origin)
|
||||
const apiBaseUrl = computed(() => (configuredApiBaseUrl || frontendBaseUrl.value).replace(/\/$/, ''))
|
||||
const docLocale = computed(() => locale.value === 'zh' ? 'zh' : 'en')
|
||||
const agentDocUrl = computed(() => `https://temp-mail-docs.awsl.uk/${docLocale.value}/guide/feature/agent-email.html`)
|
||||
const smtpImapDocUrl = computed(() => `https://temp-mail-docs.awsl.uk/${docLocale.value}/guide/feature/config-smtp-proxy.html`)
|
||||
const agentSkillUrl = 'https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/skills/cf-temp-mail-agent-mail/SKILL.md'
|
||||
const autoLoginUrl = computed(() => `${frontendBaseUrl.value}/?jwt=${encodeURIComponent(props.jwt)}`)
|
||||
const showAgent = computed(() => !!openSettings.value.enableAgentEmailInfo)
|
||||
const smtpImapConfig = computed(() => openSettings.value.smtpImapProxyConfig || {})
|
||||
const smtpConfig = computed(() => smtpImapConfig.value.smtp || {})
|
||||
const imapConfig = computed(() => smtpImapConfig.value.imap || {})
|
||||
const showSmtpImap = computed(() => !!smtpConfig.value.host || !!imapConfig.value.host)
|
||||
const securityLabel = computed(() =>
|
||||
smtpConfig.value.starttls || imapConfig.value.starttls ? t('starttls') : t('plainOrProxyTls')
|
||||
)
|
||||
const agentConfigJson = computed(() => JSON.stringify({
|
||||
base: apiBaseUrl.value,
|
||||
jwt: props.jwt,
|
||||
site_password: auth.value || '',
|
||||
}, null, 2))
|
||||
const agentText = computed(() => [
|
||||
`${t('currentAddress')}: ${props.address || '-'}`,
|
||||
`${t('apiBase')}: ${apiBaseUrl.value}`,
|
||||
`${t('agentSkill')}: ${agentSkillUrl}`,
|
||||
`${t('agentConfig')}:`,
|
||||
agentConfigJson.value,
|
||||
].join('\n'))
|
||||
const smtpImapText = computed(() => [
|
||||
`${t('smtpHost')}: ${smtpConfig.value.host || '-'}`,
|
||||
`${t('smtpPort')}: ${smtpConfig.value.port || 8025}`,
|
||||
`${t('imapHost')}: ${imapConfig.value.host || '-'}`,
|
||||
`${t('imapPort')}: ${imapConfig.value.port || 11143}`,
|
||||
`${t('security')}: ${securityLabel.value}`,
|
||||
`${t('username')}: ${props.address || '-'}`,
|
||||
`${t('password')}: ${props.jwt}`,
|
||||
].join('\n'))
|
||||
|
||||
const copyText = async (text) => {
|
||||
if (!text) return
|
||||
try {
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(text)
|
||||
message.success(t('copySuccess'))
|
||||
return
|
||||
}
|
||||
|
||||
const textarea = document.createElement('textarea')
|
||||
try {
|
||||
textarea.value = text
|
||||
textarea.setAttribute('readonly', '')
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
if (document.execCommand('copy')) {
|
||||
message.success(t('copySuccess'))
|
||||
return
|
||||
}
|
||||
message.error(t('copyFailed'))
|
||||
} finally {
|
||||
textarea.parentNode?.removeChild(textarea)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
message.error(t('copyFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal v-model:show="modalShow" preset="card" :title="t('title')"
|
||||
style="width: min(760px, calc(100vw - 32px));">
|
||||
<n-alert type="info" :show-icon="false" :bordered="false">
|
||||
{{ t('tip') }}
|
||||
</n-alert>
|
||||
<section class="credential-panel">
|
||||
<h3 class="credential-title">{{ t('addressCredential') }}</h3>
|
||||
<div class="credential-section">
|
||||
<div class="credential-field" v-if="address">
|
||||
<span class="credential-label">{{ t('currentAddress') }}</span>
|
||||
<div class="credential-copy-row">
|
||||
<code class="credential-code">{{ address }}</code>
|
||||
<n-button size="tiny" tertiary type="primary" @click="copyText(address)">
|
||||
{{ t('copySection') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('addressCredentialLabel') }}</span>
|
||||
<div class="credential-copy-row">
|
||||
<code class="credential-code">{{ jwt }}</code>
|
||||
<n-button size="tiny" tertiary type="primary" @click="copyText(jwt)">
|
||||
{{ t('copySection') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="credential-field" v-if="addressPassword">
|
||||
<span class="credential-label">{{ t('addressPassword') }}</span>
|
||||
<code class="credential-code">{{ addressPassword }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<n-collapse accordion class="credential-collapse">
|
||||
<n-collapse-item v-if="showAgent" name="agent" :title="t('agentAccess')">
|
||||
<template #header-extra>
|
||||
<n-button size="tiny" tertiary type="primary" @click.stop="copyText(agentText)">
|
||||
{{ t('copySection') }}
|
||||
</n-button>
|
||||
</template>
|
||||
<div class="credential-section">
|
||||
<p class="credential-tip">{{ t('agentAccessTip') }}</p>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('apiBase') }}</span>
|
||||
<code class="credential-code">{{ apiBaseUrl }}</code>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('agentSkill') }}</span>
|
||||
<code class="credential-code">
|
||||
<a :href="agentSkillUrl" target="_blank" rel="noopener noreferrer">{{ agentSkillUrl }}</a>
|
||||
</code>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('agentConfig') }}</span>
|
||||
<pre class="credential-code credential-code-block">{{ agentConfigJson }}</pre>
|
||||
</div>
|
||||
<div class="credential-actions">
|
||||
<n-button tag="a" :href="agentDocUrl" target="_blank" rel="noopener noreferrer" text type="primary">
|
||||
{{ t('docs') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item v-if="showSmtpImap" name="smtp-imap" :title="t('smtpImapAccess')">
|
||||
<template #header-extra>
|
||||
<n-button size="tiny" tertiary type="primary" @click.stop="copyText(smtpImapText)">
|
||||
{{ t('copySection') }}
|
||||
</n-button>
|
||||
</template>
|
||||
<div class="credential-section">
|
||||
<p class="credential-tip">{{ t('smtpImapTip') }}</p>
|
||||
<div class="credential-grid">
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('smtpHost') }}</span>
|
||||
<code class="credential-code">{{ smtpConfig.host || '-' }}</code>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('smtpPort') }}</span>
|
||||
<code class="credential-code">{{ smtpConfig.port || 8025 }}</code>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('imapHost') }}</span>
|
||||
<code class="credential-code">{{ imapConfig.host || '-' }}</code>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('imapPort') }}</span>
|
||||
<code class="credential-code">{{ imapConfig.port || 11143 }}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('security') }}</span>
|
||||
<code class="credential-code">{{ securityLabel }}</code>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('username') }}</span>
|
||||
<code class="credential-code">{{ address }}</code>
|
||||
</div>
|
||||
<div class="credential-field">
|
||||
<span class="credential-label">{{ t('password') }}</span>
|
||||
<code class="credential-code">{{ jwt }}</code>
|
||||
</div>
|
||||
<div class="credential-actions">
|
||||
<n-button tag="a" :href="smtpImapDocUrl" target="_blank" rel="noopener noreferrer" text type="primary">
|
||||
{{ t('docs') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item name="share-link" :title="t('autoLoginLink')">
|
||||
<template #header-extra>
|
||||
<n-button size="tiny" tertiary type="primary" @click.stop="copyText(autoLoginUrl)">
|
||||
{{ t('copySection') }}
|
||||
</n-button>
|
||||
</template>
|
||||
<div class="credential-section">
|
||||
<div class="credential-field">
|
||||
<code class="credential-code">{{ autoLoginUrl }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.credential-collapse {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.credential-panel {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.credential-title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.credential-section {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.credential-tip {
|
||||
margin: 0;
|
||||
color: var(--n-text-color-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.credential-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.credential-field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.credential-label {
|
||||
color: var(--n-text-color-2);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.credential-code {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
background: var(--n-color-embedded);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.credential-copy-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.credential-code-block {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.credential-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.credential-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.credential-copy-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -586,5 +586,32 @@ export const deMessages = {
|
||||
"views.admin.AccountSettings.tip": "Die folgenden Mehrfachauswahlwerte können manuell eingegeben und mit Enter hinzugefügt werden",
|
||||
"components.MailBox.emptyInbox": "Dein Posteingang ist leer",
|
||||
"views.index.SendMail.fromName": "Dein Name und deine Adresse; Namen leer lassen, um die E-Mail-Adresse zu verwenden",
|
||||
"views.admin.SendMail.fromName": "Dein Name und deine Adresse; Namen leer lassen, um die E-Mail-Adresse zu verwenden"
|
||||
"views.admin.SendMail.fromName": "Dein Name und deine Adresse; Namen leer lassen, um die E-Mail-Adresse zu verwenden",
|
||||
"components.AddressCredentialModal.addressCredential": "Adresszugangsdaten",
|
||||
"components.AddressCredentialModal.addressCredentialLabel": "Address JWT",
|
||||
"components.AddressCredentialModal.addressPassword": "Adresspasswort",
|
||||
"components.AddressCredentialModal.agentAccess": "AI Agent",
|
||||
"components.AddressCredentialModal.agentAccessTip": "Verwende dieses Postfach in einem AI Agent mit dem Address JWT und den parsed-mail APIs.",
|
||||
"components.AddressCredentialModal.agentConfig": "Agent-Konfiguration",
|
||||
"components.AddressCredentialModal.agentSkill": "Agent skill",
|
||||
"components.AddressCredentialModal.apiBase": "API-Basisadresse",
|
||||
"components.AddressCredentialModal.autoLoginLink": "Auto-Login-Link",
|
||||
"components.AddressCredentialModal.copyFailed": "Kopieren fehlgeschlagen",
|
||||
"components.AddressCredentialModal.copySection": "Kopieren",
|
||||
"components.AddressCredentialModal.copySuccess": "Kopiert",
|
||||
"components.AddressCredentialModal.currentAddress": "Aktuelle Adresse",
|
||||
"components.AddressCredentialModal.docs": "Dokumentation",
|
||||
"components.AddressCredentialModal.imapHost": "IMAP-Host",
|
||||
"components.AddressCredentialModal.imapPort": "IMAP-Port",
|
||||
"components.AddressCredentialModal.password": "Passwort",
|
||||
"components.AddressCredentialModal.plainOrProxyTls": "Klartext oder Proxy-TLS",
|
||||
"components.AddressCredentialModal.security": "Sicherheit",
|
||||
"components.AddressCredentialModal.smtpHost": "SMTP-Host",
|
||||
"components.AddressCredentialModal.smtpImapAccess": "SMTP / IMAP",
|
||||
"components.AddressCredentialModal.smtpImapTip": "Verwende diese Werte in Mail-Clients, nachdem der Administrator den SMTP/IMAP-Proxy konfiguriert hat. Als Passwort kannst du den hier angezeigten Address JWT oder ein vorhandenes Adresspasswort verwenden.",
|
||||
"components.AddressCredentialModal.smtpPort": "SMTP-Port",
|
||||
"components.AddressCredentialModal.starttls": "STARTTLS",
|
||||
"components.AddressCredentialModal.tip": "Verwende diese Zugangsdaten nur mit Clients und Agents, denen du vertraust.",
|
||||
"components.AddressCredentialModal.title": "Adresszugangsdaten & Verbindungsmethoden",
|
||||
"components.AddressCredentialModal.username": "Benutzername"
|
||||
}
|
||||
|
||||
@@ -586,5 +586,32 @@ export const esMessages = {
|
||||
"views.admin.AccountSettings.tip": "Puedes introducir manualmente los siguientes valores y pulsar Enter para añadirlos",
|
||||
"components.MailBox.emptyInbox": "Tu bandeja de entrada está vacía",
|
||||
"views.index.SendMail.fromName": "Tu nombre y dirección; deja el nombre vacío para usar el correo",
|
||||
"views.admin.SendMail.fromName": "Tu nombre y dirección; deja el nombre vacío para usar el correo"
|
||||
"views.admin.SendMail.fromName": "Tu nombre y dirección; deja el nombre vacío para usar el correo",
|
||||
"components.AddressCredentialModal.addressCredential": "Credencial de dirección",
|
||||
"components.AddressCredentialModal.addressCredentialLabel": "Address JWT",
|
||||
"components.AddressCredentialModal.addressPassword": "Contraseña de la dirección",
|
||||
"components.AddressCredentialModal.agentAccess": "AI Agent",
|
||||
"components.AddressCredentialModal.agentAccessTip": "Usa este buzón desde un AI Agent con el Address JWT y las APIs parsed-mail.",
|
||||
"components.AddressCredentialModal.agentConfig": "Configuración del agente",
|
||||
"components.AddressCredentialModal.agentSkill": "Agent skill",
|
||||
"components.AddressCredentialModal.apiBase": "Base de la API",
|
||||
"components.AddressCredentialModal.autoLoginLink": "Enlace de inicio automático",
|
||||
"components.AddressCredentialModal.copyFailed": "Error al copiar",
|
||||
"components.AddressCredentialModal.copySection": "Copiar",
|
||||
"components.AddressCredentialModal.copySuccess": "Copiado",
|
||||
"components.AddressCredentialModal.currentAddress": "Dirección actual",
|
||||
"components.AddressCredentialModal.docs": "Documentación",
|
||||
"components.AddressCredentialModal.imapHost": "Host IMAP",
|
||||
"components.AddressCredentialModal.imapPort": "Puerto IMAP",
|
||||
"components.AddressCredentialModal.password": "Contraseña",
|
||||
"components.AddressCredentialModal.plainOrProxyTls": "Texto plano o TLS del proxy",
|
||||
"components.AddressCredentialModal.security": "Seguridad",
|
||||
"components.AddressCredentialModal.smtpHost": "Host SMTP",
|
||||
"components.AddressCredentialModal.smtpImapAccess": "SMTP / IMAP",
|
||||
"components.AddressCredentialModal.smtpImapTip": "Usa estos valores en clientes de correo después de que el administrador configure el proxy SMTP/IMAP. Como contraseña puedes usar el Address JWT mostrado aquí o la contraseña de la dirección si la tienes.",
|
||||
"components.AddressCredentialModal.smtpPort": "Puerto SMTP",
|
||||
"components.AddressCredentialModal.starttls": "STARTTLS",
|
||||
"components.AddressCredentialModal.tip": "Usa estas credenciales solo con clientes y agentes de confianza.",
|
||||
"components.AddressCredentialModal.title": "Credenciales de dirección y métodos de conexión",
|
||||
"components.AddressCredentialModal.username": "Usuario"
|
||||
}
|
||||
|
||||
@@ -586,5 +586,32 @@ export const jaMessages = {
|
||||
"views.admin.AccountSettings.tip": "以下の複数選択項目は手動入力して Enter で追加できます",
|
||||
"components.MailBox.emptyInbox": "受信箱は空です",
|
||||
"views.index.SendMail.fromName": "あなたの名前とアドレス。名前を空欄にするとメールアドレスを使用します",
|
||||
"views.admin.SendMail.fromName": "あなたの名前とアドレス。名前を空欄にするとメールアドレスを使用します"
|
||||
"views.admin.SendMail.fromName": "あなたの名前とアドレス。名前を空欄にするとメールアドレスを使用します",
|
||||
"components.AddressCredentialModal.addressCredential": "アドレス認証情報",
|
||||
"components.AddressCredentialModal.addressCredentialLabel": "Address JWT",
|
||||
"components.AddressCredentialModal.addressPassword": "アドレスパスワード",
|
||||
"components.AddressCredentialModal.agentAccess": "AI Agent",
|
||||
"components.AddressCredentialModal.agentAccessTip": "AI Agent から Address JWT と parsed-mail API を使ってこのメールボックスを利用できます。",
|
||||
"components.AddressCredentialModal.agentConfig": "Agent 設定",
|
||||
"components.AddressCredentialModal.agentSkill": "Agent skill",
|
||||
"components.AddressCredentialModal.apiBase": "API ベース",
|
||||
"components.AddressCredentialModal.autoLoginLink": "自動ログインリンク",
|
||||
"components.AddressCredentialModal.copyFailed": "コピーに失敗しました",
|
||||
"components.AddressCredentialModal.copySection": "コピー",
|
||||
"components.AddressCredentialModal.copySuccess": "コピーしました",
|
||||
"components.AddressCredentialModal.currentAddress": "現在のアドレス",
|
||||
"components.AddressCredentialModal.docs": "ドキュメント",
|
||||
"components.AddressCredentialModal.imapHost": "IMAP ホスト",
|
||||
"components.AddressCredentialModal.imapPort": "IMAP ポート",
|
||||
"components.AddressCredentialModal.password": "パスワード",
|
||||
"components.AddressCredentialModal.plainOrProxyTls": "平文またはプロキシ側 TLS",
|
||||
"components.AddressCredentialModal.security": "セキュリティ",
|
||||
"components.AddressCredentialModal.smtpHost": "SMTP ホスト",
|
||||
"components.AddressCredentialModal.smtpImapAccess": "SMTP / IMAP",
|
||||
"components.AddressCredentialModal.smtpImapTip": "管理者が SMTP/IMAP プロキシを設定した後、メールクライアントでこれらの値を使用できます。パスワードにはここに表示される Address JWT、または手元にあるアドレスパスワードを使用できます。",
|
||||
"components.AddressCredentialModal.smtpPort": "SMTP ポート",
|
||||
"components.AddressCredentialModal.starttls": "STARTTLS",
|
||||
"components.AddressCredentialModal.tip": "これらの認証情報は信頼できるクライアントと Agent でのみ使用してください。",
|
||||
"components.AddressCredentialModal.title": "アドレス認証情報と接続方法",
|
||||
"components.AddressCredentialModal.username": "ユーザー名"
|
||||
}
|
||||
|
||||
@@ -586,5 +586,32 @@ export const ptBRMessages = {
|
||||
"views.admin.AccountSettings.tip": "Você pode inserir manualmente os seguintes valores e pressionar Enter para adicioná-los",
|
||||
"components.MailBox.emptyInbox": "Sua caixa de entrada está vazia",
|
||||
"views.index.SendMail.fromName": "Seu nome e endereço; deixe o nome em branco para usar o e-mail",
|
||||
"views.admin.SendMail.fromName": "Seu nome e endereço; deixe o nome em branco para usar o e-mail"
|
||||
"views.admin.SendMail.fromName": "Seu nome e endereço; deixe o nome em branco para usar o e-mail",
|
||||
"components.AddressCredentialModal.addressCredential": "Credencial do endereço",
|
||||
"components.AddressCredentialModal.addressCredentialLabel": "Address JWT",
|
||||
"components.AddressCredentialModal.addressPassword": "Senha do endereço",
|
||||
"components.AddressCredentialModal.agentAccess": "AI Agent",
|
||||
"components.AddressCredentialModal.agentAccessTip": "Use esta caixa de entrada em um AI Agent com o Address JWT e as APIs parsed-mail.",
|
||||
"components.AddressCredentialModal.agentConfig": "Configuração do Agent",
|
||||
"components.AddressCredentialModal.agentSkill": "Agent skill",
|
||||
"components.AddressCredentialModal.apiBase": "Base da API",
|
||||
"components.AddressCredentialModal.autoLoginLink": "Link de login automático",
|
||||
"components.AddressCredentialModal.copyFailed": "Falha ao copiar",
|
||||
"components.AddressCredentialModal.copySection": "Copiar",
|
||||
"components.AddressCredentialModal.copySuccess": "Copiado",
|
||||
"components.AddressCredentialModal.currentAddress": "Endereço atual",
|
||||
"components.AddressCredentialModal.docs": "Documentação",
|
||||
"components.AddressCredentialModal.imapHost": "Host IMAP",
|
||||
"components.AddressCredentialModal.imapPort": "Porta IMAP",
|
||||
"components.AddressCredentialModal.password": "Senha",
|
||||
"components.AddressCredentialModal.plainOrProxyTls": "Texto puro ou TLS do proxy",
|
||||
"components.AddressCredentialModal.security": "Segurança",
|
||||
"components.AddressCredentialModal.smtpHost": "Host SMTP",
|
||||
"components.AddressCredentialModal.smtpImapAccess": "SMTP / IMAP",
|
||||
"components.AddressCredentialModal.smtpImapTip": "Use estes valores em clientes de e-mail depois que o administrador configurar o proxy SMTP/IMAP. Como senha, use o Address JWT mostrado aqui ou a senha do endereço quando você a tiver.",
|
||||
"components.AddressCredentialModal.smtpPort": "Porta SMTP",
|
||||
"components.AddressCredentialModal.starttls": "STARTTLS",
|
||||
"components.AddressCredentialModal.tip": "Use estas credenciais somente com clientes e agents confiáveis.",
|
||||
"components.AddressCredentialModal.title": "Credenciais do endereço e métodos de conexão",
|
||||
"components.AddressCredentialModal.username": "Nome de usuário"
|
||||
}
|
||||
|
||||
@@ -281,6 +281,116 @@ export const MESSAGE_REGISTRY = {
|
||||
"zh": "用户地址"
|
||||
}
|
||||
},
|
||||
"components.AddressCredentialModal": {
|
||||
"addressCredential": {
|
||||
"en": "Address Credential",
|
||||
"zh": "地址凭证"
|
||||
},
|
||||
"addressCredentialLabel": {
|
||||
"en": "Address JWT",
|
||||
"zh": "Address JWT"
|
||||
},
|
||||
"addressPassword": {
|
||||
"en": "Address Password",
|
||||
"zh": "地址密码"
|
||||
},
|
||||
"agentAccess": {
|
||||
"en": "AI Agent",
|
||||
"zh": "AI Agent"
|
||||
},
|
||||
"agentAccessTip": {
|
||||
"en": "Use this mailbox from an AI agent with the Address JWT and parsed-mail APIs.",
|
||||
"zh": "AI Agent 可使用 Address JWT 和 parsed-mail API 读取这个邮箱。"
|
||||
},
|
||||
"agentConfig": {
|
||||
"en": "Agent config",
|
||||
"zh": "Agent 配置"
|
||||
},
|
||||
"agentSkill": {
|
||||
"en": "Agent skill",
|
||||
"zh": "Agent skill"
|
||||
},
|
||||
"apiBase": {
|
||||
"en": "API Base",
|
||||
"zh": "API 地址"
|
||||
},
|
||||
"autoLoginLink": {
|
||||
"en": "Auto-login link",
|
||||
"zh": "自动登录链接"
|
||||
},
|
||||
"copyFailed": {
|
||||
"en": "Copy failed",
|
||||
"zh": "复制失败"
|
||||
},
|
||||
"copySection": {
|
||||
"en": "Copy",
|
||||
"zh": "复制"
|
||||
},
|
||||
"copySuccess": {
|
||||
"en": "Copied",
|
||||
"zh": "已复制"
|
||||
},
|
||||
"currentAddress": {
|
||||
"en": "Current address",
|
||||
"zh": "当前邮箱"
|
||||
},
|
||||
"docs": {
|
||||
"en": "Docs",
|
||||
"zh": "文档"
|
||||
},
|
||||
"imapHost": {
|
||||
"en": "IMAP host",
|
||||
"zh": "IMAP 主机"
|
||||
},
|
||||
"imapPort": {
|
||||
"en": "IMAP port",
|
||||
"zh": "IMAP 端口"
|
||||
},
|
||||
"password": {
|
||||
"en": "Password",
|
||||
"zh": "密码"
|
||||
},
|
||||
"plainOrProxyTls": {
|
||||
"en": "Plain or proxy TLS",
|
||||
"zh": "明文或代理层 TLS"
|
||||
},
|
||||
"security": {
|
||||
"en": "Security",
|
||||
"zh": "安全"
|
||||
},
|
||||
"smtpHost": {
|
||||
"en": "SMTP host",
|
||||
"zh": "SMTP 主机"
|
||||
},
|
||||
"smtpImapAccess": {
|
||||
"en": "SMTP / IMAP",
|
||||
"zh": "SMTP / IMAP"
|
||||
},
|
||||
"smtpImapTip": {
|
||||
"en": "Use these values in mail clients after the administrator configures the SMTP/IMAP proxy. The password can be the Address JWT shown here, or the address password when you have it.",
|
||||
"zh": "管理员配置 SMTP/IMAP 代理后,可在邮件客户端中使用这些信息。密码可使用这里展示的 Address JWT,也可使用你持有的地址密码。"
|
||||
},
|
||||
"smtpPort": {
|
||||
"en": "SMTP port",
|
||||
"zh": "SMTP 端口"
|
||||
},
|
||||
"starttls": {
|
||||
"en": "STARTTLS",
|
||||
"zh": "STARTTLS"
|
||||
},
|
||||
"tip": {
|
||||
"en": "Use these credentials only with clients and agents you trust.",
|
||||
"zh": "请只在可信的客户端和 Agent 中使用这些凭证。"
|
||||
},
|
||||
"title": {
|
||||
"en": "Address Credentials & Connection Methods",
|
||||
"zh": "地址凭证与连接方式"
|
||||
},
|
||||
"username": {
|
||||
"en": "Username",
|
||||
"zh": "用户名"
|
||||
}
|
||||
},
|
||||
"views.user.UserMailBox": {
|
||||
"addressQueryTip": {
|
||||
"en": "Leave blank to query all addresses",
|
||||
@@ -817,8 +927,8 @@ export const MESSAGE_REGISTRY = {
|
||||
"zh": "密码不匹配"
|
||||
},
|
||||
"showAddressCredential": {
|
||||
"en": "Show Address Credential",
|
||||
"zh": "查看邮箱地址凭证"
|
||||
"en": "Credentials & Connection Methods",
|
||||
"zh": "地址凭证与连接方式"
|
||||
},
|
||||
"success": {
|
||||
"en": "Success",
|
||||
|
||||
@@ -61,8 +61,20 @@ router.beforeEach((to, from, next) => {
|
||||
preferredLocale.value = getPreferredLocale('', getBrowserLocales())
|
||||
}
|
||||
|
||||
if (to.query.jwt) {
|
||||
jwt.value = to.query.jwt
|
||||
if (Object.prototype.hasOwnProperty.call(to.query, 'jwt')) {
|
||||
const jwtQuery = Array.isArray(to.query.jwt) ? to.query.jwt[0] : to.query.jwt
|
||||
if (typeof jwtQuery === 'string') {
|
||||
jwt.value = jwtQuery
|
||||
}
|
||||
const query = { ...to.query }
|
||||
delete query.jwt
|
||||
next({
|
||||
path: to.path,
|
||||
query,
|
||||
hash: to.hash,
|
||||
replace: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (routeLocale) {
|
||||
|
||||
@@ -40,6 +40,19 @@ export const useGlobalState = createGlobalState(
|
||||
showGithub: true,
|
||||
disableAdminPasswordCheck: false,
|
||||
enableAddressPassword: false,
|
||||
enableAgentEmailInfo: false,
|
||||
smtpImapProxyConfig: {
|
||||
smtp: {
|
||||
host: '',
|
||||
port: 8025,
|
||||
starttls: false,
|
||||
},
|
||||
imap: {
|
||||
host: '',
|
||||
port: 11143,
|
||||
starttls: false,
|
||||
},
|
||||
},
|
||||
statusUrl: '',
|
||||
enableGlobalTurnstileCheck: false,
|
||||
})
|
||||
|
||||
@@ -110,7 +110,7 @@ onMounted(async () => {
|
||||
<n-modal v-model:show="showAdminPasswordModal" :closable="false" :closeOnEsc="false" :maskClosable="false"
|
||||
preset="dialog" :title="t('accessHeader')">
|
||||
<p>{{ t('accessTip') }}</p>
|
||||
<n-input v-model:value="tmpAdminAuth" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="tmpAdminAuth" type="password" show-password-on="click" @keyup.enter="authFunc" />
|
||||
<Turnstile ref="turnstileRef" v-if="openSettings.enableGlobalTurnstileCheck" v-model:value="cfToken" />
|
||||
<template #action>
|
||||
<n-button @click="authFunc" type="primary" :loading="loading">
|
||||
|
||||
@@ -314,7 +314,7 @@ onMounted(async () => {
|
||||
<n-modal v-model:show="showAuth" :closable="false" :closeOnEsc="false" :maskClosable="false" preset="dialog"
|
||||
:title="t('accessHeader')">
|
||||
<p>{{ t('accessTip') }}</p>
|
||||
<n-input v-model:value="auth" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="auth" type="password" show-password-on="click" @keyup.enter="authFunc" />
|
||||
<Turnstile ref="turnstileRef" v-if="openSettings.enableGlobalTurnstileCheck" v-model:value="cfToken" />
|
||||
<template #action>
|
||||
<n-button :loading="loading" @click="authFunc" type="primary">
|
||||
|
||||
@@ -5,8 +5,10 @@ import { useScopedI18n } from '@/i18n/app'
|
||||
|
||||
import { useGlobalState } from '../../store'
|
||||
import { api } from '../../api'
|
||||
import { hashPassword } from '../../utils'
|
||||
import { NButton, NMenu } from 'naive-ui';
|
||||
import { MenuFilled } from '@vicons/material'
|
||||
import AddressCredentialModal from '../../components/AddressCredentialModal.vue'
|
||||
|
||||
const {
|
||||
loading, adminTab, openSettings,
|
||||
@@ -18,6 +20,7 @@ const { t } = useScopedI18n('views.admin.Account')
|
||||
|
||||
const showEmailCredential = ref(false)
|
||||
const curEmailCredential = ref("")
|
||||
const curEmailAddress = ref("")
|
||||
const curDeleteAddressId = ref(0);
|
||||
const curClearInboxAddressId = ref(0);
|
||||
const curClearSentItemsAddressId = ref(0);
|
||||
@@ -46,14 +49,16 @@ const showDeleteAccount = ref(false)
|
||||
const showClearInbox = ref(false)
|
||||
const showClearSentItems = ref(false)
|
||||
|
||||
const showCredential = async (id) => {
|
||||
const showCredential = async (row) => {
|
||||
try {
|
||||
curEmailCredential.value = await api.adminShowAddressCredential(id)
|
||||
curEmailAddress.value = row.name
|
||||
curEmailCredential.value = await api.adminShowAddressCredential(row.id)
|
||||
showEmailCredential.value = true
|
||||
} catch (error) {
|
||||
message.error(error.message || "error");
|
||||
showEmailCredential.value = false
|
||||
curEmailCredential.value = ""
|
||||
curEmailAddress.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +103,16 @@ const clearSentItems = async () => {
|
||||
}
|
||||
|
||||
const resetPassword = async () => {
|
||||
const normalizedPassword = newPassword.value.trim()
|
||||
if (!normalizedPassword) {
|
||||
message.error(t("newPassword"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.fetch(`/admin/address/${curResetPasswordAddressId.value}/reset_password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
password: newPassword.value
|
||||
password: await hashPassword(normalizedPassword)
|
||||
})
|
||||
});
|
||||
message.success(t("passwordResetSuccess"));
|
||||
@@ -365,7 +375,7 @@ const columns = computed(() => [
|
||||
label: () => h(NButton,
|
||||
{
|
||||
text: true,
|
||||
onClick: () => showCredential(row.id)
|
||||
onClick: () => showCredential(row)
|
||||
},
|
||||
{ default: () => t('showCredential') }
|
||||
),
|
||||
@@ -467,19 +477,8 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div style="margin-top: 10px;">
|
||||
<n-modal v-model:show="showEmailCredential" preset="dialog" title="Dialog">
|
||||
<template #header>
|
||||
<div>{{ t("addressCredential") }}</div>
|
||||
</template>
|
||||
<span>
|
||||
<p>{{ t("addressCredentialTip") }}</p>
|
||||
</span>
|
||||
<n-card :bordered="false" embedded>
|
||||
<b>{{ curEmailCredential }}</b>
|
||||
</n-card>
|
||||
<template #action>
|
||||
</template>
|
||||
</n-modal>
|
||||
<AddressCredentialModal v-model:show="showEmailCredential" :address="curEmailAddress"
|
||||
:jwt="curEmailCredential" />
|
||||
<n-modal v-model:show="showDeleteAccount" preset="dialog" :title="t('deleteAccount')">
|
||||
<p>{{ t('deleteTip') }}</p>
|
||||
<template #action>
|
||||
@@ -507,7 +506,8 @@ onMounted(async () => {
|
||||
|
||||
<n-modal v-model:show="showResetPassword" preset="dialog" :title="t('resetPassword')">
|
||||
<n-form-item :label="t('newPassword')">
|
||||
<n-input v-model:value="newPassword" type="password" placeholder="" show-password-on="click" />
|
||||
<n-input v-model:value="newPassword" type="password" placeholder="" show-password-on="click"
|
||||
@keyup.enter="resetPassword" />
|
||||
</n-form-item>
|
||||
<template #action>
|
||||
<n-button :loading="loading" @click="resetPassword" size="small" tertiary type="info">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useScopedI18n } from '@/i18n/app'
|
||||
|
||||
import { useGlobalState } from '../../store'
|
||||
import { api } from '../../api'
|
||||
import AddressCredentialModal from '../../components/AddressCredentialModal.vue'
|
||||
|
||||
const {
|
||||
loading, openSettings,
|
||||
@@ -59,10 +60,6 @@ const newEmail = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getUrlWithJwt = () => {
|
||||
return `${window.location.origin}/?jwt=${result.value}`
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (openSettings.prefix) {
|
||||
enablePrefix.value = true
|
||||
@@ -73,27 +70,8 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div class="center">
|
||||
<n-modal v-model:show="showReultModal" preset="dialog" :title="t('addressCredential')">
|
||||
<span>
|
||||
<p>{{ t("addressCredentialTip") }}</p>
|
||||
</span>
|
||||
<n-card embedded>
|
||||
<b>{{ result }}</b>
|
||||
</n-card>
|
||||
<n-card embedded v-if="addressPassword">
|
||||
<p><b>{{ createdAddress }}</b></p>
|
||||
<p>{{ t('addressPassword') }}: <b>{{ addressPassword }}</b></p>
|
||||
</n-card>
|
||||
<n-card embedded>
|
||||
<n-collapse>
|
||||
<n-collapse-item :title='t("linkWithAddressCredential")'>
|
||||
<n-card embedded>
|
||||
<b>{{ getUrlWithJwt() }}</b>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
<AddressCredentialModal v-model:show="showReultModal" :address="createdAddress" :jwt="result"
|
||||
:address-password="addressPassword" />
|
||||
<n-card :bordered="false" embedded style="max-width: 600px;">
|
||||
<n-form-item-row v-if="openSettings.prefix" :label="t('enablePrefix')">
|
||||
<n-switch v-model:value="enablePrefix" :round="false" />
|
||||
|
||||
@@ -304,7 +304,8 @@ onMounted(async () => {
|
||||
<n-input v-model:value="user.email" />
|
||||
</n-form-item-row>
|
||||
<n-form-item-row :label="t('password')" required>
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click"
|
||||
@keyup.enter="createUser" />
|
||||
</n-form-item-row>
|
||||
</n-form>
|
||||
<template #action>
|
||||
@@ -315,7 +316,8 @@ onMounted(async () => {
|
||||
</n-modal>
|
||||
<n-modal v-model:show="showResetPassword" preset="dialog" :title="t('resetPassword')">
|
||||
<n-form-item-row :label="t('password')" required>
|
||||
<n-input v-model:value="newResetPassword" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="newResetPassword" type="password" show-password-on="click"
|
||||
@keyup.enter="resetPassword" />
|
||||
</n-form-item-row>
|
||||
<template #action>
|
||||
<n-button :loading="loading" @click="resetPassword" size="small" tertiary type="primary">
|
||||
|
||||
@@ -260,7 +260,8 @@ onMounted(async () => {
|
||||
<n-input v-model:value="loginAddress" />
|
||||
</n-form-item-row>
|
||||
<n-form-item-row :label="t('password')" required>
|
||||
<n-input v-model:value="loginPassword" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="loginPassword" type="password" show-password-on="click"
|
||||
@keyup.enter="login" />
|
||||
</n-form-item-row>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ const changePassword = async () => {
|
||||
|
||||
<template>
|
||||
<div class="center" v-if="settings.address">
|
||||
<n-card :bordered="false" embedded>
|
||||
<n-card :bordered="false" embedded class="account-card">
|
||||
<n-button @click="showAddressCredential = true" type="primary" secondary block strong>
|
||||
{{ t('showAddressCredential') }}
|
||||
</n-button>
|
||||
@@ -110,11 +110,13 @@ const changePassword = async () => {
|
||||
<n-button @click="showLogout = true" secondary block strong>
|
||||
{{ t('logout') }}
|
||||
</n-button>
|
||||
<n-divider v-if="openSettings.enableUserDeleteEmail" />
|
||||
<n-button v-if="openSettings.enableUserDeleteEmail" @click="showDeleteAccount = true" type="error" secondary
|
||||
block strong>
|
||||
{{ t('deleteAccount') }}
|
||||
</n-button>
|
||||
</n-card>
|
||||
|
||||
<n-modal v-model:show="showLogout" preset="dialog" :title="t('logout')">
|
||||
<p>{{ t('logoutConfirm') }}</p>
|
||||
<template #action>
|
||||
@@ -151,10 +153,12 @@ const changePassword = async () => {
|
||||
<n-modal v-model:show="showChangePassword" preset="dialog" :title="t('changePassword')">
|
||||
<n-form :model="{ newPassword, confirmPassword }">
|
||||
<n-form-item :label="t('newPassword')">
|
||||
<n-input v-model:value="newPassword" type="password" placeholder="" show-password-on="click" />
|
||||
<n-input v-model:value="newPassword" type="password" placeholder="" show-password-on="click"
|
||||
@keyup.enter="changePassword" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('confirmPassword')">
|
||||
<n-input v-model:value="confirmPassword" type="password" placeholder="" show-password-on="click" />
|
||||
<n-input v-model:value="confirmPassword" type="password" placeholder="" show-password-on="click"
|
||||
@keyup.enter="changePassword" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
@@ -172,13 +176,13 @@ const changePassword = async () => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.n-card {
|
||||
.account-card {
|
||||
max-width: 800px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.n-button {
|
||||
margin-top: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -12,6 +12,7 @@ import LocalAddress from './LocalAddress.vue'
|
||||
import AddressManagement from '../user/AddressManagement.vue'
|
||||
import { getRouterPathWithLang } from '../../utils'
|
||||
import AddressSelect from '../../components/AddressSelect.vue'
|
||||
import AddressCredentialModal from '../../components/AddressCredentialModal.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -24,10 +25,6 @@ const { locale, t } = useScopedI18n('views.index.AddressBar')
|
||||
|
||||
const showAddressManage = ref(false)
|
||||
|
||||
const getUrlWithJwt = () => {
|
||||
return `${window.location.origin}/?jwt=${jwt.value}`
|
||||
}
|
||||
|
||||
const onUserLogin = async () => {
|
||||
await router.push(getRouterPathWithLang("/user", locale.value))
|
||||
}
|
||||
@@ -78,27 +75,8 @@ onMounted(async () => {
|
||||
</n-button>
|
||||
</n-card>
|
||||
</div>
|
||||
<n-modal v-model:show="showAddressCredential" preset="dialog" :title="t('addressCredential')">
|
||||
<span>
|
||||
<p>{{ t("addressCredentialTip") }}</p>
|
||||
</span>
|
||||
<n-card embedded>
|
||||
<b>{{ jwt }}</b>
|
||||
</n-card>
|
||||
<n-card embedded v-if="addressPassword">
|
||||
<p><b>{{ settings.address }}</b></p>
|
||||
<p>{{ t('addressPassword') }}: <b>{{ addressPassword }}</b></p>
|
||||
</n-card>
|
||||
<n-card embedded>
|
||||
<n-collapse>
|
||||
<n-collapse-item :title='t("linkWithAddressCredential")'>
|
||||
<n-card embedded>
|
||||
<b>{{ getUrlWithJwt() }}</b>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
<AddressCredentialModal v-model:show="showAddressCredential" :address="settings.address" :jwt="jwt"
|
||||
:address-password="addressPassword" />
|
||||
<n-modal v-model:show="showAddressManage" preset="card" :title="t('addressManage')"
|
||||
style="width: 720px;">
|
||||
<TelegramAddress v-if="isTelegram" />
|
||||
|
||||
@@ -187,7 +187,8 @@ onMounted(async () => {
|
||||
<n-input v-model:value="user.email" />
|
||||
</n-form-item-row>
|
||||
<n-form-item-row :label="t('password')" required>
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click"
|
||||
@keyup.enter="emailLogin" />
|
||||
</n-form-item-row>
|
||||
<Turnstile ref="loginTurnstileRef" v-if="openSettings.enableGlobalTurnstileCheck" v-model:value="loginCfToken" />
|
||||
<n-button @click="emailLogin" type="primary" block secondary strong>
|
||||
@@ -218,7 +219,8 @@ onMounted(async () => {
|
||||
<n-input v-model:value="user.email" />
|
||||
</n-form-item-row>
|
||||
<n-form-item-row :label="t('password')" required>
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click"
|
||||
@keyup.enter="emailSignup" />
|
||||
</n-form-item-row>
|
||||
<Turnstile ref="signupTurnstileRef" v-if="userOpenSettings.enableMailVerify" v-model:value="signupCfToken" />
|
||||
<n-form-item-row v-if="userOpenSettings.enableMailVerify" :label="t('verifyCode')" required>
|
||||
@@ -244,7 +246,8 @@ onMounted(async () => {
|
||||
<n-input v-model:value="user.email" />
|
||||
</n-form-item-row>
|
||||
<n-form-item-row :label="t('password')" required>
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click" />
|
||||
<n-input v-model:value="user.password" type="password" show-password-on="click"
|
||||
@keyup.enter="emailSignup" />
|
||||
</n-form-item-row>
|
||||
<Turnstile ref="resetTurnstileRef" v-model:value="resetCfToken" />
|
||||
<n-form-item-row :label="t('verifyCode')" required>
|
||||
|
||||
Reference in New Issue
Block a user