Fix nonblocking preview actions (#1188)

This commit is contained in:
ekko
2026-05-31 19:47:04 +08:00
committed by GitHub
parent d2b69baf7f
commit e1027ec5d7
13 changed files with 522 additions and 165 deletions
+8
View File
@@ -28,12 +28,20 @@ export interface PreviewStatus {
webui_home: string
action_log_path: string
dev_log_path: string
active_action: string | null
active_action_started_at: string | null
last_action: string | null
last_action_completed_at: string | null
last_action_success: boolean | null
last_action_message: string
last_action_code: string
action_log: string
dev_log: string
}
export interface PreviewActionResponse extends PreviewStatus {
success: boolean
accepted?: boolean
message?: string
code?: string
}
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { NAlert, NButton, NDescriptions, NDescriptionsItem, NSelect, NSpace, NTag, useMessage } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import {
@@ -9,6 +9,7 @@ import {
preparePreview,
startPreview,
stopPreview,
type PreviewActionResponse,
type PreviewStatus,
type PreviewTag,
} from '@/api/hermes/system'
@@ -22,6 +23,9 @@ const actionLoading = ref('')
const tags = ref<PreviewTag[]>([])
const selectedTag = ref('')
const status = ref<PreviewStatus | null>(null)
const lastHandledCompletion = ref('')
const completionNotificationsReady = ref(false)
let pollTimer: number | null = null
const tagOptions = computed(() => tags.value.map(tag => ({
label: tag.name,
@@ -29,6 +33,14 @@ const tagOptions = computed(() => tags.value.map(tag => ({
})))
const actionLog = computed(() => status.value?.action_log || '')
const devLog = computed(() => status.value?.dev_log || '')
const activeAction = computed(() => actionLoading.value || status.value?.active_action || '')
const hasActiveAction = computed(() => Boolean(activeAction.value))
const actionSuccessKeys: Record<string, string> = {
prepare: 'githubPreview.prepareSuccess',
install: 'githubPreview.installSuccess',
start: 'githubPreview.startSuccess',
stop: 'githubPreview.stopSuccess',
}
function applyErrorStatus(err: any) {
const messageText = String(err?.message || '')
@@ -88,7 +100,7 @@ async function handleRefresh() {
}
}
async function runAction(action: string, fn: () => Promise<PreviewStatus & { success?: boolean; message?: string; code?: string }>, successKey: string) {
async function runAction(action: string, fn: () => Promise<PreviewActionResponse>, successKey: string) {
actionLoading.value = action
try {
const res = await fn()
@@ -97,7 +109,9 @@ async function runAction(action: string, fn: () => Promise<PreviewStatus & { suc
message.warning(errorCodeMessage(res.code, res.message))
return
}
message.success(t(successKey))
if (!res.accepted && !res.active_action) {
message.success(t(successKey))
}
} catch (err: any) {
applyErrorStatus(err)
const payload = parseErrorPayload(err)
@@ -107,6 +121,25 @@ async function runAction(action: string, fn: () => Promise<PreviewStatus & { suc
}
}
async function pollStatus() {
try {
await loadStatus()
} catch {}
}
function startPolling() {
if (pollTimer) return
pollTimer = window.setInterval(() => {
void pollStatus()
}, 2000)
}
function stopPolling() {
if (!pollTimer) return
window.clearInterval(pollTimer)
pollTimer = null
}
function requireTag(): string | null {
if (!selectedTag.value) {
message.warning(t('githubPreview.selectTag'))
@@ -124,7 +157,7 @@ async function handlePrepare() {
async function handleInstall() {
await runAction('install', async () => {
const res = await installPreview()
if (res.success !== false && !res.installed) {
if (res.success !== false && !res.accepted && !res.active_action && !res.installed) {
return {
...res,
success: false,
@@ -145,7 +178,41 @@ async function handleStop() {
onMounted(async () => {
await handleRefresh()
lastHandledCompletion.value = status.value?.last_action_completed_at || ''
completionNotificationsReady.value = true
})
onUnmounted(() => {
stopPolling()
})
watch(
() => status.value?.active_action || '',
(action) => {
if (action) startPolling()
else stopPolling()
},
)
watch(
() => status.value?.last_action_completed_at || '',
(completedAt) => {
if (!completedAt) return
if (!completionNotificationsReady.value) {
lastHandledCompletion.value = completedAt
return
}
if (completedAt === lastHandledCompletion.value || actionLoading.value) return
lastHandledCompletion.value = completedAt
const completedAction = status.value?.last_action || ''
if (status.value?.last_action_success === false) {
message.error(errorCodeMessage(status.value.last_action_code, status.value.last_action_message))
return
}
const successKey = actionSuccessKeys[completedAction]
if (successKey) message.success(t(successKey))
},
)
</script>
<template>
@@ -161,16 +228,16 @@ onMounted(async () => {
:placeholder="t('githubPreview.selectTag')"
/>
<NSpace>
<NButton type="primary" :loading="actionLoading === 'prepare'" :disabled="!selectedTag" @click="handlePrepare">
<NButton type="primary" :loading="activeAction === 'prepare'" :disabled="hasActiveAction || !selectedTag" @click="handlePrepare">
{{ t('githubPreview.prepare') }}
</NButton>
<NButton :loading="actionLoading === 'install'" :disabled="!status?.has_package" @click="handleInstall">
<NButton :loading="activeAction === 'install'" :disabled="hasActiveAction || !status?.has_package" @click="handleInstall">
{{ t('githubPreview.install') }}
</NButton>
<NButton type="success" :loading="actionLoading === 'start'" :disabled="!status?.installed" @click="handleStart">
<NButton type="success" :loading="activeAction === 'start'" :disabled="hasActiveAction || !status?.installed" @click="handleStart">
{{ t('githubPreview.start') }}
</NButton>
<NButton :loading="actionLoading === 'stop'" :disabled="!status?.running" @click="handleStop">
<NButton :loading="activeAction === 'stop'" :disabled="hasActiveAction || !status?.running" @click="handleStop">
{{ t('githubPreview.stop') }}
</NButton>
<NButton :loading="loading || tagsLoading" @click="handleRefresh">
+1 -1
View File
@@ -1036,7 +1036,7 @@ jobTriggered: 'Job ausgelost',
nodeEnvironmentMissing: "Node/npm wurde nicht erkannt. Bitte installiere Node.js und versuche es erneut.",
prepareSuccess: "Vorschaucode ist bereit",
installSuccess: "Abhängigkeiten installiert",
startSuccess: "Vorschau gestartet",
startSuccess: "Vorschau abgeschlossen",
stopSuccess: "Vorschau gestoppt",
},
+1 -1
View File
@@ -1138,7 +1138,7 @@ export default {
nodeEnvironmentMissing: "Node/npm was not detected. Please install Node.js and try again.",
prepareSuccess: "Preview code is ready",
installSuccess: "Dependencies installed",
startSuccess: "Preview started",
startSuccess: "Preview completed",
stopSuccess: "Preview stopped",
},
+1 -1
View File
@@ -1036,7 +1036,7 @@ jobTriggered: 'Job ejecutado',
nodeEnvironmentMissing: "No se detectó Node/npm. Instala Node.js y vuelve a intentarlo.",
prepareSuccess: "Código de vista previa listo",
installSuccess: "Dependencias instaladas",
startSuccess: "Vista previa iniciada",
startSuccess: "Vista previa completada",
stopSuccess: "Vista previa detenida",
},
+1 -1
View File
@@ -1036,7 +1036,7 @@ jobTriggered: 'Job declenche',
nodeEnvironmentMissing: "Node/npm na pas été détecté. Installez Node.js puis réessayez.",
prepareSuccess: "Code de prévisualisation prêt",
installSuccess: "Dépendances installées",
startSuccess: "Prévisualisation démarrée",
startSuccess: "Prévisualisation terminée",
stopSuccess: "Prévisualisation arrêtée",
},
+1 -1
View File
@@ -1036,7 +1036,7 @@ export default {
nodeEnvironmentMissing: "Node/npm が検出されませんでした。Node.js をインストールしてから再試行してください。",
prepareSuccess: "プレビューコードの準備が完了しました",
installSuccess: "依存関係をインストールしました",
startSuccess: "プレビューを起動しました",
startSuccess: "プレビューが完了しました",
stopSuccess: "プレビューを停止しました",
},
+1 -1
View File
@@ -1036,7 +1036,7 @@ export default {
nodeEnvironmentMissing: "Node/npm 환경을 찾을 수 없습니다. Node.js를 설치한 뒤 다시 시도하세요.",
prepareSuccess: "미리보기 코드가 준비되었습니다",
installSuccess: "의존성이 설치되었습니다",
startSuccess: "미리보기가 시작되었습니다",
startSuccess: "미리보기가 완료되었습니다",
stopSuccess: "미리보기가 중지되었습니다",
},
+1 -1
View File
@@ -1036,7 +1036,7 @@ jobTriggered: 'Job acionado',
nodeEnvironmentMissing: "Node/npm não foi detectado. Instale o Node.js e tente novamente.",
prepareSuccess: "Código de prévia pronto",
installSuccess: "Dependências instaladas",
startSuccess: "Prévia iniciada",
startSuccess: "Prévia concluída",
stopSuccess: "Prévia parada",
},
+1 -1
View File
@@ -1130,7 +1130,7 @@ export default {
nodeEnvironmentMissing: "未偵測到可用的 Node/npm 環境,請先安裝 Node.js 後重試。",
prepareSuccess: "預覽程式碼已準備好",
installSuccess: "依賴安裝完成",
startSuccess: "預覽已啟動",
startSuccess: "預覽已完成",
stopSuccess: "預覽已停止",
},
+1 -1
View File
@@ -1130,7 +1130,7 @@ export default {
nodeEnvironmentMissing: "未检测到可用的 Node/npm 环境,请先安装 Node.js 后重试。",
prepareSuccess: "预览代码已准备好",
installSuccess: "依赖安装完成",
startSuccess: "预览已启动",
startSuccess: "预览已完成",
stopSuccess: "预览已停止",
},