feat(delivery): stream task updates with SSE
This commit is contained in:
@@ -72,6 +72,11 @@ export interface TaskEvent {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface DeliveryTaskSnapshot {
|
||||
task: DeliveryTask
|
||||
events: TaskEvent[]
|
||||
}
|
||||
|
||||
export const deliveryApi = {
|
||||
async listTargets(): Promise<DeliveryTarget[]> {
|
||||
const data = await authRequest('/auth/api/v1/delivery/targets?component=mysql')
|
||||
@@ -112,6 +117,14 @@ export const deliveryApi = {
|
||||
method: 'POST',
|
||||
})
|
||||
},
|
||||
|
||||
streamTask(taskId: string): EventSource {
|
||||
const token = getToken()
|
||||
const search = new URLSearchParams()
|
||||
if (token) search.set('access_token', token)
|
||||
const query = search.toString()
|
||||
return new EventSource(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}/stream${query ? `?${query}` : ''}`)
|
||||
},
|
||||
}
|
||||
|
||||
function createIdempotencyKey(payload: CreateMySQLDeliveryPayload) {
|
||||
|
||||
@@ -332,7 +332,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Back, CircleCheck, Promotion, Refresh } from '@element-plus/icons-vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@@ -553,6 +553,7 @@ const taskRefreshing = ref(false)
|
||||
const taskRestoring = ref(false)
|
||||
const seenEventIds = ref(new Set<number>())
|
||||
const deliveryLog = ref('[ready] 等待创建交付任务...')
|
||||
let taskEventSource: EventSource | undefined
|
||||
|
||||
const deliveryForm = reactive({
|
||||
instanceName: `mysql-${currentName.value}-billing-02`,
|
||||
@@ -674,6 +675,10 @@ onMounted(async () => {
|
||||
await restoreActiveTask()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTaskStream()
|
||||
})
|
||||
|
||||
function handleCardClick(service: Service) {
|
||||
if (service.disabled) {
|
||||
if (service.key !== 'new') ElMessage.warning(`${service.name} 功能规划中,敬请期待`)
|
||||
@@ -730,6 +735,7 @@ function startNewTask() {
|
||||
}
|
||||
|
||||
function resetExecutionState() {
|
||||
stopTaskStream()
|
||||
precheckPassed.value = false
|
||||
running.value = false
|
||||
deliveryDone.value = false
|
||||
@@ -816,6 +822,7 @@ async function createTask() {
|
||||
deliveryLog.value += `\n[task] ${task.id} created by ${currentName.value}`
|
||||
applyDeliveryStatus(task.status, '')
|
||||
await refreshTaskSnapshot()
|
||||
startTaskStream(task.id)
|
||||
ElMessage.success('交付任务已创建')
|
||||
} catch (error) {
|
||||
running.value = false
|
||||
@@ -909,6 +916,8 @@ async function restoreActiveTask() {
|
||||
applyDeliveryStatus(detail.task.status, detail.task.error_message || '')
|
||||
if (isTerminalDeliveryStatus(detail.task.status)) {
|
||||
activeView.value = 'result'
|
||||
} else {
|
||||
startTaskStream(detail.task.id)
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '恢复交付任务失败')
|
||||
@@ -933,6 +942,35 @@ function hydrateTaskSnapshot(task: DeliveryTask) {
|
||||
}
|
||||
}
|
||||
|
||||
function startTaskStream(taskId: string) {
|
||||
stopTaskStream()
|
||||
taskEventSource = deliveryApi.streamTask(taskId)
|
||||
taskEventSource.addEventListener('snapshot', (event) => {
|
||||
try {
|
||||
const data = JSON.parse((event as MessageEvent).data) as { task?: DeliveryTask; events?: TaskEvent[] }
|
||||
if (!data.task) return
|
||||
hydrateTaskSnapshot(data.task)
|
||||
applyTaskEvents(Array.isArray(data.events) ? data.events : [])
|
||||
applyDeliveryStatus(data.task.status, data.task.error_message || '')
|
||||
if (isTerminalDeliveryStatus(data.task.status)) {
|
||||
activeView.value = 'result'
|
||||
stopTaskStream()
|
||||
}
|
||||
} catch (error) {
|
||||
appendLog(`[stream] ${error instanceof Error ? error.message : '解析任务状态失败'}`)
|
||||
}
|
||||
})
|
||||
taskEventSource.onerror = () => {
|
||||
if (running.value) appendLog('[stream] 实时连接中断,正在等待浏览器自动重连')
|
||||
}
|
||||
}
|
||||
|
||||
function stopTaskStream() {
|
||||
if (!taskEventSource) return
|
||||
taskEventSource.close()
|
||||
taskEventSource = undefined
|
||||
}
|
||||
|
||||
async function cancelDeployment() {
|
||||
if (!deploymentId.value) return
|
||||
canceling.value = true
|
||||
|
||||
Reference in New Issue
Block a user