Commit 0743652d authored by shaw's avatar shaw
Browse files

Merge branch 'feature/ui-and-backend-improvements'

parents f133b051 96bec5c9
...@@ -203,8 +203,7 @@ ...@@ -203,8 +203,7 @@
<!-- 主要操作:编辑和删除(始终显示) --> <!-- 主要操作:编辑和删除(始终显示) -->
<button <button
@click="handleEdit(row)" @click="handleEdit(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
:title="t('common.edit')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -219,12 +218,12 @@ ...@@ -219,12 +218,12 @@
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
/> />
</svg> </svg>
<span class="text-xs">{{ t('common.edit') }}</span>
</button> </button>
<button <button
v-if="row.role !== 'admin'" v-if="row.role !== 'admin'"
@click="handleDelete(row)" @click="handleDelete(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
:title="t('common.delete')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -239,6 +238,7 @@ ...@@ -239,6 +238,7 @@
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/> />
</svg> </svg>
<span class="text-xs">{{ t('common.delete') }}</span>
</button> </button>
<!-- 次要操作:展开时显示 --> <!-- 次要操作:展开时显示 -->
...@@ -248,16 +248,11 @@ ...@@ -248,16 +248,11 @@
v-if="row.role !== 'admin'" v-if="row.role !== 'admin'"
@click="handleToggleStatus(row)" @click="handleToggleStatus(row)"
:class="[ :class="[
'rounded-lg p-2 transition-colors', 'flex flex-col items-center gap-0.5 rounded-lg p-1.5 transition-colors',
row.status === 'active' row.status === 'active'
? 'text-gray-500 hover:bg-orange-50 hover:text-orange-600 dark:hover:bg-orange-900/20 dark:hover:text-orange-400' ? 'text-gray-500 hover:bg-orange-50 hover:text-orange-600 dark:hover:bg-orange-900/20 dark:hover:text-orange-400'
: 'text-gray-500 hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400' : 'text-gray-500 hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400'
]" ]"
:title="
row.status === 'active'
? t('admin.users.disableUser')
: t('admin.users.enableUser')
"
> >
<svg <svg
v-if="row.status === 'active'" v-if="row.status === 'active'"
...@@ -287,12 +282,12 @@ ...@@ -287,12 +282,12 @@
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/> />
</svg> </svg>
<span class="text-xs">{{ row.status === 'active' ? t('admin.users.disable') : t('admin.users.enable') }}</span>
</button> </button>
<!-- Allowed Groups --> <!-- Allowed Groups -->
<button <button
@click="handleAllowedGroups(row)" @click="handleAllowedGroups(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
:title="t('admin.users.setAllowedGroups')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -307,12 +302,12 @@ ...@@ -307,12 +302,12 @@
d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"
/> />
</svg> </svg>
<span class="text-xs">{{ t('admin.users.groups') }}</span>
</button> </button>
<!-- View API Keys --> <!-- View API Keys -->
<button <button
@click="handleViewApiKeys(row)" @click="handleViewApiKeys(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-purple-50 hover:text-purple-600 dark:hover:bg-purple-900/20 dark:hover:text-purple-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-purple-50 hover:text-purple-600 dark:hover:bg-purple-900/20 dark:hover:text-purple-400"
:title="t('admin.users.viewApiKeys')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -324,15 +319,15 @@ ...@@ -324,15 +319,15 @@
<path <path
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1221.75 8.25z" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"
/> />
</svg> </svg>
<span class="text-xs">{{ t('admin.users.apiKeys') }}</span>
</button> </button>
<!-- Deposit --> <!-- Deposit -->
<button <button
@click="handleDeposit(row)" @click="handleDeposit(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-emerald-50 hover:text-emerald-600 dark:hover:bg-emerald-900/20 dark:hover:text-emerald-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-emerald-50 hover:text-emerald-600 dark:hover:bg-emerald-900/20 dark:hover:text-emerald-400"
:title="t('admin.users.deposit')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -343,12 +338,12 @@ ...@@ -343,12 +338,12 @@
> >
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg> </svg>
<span class="text-xs">{{ t('admin.users.deposit') }}</span>
</button> </button>
<!-- Withdraw --> <!-- Withdraw -->
<button <button
@click="handleWithdraw(row)" @click="handleWithdraw(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
:title="t('admin.users.withdraw')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -359,6 +354,7 @@ ...@@ -359,6 +354,7 @@
> >
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" /> <path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
</svg> </svg>
<span class="text-xs">{{ t('admin.users.withdraw') }}</span>
</button> </button>
</template> </template>
</div> </div>
......
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
v-model="formData.database.password" v-model="formData.database.password"
type="password" type="password"
class="input" class="input"
placeholder="Password" :placeholder="t('setup.database.passwordPlaceholder')"
/> />
</div> </div>
</div> </div>
...@@ -234,7 +234,7 @@ ...@@ -234,7 +234,7 @@
v-model="formData.redis.password" v-model="formData.redis.password"
type="password" type="password"
class="input" class="input"
placeholder="Password" :placeholder="t('setup.redis.passwordPlaceholder')"
/> />
</div> </div>
<div> <div>
...@@ -320,7 +320,7 @@ ...@@ -320,7 +320,7 @@
v-model="formData.admin.password" v-model="formData.admin.password"
type="password" type="password"
class="input" class="input"
placeholder="Min 6 characters" :placeholder="t('setup.admin.passwordPlaceholder')"
/> />
</div> </div>
...@@ -330,13 +330,13 @@ ...@@ -330,13 +330,13 @@
v-model="confirmPassword" v-model="confirmPassword"
type="password" type="password"
class="input" class="input"
placeholder="Confirm password" :placeholder="t('setup.admin.confirmPasswordPlaceholder')"
/> />
<p <p
v-if="confirmPassword && formData.admin.password !== confirmPassword" v-if="confirmPassword && formData.admin.password !== confirmPassword"
class="input-error-text" class="input-error-text"
> >
Passwords do not match {{ t('setup.admin.passwordMismatch') }}
</p> </p>
</div> </div>
</div> </div>
......
...@@ -154,8 +154,7 @@ ...@@ -154,8 +154,7 @@
<!-- Use Key Button --> <!-- Use Key Button -->
<button <button
@click="openUseKeyModal(row)" @click="openUseKeyModal(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400"
:title="t('keys.useKey')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -170,12 +169,12 @@ ...@@ -170,12 +169,12 @@
d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"
/> />
</svg> </svg>
<span class="text-xs">{{ t('keys.useKey') }}</span>
</button> </button>
<!-- Import to CC Switch Button --> <!-- Import to CC Switch Button -->
<button <button
@click="importToCcswitch(row.key)" @click="importToCcswitch(row.key)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
:title="t('keys.importToCcSwitch')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -190,17 +189,17 @@ ...@@ -190,17 +189,17 @@
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
/> />
</svg> </svg>
<span class="text-xs">{{ t('keys.importToCcSwitch') }}</span>
</button> </button>
<!-- Toggle Status Button --> <!-- Toggle Status Button -->
<button <button
@click="toggleKeyStatus(row)" @click="toggleKeyStatus(row)"
:class="[ :class="[
'rounded-lg p-2 transition-colors', 'flex flex-col items-center gap-0.5 rounded-lg p-1.5 transition-colors',
row.status === 'active' row.status === 'active'
? 'text-gray-500 hover:bg-yellow-50 hover:text-yellow-600 dark:hover:bg-yellow-900/20 dark:hover:text-yellow-400' ? 'text-gray-500 hover:bg-yellow-50 hover:text-yellow-600 dark:hover:bg-yellow-900/20 dark:hover:text-yellow-400'
: 'text-gray-500 hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400' : 'text-gray-500 hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400'
]" ]"
:title="row.status === 'active' ? t('keys.disable') : t('keys.enable')"
> >
<svg <svg
v-if="row.status === 'active'" v-if="row.status === 'active'"
...@@ -230,12 +229,12 @@ ...@@ -230,12 +229,12 @@
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/> />
</svg> </svg>
<span class="text-xs">{{ row.status === 'active' ? t('keys.disable') : t('keys.enable') }}</span>
</button> </button>
<!-- Edit Button --> <!-- Edit Button -->
<button <button
@click="editKey(row)" @click="editKey(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
:title="t('common.edit')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -250,12 +249,12 @@ ...@@ -250,12 +249,12 @@
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
/> />
</svg> </svg>
<span class="text-xs">{{ t('common.edit') }}</span>
</button> </button>
<!-- Delete Button --> <!-- Delete Button -->
<button <button
@click="confirmDelete(row)" @click="confirmDelete(row)"
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
:title="t('common.delete')"
> >
<svg <svg
class="h-4 w-4" class="h-4 w-4"
...@@ -270,6 +269,7 @@ ...@@ -270,6 +269,7 @@
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/> />
</svg> </svg>
<span class="text-xs">{{ t('common.delete') }}</span>
</button> </button>
</div> </div>
</template> </template>
......
...@@ -294,7 +294,11 @@ ...@@ -294,7 +294,11 @@
${{ row.actual_cost.toFixed(6) }} ${{ row.actual_cost.toFixed(6) }}
</span> </span>
<!-- Cost Detail Tooltip --> <!-- Cost Detail Tooltip -->
<div class="group relative"> <div
class="group relative"
@mouseenter="showTooltip($event, row)"
@mouseleave="hideTooltip"
>
<div <div
class="flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-100 transition-colors group-hover:bg-blue-100 dark:bg-gray-700 dark:group-hover:bg-blue-900/50" class="flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-100 transition-colors group-hover:bg-blue-100 dark:bg-gray-700 dark:group-hover:bg-blue-900/50"
> >
...@@ -310,39 +314,6 @@ ...@@ -310,39 +314,6 @@
/> />
</svg> </svg>
</div> </div>
<!-- Tooltip Content (right side) -->
<div
class="invisible absolute left-full top-1/2 z-[100] ml-2 -translate-y-1/2 opacity-0 transition-all duration-200 group-hover:visible group-hover:opacity-100"
>
<div
class="whitespace-nowrap rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-xs text-white shadow-xl dark:border-gray-600 dark:bg-gray-800"
>
<div class="space-y-1.5">
<div class="flex items-center justify-between gap-6">
<span class="text-gray-400">{{ t('usage.rate') }}</span>
<span class="font-semibold text-blue-400"
>{{ (row.rate_multiplier || 1).toFixed(2) }}x</span
>
</div>
<div class="flex items-center justify-between gap-6">
<span class="text-gray-400">{{ t('usage.original') }}</span>
<span class="font-medium text-white">${{ row.total_cost.toFixed(6) }}</span>
</div>
<div
class="flex items-center justify-between gap-6 border-t border-gray-700 pt-1.5"
>
<span class="text-gray-400">{{ t('usage.billed') }}</span>
<span class="font-semibold text-green-400"
>${{ row.actual_cost.toFixed(6) }}</span
>
</div>
</div>
<!-- Tooltip Arrow (left side) -->
<div
class="absolute right-full top-1/2 h-0 w-0 -translate-y-1/2 border-b-[6px] border-r-[6px] border-t-[6px] border-b-transparent border-r-gray-900 border-t-transparent dark:border-r-gray-800"
></div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -399,6 +370,45 @@ ...@@ -399,6 +370,45 @@
</template> </template>
</TablePageLayout> </TablePageLayout>
</AppLayout> </AppLayout>
<!-- Tooltip Portal -->
<Teleport to="body">
<div
v-if="tooltipVisible"
class="fixed z-[9999] pointer-events-none -translate-y-1/2"
:style="{
left: tooltipPosition.x + 'px',
top: tooltipPosition.y + 'px'
}"
>
<div
class="whitespace-nowrap rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-xs text-white shadow-xl dark:border-gray-600 dark:bg-gray-800"
>
<div class="space-y-1.5">
<div class="flex items-center justify-between gap-6">
<span class="text-gray-400">{{ t('usage.rate') }}</span>
<span class="font-semibold text-blue-400"
>{{ (tooltipData?.rate_multiplier || 1).toFixed(2) }}x</span
>
</div>
<div class="flex items-center justify-between gap-6">
<span class="text-gray-400">{{ t('usage.original') }}</span>
<span class="font-medium text-white">${{ tooltipData?.total_cost.toFixed(6) }}</span>
</div>
<div class="flex items-center justify-between gap-6 border-t border-gray-700 pt-1.5">
<span class="text-gray-400">{{ t('usage.billed') }}</span>
<span class="font-semibold text-green-400"
>${{ tooltipData?.actual_cost.toFixed(6) }}</span
>
</div>
</div>
<!-- Tooltip Arrow (left side) -->
<div
class="absolute right-full top-1/2 h-0 w-0 -translate-y-1/2 border-b-[6px] border-r-[6px] border-t-[6px] border-b-transparent border-r-gray-900 border-t-transparent dark:border-r-gray-800"
></div>
</div>
</div>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
...@@ -420,6 +430,11 @@ import { formatDateTime } from '@/utils/format' ...@@ -420,6 +430,11 @@ import { formatDateTime } from '@/utils/format'
const { t } = useI18n() const { t } = useI18n()
const appStore = useAppStore() const appStore = useAppStore()
// Tooltip state
const tooltipVisible = ref(false)
const tooltipPosition = ref({ x: 0, y: 0 })
const tooltipData = ref<UsageLog | null>(null)
// Usage stats from API // Usage stats from API
const usageStats = ref<UsageStatsResponse | null>(null) const usageStats = ref<UsageStatsResponse | null>(null)
...@@ -629,6 +644,23 @@ const exportToCSV = () => { ...@@ -629,6 +644,23 @@ const exportToCSV = () => {
appStore.showSuccess(t('usage.exportSuccess')) appStore.showSuccess(t('usage.exportSuccess'))
} }
// Tooltip functions
const showTooltip = (event: MouseEvent, row: UsageLog) => {
const target = event.currentTarget as HTMLElement
const rect = target.getBoundingClientRect()
tooltipData.value = row
// Position to the right of the icon, vertically centered
tooltipPosition.value.x = rect.right + 8
tooltipPosition.value.y = rect.top + rect.height / 2
tooltipVisible.value = true
}
const hideTooltip = () => {
tooltipVisible.value = false
tooltipData.value = null
}
onMounted(() => { onMounted(() => {
initializeDateRange() initializeDateRange()
loadApiKeys() loadApiKeys()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment