feat(notify): add percentage threshold type for balance low notification
- Add threshold_type field (fixed/percentage) to system and user settings - Add total_recharged field to users table, auto-incremented on balance credit - Percentage mode: effective threshold = total_recharged × percentage / 100 - User-level threshold_type inherits from system default when not set - Update admin settings UI with radio selector (fixed amount / percentage) - Migration: 102_add_balance_notify_threshold_type.sql
This commit is contained in:
@@ -2660,6 +2660,90 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Balance Low Notification -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
<h3 class="text-base font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.settings.balanceNotify.title') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.balanceNotify.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="px-6 py-6 space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="mb-0 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.settings.balanceNotify.enabled') }}</label>
|
||||
<Toggle v-model="form.balance_low_notify_enabled" />
|
||||
</div>
|
||||
<div v-if="form.balance_low_notify_enabled" class="space-y-3">
|
||||
<!-- Threshold type selector -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.settings.balanceNotify.thresholdType') }}</label>
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center gap-1.5 text-sm cursor-pointer">
|
||||
<input type="radio" v-model="form.balance_low_notify_threshold_type" value="fixed" class="accent-primary-500" />
|
||||
{{ t('admin.settings.balanceNotify.typeFixed') }}
|
||||
</label>
|
||||
<label class="flex items-center gap-1.5 text-sm cursor-pointer">
|
||||
<input type="radio" v-model="form.balance_low_notify_threshold_type" value="percentage" class="accent-primary-500" />
|
||||
{{ t('admin.settings.balanceNotify.typePercentage') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Threshold value -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.settings.balanceNotify.threshold') }}</label>
|
||||
<div class="relative">
|
||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
{{ form.balance_low_notify_threshold_type === 'percentage' ? '%' : '$' }}
|
||||
</span>
|
||||
<input
|
||||
v-model.number="form.balance_low_notify_threshold"
|
||||
type="number"
|
||||
:min="0"
|
||||
:max="form.balance_low_notify_threshold_type === 'percentage' ? 100 : undefined"
|
||||
:step="form.balance_low_notify_threshold_type === 'percentage' ? 1 : 0.01"
|
||||
class="input pl-7"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ form.balance_low_notify_threshold_type === 'percentage'
|
||||
? t('admin.settings.balanceNotify.percentageHint')
|
||||
: t('admin.settings.balanceNotify.thresholdHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Account Quota Notification -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
<h3 class="text-base font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.settings.quotaNotify.title') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.quotaNotify.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="px-6 py-6 space-y-4">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.settings.quotaNotify.emails') }}</label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(_, index) in (form.account_quota_notify_emails || [])" :key="index" class="flex items-center gap-2">
|
||||
<input v-model="form.account_quota_notify_emails[index]" type="email" class="input flex-1" />
|
||||
<button @click="form.account_quota_notify_emails.splice(index, 1)" class="btn btn-secondary px-2" type="button">
|
||||
<Icon name="x" size="xs" class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<button @click="addQuotaNotifyEmail" class="btn btn-secondary btn-sm" type="button">
|
||||
+ {{ t('admin.settings.quotaNotify.addEmail') }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ t('admin.settings.quotaNotify.emailsHint') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /Tab: Email -->
|
||||
|
||||
<!-- Tab: Backup -->
|
||||
@@ -2939,7 +3023,12 @@ const form = reactive<SettingsForm>({
|
||||
// Gateway forwarding behavior
|
||||
enable_fingerprint_unification: true,
|
||||
enable_metadata_passthrough: false,
|
||||
enable_cch_signing: false
|
||||
enable_cch_signing: false,
|
||||
// Balance & quota notification
|
||||
balance_low_notify_enabled: false,
|
||||
balance_low_notify_threshold_type: 'fixed' as 'fixed' | 'percentage',
|
||||
balance_low_notify_threshold: 0,
|
||||
account_quota_notify_emails: [] as string[]
|
||||
})
|
||||
|
||||
// Proxies for web search emulation ProxySelector
|
||||
@@ -3149,6 +3238,14 @@ function handleRegistrationEmailSuffixWhitelistPaste(event: ClipboardEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
// Quota notify email helpers
|
||||
const addQuotaNotifyEmail = () => {
|
||||
if (!form.account_quota_notify_emails) {
|
||||
form.account_quota_notify_emails = []
|
||||
}
|
||||
form.account_quota_notify_emails.push('')
|
||||
}
|
||||
|
||||
// LinuxDo OAuth redirect URL suggestion
|
||||
const linuxdoRedirectUrlSuggestion = computed(() => {
|
||||
if (typeof window === 'undefined') return ''
|
||||
@@ -3488,6 +3585,11 @@ async function saveSettings() {
|
||||
payment_cancel_rate_limit_window: Number(form.payment_cancel_rate_limit_window) || 1,
|
||||
payment_cancel_rate_limit_unit: form.payment_cancel_rate_limit_unit,
|
||||
payment_cancel_rate_limit_window_mode: form.payment_cancel_rate_limit_window_mode,
|
||||
// Balance & quota notification
|
||||
balance_low_notify_enabled: form.balance_low_notify_enabled,
|
||||
balance_low_notify_threshold_type: form.balance_low_notify_threshold_type,
|
||||
balance_low_notify_threshold: Number(form.balance_low_notify_threshold) || 0,
|
||||
account_quota_notify_emails: (form.account_quota_notify_emails || []).filter((e: string) => e.trim() !== ''),
|
||||
}
|
||||
|
||||
const updated = await adminAPI.settings.updateSettings(payload)
|
||||
|
||||
Reference in New Issue
Block a user