diff --git a/src/features/edgeGateways/EdgeGatewayManager.vue b/src/features/edgeGateways/EdgeGatewayManager.vue index 9a00f995..71f5c414 100644 --- a/src/features/edgeGateways/EdgeGatewayManager.vue +++ b/src/features/edgeGateways/EdgeGatewayManager.vue @@ -87,6 +87,8 @@ const fleetFilter = ref("ALL"); const fleetUsage = ref(createEmptyFleetUsage()); const rotateBundle = ref(null); const editableBindings = ref([]); +const selectedOperationId = ref(null); +const updateTargetVersion = ref(""); const loading = ref({ init: false, detail: false, @@ -302,6 +304,11 @@ const view = (gateway) => { }; const selectedGatewayView = computed(() => view(selectedGateway.value)); +const selectedOperation = computed(() => { + const operations = Array.isArray(selectedGatewayView.value?.operations) ? selectedGatewayView.value.operations : []; + return operations.find((operation) => String(operation.id) === String(selectedOperationId.value)) || null; +}); +const selectedOperationEvents = computed(() => selectedOperation.value?.events || []); const summaryCards = computed(() => { const rows = gateways.value.map(view).filter(Boolean); @@ -385,6 +392,29 @@ const syncDrafts = (gateway) => { })); }; +const syncSelectedOperation = (gateway) => { + const operations = Array.isArray(gateway?.operations) ? gateway.operations : []; + if (!operations.length) { + selectedOperationId.value = null; + return; + } + + const currentSelection = operations.find((operation) => String(operation.id) === String(selectedOperationId.value)) || null; + if (currentSelection) { + selectedOperationId.value = String(currentSelection.id); + return; + } + + const preferredOperation = + operations.find((operation) => ["PENDING", "IN_PROGRESS", "CANCEL_REQUESTED"].includes(String(operation.status || ""))) || + operations[0]; + selectedOperationId.value = preferredOperation ? String(preferredOperation.id) : null; +}; + +const selectOperation = (operation) => { + selectedOperationId.value = operation?.id ? String(operation.id) : null; +}; + const clearError = () => { errorState.value = null; }; @@ -507,6 +537,7 @@ const clearSelection = () => { selectedGateway.value = null; activeGatewayId.value = null; unavailableGatewayId.value = null; + selectedOperationId.value = null; }; const setSelectedGatewaySnapshot = (gateway) => { @@ -519,6 +550,7 @@ const setSelectedGatewaySnapshot = (gateway) => { activeGatewayId.value = String(mergedGateway.id); unavailableGatewayId.value = null; syncDrafts(mergedGateway); + syncSelectedOperation(mergedGateway); return mergedGateway; }; @@ -862,10 +894,14 @@ const queueOperation = async ({ type, request }) => { loading.value.operation = true; try { const response = await createEdgeGatewayOperation(selectedGateway.value.id, type, request || {}); + const operation = unwrap(response, null)?.operation || response?.data?.data?.operation || null; const gateway = unwrap(response, null)?.gateway || response?.data?.data?.gateway || null; if (gateway) { setSelectedGatewaySnapshot(gateway); } + if (operation?.id) { + selectedOperationId.value = String(operation.id); + } flashMessage.value = `Gateway operation ${type} queued.`; clearError(); } catch (error) { @@ -880,10 +916,14 @@ const cancelOperation = async (operation) => { loading.value.operationCancel = true; try { const response = await cancelEdgeGatewayOperation(selectedGateway.value.id, operation.id); + const updatedOperation = response?.data?.data?.operation || null; const gateway = unwrap(response, null)?.gateway || response?.data?.data?.gateway || null; if (gateway) { setSelectedGatewaySnapshot(gateway); } + if (updatedOperation?.id) { + selectedOperationId.value = String(updatedOperation.id); + } flashMessage.value = String(response?.data?.data?.operation?.status || "") === "CANCEL_REQUESTED" ? `Cancellation requested for ${operation.type}.` @@ -915,6 +955,48 @@ const queueDiscovery = async () => { } }; +const queueUpdate = async () => { + const targetVersion = String(updateTargetVersion.value || "").trim(); + if (!targetVersion) { + flashMessage.value = "Target version is required."; + return; + } + + await queueOperation({ + type: "UPDATE", + request: { + target_version: targetVersion, + release_channel: "stable", + }, + }); +}; + +const queueUninstall = async () => { + await queueOperation({ type: "UNINSTALL", request: {} }); +}; + +const retryOperation = async (operation) => { + if (!operation?.type) { + return; + } + + const request = + operation.type === "UPDATE" + ? { + target_version: + String(operation?.request?.target_version || updateTargetVersion.value || selectedGateway.value?.target_version || "").trim(), + release_channel: String(operation?.request?.release_channel || "stable"), + } + : operation?.request || {}; + + if (operation.type === "UPDATE" && !request.target_version) { + flashMessage.value = "Target version is required."; + return; + } + + await queueOperation({ type: operation.type, request }); +}; + const addBindingRow = () => editableBindings.value.push({ relay_id: "", device_id: "", local_ip: "", channel: 0, fallback_mode: "PREFER_LOCAL" }); @@ -1284,11 +1366,9 @@ onUnmounted(() => { { { }; const getStripeHostedInvoiceStatusLabel = () => { - return t(`admin.pos.stripe.email.states.${getStripeHostedInvoiceStatusKey()}`); + switch (getStripeHostedInvoiceStatusKey()) { + case 'open': + return t('admin.pos.stripe.email.states.open'); + case 'paid': + return t('admin.pos.stripe.email.states.paid'); + case 'void': + return t('admin.pos.stripe.email.states.void'); + case 'uncollectible': + return t('admin.pos.stripe.email.states.uncollectible'); + case 'deleted': + return t('admin.pos.stripe.email.states.deleted'); + case 'draft': + return t('admin.pos.stripe.email.states.draft'); + default: + return t('admin.pos.stripe.email.states.unknown'); + } }; const getStripeHostedInvoiceStatusToneClass = () => {