([])
+ const loading = ref(false)
+ const params = reactive({ ...(initialParams || {}) } as P)
+ const pagination = reactive({
+ page: 1,
+ page_size: pageSize,
+ total: 0,
+ pages: 0
+ })
+
+ let abortController: AbortController | null = null
+
+ const isAbortError = (error: any) => {
+ return error?.name === 'AbortError' || error?.code === 'ERR_CANCELED'
+ }
+
+ const load = async () => {
+ if (abortController) {
+ abortController.abort()
+ }
+ abortController = new AbortController()
+ loading.value = true
+
+ try {
+ const response = await fetchFn(
+ pagination.page,
+ pagination.page_size,
+ params,
+ { signal: abortController.signal }
+ )
+
+ items.value = response.items
+ pagination.total = response.total
+ pagination.pages = response.pages
+ } catch (error) {
+ if (!isAbortError(error)) {
+ throw error
+ }
+ } finally {
+ if (abortController?.signal.aborted === false) {
+ loading.value = false
+ }
+ }
+ }
+
+ const reload = () => {
+ pagination.page = 1
+ return load()
+ }
+
+ const debouncedLoad = useDebounceFn(reload, debounceMs)
+
+ const handlePageChange = (page: number) => {
+ pagination.page = page
+ load()
+ }
+
+ const handlePageSizeChange = (size: number) => {
+ pagination.page_size = size
+ reload()
+ }
+
+ onUnmounted(() => {
+ abortController?.abort()
+ })
+
+ return {
+ items,
+ loading,
+ params,
+ pagination,
+ load,
+ reload,
+ debouncedLoad,
+ handlePageChange,
+ handlePageSizeChange
+ }
+}
diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue
index d684e085..e1f66cb3 100644
--- a/frontend/src/views/admin/AccountsView.vue
+++ b/frontend/src/views/admin/AccountsView.vue
@@ -1,974 +1,64 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('admin.accounts.createAccount') }}
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
- {{ t('admin.accounts.bulkActions.selected', { count: selectedAccountIds.length }) }}
-
-
- {{ t('admin.accounts.bulkActions.selectCurrentPage') }}
-
- âĸ
-
- {{ t('admin.accounts.bulkActions.clear') }}
-
-
-
-
-
-
-
- {{ t('admin.accounts.bulkActions.delete') }}
-
-
-
-
-
- {{ t('admin.accounts.bulkActions.edit') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ value }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ row.current_concurrency || 0 }}
- /
- {{ row.concurrency }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- {{ value }}
-
-
-
-
- {{ formatRelativeTime(value) }}
-
-
-
-
-
-
-
-
-
-
- {{ t('common.edit') }}
-
-
-
-
-
-
-
- {{ t('common.delete') }}
-
-
-
-
-
-
-
-
-
-
+
+
+
+ {{ value }}
+
+ {{ t('common.edit') }} {{ t('common.more') }}
-
-
-
-
+
-
-
- { loadAccounts(); if (onboardingStore.isCurrentStep(`[data-tour='account-form-submit']`)) onboardingStore.nextStep(500) }"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('admin.accounts.testConnection') }}
-
-
-
- {{ t('admin.accounts.viewStats') }}
-
-
-
-
- {{ t('admin.accounts.reAuthorize') }}
-
-
-
- {{ t('admin.accounts.refreshToken') }}
-
-
-
-
-
-
-
- {{ t('admin.accounts.resetStatus') }}
-
-
-
- {{ t('admin.accounts.clearRateLimit') }}
-
-
-
-
-
-
+
+
+
+
+
diff --git a/frontend/src/views/admin/UsageView.vue b/frontend/src/views/admin/UsageView.vue
index ac5d1e05..8d3fe19f 100644
--- a/frontend/src/views/admin/UsageView.vue
+++ b/frontend/src/views/admin/UsageView.vue
@@ -1,1436 +1,70 @@
-
-
-
-
-
-
-
-
- {{ t('usage.totalRequests') }}
-
-
- {{ usageStats?.total_requests?.toLocaleString() || '0' }}
-
-
- {{ t('usage.inSelectedRange') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('usage.totalTokens') }}
-
-
- {{ formatTokens(usageStats?.total_tokens || 0) }}
-
-
- {{ t('usage.in') }}: {{ formatTokens(usageStats?.total_input_tokens || 0) }} /
- {{ t('usage.out') }}: {{ formatTokens(usageStats?.total_output_tokens || 0) }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('usage.totalCost') }}
-
-
- ${{ (usageStats?.total_actual_cost || 0).toFixed(4) }}
-
-
- ${{ (usageStats?.total_cost || 0).toFixed(4) }}
- {{ t('usage.standardCost') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('usage.avgDuration') }}
-
-
- {{ formatDuration(usageStats?.average_duration_ms || 0) }}
-
-
{{ t('usage.perRequest') }}
-
-
-
-
-
-
-
-
-
-
-
{{ t('admin.dashboard.granularity') }}:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ t('admin.usage.userFilter') }}
-
-
-
-
-
-
-
-
-
-
- {{ t('common.loading') }}
-
-
- {{ t('common.noOptionsFound') }}
-
-
- {{ user.email }}
- #{{ user.id }}
-
-
-
-
-
-
-
- {{ t('usage.apiKeyFilter') }}
-
-
-
-
-
- {{ t('usage.model') }}
-
-
-
-
-
- {{ t('admin.usage.account') }}
-
-
-
-
-
- {{ t('usage.type') }}
-
-
-
-
-
- {{ t('usage.billingType') }}
-
-
-
-
-
- {{ t('admin.usage.group') }}
-
-
-
-
-
- {{ t('usage.timeRange') }}
-
-
-
-
-
-
- {{ t('common.reset') }}
-
-
- {{ t('usage.exportExcel') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{
- row.user?.email || '-'
- }}
- #{{ row.user_id }}
-
-
-
-
- {{
- row.api_key?.name || '-'
- }}
-
-
-
- {{
- row.account?.name || '-'
- }}
-
-
-
- {{ value }}
-
-
-
-
- {{ row.group.name }}
-
- -
-
-
-
-
- {{ row.stream ? t('usage.stream') : t('usage.sync') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{
- row.input_tokens.toLocaleString()
- }}
-
-
-
-
-
-
-
{{
- row.output_tokens.toLocaleString()
- }}
-
-
-
-
-
-
-
-
-
-
{{
- formatCacheTokens(row.cache_read_tokens)
- }}
-
-
-
-
-
-
-
{{
- formatCacheTokens(row.cache_creation_tokens)
- }}
-
-
-
-
-
-
-
-
-
-
-
- ${{ row.actual_cost.toFixed(6) }}
-
-
-
-
-
-
-
-
- {{ row.billing_type === 1 ? t('usage.subscription') : t('usage.balance') }}
-
-
-
-
-
- {{ formatDuration(row.first_token_ms) }}
-
- -
-
-
-
- {{
- formatDuration(row.duration_ms)
- }}
-
-
-
- {{
- formatDateTime(value)
- }}
-
-
-
-
-
- {{ row.request_id }}
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
Token æįģ
-
- {{ t('admin.usage.inputTokens') }}
- {{ tokenTooltipData.input_tokens.toLocaleString() }}
-
-
- {{ t('admin.usage.outputTokens') }}
- {{ tokenTooltipData.output_tokens.toLocaleString() }}
-
-
- {{ t('admin.usage.cacheCreationTokens') }}
- {{ tokenTooltipData.cache_creation_tokens.toLocaleString() }}
-
-
- {{ t('admin.usage.cacheReadTokens') }}
- {{ tokenTooltipData.cache_read_tokens.toLocaleString() }}
-
-
-
-
- {{ t('usage.totalTokens') }}
- {{ ((tokenTooltipData?.input_tokens || 0) + (tokenTooltipData?.output_tokens || 0) + (tokenTooltipData?.cache_creation_tokens || 0) + (tokenTooltipData?.cache_read_tokens || 0)).toLocaleString() }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ææŦæįģ
-
- {{ t('admin.usage.inputCost') }}
- ${{ tooltipData.input_cost.toFixed(6) }}
-
-
- {{ t('admin.usage.outputCost') }}
- ${{ tooltipData.output_cost.toFixed(6) }}
-
-
- {{ t('admin.usage.cacheCreationCost') }}
- ${{ tooltipData.cache_creation_cost.toFixed(6) }}
-
-
- {{ t('admin.usage.cacheReadCost') }}
- ${{ tooltipData.cache_read_cost.toFixed(6) }}
-
-
-
-
- {{ t('usage.rate') }}
- {{ (tooltipData?.rate_multiplier || 1).toFixed(2) }}x
-
-
- {{ t('usage.original') }}
- ${{ tooltipData?.total_cost.toFixed(6) }}
-
-
- {{ t('usage.billed') }}
- ${{ tooltipData?.actual_cost.toFixed(6) }}
-
-
-
-
-
-
-
+
+onMounted(() => { loadLogs(); loadStats() })
+onUnmounted(() => { abortController?.abort(); exportAbortController?.abort() })
+
\ No newline at end of file
diff --git a/frontend/src/views/admin/UsersView.vue b/frontend/src/views/admin/UsersView.vue
index ca543c4b..2ee8af08 100644
--- a/frontend/src/views/admin/UsersView.vue
+++ b/frontend/src/views/admin/UsersView.vue
@@ -1,2206 +1,222 @@
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
- {{ t('admin.users.filterSettings') }}
-
-
-
-
-
- {{ filter.name }}
-
-
-
-
-
-
-
-
- {{ attr.name }}
-
-
-
-
+
{{ t('admin.users.filterSettings') }}
+
+
{{ f.name }}
+
+
{{ a.name }}
-
-
-
-
-
-
- {{ t('admin.users.columnSettings') }}
-
-
-
-
- {{ col.label }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('admin.users.attributes.configButton') }}
-
-
-
-
-
-
- {{ t('admin.users.createUser') }}
-
+
{{ t('admin.users.attributes.configButton') }}
+
{{ t('admin.users.createUser') }}
-
-
-
-
-
- {{ value.charAt(0).toUpperCase() }}
-
-
-
{{ value }}
-
-
-
-
- {{ value || '-' }}
-
-
-
-
-
- {{ value.length > 30 ? value.substring(0, 25) + '...' : value }}
-
- -
-
-
-
-
-
-
-
- {{ getAttributeValue(row.id, def.id) }}
-
-
-
-
-
-
- {{ t('admin.users.roles.' + value) }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('admin.users.noSubscription') }}
-
-
-
-
- ${{ value.toFixed(2) }}
-
-
-
-
-
- {{ t('admin.users.today') }}:
-
- ${{ (usageStats[row.id]?.today_actual_cost ?? 0).toFixed(4) }}
-
-
-
- {{ t('admin.users.total') }}:
-
- ${{ (usageStats[row.id]?.total_actual_cost ?? 0).toFixed(4) }}
-
-
-
-
-
-
- {{ value }}
-
-
-
-
-
-
- {{ t('admin.accounts.status.' + (value === 'disabled' ? 'inactive' : value)) }}
-
-
-
-
-
- {{ formatDateTime(value) }}
-
-
-
-
-
-
-
-
-
+ {{ value.charAt(0).toUpperCase() }}
{{ value }}
+ {{ t('admin.users.roles.' + value) }}
+ ${{ value.toFixed(2) }}
+ {{ t('admin.accounts.status.' + (value === 'disabled' ? 'inactive' : value)) }}
+ {{ formatDateTime(value) }}
+
-
-
+
-
-
+
-
-
-
-
-
- {{ t('admin.users.apiKeys') }}
-
-
-
-
-
-
-
- {{ t('admin.users.groups') }}
-
-
-
-
-
-
-
-
-
- {{ t('admin.users.deposit') }}
-
-
-
-
-
-
-
- {{ t('admin.users.withdraw') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ user.status === 'active' ? t('admin.users.disable') : t('admin.users.enable') }}
-
-
-
-
-
-
-
- {{ t('common.delete') }}
-
+ {{ t('admin.users.apiKeys') }}
+ {{ t('admin.users.groups') }}
+
+ {{ t('admin.users.deposit') }}
+ {{ t('admin.users.withdraw') }}
+
+ {{ user.status === 'active' ? t('admin.users.disable') : t('admin.users.enable') }}
+ {{ t('common.delete') }}
-
-
-
-
-
-
-
- {{ t('common.cancel') }}
-
-
-
-
-
-
- {{ submitting ? t('admin.users.creating') : t('common.create') }}
-
-
-
-
-
-
-
-
-
- {{ t('admin.users.email') }}
-
-
-
-
{{ t('admin.users.password') }}
-
- {{ t('admin.users.leaveEmptyToKeep') }}
-
-
-
-
- {{ t('admin.users.username') }}
-
-
-
-
{{ t('admin.users.notes') }}
-
-
{{ t('admin.users.notesHint') }}
-
-
- {{ t('admin.users.columns.concurrency') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('common.cancel') }}
-
-
-
-
-
-
- {{ submitting ? t('admin.users.updating') : t('common.update') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ viewingUser.email.charAt(0).toUpperCase() }}
-
-
-
-
{{ viewingUser.email }}
-
{{ viewingUser.username }}
-
-
-
-
-
-
-
-
-
-
- {{ t('admin.users.noApiKeys') }}
-
-
-
-
-
-
-
- {{ key.name }}
-
- {{ key.status }}
-
-
-
- {{ key.key.substring(0, 20) }}...{{ key.key.substring(key.key.length - 8) }}
-
-
-
-
-
-
-
-
-
{{ t('admin.users.group') }}:
- {{ key.group?.name || t('admin.users.none') }}
-
-
-
-
-
-
{{ t('admin.users.columns.created') }}: {{ formatDateTime(key.created_at) }}
-
-
-
-
-
-
-
-
-
- {{ t('common.cancel') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ allowedGroupsUser.email.charAt(0).toUpperCase() }}
-
-
-
-
{{ allowedGroupsUser.email }}
-
-
-
-
-
-
-
-
-
- {{ t('admin.users.allowedGroupsHint') }}
-
-
-
-
-
-
-
-
- {{ t('admin.users.noStandardGroups') }}
-
-
-
-
-
-
-
-
-
{{ group.name }}
-
- {{ group.description }}
-
-
-
- {{ group.platform }}
- {{
- t('admin.groups.exclusive')
- }}
-
-
-
-
-
-
-
-
-
-
- {{ t('admin.users.allowAllGroups') }}
-
-
- {{ t('admin.users.allowAllGroupsHint') }}
-
-
-
-
-
-
-
-
-
-
- {{ t('common.cancel') }}
-
-
-
-
-
-
- {{ savingAllowedGroups ? t('common.saving') : t('common.save') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ balanceUser.email.charAt(0).toUpperCase() }}
-
-
-
-
{{ balanceUser.email }}
-
- {{ t('admin.users.currentBalance') }}: ${{ balanceUser.balance.toFixed(2) }}
-
-
-
-
-
-
- {{
- balanceOperation === 'add'
- ? t('admin.users.depositAmount')
- : t('admin.users.withdrawAmount')
- }}
-
-
-
- {{ t('admin.users.amountHint') }}
-
-
-
-
-
{{ t('admin.users.notes') }}
-
-
{{ t('admin.users.notesOptional') }}
-
-
-
-
- {{ t('admin.users.newBalance') }}:
-
- ${{ calculateNewBalance().toFixed(2) }}
-
-
-
-
-
-
-
-
-
-
{{ t('admin.users.insufficientBalance') }}
-
-
-
-
-
-
-
-
- {{ t('common.cancel') }}
-
-
-
-
-
-
- {{
- balanceSubmitting
- ? balanceOperation === 'add'
- ? t('admin.users.depositing')
- : t('admin.users.withdrawing')
- : balanceOperation === 'add'
- ? t('admin.users.confirmDeposit')
- : t('admin.users.confirmWithdraw')
- }}
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+onMounted(async () => { await loadAttributeDefinitions(); loadSavedFilters(); loadSavedColumns(); loadUsers(); document.addEventListener('click', handleClickOutside) })
+onUnmounted(() => { abortController?.abort(); document.removeEventListener('click', handleClickOutside) })
+
\ No newline at end of file
diff --git a/frontend/src/views/user/DashboardView.vue b/frontend/src/views/user/DashboardView.vue
index 419c9502..ef406bea 100644
--- a/frontend/src/views/user/DashboardView.vue
+++ b/frontend/src/views/user/DashboardView.vue
@@ -1,661 +1,13 @@
-
-
-
-
-
+
-
-
-
-
-
-
-
-
- {{ t('dashboard.balance') }}
-
-
- ${{ formatBalance(user?.balance || 0) }}
-
-
{{ t('common.available') }}
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.apiKeys') }}
-
-
- {{ stats.total_api_keys }}
-
-
- {{ stats.active_api_keys }} {{ t('common.active') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.todayRequests') }}
-
-
- {{ stats.today_requests }}
-
-
- {{ t('common.total') }}: {{ formatNumber(stats.total_requests) }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.todayCost') }}
-
-
- ${{ formatCost(stats.today_actual_cost) }}
-
- / ${{ formatCost(stats.today_cost) }}
-
-
- {{ t('common.total') }}:
- ${{ formatCost(stats.total_actual_cost) }}
-
- / ${{ formatCost(stats.total_cost) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.todayTokens') }}
-
-
- {{ formatTokens(stats.today_tokens) }}
-
-
- {{ t('dashboard.input') }}: {{ formatTokens(stats.today_input_tokens) }} /
- {{ t('dashboard.output') }}: {{ formatTokens(stats.today_output_tokens) }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.totalTokens') }}
-
-
- {{ formatTokens(stats.total_tokens) }}
-
-
- {{ t('dashboard.input') }}: {{ formatTokens(stats.total_input_tokens) }} /
- {{ t('dashboard.output') }}: {{ formatTokens(stats.total_output_tokens) }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.performance') }}
-
-
-
- {{ formatTokens(stats.rpm) }}
-
-
RPM
-
-
-
- {{ formatTokens(stats.tpm) }}
-
-
TPM
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.avgResponse') }}
-
-
- {{ formatDuration(stats.average_duration_ms) }}
-
-
- {{ t('dashboard.averageTime') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.timeRange') }}:
-
-
-
-
{{ t('dashboard.granularity') }}:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.modelDistribution') }}
-
-
-
-
-
- {{ t('dashboard.noDataAvailable') }}
-
-
-
-
-
-
- {{ t('dashboard.model') }}
- {{ t('dashboard.requests') }}
- {{ t('dashboard.tokens') }}
- {{ t('dashboard.actual') }}
- {{ t('dashboard.standard') }}
-
-
-
-
-
- {{ model.model }}
-
-
- {{ formatNumber(model.requests) }}
-
-
- {{ formatTokens(model.total_tokens) }}
-
-
- ${{ formatCost(model.actual_cost) }}
-
-
- ${{ formatCost(model.cost) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.tokenUsageTrend') }}
-
-
-
-
- {{ t('dashboard.noDataAvailable') }}
-
-
-
-
-
-
-
+
+
-
-
-
-
-
- {{ t('dashboard.recentUsage') }}
-
- {{ t('dashboard.last7Days') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ log.model }}
-
-
- {{ formatDateTime(log.created_at) }}
-
-
-
-
-
- ${{ formatCost(log.actual_cost) }}
-
- / ${{ formatCost(log.total_cost) }}
-
-
- {{ (log.input_tokens + log.output_tokens).toLocaleString() }} tokens
-
-
-
-
-
- {{ t('dashboard.viewAllUsage') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.quickActions') }}
-
-
-
-
-
-
-
- {{ t('dashboard.createApiKey') }}
-
-
- {{ t('dashboard.generateNewKey') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.viewUsage') }}
-
-
- {{ t('dashboard.checkDetailedLogs') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dashboard.redeemCode') }}
-
-
- {{ t('dashboard.addBalanceWithCode') }}
-
-
-
-
-
-
-
-
-
+
+
@@ -663,393 +15,22 @@
-
-