更新日志展示
This commit is contained in:
@@ -67,6 +67,13 @@ func (h *AdminReadHandler) Logs(c *gin.Context) {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Resolve account_id -> account label (email) so the log table can show which
|
||||||
|
// provider account fulfilled each generation under the user.
|
||||||
|
accountByID, err := h.admin.AccountNameMap(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
||||||
|
return
|
||||||
|
}
|
||||||
out := make([]gin.H, 0, len(items))
|
out := make([]gin.H, 0, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
var userName any
|
var userName any
|
||||||
@@ -77,6 +84,14 @@ func (h *AdminReadHandler) Logs(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
userName = item.UserID
|
userName = item.UserID
|
||||||
}
|
}
|
||||||
|
var accountName any
|
||||||
|
if item.AccountID != "" {
|
||||||
|
if label, ok := accountByID[item.AccountID]; ok {
|
||||||
|
accountName = label
|
||||||
|
} else {
|
||||||
|
accountName = item.AccountID
|
||||||
|
}
|
||||||
|
}
|
||||||
out = append(out, gin.H{
|
out = append(out, gin.H{
|
||||||
"id": item.ID,
|
"id": item.ID,
|
||||||
"ts": item.TS.Unix(),
|
"ts": item.TS.Unix(),
|
||||||
@@ -92,6 +107,8 @@ func (h *AdminReadHandler) Logs(c *gin.Context) {
|
|||||||
"source": item.Source,
|
"source": item.Source,
|
||||||
"user_id": emptyStringNil(item.UserID),
|
"user_id": emptyStringNil(item.UserID),
|
||||||
"user_name": userName,
|
"user_name": userName,
|
||||||
|
"account_id": emptyStringNil(item.AccountID),
|
||||||
|
"account": accountName,
|
||||||
"cost": item.Cost,
|
"cost": item.Cost,
|
||||||
"elapsed_ms": item.ElapsedMS,
|
"elapsed_ms": item.ElapsedMS,
|
||||||
"file": emptyStringNil(item.File),
|
"file": emptyStringNil(item.File),
|
||||||
|
|||||||
@@ -218,6 +218,13 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Resolve account_id -> account label so the log table can show which
|
||||||
|
// provider account fulfilled each generation under the user.
|
||||||
|
accountByID, err := h.admin.AccountNameMap(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
out := make([]gin.H, 0, len(items))
|
out := make([]gin.H, 0, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
@@ -229,6 +236,14 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
userName = item.UserID
|
userName = item.UserID
|
||||||
}
|
}
|
||||||
|
var accountName any
|
||||||
|
if item.AccountID != "" {
|
||||||
|
if label, ok := accountByID[item.AccountID]; ok {
|
||||||
|
accountName = label
|
||||||
|
} else {
|
||||||
|
accountName = item.AccountID
|
||||||
|
}
|
||||||
|
}
|
||||||
out = append(out, gin.H{
|
out = append(out, gin.H{
|
||||||
"id": item.ID,
|
"id": item.ID,
|
||||||
"ts": item.TS.Unix(),
|
"ts": item.TS.Unix(),
|
||||||
@@ -244,6 +259,8 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
|
|||||||
"source": emptyStringNil(item.Source),
|
"source": emptyStringNil(item.Source),
|
||||||
"user_id": emptyStringNil(item.UserID),
|
"user_id": emptyStringNil(item.UserID),
|
||||||
"user_name": userName,
|
"user_name": userName,
|
||||||
|
"account_id": emptyStringNil(item.AccountID),
|
||||||
|
"account": accountName,
|
||||||
"cost": item.Cost,
|
"cost": item.Cost,
|
||||||
"elapsed_ms": item.ElapsedMS,
|
"elapsed_ms": item.ElapsedMS,
|
||||||
"file": emptyStringNil(item.File),
|
"file": emptyStringNil(item.File),
|
||||||
|
|||||||
@@ -136,6 +136,28 @@ func (s *AdminReadService) UserNameMap(ctx context.Context) (map[string]string,
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccountNameMap builds a token-account id -> display label lookup (account
|
||||||
|
// email, else display name, else id) used to annotate log rows with which
|
||||||
|
// provider account fulfilled each generation (event_logs.account_id).
|
||||||
|
func (s *AdminReadService) AccountNameMap(ctx context.Context) (map[string]string, error) {
|
||||||
|
accounts, err := s.tokens.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out := make(map[string]string, len(accounts))
|
||||||
|
for _, a := range accounts {
|
||||||
|
label := strings.TrimSpace(a.AccountEmail)
|
||||||
|
if label == "" {
|
||||||
|
label = strings.TrimSpace(a.AccountDisplayName)
|
||||||
|
}
|
||||||
|
if label == "" {
|
||||||
|
label = a.ID
|
||||||
|
}
|
||||||
|
out[a.ID] = label
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AdminReadService) Stats(ctx context.Context) (map[string]any, error) {
|
func (s *AdminReadService) Stats(ctx context.Context) (map[string]any, error) {
|
||||||
stats, err := s.events.Stats(ctx)
|
stats, err := s.events.Stats(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ const sourcePill = (s) => ({
|
|||||||
<th class="text-center px-4 py-3 font-medium">预览</th>
|
<th class="text-center px-4 py-3 font-medium">预览</th>
|
||||||
<th class="text-left px-4 py-3 font-medium">时间</th>
|
<th class="text-left px-4 py-3 font-medium">时间</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">状态</th>
|
<th class="text-left px-3 py-3 font-medium">状态</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">用户</th>
|
<th class="text-left px-3 py-3 font-medium">用户 / 账号</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">模型</th>
|
<th class="text-left px-3 py-3 font-medium">模型</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">提示词 / 错误</th>
|
<th class="text-left px-3 py-3 font-medium">提示词 / 错误</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">参数</th>
|
<th class="text-left px-3 py-3 font-medium">参数</th>
|
||||||
@@ -280,6 +280,7 @@ const sourcePill = (s) => ({
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-3.5 align-middle min-w-0">
|
<td class="px-3 py-3.5 align-middle min-w-0">
|
||||||
<div class="text-xs text-white/80 truncate" :title="e.user_name || '匿名'">{{ e.user_name || '匿名' }}</div>
|
<div class="text-xs text-white/80 truncate" :title="e.user_name || '匿名'">{{ e.user_name || '匿名' }}</div>
|
||||||
|
<div v-if="e.account" class="mt-0.5 text-[11px] text-white/45 truncate" :title="e.account">{{ e.account }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-3.5 align-middle min-w-0">
|
<td class="px-3 py-3.5 align-middle min-w-0">
|
||||||
<div class="font-mono text-xs text-white/90 truncate" :title="e.model">{{ e.model }}</div>
|
<div class="font-mono text-xs text-white/90 truncate" :title="e.model">{{ e.model }}</div>
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ const params = (e) => {
|
|||||||
<col class="w-16" /> <!-- preview -->
|
<col class="w-16" /> <!-- preview -->
|
||||||
<col class="w-28" /> <!-- time -->
|
<col class="w-28" /> <!-- time -->
|
||||||
<col class="w-24" /> <!-- status -->
|
<col class="w-24" /> <!-- status -->
|
||||||
|
<col class="w-28" /> <!-- user/account -->
|
||||||
<col class="w-36" /> <!-- model -->
|
<col class="w-36" /> <!-- model -->
|
||||||
<col /> <!-- prompt/error -->
|
<col /> <!-- prompt/error -->
|
||||||
<col class="w-40" /> <!-- params -->
|
<col class="w-40" /> <!-- params -->
|
||||||
@@ -205,6 +206,7 @@ const params = (e) => {
|
|||||||
<th class="text-center px-3 py-3 font-medium">预览</th>
|
<th class="text-center px-3 py-3 font-medium">预览</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">时间</th>
|
<th class="text-left px-3 py-3 font-medium">时间</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">状态</th>
|
<th class="text-left px-3 py-3 font-medium">状态</th>
|
||||||
|
<th class="text-left px-3 py-3 font-medium">用户 / 账号</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">模型</th>
|
<th class="text-left px-3 py-3 font-medium">模型</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">提示词 / 错误</th>
|
<th class="text-left px-3 py-3 font-medium">提示词 / 错误</th>
|
||||||
<th class="text-left px-3 py-3 font-medium">参数</th>
|
<th class="text-left px-3 py-3 font-medium">参数</th>
|
||||||
@@ -236,6 +238,10 @@ const params = (e) => {
|
|||||||
<span class="w-1.5 h-1.5 rounded-full" :class="statusDot(e.status)"></span>{{ statusLabel(e.status) }}
|
<span class="w-1.5 h-1.5 rounded-full" :class="statusDot(e.status)"></span>{{ statusLabel(e.status) }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-3 py-3 align-middle min-w-0">
|
||||||
|
<div class="text-xs text-slate-700 truncate" :title="e.user_name || '匿名'">{{ e.user_name || '匿名' }}</div>
|
||||||
|
<div v-if="e.account" class="mt-0.5 text-[11px] text-slate-400 truncate" :title="e.account">{{ e.account }}</div>
|
||||||
|
</td>
|
||||||
<td class="px-3 py-3 align-middle min-w-0">
|
<td class="px-3 py-3 align-middle min-w-0">
|
||||||
<div class="font-mono text-xs text-slate-800 truncate" :title="e.model">{{ e.model }}</div>
|
<div class="font-mono text-xs text-slate-800 truncate" :title="e.model">{{ e.model }}</div>
|
||||||
<div class="mt-0.5 flex items-center gap-1.5">
|
<div class="mt-0.5 flex items-center gap-1.5">
|
||||||
|
|||||||
Reference in New Issue
Block a user