diff --git a/backend/internal/service/account.go b/backend/internal/service/account.go index 0b225dac..6e5a768f 100644 --- a/backend/internal/service/account.go +++ b/backend/internal/service/account.go @@ -1432,6 +1432,14 @@ func (a *Account) getExtraString(key string) string { return "" } +// getExtraStringDefault 从 Extra 中读取指定 key 的字符串值,不存在时返回 defaultVal +func (a *Account) getExtraStringDefault(key, defaultVal string) string { + if v := a.getExtraString(key); v != "" { + return v + } + return defaultVal +} + // getExtraInt 从 Extra 中读取指定 key 的 int 值 func (a *Account) getExtraInt(key string) int { if a.Extra == nil { @@ -1498,6 +1506,10 @@ func (a *Account) GetQuotaNotifyDailyThreshold() float64 { return a.getExtraFloat64("quota_notify_daily_threshold") } +func (a *Account) GetQuotaNotifyDailyThresholdType() string { + return a.getExtraStringDefault("quota_notify_daily_threshold_type", "fixed") +} + func (a *Account) GetQuotaNotifyWeeklyEnabled() bool { return a.getExtraBool("quota_notify_weekly_enabled") } @@ -1506,6 +1518,10 @@ func (a *Account) GetQuotaNotifyWeeklyThreshold() float64 { return a.getExtraFloat64("quota_notify_weekly_threshold") } +func (a *Account) GetQuotaNotifyWeeklyThresholdType() string { + return a.getExtraStringDefault("quota_notify_weekly_threshold_type", "fixed") +} + func (a *Account) GetQuotaNotifyTotalEnabled() bool { return a.getExtraBool("quota_notify_total_enabled") } @@ -1514,6 +1530,10 @@ func (a *Account) GetQuotaNotifyTotalThreshold() float64 { return a.getExtraFloat64("quota_notify_total_threshold") } +func (a *Account) GetQuotaNotifyTotalThresholdType() string { + return a.getExtraStringDefault("quota_notify_total_threshold_type", "fixed") +} + // nextFixedDailyReset 计算在 after 之后的下一个每日固定重置时间点 func nextFixedDailyReset(hour int, tz *time.Location, after time.Time) time.Time { t := after.In(tz) diff --git a/backend/internal/service/balance_notify_service.go b/backend/internal/service/balance_notify_service.go index 0d7e4c09..65cec594 100644 --- a/backend/internal/service/balance_notify_service.go +++ b/backend/internal/service/balance_notify_service.go @@ -82,19 +82,29 @@ func (s *BalanceNotifyService) CheckBalanceAfterDeduction(ctx context.Context, u // quotaDim describes one quota dimension for notification checking. type quotaDim struct { - name string - enabled bool - threshold float64 - oldUsed float64 - limit float64 + name string + enabled bool + threshold float64 + thresholdType string // "fixed" (default) or "percentage" + oldUsed float64 + limit float64 +} + +// resolvedThreshold returns the effective threshold value. +// For percentage type, it computes threshold = limit * percentage / 100. +func (d quotaDim) resolvedThreshold() float64 { + if d.thresholdType == "percentage" && d.limit > 0 { + return d.limit * d.threshold / 100 + } + return d.threshold } // buildQuotaDims returns the three quota dimensions for notification checking. func buildQuotaDims(account *Account) []quotaDim { return []quotaDim{ - {quotaDimDaily, account.GetQuotaNotifyDailyEnabled(), account.GetQuotaNotifyDailyThreshold(), account.GetQuotaDailyUsed(), account.GetQuotaDailyLimit()}, - {quotaDimWeekly, account.GetQuotaNotifyWeeklyEnabled(), account.GetQuotaNotifyWeeklyThreshold(), account.GetQuotaWeeklyUsed(), account.GetQuotaWeeklyLimit()}, - {quotaDimTotal, account.GetQuotaNotifyTotalEnabled(), account.GetQuotaNotifyTotalThreshold(), account.GetQuotaUsed(), account.GetQuotaLimit()}, + {quotaDimDaily, account.GetQuotaNotifyDailyEnabled(), account.GetQuotaNotifyDailyThreshold(), account.GetQuotaNotifyDailyThresholdType(), account.GetQuotaDailyUsed(), account.GetQuotaDailyLimit()}, + {quotaDimWeekly, account.GetQuotaNotifyWeeklyEnabled(), account.GetQuotaNotifyWeeklyThreshold(), account.GetQuotaNotifyWeeklyThresholdType(), account.GetQuotaWeeklyUsed(), account.GetQuotaWeeklyLimit()}, + {quotaDimTotal, account.GetQuotaNotifyTotalEnabled(), account.GetQuotaNotifyTotalThreshold(), account.GetQuotaNotifyTotalThresholdType(), account.GetQuotaUsed(), account.GetQuotaLimit()}, } } @@ -104,6 +114,9 @@ func (s *BalanceNotifyService) CheckAccountQuotaAfterIncrement(ctx context.Conte if account == nil || s.emailService == nil || s.settingRepo == nil || cost <= 0 { return } + if !s.isAccountQuotaNotifyEnabled(ctx) { + return + } adminEmails := s.getAccountQuotaNotifyEmails(ctx) if len(adminEmails) == 0 { return @@ -114,22 +127,26 @@ func (s *BalanceNotifyService) CheckAccountQuotaAfterIncrement(ctx context.Conte if !dim.enabled || dim.threshold <= 0 { continue } + effectiveThreshold := dim.resolvedThreshold() + if effectiveThreshold <= 0 { + continue + } newUsed := dim.oldUsed + cost - if dim.oldUsed < dim.threshold && newUsed >= dim.threshold { - s.asyncSendQuotaAlert(adminEmails, account.Name, dim, newUsed, siteName) + if dim.oldUsed < effectiveThreshold && newUsed >= effectiveThreshold { + s.asyncSendQuotaAlert(adminEmails, account.Name, dim, newUsed, effectiveThreshold, siteName) } } } // asyncSendQuotaAlert sends quota alert email in a goroutine with panic recovery. -func (s *BalanceNotifyService) asyncSendQuotaAlert(adminEmails []string, accountName string, dim quotaDim, newUsed float64, siteName string) { +func (s *BalanceNotifyService) asyncSendQuotaAlert(adminEmails []string, accountName string, dim quotaDim, newUsed, effectiveThreshold float64, siteName string) { go func() { defer func() { if r := recover(); r != nil { slog.Error("panic in quota notification", "recover", r) } }() - s.sendQuotaAlertEmails(adminEmails, accountName, dim.name, newUsed, dim.limit, dim.threshold, siteName) + s.sendQuotaAlertEmails(adminEmails, accountName, dim.name, newUsed, dim.limit, effectiveThreshold, siteName) }() } @@ -149,6 +166,15 @@ func (s *BalanceNotifyService) getBalanceNotifyConfig(ctx context.Context) (enab return } +// isAccountQuotaNotifyEnabled checks the global account quota notification toggle. +func (s *BalanceNotifyService) isAccountQuotaNotifyEnabled(ctx context.Context) bool { + val, err := s.settingRepo.GetValue(ctx, SettingKeyAccountQuotaNotifyEnabled) + if err != nil { + return false + } + return val == "true" +} + // getAccountQuotaNotifyEmails reads admin notification emails from settings. func (s *BalanceNotifyService) getAccountQuotaNotifyEmails(ctx context.Context) []string { raw, err := s.settingRepo.GetValue(ctx, SettingKeyAccountQuotaNotifyEmails) diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index 2704e0d0..f07ddfd4 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -255,7 +255,8 @@ const ( SettingKeyBalanceLowNotifyThreshold = "balance_low_notify_threshold" // 默认阈值(USD) // Account Quota Notification - SettingKeyAccountQuotaNotifyEmails = "account_quota_notify_emails" // 管理员通知邮箱列表(JSON 数组) + SettingKeyAccountQuotaNotifyEnabled = "account_quota_notify_enabled" // 全局开关 + SettingKeyAccountQuotaNotifyEmails = "account_quota_notify_emails" // 管理员通知邮箱列表(JSON 数组) ) // AdminAPIKeyPrefix is the prefix for admin API keys (distinct from user "sk-" keys). diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index f0cf750a..abcae9c1 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -182,6 +182,8 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings SettingPaymentEnabled, SettingKeyOIDCConnectEnabled, SettingKeyOIDCConnectProviderName, + SettingKeyBalanceLowNotifyEnabled, + SettingKeyAccountQuotaNotifyEnabled, } settings, err := s.settingRepo.GetMultiple(ctx, keys) @@ -249,6 +251,8 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings PaymentEnabled: settings[SettingPaymentEnabled] == "true", OIDCOAuthEnabled: oidcEnabled, OIDCOAuthProviderName: oidcProviderName, + BalanceLowNotifyEnabled: settings[SettingKeyBalanceLowNotifyEnabled] == "true", + AccountQuotaNotifyEnabled: settings[SettingKeyAccountQuotaNotifyEnabled] == "true", }, nil } @@ -302,6 +306,8 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"` OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"` Version string `json:"version,omitempty"` + BalanceLowNotifyEnabled bool `json:"balance_low_notify_enabled"` + AccountQuotaNotifyEnabled bool `json:"account_quota_notify_enabled"` }{ RegistrationEnabled: settings.RegistrationEnabled, EmailVerifyEnabled: settings.EmailVerifyEnabled, @@ -332,6 +338,8 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any OIDCOAuthEnabled: settings.OIDCOAuthEnabled, OIDCOAuthProviderName: settings.OIDCOAuthProviderName, Version: s.version, + BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled, + AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled, }, nil } @@ -609,6 +617,7 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet // Balance low notification updates[SettingKeyBalanceLowNotifyEnabled] = strconv.FormatBool(settings.BalanceLowNotifyEnabled) updates[SettingKeyBalanceLowNotifyThreshold] = strconv.FormatFloat(settings.BalanceLowNotifyThreshold, 'f', 8, 64) + updates[SettingKeyAccountQuotaNotifyEnabled] = strconv.FormatBool(settings.AccountQuotaNotifyEnabled) accountQuotaNotifyEmailsJSON, err := json.Marshal(settings.AccountQuotaNotifyEmails) if err != nil { return fmt.Errorf("marshal account quota notify emails: %w", err) @@ -1251,7 +1260,8 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin result.BalanceLowNotifyThreshold = v } - // Account quota notification emails + // Account quota notification + result.AccountQuotaNotifyEnabled = settings[SettingKeyAccountQuotaNotifyEnabled] == "true" if raw := strings.TrimSpace(settings[SettingKeyAccountQuotaNotifyEmails]); raw != "" { var emails []string if err := json.Unmarshal([]byte(raw), &emails); err == nil { diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index debc2b19..b79b930a 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -112,7 +112,8 @@ type SystemSettings struct { BalanceLowNotifyThreshold float64 // Account quota notification - AccountQuotaNotifyEmails []string + AccountQuotaNotifyEnabled bool + AccountQuotaNotifyEmails []string } type DefaultSubscriptionSetting struct { @@ -152,6 +153,9 @@ type PublicSettings struct { OIDCOAuthEnabled bool OIDCOAuthProviderName string Version string + + BalanceLowNotifyEnabled bool + AccountQuotaNotifyEnabled bool } // StreamTimeoutSettings 流超时处理配置(仅控制超时后的处理方式,超时判定由网关配置控制) diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index 31284289..5c5de2d1 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -138,6 +138,7 @@ export interface SystemSettings { // Balance & quota notification balance_low_notify_enabled: boolean balance_low_notify_threshold: number + account_quota_notify_enabled: boolean account_quota_notify_emails: string[] } @@ -241,6 +242,7 @@ export interface UpdateSettingsRequest { // Balance & quota notification balance_low_notify_enabled?: boolean balance_low_notify_threshold?: number + account_quota_notify_enabled?: boolean account_quota_notify_emails?: string[] } diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue index 086575e6..abb9569e 100644 --- a/frontend/src/components/account/EditAccountModal.vue +++ b/frontend/src/components/account/EditAccountModal.vue @@ -1188,10 +1188,13 @@ :resetTimezone="editResetTimezone" :quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled" :quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold" + :quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType" :quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled" :quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold" + :quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType" :quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled" :quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold" + :quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType" @update:totalLimit="editQuotaLimit = $event" @update:dailyLimit="editQuotaDailyLimit = $event" @update:weeklyLimit="editQuotaWeeklyLimit = $event" @@ -1203,10 +1206,13 @@ @update:resetTimezone="editResetTimezone = $event" @update:quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled = $event" @update:quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold = $event" + @update:quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType = $event" @update:quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled = $event" @update:quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold = $event" + @update:quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType = $event" @update:quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled = $event" @update:quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold = $event" + @update:quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType = $event" /> @@ -1232,10 +1238,13 @@ :resetTimezone="editResetTimezone" :quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled" :quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold" + :quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType" :quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled" :quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold" + :quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType" :quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled" :quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold" + :quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType" @update:totalLimit="editQuotaLimit = $event" @update:dailyLimit="editQuotaDailyLimit = $event" @update:weeklyLimit="editQuotaWeeklyLimit = $event" @@ -1247,10 +1256,13 @@ @update:resetTimezone="editResetTimezone = $event" @update:quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled = $event" @update:quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold = $event" + @update:quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType = $event" @update:quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled = $event" @update:quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold = $event" + @update:quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType = $event" @update:quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled = $event" @update:quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold = $event" + @update:quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType = $event" /> @@ -1992,10 +2004,13 @@ const editWeeklyResetHour = ref(null) const editResetTimezone = ref(null) const editQuotaNotifyDailyEnabled = ref(null) const editQuotaNotifyDailyThreshold = ref(null) +const editQuotaNotifyDailyThresholdType = ref(null) const editQuotaNotifyWeeklyEnabled = ref(null) const editQuotaNotifyWeeklyThreshold = ref(null) +const editQuotaNotifyWeeklyThresholdType = ref(null) const editQuotaNotifyTotalEnabled = ref(null) const editQuotaNotifyTotalThreshold = ref(null) +const editQuotaNotifyTotalThresholdType = ref(null) const openAIWSModeOptions = computed(() => [ { value: OPENAI_WS_MODE_OFF, label: t('admin.accounts.openai.wsModeOff') }, // TODO: ctx_pool 选项暂时隐藏,待测试完成后恢复 @@ -2198,10 +2213,13 @@ const syncFormFromAccount = (newAccount: Account | null) => { // Load quota notify config editQuotaNotifyDailyEnabled.value = (extra?.quota_notify_daily_enabled as boolean) ?? null editQuotaNotifyDailyThreshold.value = (extra?.quota_notify_daily_threshold as number) ?? null + editQuotaNotifyDailyThresholdType.value = (extra?.quota_notify_daily_threshold_type as string) ?? null editQuotaNotifyWeeklyEnabled.value = (extra?.quota_notify_weekly_enabled as boolean) ?? null editQuotaNotifyWeeklyThreshold.value = (extra?.quota_notify_weekly_threshold as number) ?? null + editQuotaNotifyWeeklyThresholdType.value = (extra?.quota_notify_weekly_threshold_type as string) ?? null editQuotaNotifyTotalEnabled.value = (extra?.quota_notify_total_enabled as boolean) ?? null editQuotaNotifyTotalThreshold.value = (extra?.quota_notify_total_threshold as number) ?? null + editQuotaNotifyTotalThresholdType.value = (extra?.quota_notify_total_threshold_type as string) ?? null } else { editQuotaLimit.value = null editQuotaDailyLimit.value = null @@ -3262,9 +3280,11 @@ const handleSubmit = async () => { } else { delete newExtra.quota_notify_daily_threshold } + newExtra.quota_notify_daily_threshold_type = editQuotaNotifyDailyThresholdType.value || 'fixed' } else { delete newExtra.quota_notify_daily_enabled delete newExtra.quota_notify_daily_threshold + delete newExtra.quota_notify_daily_threshold_type } if (editQuotaNotifyWeeklyEnabled.value) { newExtra.quota_notify_weekly_enabled = true @@ -3273,9 +3293,11 @@ const handleSubmit = async () => { } else { delete newExtra.quota_notify_weekly_threshold } + newExtra.quota_notify_weekly_threshold_type = editQuotaNotifyWeeklyThresholdType.value || 'fixed' } else { delete newExtra.quota_notify_weekly_enabled delete newExtra.quota_notify_weekly_threshold + delete newExtra.quota_notify_weekly_threshold_type } if (editQuotaNotifyTotalEnabled.value) { newExtra.quota_notify_total_enabled = true @@ -3284,9 +3306,11 @@ const handleSubmit = async () => { } else { delete newExtra.quota_notify_total_threshold } + newExtra.quota_notify_total_threshold_type = editQuotaNotifyTotalThresholdType.value || 'fixed' } else { delete newExtra.quota_notify_total_enabled delete newExtra.quota_notify_total_threshold + delete newExtra.quota_notify_total_threshold_type } updatePayload.extra = newExtra } diff --git a/frontend/src/components/account/QuotaLimitCard.vue b/frontend/src/components/account/QuotaLimitCard.vue index 7c3afd23..64bdb08a 100644 --- a/frontend/src/components/account/QuotaLimitCard.vue +++ b/frontend/src/components/account/QuotaLimitCard.vue @@ -17,17 +17,23 @@ const props = withDefaults(defineProps<{ resetTimezone: string | null quotaNotifyDailyEnabled?: boolean | null quotaNotifyDailyThreshold?: number | null + quotaNotifyDailyThresholdType?: string | null quotaNotifyWeeklyEnabled?: boolean | null quotaNotifyWeeklyThreshold?: number | null + quotaNotifyWeeklyThresholdType?: string | null quotaNotifyTotalEnabled?: boolean | null quotaNotifyTotalThreshold?: number | null + quotaNotifyTotalThresholdType?: string | null }>(), { quotaNotifyDailyEnabled: null, quotaNotifyDailyThreshold: null, + quotaNotifyDailyThresholdType: null, quotaNotifyWeeklyEnabled: null, quotaNotifyWeeklyThreshold: null, + quotaNotifyWeeklyThresholdType: null, quotaNotifyTotalEnabled: null, quotaNotifyTotalThreshold: null, + quotaNotifyTotalThresholdType: null, }) const emit = defineEmits<{ @@ -42,10 +48,13 @@ const emit = defineEmits<{ 'update:resetTimezone': [value: string | null] 'update:quotaNotifyDailyEnabled': [value: boolean | null] 'update:quotaNotifyDailyThreshold': [value: number | null] + 'update:quotaNotifyDailyThresholdType': [value: string | null] 'update:quotaNotifyWeeklyEnabled': [value: boolean | null] 'update:quotaNotifyWeeklyThreshold': [value: number | null] + 'update:quotaNotifyWeeklyThresholdType': [value: string | null] 'update:quotaNotifyTotalEnabled': [value: boolean | null] 'update:quotaNotifyTotalThreshold': [value: number | null] + 'update:quotaNotifyTotalThresholdType': [value: string | null] }>() const enabled = computed(() => @@ -228,8 +237,10 @@ const onWeeklyModeChange = (e: Event) => { v-if="dailyLimit && dailyLimit > 0" :enabled="props.quotaNotifyDailyEnabled" :threshold="props.quotaNotifyDailyThreshold" + :threshold-type="props.quotaNotifyDailyThresholdType" @update:enabled="emit('update:quotaNotifyDailyEnabled', $event)" @update:threshold="emit('update:quotaNotifyDailyThreshold', $event)" + @update:threshold-type="emit('update:quotaNotifyDailyThresholdType', $event)" /> @@ -292,8 +303,10 @@ const onWeeklyModeChange = (e: Event) => { v-if="weeklyLimit && weeklyLimit > 0" :enabled="props.quotaNotifyWeeklyEnabled" :threshold="props.quotaNotifyWeeklyThreshold" + :threshold-type="props.quotaNotifyWeeklyThresholdType" @update:enabled="emit('update:quotaNotifyWeeklyEnabled', $event)" @update:threshold="emit('update:quotaNotifyWeeklyThreshold', $event)" + @update:threshold-type="emit('update:quotaNotifyWeeklyThresholdType', $event)" /> @@ -330,8 +343,10 @@ const onWeeklyModeChange = (e: Event) => { v-if="totalLimit && totalLimit > 0" :enabled="props.quotaNotifyTotalEnabled" :threshold="props.quotaNotifyTotalThreshold" + :threshold-type="props.quotaNotifyTotalThresholdType" @update:enabled="emit('update:quotaNotifyTotalEnabled', $event)" @update:threshold="emit('update:quotaNotifyTotalThreshold', $event)" + @update:threshold-type="emit('update:quotaNotifyTotalThresholdType', $event)" /> diff --git a/frontend/src/components/account/QuotaNotifyToggle.vue b/frontend/src/components/account/QuotaNotifyToggle.vue index 4634f5b1..b1c22fe2 100644 --- a/frontend/src/components/account/QuotaNotifyToggle.vue +++ b/frontend/src/components/account/QuotaNotifyToggle.vue @@ -6,12 +6,18 @@ const { t } = useI18n() defineProps<{ enabled: boolean | null threshold: number | null + thresholdType: string | null // "fixed" (default) or "percentage" }>() const emit = defineEmits<{ 'update:enabled': [value: boolean | null] 'update:threshold': [value: number | null] + 'update:thresholdType': [value: string | null] }>() + +function toggleType(current: string | null) { + emit('update:thresholdType', current === 'percentage' ? 'fixed' : 'percentage') +}