feat: initial commit for HookPilot
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { api, errorMessage } from '../api/client';
|
||||
import type { Overview } from '../api/types';
|
||||
import StatusBadge from '../components/StatusBadge.vue';
|
||||
import { toast } from '../composables/toast';
|
||||
import { formatDuration, formatRelative, TRIGGER_LABELS } from '../utils/format';
|
||||
|
||||
const router = useRouter();
|
||||
const data = ref<Overview | null>(null);
|
||||
let timer: number | undefined;
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
data.value = await api.overview();
|
||||
} catch (err) {
|
||||
toast.error(errorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void load();
|
||||
timer = window.setInterval(load, 5000);
|
||||
});
|
||||
onBeforeUnmount(() => window.clearInterval(timer));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1>概览</h1>
|
||||
<div class="subtitle">接口与任务的整体运行状态</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="data">
|
||||
<div class="stat-grid">
|
||||
<div class="card stat-card">
|
||||
<div class="label">接口数量</div>
|
||||
<div class="value">{{ data.hooks.total }}</div>
|
||||
<div class="sub">已启用 {{ data.hooks.enabled }} 个</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="label">运行中任务</div>
|
||||
<div class="value" :style="{ color: data.running ? 'var(--primary)' : undefined }">{{ data.running }}</div>
|
||||
<div class="sub">排队中 {{ data.queued }} 个</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="label">近 24 小时成功</div>
|
||||
<div class="value" style="color: var(--success)">{{ data.last24h.success }}</div>
|
||||
<div class="sub">共 {{ data.last24h.total }} 次执行</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="label">近 24 小时失败</div>
|
||||
<div class="value" :style="{ color: data.last24h.failed ? 'var(--danger)' : undefined }">{{ data.last24h.failed }}</div>
|
||||
<div class="sub">含超时、终止与中断</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overview-grid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>近期执行</h2>
|
||||
<span class="spacer" />
|
||||
<RouterLink :to="{ name: 'executions' }" class="small">查看全部</RouterLink>
|
||||
</div>
|
||||
<div class="card-body flush table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>接口</th>
|
||||
<th>来源</th>
|
||||
<th>状态</th>
|
||||
<th>耗时</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="e in data.recent"
|
||||
:key="e.id"
|
||||
class="clickable"
|
||||
@click="router.push({ name: 'execution-detail', params: { id: e.id } })"
|
||||
>
|
||||
<td class="mono">#{{ e.id }}</td>
|
||||
<td>{{ e.hookName }}</td>
|
||||
<td class="muted">{{ TRIGGER_LABELS[e.trigger] }}</td>
|
||||
<td><StatusBadge :status="e.status" /></td>
|
||||
<td class="nowrap">{{ formatDuration(e.durationMs) }}</td>
|
||||
<td class="muted nowrap">{{ formatRelative(e.createdAt) }}</td>
|
||||
</tr>
|
||||
<tr v-if="!data.recent.length">
|
||||
<td colspan="6" class="empty">暂无执行记录</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>近期失败</h2></div>
|
||||
<div class="card-body stack">
|
||||
<RouterLink
|
||||
v-for="e in data.recentFailures"
|
||||
:key="e.id"
|
||||
:to="{ name: 'execution-detail', params: { id: e.id } }"
|
||||
class="failure-item"
|
||||
>
|
||||
<div class="row">
|
||||
<strong>{{ e.hookName }}</strong>
|
||||
<span class="spacer" />
|
||||
<StatusBadge :status="e.status" />
|
||||
</div>
|
||||
<div class="small muted">
|
||||
#{{ e.id }} · {{ formatRelative(e.createdAt) }}
|
||||
<template v-if="e.exitCode !== null"> · 退出码 {{ e.exitCode }}</template>
|
||||
</div>
|
||||
<div v-if="e.error" class="small failure-error">{{ e.error }}</div>
|
||||
</RouterLink>
|
||||
<div v-if="!data.recentFailures.length" class="empty" style="padding: 20px">暂无失败记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
.failure-item {
|
||||
display: block;
|
||||
color: inherit;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.failure-item:hover {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--surface-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
.failure-error {
|
||||
color: var(--danger);
|
||||
margin-top: 2px;
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.overview-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user