mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-07 17:00:20 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76d242e024 | ||
|
|
260c152166 | ||
|
|
9f4c1ef9f9 |
@@ -424,6 +424,16 @@ func isSensitiveKey(key string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whitelist: known non-sensitive fields that contain sensitive substrings
|
||||||
|
// (e.g., "max_tokens" contains "token" but is just an API parameter).
|
||||||
|
switch k {
|
||||||
|
case "max_tokens", "max_completion_tokens", "max_output_tokens",
|
||||||
|
"completion_tokens", "prompt_tokens", "total_tokens",
|
||||||
|
"input_tokens", "output_tokens",
|
||||||
|
"cache_creation_input_tokens", "cache_read_input_tokens":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Exact matches (common credential fields).
|
// Exact matches (common credential fields).
|
||||||
switch k {
|
switch k {
|
||||||
case "authorization",
|
case "authorization",
|
||||||
|
|||||||
@@ -491,7 +491,7 @@ async function checkServiceAndReload() {
|
|||||||
|
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/health', {
|
const response = await fetch('/health', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
cache: 'no-cache'
|
cache: 'no-cache'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -55,16 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Token Usage Trend Chart -->
|
<!-- Token Usage Trend Chart -->
|
||||||
<div class="card relative overflow-hidden p-4">
|
<TokenUsageTrend :trend-data="trend" :loading="loading" />
|
||||||
<div v-if="loading" class="absolute inset-0 z-10 flex items-center justify-center bg-white/50 backdrop-blur-sm dark:bg-dark-800/50">
|
|
||||||
<LoadingSpinner size="md" />
|
|
||||||
</div>
|
|
||||||
<h3 class="mb-4 text-sm font-semibold text-gray-900 dark:text-white">{{ t('dashboard.tokenUsageTrend') }}</h3>
|
|
||||||
<div class="h-48">
|
|
||||||
<Line v-if="trendData" :data="trendData" :options="lineOptions" />
|
|
||||||
<div v-else class="flex h-full items-center justify-center text-sm text-gray-500 dark:text-gray-400">{{ t('dashboard.noDataAvailable') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -75,7 +66,8 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'
|
||||||
import DateRangePicker from '@/components/common/DateRangePicker.vue'
|
import DateRangePicker from '@/components/common/DateRangePicker.vue'
|
||||||
import Select from '@/components/common/Select.vue'
|
import Select from '@/components/common/Select.vue'
|
||||||
import { Line, Doughnut } from 'vue-chartjs'
|
import { Doughnut } from 'vue-chartjs'
|
||||||
|
import TokenUsageTrend from '@/components/charts/TokenUsageTrend.vue'
|
||||||
import type { TrendDataPoint, ModelStat } from '@/types'
|
import type { TrendDataPoint, ModelStat } from '@/types'
|
||||||
import { formatCostFixed as formatCost, formatNumberLocaleString as formatNumber, formatTokensK as formatTokens } from '@/utils/format'
|
import { formatCostFixed as formatCost, formatNumberLocaleString as formatNumber, formatTokensK as formatTokens } from '@/utils/format'
|
||||||
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, ArcElement, Title, Tooltip, Legend, Filler } from 'chart.js'
|
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, ArcElement, Title, Tooltip, Legend, Filler } from 'chart.js'
|
||||||
@@ -93,28 +85,6 @@ const modelData = computed(() => !props.models?.length ? null : {
|
|||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
|
|
||||||
const trendData = computed(() => !props.trend?.length ? null : {
|
|
||||||
labels: props.trend.map((d: TrendDataPoint) => d.date),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: t('dashboard.input'),
|
|
||||||
data: props.trend.map((d: TrendDataPoint) => d.input_tokens),
|
|
||||||
borderColor: '#3b82f6',
|
|
||||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
||||||
tension: 0.3,
|
|
||||||
fill: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('dashboard.output'),
|
|
||||||
data: props.trend.map((d: TrendDataPoint) => d.output_tokens),
|
|
||||||
borderColor: '#10b981',
|
|
||||||
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
||||||
tension: 0.3,
|
|
||||||
fill: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const doughnutOptions = {
|
const doughnutOptions = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
@@ -127,25 +97,4 @@ const doughnutOptions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineOptions = {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: { display: true, position: 'top' as const },
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: (context: any) => `${context.dataset.label}: ${formatTokens(context.parsed.y)} tokens`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
ticks: {
|
|
||||||
callback: (value: any) => formatTokens(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user