chore: update cron desc mobile
This commit is contained in:
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "baihu2",
|
||||
"name": "baihu",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
Generated
+1
-1
@@ -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
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user