chore: update cron desc mobile

This commit is contained in:
engigu
2026-04-08 20:23:57 +08:00
parent 83c0dff711
commit ad803da843
8 changed files with 72 additions and 19 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "baihu2",
"name": "baihu",
"lockfileVersion": 3,
"requires": true,
"packages": {
+1 -1
View File
@@ -22,7 +22,7 @@
"chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cronstrue": "^3.13.0",
"cronstrue": "^3.14.0",
"date-fns": "^4.1.0",
"lucide-react": "^0.562.0",
"lucide-vue-next": "^0.562.0",
+1 -1
View File
@@ -23,7 +23,7 @@
"chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cronstrue": "^3.13.0",
"cronstrue": "^3.14.0",
"date-fns": "^4.1.0",
"lucide-react": "^0.562.0",
"lucide-vue-next": "^0.562.0",
+17 -7
View File
@@ -2,13 +2,13 @@
import { ref } from 'vue'
import {
X, Trash2, Maximize2, CheckCircle2, XCircle, AlertCircle, Clock, Ban,
Zap as ZapIcon, Search
Zap as ZapIcon, Search, MinusCircle
} from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import LogContent from './LogContent.vue'
import { TASK_STATUS } from '@/constants'
import { TASK_STATUS, TASK_STATUS_TEXT } from '@/constants'
import type { TaskLog } from '@/api'
interface Props {
@@ -19,6 +19,8 @@ interface Props {
isStopping?: boolean
showClose?: boolean
variant?: 'full' | 'simple'
emptyTitle?: string
emptyDescription?: string
}
const props = withDefaults(defineProps<Props>(), {
@@ -28,7 +30,9 @@ const props = withDefaults(defineProps<Props>(), {
loading: false,
isStopping: false,
showClose: true,
variant: 'full'
variant: 'full',
emptyTitle: undefined,
emptyDescription: undefined
})
defineEmits<{
@@ -60,6 +64,8 @@ function getStatusBadgeClass(status: string) {
return 'bg-orange-500/10 text-orange-600 border-orange-500/20 dark:bg-orange-500/20 dark:text-orange-400 dark:border-orange-500/30'
case TASK_STATUS.CANCELLED:
return 'bg-muted/50 text-muted-foreground border-muted-foreground/10'
case 'UNEXECUTED':
return 'bg-muted/30 text-muted-foreground/60 border-dashed border-muted-foreground/20 shadow-none'
default:
return 'bg-secondary text-secondary-foreground border-transparent'
}
@@ -75,10 +81,10 @@ function getStatusBadgeClass(status: string) {
<!-- Simple 模式下的状态显示 -->
<Badge v-if="variant === 'simple'" variant="outline" :class="[
'capitalize px-2 py-0.5 font-normal rounded-full border text-[10px] hidden sm:flex',
'px-2 py-0.5 font-normal rounded-full border text-[10px] hidden sm:flex',
getStatusBadgeClass(log.status)
]">
{{ log.status }}
{{ TASK_STATUS_TEXT[log.status] || log.status }}
</Badge>
<Button v-if="log.status === TASK_STATUS.RUNNING" variant="destructive" size="sm"
@@ -130,7 +136,10 @@ function getStatusBadgeClass(status: string) {
<Clock v-else-if="log.status === TASK_STATUS.PENDING" class="h-3.5 w-3.5 fill-amber-500/20" />
<AlertCircle v-else-if="log.status === TASK_STATUS.TIMEOUT" class="h-3.5 w-3.5 fill-orange-500/20" />
<Ban v-else-if="log.status === TASK_STATUS.CANCELLED" class="h-3.5 w-3.5" />
<span class="text-[10px] font-normal uppercase">{{ log.status === TASK_STATUS.SUCCESS ? 'SUCCESS' : log.status }}</span>
<MinusCircle v-else-if="log.status === 'UNEXECUTED'" class="h-3.5 w-3.5 opacity-40" />
<span class="text-[10px] font-normal">
{{ TASK_STATUS_TEXT[log.status] || log.status }}
</span>
</div>
</Badge>
</div>
@@ -184,7 +193,8 @@ function getStatusBadgeClass(status: string) {
class="h-full"
:content="content"
:loading="loading"
empty-description="此任务执行期间未产生标准输出日志"
:empty-title="emptyTitle"
:empty-description="emptyDescription || '此任务执行期间未产生标准输出日志'"
/>
</div>
</div>
+10
View File
@@ -28,6 +28,16 @@ export const TASK_STATUS = {
CANCELLED: 'cancelled',
} as const
export const TASK_STATUS_TEXT: Record<string, string> = {
[TASK_STATUS.SUCCESS]: '已成功',
[TASK_STATUS.FAILED]: '执行失败',
[TASK_STATUS.RUNNING]: '正在运行',
[TASK_STATUS.PENDING]: '等待队列',
[TASK_STATUS.TIMEOUT]: '执行超时',
[TASK_STATUS.CANCELLED]: '手动取消',
'UNEXECUTED': '尚未执行',
} as const
// 任务类型
export const TASK_TYPE = {
NORMAL: 'task',
+6 -6
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, computed, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { TASK_STATUS, TASK_TYPE } from '@/constants'
import { TASK_STATUS, TASK_TYPE, TASK_STATUS_TEXT } from '@/constants'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import Pagination from '@/components/Pagination.vue'
@@ -329,11 +329,11 @@ watch(() => route.query, (newQuery) => {
</SelectTrigger>
<SelectContent>
<SelectItem value="all">所有状态</SelectItem>
<SelectItem value="running">正在运行</SelectItem>
<SelectItem value="success">成功</SelectItem>
<SelectItem value="failed">失败</SelectItem>
<SelectItem value="timeout">超时</SelectItem>
<SelectItem value="cancelled">取消</SelectItem>
<SelectItem :value="TASK_STATUS.RUNNING">{{ TASK_STATUS_TEXT[TASK_STATUS.RUNNING] }}</SelectItem>
<SelectItem :value="TASK_STATUS.SUCCESS">{{ TASK_STATUS_TEXT[TASK_STATUS.SUCCESS] }}</SelectItem>
<SelectItem :value="TASK_STATUS.FAILED">{{ TASK_STATUS_TEXT[TASK_STATUS.FAILED] }}</SelectItem>
<SelectItem :value="TASK_STATUS.TIMEOUT">{{ TASK_STATUS_TEXT[TASK_STATUS.TIMEOUT] }}</SelectItem>
<SelectItem :value="TASK_STATUS.CANCELLED">{{ TASK_STATUS_TEXT[TASK_STATUS.CANCELLED] }}</SelectItem>
</SelectContent>
</Select>
</div>
+4
View File
@@ -10,6 +10,8 @@ const props = withDefaults(defineProps<{
title?: string
loading?: boolean
variant?: 'full' | 'simple'
emptyTitle?: string
emptyDescription?: string
}>(), {
variant: 'simple'
})
@@ -68,6 +70,8 @@ onUnmounted(() => {
:title="title"
:loading="loading"
:variant="isFullscreen ? 'simple' : variant"
:empty-title="emptyTitle"
:empty-description="emptyDescription"
@close="close"
@maximize="isFullscreen = !isFullscreen"
/>
+32 -3
View File
@@ -16,6 +16,7 @@ import { useSiteSettings } from '@/composables/useSiteSettings'
import { useRouter, useRoute } from 'vue-router'
import { TASK_TYPE, AGENT_STATUS, TRIGGER_TYPE, TASK_STATUS } from '@/constants'
import TextOverflow from '@/components/TextOverflow.vue'
import { getCronDescription } from '@/utils/cron'
const router = useRouter()
@@ -28,6 +29,7 @@ const showTaskDialog = ref(false)
const showRepoDialog = ref(false)
const editingTask = ref<Partial<Task>>({})
const isEdit = ref(false)
const showDeleteDialog = ref(false)
const deleteTaskId = ref<string | null>(null)
@@ -222,6 +224,8 @@ async function toggleTask(task: Task, enabled: boolean) {
const showLogViewer = ref(false)
const selectedLog = ref<TaskLog | null>(null)
const logContent = ref('')
const logEmptyTitle = ref<string | undefined>(undefined)
const logEmptyDesc = ref<string | undefined>(undefined)
let logSocket: WebSocket | null = null
function cleanupLogSocket() {
@@ -261,6 +265,8 @@ async function viewLogs(taskId: string) {
if (!latestLog) return
selectedLog.value = latestLog
logContent.value = ''
logEmptyTitle.value = undefined
logEmptyDesc.value = undefined
showLogViewer.value = true
if (latestLog.status !== TASK_STATUS.RUNNING) {
@@ -286,7 +292,22 @@ async function viewLogs(taskId: string) {
logContent.value += event.data
}
} else {
toast.info('该任务暂无执行日志')
// 如果没有日志,构造一个基础的任务信息对象用于展示弹窗
const task = tasks.value.find(t => t.id === taskId)
selectedLog.value = {
id: '',
task_id: taskId,
task_name: task?.name || '未知任务',
command: task?.command || '',
status: 'UNEXECUTED',
duration: 0,
start_time: '-',
end_time: '-',
} as TaskLog
logContent.value = ''
logEmptyTitle.value = '该任务暂无执行记录'
logEmptyDesc.value = '此任务尚未被触发执行,目前没有任何运行日志产生。'
showLogViewer.value = true
}
} catch {
toast.error('获取日志失败')
@@ -538,7 +559,13 @@ watch(() => route.query.agent_id, (newVal: any) => {
<div class="flex items-center gap-3">
<span class="w-10 shrink-0 font-medium opacity-70">定时:</span>
<span v-if="task.trigger_type === TRIGGER_TYPE.BAIHU_STARTUP" class="text-[10px] leading-tight bg-blue-500/10 text-blue-500 px-1.5 py-0.5 rounded font-medium">服务启动时</span>
<span v-else-if="task.schedule" class="text-xs text-foreground bg-muted/40 px-1.5 py-0.5 rounded">{{ task.schedule }}</span>
<div v-else-if="task.schedule" class="flex items-center gap-1.5 flex-1 min-w-0">
<span class="text-xs text-foreground bg-muted/40 px-1.5 py-0.5 rounded shrink-0">{{ task.schedule }}</span>
<div class="flex items-center gap-1 text-[10px] text-muted-foreground/60 min-w-0 flex-1">
<Zap class="h-2.5 w-2.5 fill-current opacity-50 shrink-0" />
<TextOverflow :text="getCronDescription(task.schedule)" class="truncate" />
</div>
</div>
</div>
<div class="flex items-start gap-3">
<span class="w-10 shrink-0 font-medium mt-0.5 opacity-70">命令:</span>
@@ -581,7 +608,9 @@ watch(() => route.query.agent_id, (newVal: any) => {
title="最新日志"
variant="full"
:log="selectedLog"
:content="displayLogContent" />
:content="displayLogContent"
:empty-title="logEmptyTitle"
:empty-description="logEmptyDesc" />
<!-- 删除确认 (批量) -->
<AlertDialog v-model:open="showBatchDeleteDialog">