chore: update cron desc mobile
This commit is contained in:
Generated
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "baihu2",
|
"name": "baihu",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
Generated
+1
-1
@@ -22,7 +22,7 @@
|
|||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cronstrue": "^3.13.0",
|
"cronstrue": "^3.14.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cronstrue": "^3.13.0",
|
"cronstrue": "^3.14.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import {
|
import {
|
||||||
X, Trash2, Maximize2, CheckCircle2, XCircle, AlertCircle, Clock, Ban,
|
X, Trash2, Maximize2, CheckCircle2, XCircle, AlertCircle, Clock, Ban,
|
||||||
Zap as ZapIcon, Search
|
Zap as ZapIcon, Search, MinusCircle
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import LogContent from './LogContent.vue'
|
import LogContent from './LogContent.vue'
|
||||||
import { TASK_STATUS } from '@/constants'
|
import { TASK_STATUS, TASK_STATUS_TEXT } from '@/constants'
|
||||||
import type { TaskLog } from '@/api'
|
import type { TaskLog } from '@/api'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -19,6 +19,8 @@ interface Props {
|
|||||||
isStopping?: boolean
|
isStopping?: boolean
|
||||||
showClose?: boolean
|
showClose?: boolean
|
||||||
variant?: 'full' | 'simple'
|
variant?: 'full' | 'simple'
|
||||||
|
emptyTitle?: string
|
||||||
|
emptyDescription?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -28,7 +30,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
loading: false,
|
loading: false,
|
||||||
isStopping: false,
|
isStopping: false,
|
||||||
showClose: true,
|
showClose: true,
|
||||||
variant: 'full'
|
variant: 'full',
|
||||||
|
emptyTitle: undefined,
|
||||||
|
emptyDescription: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits<{
|
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'
|
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:
|
case TASK_STATUS.CANCELLED:
|
||||||
return 'bg-muted/50 text-muted-foreground border-muted-foreground/10'
|
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:
|
default:
|
||||||
return 'bg-secondary text-secondary-foreground border-transparent'
|
return 'bg-secondary text-secondary-foreground border-transparent'
|
||||||
}
|
}
|
||||||
@@ -75,10 +81,10 @@ function getStatusBadgeClass(status: string) {
|
|||||||
|
|
||||||
<!-- Simple 模式下的状态显示 -->
|
<!-- Simple 模式下的状态显示 -->
|
||||||
<Badge v-if="variant === 'simple'" variant="outline" :class="[
|
<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)
|
getStatusBadgeClass(log.status)
|
||||||
]">
|
]">
|
||||||
{{ log.status }}
|
{{ TASK_STATUS_TEXT[log.status] || log.status }}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<Button v-if="log.status === TASK_STATUS.RUNNING" variant="destructive" size="sm"
|
<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" />
|
<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" />
|
<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" />
|
<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>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,7 +193,8 @@ function getStatusBadgeClass(status: string) {
|
|||||||
class="h-full"
|
class="h-full"
|
||||||
:content="content"
|
:content="content"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
empty-description="此任务执行期间未产生标准输出日志"
|
:empty-title="emptyTitle"
|
||||||
|
:empty-description="emptyDescription || '此任务执行期间未产生标准输出日志'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ export const TASK_STATUS = {
|
|||||||
CANCELLED: 'cancelled',
|
CANCELLED: 'cancelled',
|
||||||
} as const
|
} 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 = {
|
export const TASK_TYPE = {
|
||||||
NORMAL: 'task',
|
NORMAL: 'task',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed, watch, nextTick } from 'vue'
|
import { ref, onMounted, computed, watch, nextTick } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
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 { 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'
|
||||||
@@ -329,11 +329,11 @@ watch(() => route.query, (newQuery) => {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">所有状态</SelectItem>
|
<SelectItem value="all">所有状态</SelectItem>
|
||||||
<SelectItem value="running">正在运行</SelectItem>
|
<SelectItem :value="TASK_STATUS.RUNNING">{{ TASK_STATUS_TEXT[TASK_STATUS.RUNNING] }}</SelectItem>
|
||||||
<SelectItem value="success">成功</SelectItem>
|
<SelectItem :value="TASK_STATUS.SUCCESS">{{ TASK_STATUS_TEXT[TASK_STATUS.SUCCESS] }}</SelectItem>
|
||||||
<SelectItem value="failed">失败</SelectItem>
|
<SelectItem :value="TASK_STATUS.FAILED">{{ TASK_STATUS_TEXT[TASK_STATUS.FAILED] }}</SelectItem>
|
||||||
<SelectItem value="timeout">超时</SelectItem>
|
<SelectItem :value="TASK_STATUS.TIMEOUT">{{ TASK_STATUS_TEXT[TASK_STATUS.TIMEOUT] }}</SelectItem>
|
||||||
<SelectItem value="cancelled">取消</SelectItem>
|
<SelectItem :value="TASK_STATUS.CANCELLED">{{ TASK_STATUS_TEXT[TASK_STATUS.CANCELLED] }}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const props = withDefaults(defineProps<{
|
|||||||
title?: string
|
title?: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
variant?: 'full' | 'simple'
|
variant?: 'full' | 'simple'
|
||||||
|
emptyTitle?: string
|
||||||
|
emptyDescription?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
variant: 'simple'
|
variant: 'simple'
|
||||||
})
|
})
|
||||||
@@ -68,6 +70,8 @@ onUnmounted(() => {
|
|||||||
:title="title"
|
:title="title"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:variant="isFullscreen ? 'simple' : variant"
|
:variant="isFullscreen ? 'simple' : variant"
|
||||||
|
:empty-title="emptyTitle"
|
||||||
|
:empty-description="emptyDescription"
|
||||||
@close="close"
|
@close="close"
|
||||||
@maximize="isFullscreen = !isFullscreen"
|
@maximize="isFullscreen = !isFullscreen"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useSiteSettings } from '@/composables/useSiteSettings'
|
|||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { TASK_TYPE, AGENT_STATUS, TRIGGER_TYPE, TASK_STATUS } from '@/constants'
|
import { TASK_TYPE, AGENT_STATUS, TRIGGER_TYPE, TASK_STATUS } from '@/constants'
|
||||||
import TextOverflow from '@/components/TextOverflow.vue'
|
import TextOverflow from '@/components/TextOverflow.vue'
|
||||||
|
import { getCronDescription } from '@/utils/cron'
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -28,6 +29,7 @@ const showTaskDialog = ref(false)
|
|||||||
const showRepoDialog = ref(false)
|
const showRepoDialog = ref(false)
|
||||||
const editingTask = ref<Partial<Task>>({})
|
const editingTask = ref<Partial<Task>>({})
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
|
|
||||||
const showDeleteDialog = ref(false)
|
const showDeleteDialog = ref(false)
|
||||||
const deleteTaskId = ref<string | null>(null)
|
const deleteTaskId = ref<string | null>(null)
|
||||||
|
|
||||||
@@ -222,6 +224,8 @@ async function toggleTask(task: Task, enabled: boolean) {
|
|||||||
const showLogViewer = ref(false)
|
const showLogViewer = ref(false)
|
||||||
const selectedLog = ref<TaskLog | null>(null)
|
const selectedLog = ref<TaskLog | null>(null)
|
||||||
const logContent = ref('')
|
const logContent = ref('')
|
||||||
|
const logEmptyTitle = ref<string | undefined>(undefined)
|
||||||
|
const logEmptyDesc = ref<string | undefined>(undefined)
|
||||||
let logSocket: WebSocket | null = null
|
let logSocket: WebSocket | null = null
|
||||||
|
|
||||||
function cleanupLogSocket() {
|
function cleanupLogSocket() {
|
||||||
@@ -261,6 +265,8 @@ async function viewLogs(taskId: string) {
|
|||||||
if (!latestLog) return
|
if (!latestLog) return
|
||||||
selectedLog.value = latestLog
|
selectedLog.value = latestLog
|
||||||
logContent.value = ''
|
logContent.value = ''
|
||||||
|
logEmptyTitle.value = undefined
|
||||||
|
logEmptyDesc.value = undefined
|
||||||
showLogViewer.value = true
|
showLogViewer.value = true
|
||||||
|
|
||||||
if (latestLog.status !== TASK_STATUS.RUNNING) {
|
if (latestLog.status !== TASK_STATUS.RUNNING) {
|
||||||
@@ -286,7 +292,22 @@ async function viewLogs(taskId: string) {
|
|||||||
logContent.value += event.data
|
logContent.value += event.data
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
} catch {
|
||||||
toast.error('获取日志失败')
|
toast.error('获取日志失败')
|
||||||
@@ -538,7 +559,13 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="w-10 shrink-0 font-medium opacity-70">定时:</span>
|
<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-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>
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<span class="w-10 shrink-0 font-medium mt-0.5 opacity-70">命令:</span>
|
<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="最新日志"
|
title="最新日志"
|
||||||
variant="full"
|
variant="full"
|
||||||
:log="selectedLog"
|
:log="selectedLog"
|
||||||
:content="displayLogContent" />
|
:content="displayLogContent"
|
||||||
|
:empty-title="logEmptyTitle"
|
||||||
|
:empty-description="logEmptyDesc" />
|
||||||
|
|
||||||
<!-- 删除确认 (批量) -->
|
<!-- 删除确认 (批量) -->
|
||||||
<AlertDialog v-model:open="showBatchDeleteDialog">
|
<AlertDialog v-model:open="showBatchDeleteDialog">
|
||||||
|
|||||||
Reference in New Issue
Block a user