fix: change quota notify threshold semantics to "remaining quota"
Threshold now represents remaining quota instead of usage amount: - Fixed ($): threshold=400, limit=1000 → alert when remaining drops to $400 (i.e., usage reaches $600) - Percentage (%): threshold=30%, limit=1000 → alert when remaining drops to 30% (i.e., usage reaches $700) Also: - Rename 告警阈值 → 提醒阈值 in i18n - Widen type dropdown to w-16 for proper $ / % display
This commit is contained in:
@@ -1 +1 @@
|
|||||||
0.1.110.31
|
0.1.110.34
|
||||||
|
|||||||
@@ -115,13 +115,18 @@ type quotaDim struct {
|
|||||||
limit float64
|
limit float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolvedThreshold returns the effective threshold value.
|
// resolvedThreshold converts the user-facing "remaining" threshold into a usage-based trigger point.
|
||||||
// For percentage type, it computes threshold = limit * percentage / 100.
|
// The threshold represents how much quota REMAINS when the alert fires:
|
||||||
|
// - Fixed ($): threshold=400, limit=1000 → fires when usage reaches 600 (remaining drops to 400)
|
||||||
|
// - Percentage (%): threshold=30, limit=1000 → fires when usage reaches 700 (remaining drops to 30%)
|
||||||
func (d quotaDim) resolvedThreshold() float64 {
|
func (d quotaDim) resolvedThreshold() float64 {
|
||||||
if d.thresholdType == thresholdTypePercentage && d.limit > 0 {
|
if d.limit <= 0 {
|
||||||
return d.limit * d.threshold / 100
|
return 0
|
||||||
}
|
}
|
||||||
return d.threshold
|
if d.thresholdType == thresholdTypePercentage {
|
||||||
|
return d.limit * (1 - d.threshold/100)
|
||||||
|
}
|
||||||
|
return d.limit - d.threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildQuotaDims returns the three quota dimensions for notification checking.
|
// buildQuotaDims returns the three quota dimensions for notification checking.
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
enabled: boolean | null
|
enabled: boolean | null
|
||||||
threshold: number | null
|
threshold: number | null
|
||||||
@@ -14,15 +10,10 @@ const emit = defineEmits<{
|
|||||||
'update:threshold': [value: number | null]
|
'update:threshold': [value: number | null]
|
||||||
'update:thresholdType': [value: string | null]
|
'update:thresholdType': [value: string | null]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
function toggleType(current: string | null) {
|
|
||||||
emit('update:thresholdType', current === 'percentage' ? 'fixed' : 'percentage')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-1.5">
|
||||||
<label class="text-sm text-gray-500 whitespace-nowrap">{{ t('admin.accounts.quotaNotify.alert') }}</label>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="emit('update:enabled', !enabled)"
|
@click="emit('update:enabled', !enabled)"
|
||||||
@@ -39,38 +30,23 @@ function toggleType(current: string | null) {
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<template v-if="enabled">
|
<template v-if="enabled">
|
||||||
<button
|
<input
|
||||||
type="button"
|
:value="threshold"
|
||||||
class="px-1.5 py-0.5 text-xs font-medium rounded border transition-colors"
|
@input="emit('update:threshold', parseFloat(($event.target as HTMLInputElement).value) || null)"
|
||||||
:class="(!thresholdType || thresholdType === 'fixed') ? 'bg-primary-100 text-primary-700 border-primary-300 dark:bg-primary-900/30 dark:text-primary-400 dark:border-primary-700' : 'bg-gray-100 text-gray-500 border-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:border-dark-500'"
|
type="number"
|
||||||
@click="toggleType(thresholdType)"
|
min="0"
|
||||||
|
:max="thresholdType === 'percentage' ? 100 : undefined"
|
||||||
|
:step="thresholdType === 'percentage' ? 1 : 0.01"
|
||||||
|
class="input py-1 text-sm flex-1 min-w-0"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
:value="thresholdType || 'fixed'"
|
||||||
|
@change="emit('update:thresholdType', ($event.target as HTMLSelectElement).value)"
|
||||||
|
class="input py-1 text-xs w-16 flex-shrink-0 text-center"
|
||||||
>
|
>
|
||||||
$
|
<option value="fixed">$</option>
|
||||||
</button>
|
<option value="percentage">%</option>
|
||||||
<button
|
</select>
|
||||||
type="button"
|
|
||||||
class="px-1.5 py-0.5 text-xs font-medium rounded border transition-colors"
|
|
||||||
:class="thresholdType === 'percentage' ? 'bg-primary-100 text-primary-700 border-primary-300 dark:bg-primary-900/30 dark:text-primary-400 dark:border-primary-700' : 'bg-gray-100 text-gray-500 border-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:border-dark-500'"
|
|
||||||
@click="toggleType(thresholdType)"
|
|
||||||
>
|
|
||||||
%
|
|
||||||
</button>
|
|
||||||
<div class="relative flex-1">
|
|
||||||
<input
|
|
||||||
:value="threshold"
|
|
||||||
@input="emit('update:threshold', parseFloat(($event.target as HTMLInputElement).value) || null)"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
:max="thresholdType === 'percentage' ? 100 : undefined"
|
|
||||||
:step="thresholdType === 'percentage' ? 1 : 0.01"
|
|
||||||
class="input py-1 text-sm w-full"
|
|
||||||
:class="thresholdType === 'percentage' ? 'pr-7' : 'pr-7'"
|
|
||||||
:placeholder="thresholdType === 'percentage' ? t('admin.accounts.quotaNotify.thresholdPlaceholder') : t('admin.accounts.quotaNotify.threshold')"
|
|
||||||
/>
|
|
||||||
<span class="absolute right-2.5 top-1/2 -translate-y-1/2 text-xs text-gray-400 pointer-events-none">
|
|
||||||
{{ thresholdType === 'percentage' ? '%' : '$' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2264,7 +2264,7 @@ export default {
|
|||||||
quotaLimitAmount: 'Total Limit',
|
quotaLimitAmount: 'Total Limit',
|
||||||
quotaLimitAmountHint: 'Cumulative spending limit. Does not auto-reset.',
|
quotaLimitAmountHint: 'Cumulative spending limit. Does not auto-reset.',
|
||||||
quotaNotify: {
|
quotaNotify: {
|
||||||
alert: 'Alert Threshold',
|
alert: 'Alert',
|
||||||
enabled: 'Enable Alert',
|
enabled: 'Enable Alert',
|
||||||
threshold: 'Alert Amount',
|
threshold: 'Alert Amount',
|
||||||
thresholdPlaceholder: 'Enter percentage',
|
thresholdPlaceholder: 'Enter percentage',
|
||||||
|
|||||||
@@ -2262,7 +2262,7 @@ export default {
|
|||||||
quotaLimitAmount: '总限额',
|
quotaLimitAmount: '总限额',
|
||||||
quotaLimitAmountHint: '累计消费上限,不会自动重置。',
|
quotaLimitAmountHint: '累计消费上限,不会自动重置。',
|
||||||
quotaNotify: {
|
quotaNotify: {
|
||||||
alert: '告警阈值',
|
alert: '提醒阈值',
|
||||||
enabled: '启用告警',
|
enabled: '启用告警',
|
||||||
threshold: '告警金额',
|
threshold: '告警金额',
|
||||||
thresholdPlaceholder: '输入百分比',
|
thresholdPlaceholder: '输入百分比',
|
||||||
|
|||||||
Reference in New Issue
Block a user