feat: Provider 异步删除、可配置密码策略、Hub 超时优化及多项改进

- 新增 Provider 异步删除任务系统,后台分阶段删除子资源并清理残留引用
- 新增可配置密码策略等级(weak/medium/strong),支持系统设置面板调整
- aether-hub 升级至 0.1.4,idle timeout 支持禁用(设为 0),worker 默认超时调整为 120s
- OAuth 手动续期增加 Redis 分布式锁,防止并发刷新冲突
- ProxyNode 心跳检测改为 asyncio.to_thread,避免阻塞事件循环
- 删除 ModelMultiSelect 和 useInvalidModels,MultiSelect 组件通用化
- 明确 allowed_providers/allowed_api_formats 的 NULL 与空数组语义
- 前端 StandaloneKeyFormDialog、UserFormDialog 等多处 UI 优化
- 新增 Alembic 迁移脚本清理 Provider 删除后的残留引用
- 补充相关测试用例
This commit is contained in:
fawney19
2026-03-12 01:11:35 +08:00
parent 0d770d1c4d
commit 6e51a3f45d
55 changed files with 3219 additions and 862 deletions

View File

@@ -226,6 +226,7 @@
v-model:open="showRegisterDialog"
:require-email-verification="requireEmailVerification"
:email-configured="emailConfigured"
:password-policy-level="passwordPolicyLevel"
@success="handleRegisterSuccess"
@switch-to-login="handleSwitchToLogin"
/>
@@ -241,6 +242,7 @@ import Label from '@/components/ui/label.vue'
import { useAuthStore } from '@/stores/auth'
import { useToast } from '@/composables/useToast'
import { useSiteInfo } from '@/composables/useSiteInfo'
import { normalizePasswordPolicyLevel, type PasswordPolicyLevel } from '@/utils/passwordPolicy'
import { isDemoMode, DEMO_ACCOUNTS } from '@/config/demo'
import RegisterDialog from './RegisterDialog.vue'
import { authApi } from '@/api/auth'
@@ -266,6 +268,7 @@ const isDemo = computed(() => isDemoMode())
const showRegisterDialog = ref(false)
const requireEmailVerification = ref(false)
const emailConfigured = ref(true) // 邮箱服务是否已配置
const passwordPolicyLevel = ref<PasswordPolicyLevel>('weak')
const allowRegistration = ref(false) // 由系统配置控制,默认关闭
// LDAP authentication settings
@@ -378,6 +381,7 @@ onMounted(async () => {
allowRegistration.value = !!regSettings.enable_registration
requireEmailVerification.value = !!regSettings.require_email_verification
emailConfigured.value = !!regSettings.email_configured
passwordPolicyLevel.value = normalizePasswordPolicyLevel(regSettings.password_policy_level)
localEnabled.value = authSettings.local_enabled
ldapEnabled.value = authSettings.ldap_enabled
@@ -402,6 +406,7 @@ onMounted(async () => {
allowRegistration.value = false
requireEmailVerification.value = false
emailConfigured.value = false
passwordPolicyLevel.value = 'weak'
localEnabled.value = true
ldapEnabled.value = false
ldapExclusive.value = false

View File

@@ -157,11 +157,23 @@
data-lpignore="true"
data-1p-ignore="true"
:name="`pwd-${formNonce}`"
placeholder="至少 6 个字符"
:placeholder="getPasswordPolicyPlaceholder(props.passwordPolicyLevel)"
required
class="-webkit-text-security-disc"
:disabled="isLoading"
/>
<p
v-if="passwordError"
class="text-xs text-destructive"
>
{{ passwordError }}
</p>
<p
v-else
class="text-xs text-muted-foreground"
>
{{ passwordHint }}
</p>
</div>
<!-- Confirm Password -->
@@ -223,6 +235,12 @@ import { ref, computed, watch, onUnmounted, nextTick } from 'vue'
import { authApi } from '@/api/auth'
import { useToast } from '@/composables/useToast'
import { parseApiError } from '@/utils/errorParser'
import {
getPasswordPolicyHint,
getPasswordPolicyPlaceholder,
validatePasswordByPolicy,
type PasswordPolicyLevel,
} from '@/utils/passwordPolicy'
import { Dialog } from '@/components/ui'
import Button from '@/components/ui/button.vue'
import Input from '@/components/ui/input.vue'
@@ -232,6 +250,7 @@ interface Props {
open?: boolean
requireEmailVerification?: boolean
emailConfigured?: boolean
passwordPolicyLevel?: PasswordPolicyLevel
}
interface Emits {
@@ -243,7 +262,8 @@ interface Emits {
const props = withDefaults(defineProps<Props>(), {
open: false,
requireEmailVerification: false,
emailConfigured: true
emailConfigured: true,
passwordPolicyLevel: 'weak'
})
const emit = defineEmits<Emits>()
@@ -386,6 +406,11 @@ const usernameError = computed(() => {
return ''
})
const passwordHint = computed(() => getPasswordPolicyHint(props.passwordPolicyLevel))
const passwordError = computed(() =>
validatePasswordByPolicy(formData.value.password, props.passwordPolicyLevel)
)
const canSubmit = computed(() => {
// 基本信息:用户名和密码必填
const hasBasicInfo =
@@ -410,8 +435,7 @@ const canSubmit = computed(() => {
return false
}
// Check password length
if (formData.value.password.length < 6) {
if (passwordError.value) {
return false
}
@@ -623,9 +647,8 @@ const handleSubmit = async () => {
return
}
// Validate password length
if (formData.value.password.length < 6) {
showError('密码长度至少 6 位', '密码过短')
if (passwordError.value) {
showError(passwordError.value, '密码错误')
return
}