feat: opt pages display style

This commit is contained in:
engigu
2025-12-24 10:48:23 +08:00
parent e3131e15dc
commit 05aeea6863
9 changed files with 94 additions and 27 deletions
+17 -14
View File
@@ -26,29 +26,32 @@ func NewDashboardController(cronService *services.CronService, executorService *
} }
type StatsResponse struct { type StatsResponse struct {
Tasks int64 `json:"tasks"` Tasks int64 `json:"tasks"`
Scripts int64 `json:"scripts"` TodayExecs int64 `json:"today_execs"`
Envs int64 `json:"envs"` Envs int64 `json:"envs"`
Logs int64 `json:"logs"` Logs int64 `json:"logs"`
Scheduled int `json:"scheduled"` Scheduled int `json:"scheduled"`
Running int `json:"running"` Running int `json:"running"`
} }
func (dc *DashboardController) GetStats(c *gin.Context) { func (dc *DashboardController) GetStats(c *gin.Context) {
var taskCount, scriptCount, envCount, logCount int64 var taskCount, envCount, logCount, todayExecs int64
database.DB.Model(&models.Task{}).Count(&taskCount) database.DB.Model(&models.Task{}).Count(&taskCount)
database.DB.Model(&models.Script{}).Count(&scriptCount)
database.DB.Model(&models.EnvironmentVariable{}).Count(&envCount) database.DB.Model(&models.EnvironmentVariable{}).Count(&envCount)
database.DB.Model(&models.TaskLog{}).Count(&logCount) database.DB.Model(&models.TaskLog{}).Count(&logCount)
// 今日执行总数
today := time.Now().Format("2006-01-02")
database.DB.Model(&models.SendStats{}).Where("day = ?", today).Select("COALESCE(SUM(num), 0)").Scan(&todayExecs)
stats := StatsResponse{ stats := StatsResponse{
Tasks: taskCount, Tasks: taskCount,
Scripts: scriptCount, TodayExecs: todayExecs,
Envs: envCount, Envs: envCount,
Logs: logCount, Logs: logCount,
Scheduled: dc.cronService.GetScheduledCount(), Scheduled: dc.cronService.GetScheduledCount(),
Running: dc.executorService.GetRunningCount(), Running: dc.executorService.GetRunningCount(),
} }
utils.Success(c, stats) utils.Success(c, stats)
+1 -1
View File
@@ -259,7 +259,7 @@ export interface EnvListResponse {
export interface Stats { export interface Stats {
tasks: number tasks: number
scripts: number today_execs: number
envs: number envs: number
logs: number logs: number
scheduled: number scheduled: number
+43
View File
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
const props = withDefaults(
defineProps<{
text: string
title?: string
}>(),
{
title: '详情'
}
)
const showDialog = ref(false)
function handleClick() {
if (props.text && props.text !== '-') {
showDialog.value = true
}
}
</script>
<template>
<span
class="truncate block cursor-pointer hover:text-primary"
:title="text || '-'"
@click="handleClick"
>
{{ text || '-' }}
</span>
<Dialog v-model:open="showDialog">
<DialogContent class="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>{{ title }}</DialogTitle>
</DialogHeader>
<div class="max-h-[400px] overflow-y-auto">
<pre class="text-sm whitespace-pre-wrap break-all font-mono bg-muted p-3 rounded-lg">{{ text }}</pre>
</div>
</DialogContent>
</Dialog>
</template>
+3 -3
View File
@@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ListTodo, FileCode, Variable, Clock, Play, ScrollText } from 'lucide-vue-next' import { ListTodo, Variable, Clock, Play, ScrollText } from 'lucide-vue-next'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { api, type Stats, type DailyStats, type TaskStatsItem } from '@/api' import { api, type Stats, type DailyStats, type TaskStatsItem } from '@/api'
import ApexCharts from 'apexcharts' import ApexCharts from 'apexcharts'
const router = useRouter() const router = useRouter()
const stats = ref<Stats>({ tasks: 0, scripts: 0, envs: 0, logs: 0, scheduled: 0, running: 0 }) const stats = ref<Stats>({ tasks: 0, today_execs: 0, envs: 0, logs: 0, scheduled: 0, running: 0 })
const sendStats = ref<DailyStats[]>([]) const sendStats = ref<DailyStats[]>([])
const taskStats = ref<TaskStatsItem[]>([]) const taskStats = ref<TaskStatsItem[]>([])
const chartsLoaded = ref(false) const chartsLoaded = ref(false)
const statItems = [ const statItems = [
{ key: 'today_execs', label: '今日执行', icon: Play, route: '/history' },
{ key: 'tasks', label: '任务总数', icon: ListTodo, route: '/tasks' }, { key: 'tasks', label: '任务总数', icon: ListTodo, route: '/tasks' },
{ key: 'scripts', label: '脚本数量', icon: FileCode, route: '/editor' },
{ key: 'envs', label: '环境变量', icon: Variable, route: '/environments' }, { key: 'envs', label: '环境变量', icon: Variable, route: '/environments' },
{ key: 'logs', label: '日志总数', icon: ScrollText, route: '/history' }, { key: 'logs', label: '日志总数', icon: ScrollText, route: '/history' },
{ key: 'scheduled', label: '调度注册', icon: Clock, route: '/tasks' }, { key: 'scheduled', label: '调度注册', icon: Clock, route: '/tasks' },
+7 -2
View File
@@ -9,6 +9,7 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Trash2, Package, Search, RefreshCw, Loader2, Download, FileText, RotateCw } from 'lucide-vue-next' import { Trash2, Package, Search, RefreshCw, Loader2, Download, FileText, RotateCw } from 'lucide-vue-next'
import { api, type Dependency } from '@/api' import { api, type Dependency } from '@/api'
import TextOverflow from '@/components/TextOverflow.vue'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
const activeTab = ref('py') const activeTab = ref('py')
@@ -205,9 +206,13 @@ onMounted(loadDeps)
:key="dep.id" :key="dep.id"
class="flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors" class="flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors"
> >
<span class="flex-1 font-mono text-sm">{{ dep.name }}</span> <span class="flex-1 font-mono text-sm truncate">
<TextOverflow :text="dep.name" title="包名" />
</span>
<span class="w-32 text-sm text-muted-foreground">{{ dep.version || '-' }}</span> <span class="w-32 text-sm text-muted-foreground">{{ dep.version || '-' }}</span>
<span class="w-48 text-sm text-muted-foreground truncate hidden md:block" :title="dep.remark">{{ dep.remark || '-' }}</span> <span class="w-48 text-sm text-muted-foreground truncate hidden md:block">
<TextOverflow :text="dep.remark || '-'" title="备注" />
</span>
<span class="w-24 flex justify-center gap-1"> <span class="w-24 flex justify-center gap-1">
<Button v-if="dep.log" variant="ghost" size="icon" class="h-7 w-7" @click="showLog(dep)"> <Button v-if="dep.log" variant="ghost" size="icon" class="h-7 w-7" @click="showLog(dep)">
<FileText class="h-4 w-4" /> <FileText class="h-4 w-4" />
+5 -2
View File
@@ -8,6 +8,7 @@ import { Textarea } from '@/components/ui/textarea'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import Pagination from '@/components/Pagination.vue' import Pagination from '@/components/Pagination.vue'
import { Plus, Pencil, Trash2, Eye, EyeOff, Search } from 'lucide-vue-next' import { Plus, Pencil, Trash2, Eye, EyeOff, Search } from 'lucide-vue-next'
import TextOverflow from '@/components/TextOverflow.vue'
import { api, type EnvVar } from '@/api' import { api, type EnvVar } from '@/api'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import { useSiteSettings } from '@/composables/useSiteSettings' import { useSiteSettings } from '@/composables/useSiteSettings'
@@ -139,9 +140,11 @@ onMounted(loadEnvVars)
> >
<code class="w-48 font-medium truncate shrink-0 text-xs bg-muted px-2 py-1 rounded">{{ env.name }}</code> <code class="w-48 font-medium truncate shrink-0 text-xs bg-muted px-2 py-1 rounded">{{ env.name }}</code>
<span class="flex-1 font-mono text-muted-foreground truncate text-xs"> <span class="flex-1 font-mono text-muted-foreground truncate text-xs">
{{ showValues[env.id] ? env.value : maskValue(env.value) }} <TextOverflow :text="showValues[env.id] ? env.value : maskValue(env.value)" title="变量值" />
</span>
<span class="w-48 shrink-0 text-muted-foreground truncate text-sm hidden md:block">
<TextOverflow :text="env.remark || '-'" title="备注" />
</span> </span>
<span class="w-48 shrink-0 text-muted-foreground truncate text-sm hidden md:block">{{ env.remark || '-' }}</span>
<span class="w-24 shrink-0 flex justify-center gap-1"> <span class="w-24 shrink-0 flex justify-center gap-1">
<Button variant="ghost" size="icon" class="h-7 w-7" @click="toggleShow(env.id)" :title="showValues[env.id] ? '隐藏' : '显示'"> <Button variant="ghost" size="icon" class="h-7 w-7" @click="toggleShow(env.id)" :title="showValues[env.id] ? '隐藏' : '显示'">
<Eye v-if="!showValues[env.id]" class="h-3.5 w-3.5" /> <Eye v-if="!showValues[env.id]" class="h-3.5 w-3.5" />
+7 -2
View File
@@ -10,6 +10,7 @@ import { api, type TaskLog, type LogDetail } from '@/api'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import pako from 'pako' import pako from 'pako'
import { useSiteSettings } from '@/composables/useSiteSettings' import { useSiteSettings } from '@/composables/useSiteSettings'
import TextOverflow from '@/components/TextOverflow.vue'
const route = useRoute() const route = useRoute()
const { pageSize } = useSiteSettings() const { pageSize } = useSiteSettings()
@@ -162,8 +163,12 @@ watch(() => route.query.task_id, (newTaskId) => {
@click="selectLog(log)" @click="selectLog(log)"
> >
<span class="w-12 shrink-0 text-muted-foreground text-sm">#{{ log.id }}</span> <span class="w-12 shrink-0 text-muted-foreground text-sm">#{{ log.id }}</span>
<span class="w-32 font-medium truncate shrink-0 text-sm">{{ log.task_name }}</span> <span class="w-32 font-medium truncate shrink-0 text-sm">
<code :class="['text-muted-foreground truncate text-xs bg-muted px-2 py-1 rounded', selectedLog ? 'w-40 shrink-0 hidden sm:block' : 'flex-1']">{{ log.command }}</code> <TextOverflow :text="log.task_name" title="任务名称" />
</span>
<code :class="['text-muted-foreground truncate text-xs bg-muted px-2 py-1 rounded', selectedLog ? 'w-40 shrink-0 hidden sm:block' : 'flex-1']">
<TextOverflow :text="log.command" title="执行命令" />
</code>
<span class="w-12 flex justify-center shrink-0"> <span class="w-12 flex justify-center shrink-0">
<span :class="['w-2 h-2 rounded-full', log.status === 'success' ? 'bg-green-500' : log.status === 'failed' ? 'bg-red-500' : 'bg-yellow-500']" /> <span :class="['w-2 h-2 rounded-full', log.status === 'success' ? 'bg-green-500' : log.status === 'failed' ? 'bg-red-500' : 'bg-yellow-500']" />
</span> </span>
+4 -1
View File
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import Pagination from '@/components/Pagination.vue' import Pagination from '@/components/Pagination.vue'
import { RefreshCw, Search } from 'lucide-vue-next' import { RefreshCw, Search } from 'lucide-vue-next'
import TextOverflow from '@/components/TextOverflow.vue'
import { api } from '@/api' import { api } from '@/api'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import { useSiteSettings } from '@/composables/useSiteSettings' import { useSiteSettings } from '@/composables/useSiteSettings'
@@ -107,7 +108,9 @@ onMounted(loadLogs)
<span class="w-16 shrink-0 flex justify-center"> <span class="w-16 shrink-0 flex justify-center">
<span :class="['h-2 w-2 rounded-full', log.status === 'success' ? 'bg-green-500' : 'bg-red-500']"></span> <span :class="['h-2 w-2 rounded-full', log.status === 'success' ? 'bg-green-500' : 'bg-red-500']"></span>
</span> </span>
<span class="flex-1 text-xs text-muted-foreground truncate hidden md:block" :title="log.user_agent">{{ log.user_agent || '-' }}</span> <span class="flex-1 text-xs text-muted-foreground truncate hidden md:block">
<TextOverflow :text="log.user_agent || '-'" title="User Agent" />
</span>
<span class="w-40 shrink-0 text-right text-xs text-muted-foreground">{{ log.created_at }}</span> <span class="w-40 shrink-0 text-right text-xs text-muted-foreground">{{ log.created_at }}</span>
</div> </div>
</div> </div>
+7 -2
View File
@@ -15,6 +15,7 @@ import { api, type Task, type EnvVar } from '@/api'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import { useSiteSettings } from '@/composables/useSiteSettings' import { useSiteSettings } from '@/composables/useSiteSettings'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import TextOverflow from '@/components/TextOverflow.vue'
const router = useRouter() const router = useRouter()
const { pageSize } = useSiteSettings() const { pageSize } = useSiteSettings()
@@ -250,8 +251,12 @@ onMounted(() => {
class="flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors" class="flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors"
> >
<span class="w-12 shrink-0 text-muted-foreground text-sm">#{{ task.id }}</span> <span class="w-12 shrink-0 text-muted-foreground text-sm">#{{ task.id }}</span>
<span class="w-40 font-medium truncate shrink-0 text-sm">{{ task.name }}</span> <span class="w-40 font-medium truncate shrink-0 text-sm">
<code class="flex-1 text-muted-foreground truncate text-xs bg-muted px-2 py-1 rounded">{{ task.command }}</code> <TextOverflow :text="task.name" title="任务名称" />
</span>
<code class="flex-1 text-muted-foreground truncate text-xs bg-muted px-2 py-1 rounded">
<TextOverflow :text="task.command" title="执行命令" />
</code>
<code class="w-36 shrink-0 text-muted-foreground text-xs bg-muted px-2 py-1 rounded hidden md:block">{{ task.schedule }}</code> <code class="w-36 shrink-0 text-muted-foreground text-xs bg-muted px-2 py-1 rounded hidden md:block">{{ task.schedule }}</code>
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.last_run || '-' }}</span> <span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.last_run || '-' }}</span>
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.next_run || '-' }}</span> <span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.next_run || '-' }}</span>