diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION index ab6fbb6e..76129f5c 100644 --- a/backend/cmd/server/VERSION +++ b/backend/cmd/server/VERSION @@ -1 +1 @@ -0.1.110.42 +0.1.110.44 diff --git a/backend/internal/service/balance_notify_service.go b/backend/internal/service/balance_notify_service.go index 3951e88f..5191a26e 100644 --- a/backend/internal/service/balance_notify_service.go +++ b/backend/internal/service/balance_notify_service.go @@ -257,7 +257,7 @@ func (s *BalanceNotifyService) asyncSendQuotaAlert(adminEmails []string, account slog.Error("panic in quota notification", "recover", r) } }() - s.sendQuotaAlertEmails(adminEmails, accountID, accountName, platform, dim.name, newUsed, dim.limit, effectiveThreshold, siteName) + s.sendQuotaAlertEmails(adminEmails, accountID, accountName, platform, dim, newUsed, siteName) }() } @@ -384,15 +384,25 @@ func (s *BalanceNotifyService) sendBalanceLowEmails(recipients []string, userNam } // sendQuotaAlertEmails sends quota alert notification to admin emails. -func (s *BalanceNotifyService) sendQuotaAlertEmails(adminEmails []string, accountID int64, accountName, platform, dimension string, used, limit, threshold float64, siteName string) { - dimLabel := quotaDimLabels[dimension] +func (s *BalanceNotifyService) sendQuotaAlertEmails(adminEmails []string, accountID int64, accountName, platform string, dim quotaDim, used float64, siteName string) { + dimLabel := quotaDimLabels[dim.name] if dimLabel == "" { - dimLabel = dimension + dimLabel = dim.name + } + + // Format the remaining-based threshold for display + thresholdDisplay := fmt.Sprintf("$%.2f", dim.threshold) + if dim.thresholdType == thresholdTypePercentage { + thresholdDisplay = fmt.Sprintf("%.0f%%", dim.threshold) + } + remaining := dim.limit - used + if remaining < 0 { + remaining = 0 } subject := fmt.Sprintf("[%s] 账号限额告警 / Account Quota Alert - %s", sanitizeEmailHeader(siteName), sanitizeEmailHeader(accountName)) - body := s.buildQuotaAlertEmailBody(accountID, html.EscapeString(accountName), html.EscapeString(platform), html.EscapeString(dimLabel), used, limit, threshold, html.EscapeString(siteName)) - s.sendEmails(adminEmails, subject, body, "account", accountName, "dimension", dimension) + body := s.buildQuotaAlertEmailBody(accountID, html.EscapeString(accountName), html.EscapeString(platform), html.EscapeString(dimLabel), used, dim.limit, remaining, thresholdDisplay, html.EscapeString(siteName)) + s.sendEmails(adminEmails, subject, body, "account", accountName, "dimension", dim.name) } // sanitizeEmailHeader removes CR/LF characters to prevent SMTP header injection. @@ -440,7 +450,7 @@ const balanceLowEmailTemplate = ` ` // quotaAlertEmailTemplate is the HTML template for account quota alert notifications. -// Format args: siteName, accountID, accountName, platform, dimLabel, used, limitStr, threshold. +// Format args: siteName, accountID, accountName, platform, dimLabel, used, limitStr, remaining, thresholdDisplay. const quotaAlertEmailTemplate = ` @@ -469,10 +479,11 @@ const quotaAlertEmailTemplate = `
维度 / Dimension%s
已使用 / Used$%.2f
限额 / Limit%s
-
告警阈值 / Threshold$%.2f
+
剩余额度 / Remaining$%.2f
+
提醒阈值 / Alert Threshold%s
-

账号配额用量已达到告警阈值,请及时关注。

-

Account quota usage has reached the alert threshold.

+

账号剩余额度已低于提醒阈值,请及时关注。

+

Account remaining quota has fallen below the alert threshold.

@@ -490,11 +501,11 @@ func (s *BalanceNotifyService) buildBalanceLowEmailBody(userName string, balance } // buildQuotaAlertEmailBody builds HTML email for account quota alert. -func (s *BalanceNotifyService) buildQuotaAlertEmailBody(accountID int64, accountName, platform, dimLabel string, used, limit, threshold float64, siteName string) string { +func (s *BalanceNotifyService) buildQuotaAlertEmailBody(accountID int64, accountName, platform, dimLabel string, used, limit, remaining float64, thresholdDisplay, siteName string) string { limitStr := fmt.Sprintf("$%.2f", limit) if limit <= 0 { limitStr = "无限制 / Unlimited" } - return fmt.Sprintf(quotaAlertEmailTemplate, siteName, accountID, accountName, platform, dimLabel, used, limitStr, threshold) + return fmt.Sprintf(quotaAlertEmailTemplate, siteName, accountID, accountName, platform, dimLabel, used, limitStr, remaining, thresholdDisplay) } diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index a1496fa8..ba7bad51 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -1493,6 +1493,15 @@ :dailyLimit="editQuotaDailyLimit" :weeklyLimit="editQuotaWeeklyLimit" :quotaNotifyGlobalEnabled="quotaNotifyGlobalEnabled" + :quotaNotifyDailyEnabled="quotaNotifyDailyEnabled" + :quotaNotifyDailyThreshold="quotaNotifyDailyThreshold" + :quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType" + :quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled" + :quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold" + :quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType" + :quotaNotifyTotalEnabled="quotaNotifyTotalEnabled" + :quotaNotifyTotalThreshold="quotaNotifyTotalThreshold" + :quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType" :dailyResetMode="editDailyResetMode" :dailyResetHour="editDailyResetHour" :weeklyResetMode="editWeeklyResetMode" @@ -1502,6 +1511,15 @@ @update:totalLimit="editQuotaLimit = $event" @update:dailyLimit="editQuotaDailyLimit = $event" @update:weeklyLimit="editQuotaWeeklyLimit = $event" + @update:quotaNotifyDailyEnabled="quotaNotifyDailyEnabled = $event" + @update:quotaNotifyDailyThreshold="quotaNotifyDailyThreshold = $event" + @update:quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType = $event" + @update:quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled = $event" + @update:quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold = $event" + @update:quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType = $event" + @update:quotaNotifyTotalEnabled="quotaNotifyTotalEnabled = $event" + @update:quotaNotifyTotalThreshold="quotaNotifyTotalThreshold = $event" + @update:quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType = $event" @update:dailyResetMode="editDailyResetMode = $event" @update:dailyResetHour="editDailyResetHour = $event" @update:weeklyResetMode="editWeeklyResetMode = $event" @@ -1527,6 +1545,15 @@ :dailyLimit="editQuotaDailyLimit" :weeklyLimit="editQuotaWeeklyLimit" :quotaNotifyGlobalEnabled="quotaNotifyGlobalEnabled" + :quotaNotifyDailyEnabled="quotaNotifyDailyEnabled" + :quotaNotifyDailyThreshold="quotaNotifyDailyThreshold" + :quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType" + :quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled" + :quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold" + :quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType" + :quotaNotifyTotalEnabled="quotaNotifyTotalEnabled" + :quotaNotifyTotalThreshold="quotaNotifyTotalThreshold" + :quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType" :dailyResetMode="editDailyResetMode" :dailyResetHour="editDailyResetHour" :weeklyResetMode="editWeeklyResetMode" @@ -1536,6 +1563,15 @@ @update:totalLimit="editQuotaLimit = $event" @update:dailyLimit="editQuotaDailyLimit = $event" @update:weeklyLimit="editQuotaWeeklyLimit = $event" + @update:quotaNotifyDailyEnabled="quotaNotifyDailyEnabled = $event" + @update:quotaNotifyDailyThreshold="quotaNotifyDailyThreshold = $event" + @update:quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType = $event" + @update:quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled = $event" + @update:quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold = $event" + @update:quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType = $event" + @update:quotaNotifyTotalEnabled="quotaNotifyTotalEnabled = $event" + @update:quotaNotifyTotalThreshold="quotaNotifyTotalThreshold = $event" + @update:quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType = $event" @update:dailyResetMode="editDailyResetMode = $event" @update:dailyResetHour="editDailyResetHour = $event" @update:weeklyResetMode="editWeeklyResetMode = $event" @@ -3041,6 +3077,15 @@ const anthropicPassthroughEnabled = ref(false) const webSearchEmulationMode = ref('default') const webSearchGlobalEnabled = ref(false) const quotaNotifyGlobalEnabled = ref(false) +const quotaNotifyDailyEnabled = ref(null) +const quotaNotifyDailyThreshold = ref(null) +const quotaNotifyDailyThresholdType = ref(null) +const quotaNotifyWeeklyEnabled = ref(null) +const quotaNotifyWeeklyThreshold = ref(null) +const quotaNotifyWeeklyThresholdType = ref(null) +const quotaNotifyTotalEnabled = ref(null) +const quotaNotifyTotalThreshold = ref(null) +const quotaNotifyTotalThresholdType = ref(null) // Load global feature states once adminAPI.settings.getWebSearchEmulationConfig().then(cfg => { @@ -4153,6 +4198,22 @@ const createAccountAndFinish = async ( if (editDailyResetMode.value === 'fixed' || editWeeklyResetMode.value === 'fixed') { quotaExtra.quota_reset_timezone = editResetTimezone.value || 'UTC' } + // Quota notify config + if (quotaNotifyDailyEnabled.value) { + quotaExtra.quota_notify_daily_enabled = true + if (quotaNotifyDailyThreshold.value != null) quotaExtra.quota_notify_daily_threshold = quotaNotifyDailyThreshold.value + quotaExtra.quota_notify_daily_threshold_type = quotaNotifyDailyThresholdType.value || 'fixed' + } + if (quotaNotifyWeeklyEnabled.value) { + quotaExtra.quota_notify_weekly_enabled = true + if (quotaNotifyWeeklyThreshold.value != null) quotaExtra.quota_notify_weekly_threshold = quotaNotifyWeeklyThreshold.value + quotaExtra.quota_notify_weekly_threshold_type = quotaNotifyWeeklyThresholdType.value || 'fixed' + } + if (quotaNotifyTotalEnabled.value) { + quotaExtra.quota_notify_total_enabled = true + if (quotaNotifyTotalThreshold.value != null) quotaExtra.quota_notify_total_threshold = quotaNotifyTotalThreshold.value + quotaExtra.quota_notify_total_threshold_type = quotaNotifyTotalThresholdType.value || 'fixed' + } if (Object.keys(quotaExtra).length > 0) { finalExtra = quotaExtra } diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue index 613738d2..92761b35 100644 --- a/frontend/src/components/account/EditAccountModal.vue +++ b/frontend/src/components/account/EditAccountModal.vue @@ -2,7 +2,7 @@
{
- -
+ +
{{ t('admin.accounts.quotaDailyLimit') }} - {{ t('admin.accounts.quotaNotify.alert') }} + {{ t('admin.accounts.quotaNotify.alert') }}
+
-
+
$
@@ -238,12 +239,13 @@ const onWeeklyModeChange = (e: Event) => {
-
+
{{ t('admin.accounts.quotaWeeklyLimit') }} - {{ t('admin.accounts.quotaNotify.alert') }} + {{ t('admin.accounts.quotaNotify.alert') }}
+
-
+
$
@@ -287,12 +289,13 @@ const onWeeklyModeChange = (e: Event) => {
-
+
{{ t('admin.accounts.quotaTotalLimit') }} - {{ t('admin.accounts.quotaNotify.alert') }} + {{ t('admin.accounts.quotaNotify.alert') }}
+
-
+
$