128 lines
6.1 KiB
Vue
128 lines
6.1 KiB
Vue
<template>
|
|
<div class="space-y-3">
|
|
<p class="text-[11px] text-muted leading-relaxed">
|
|
saga compensations · external signals · long-running waits · retry policies
|
|
</p>
|
|
<div class="flex items-center gap-3 text-[10px] text-muted/60">
|
|
<span class="font-mono">app/Temporal/OrderFulfillment/</span>
|
|
<a href="https://docs.temporal.io/develop/php/message-passing#signals" target="_blank" class="text-accent/50 hover:text-accent transition-colors">signals</a>
|
|
<a href="https://github.com/temporalio/samples-php" target="_blank" class="text-accent/50 hover:text-accent transition-colors">saga pattern</a>
|
|
<a href="https://docs.temporal.io/develop/php/failure-detection" target="_blank" class="text-accent/50 hover:text-accent transition-colors">failure detection</a>
|
|
</div>
|
|
|
|
<SimulationControls v-model="simulation" />
|
|
|
|
<template v-if="localOrders.length > 0">
|
|
<table class="w-full text-[11px]">
|
|
<thead>
|
|
<tr class="text-left text-muted border-b border-border">
|
|
<th class="py-1 font-normal">Order</th>
|
|
<th class="font-normal">Customer</th>
|
|
<th class="font-normal text-right pr-4">Amount</th>
|
|
<th class="font-normal pl-4">Status</th>
|
|
<th class="font-normal text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="order in localOrders" :key="order.id"
|
|
class="border-t border-border/30 text-value hover:bg-section/30 transition-colors">
|
|
<td class="py-1.5 font-medium">{{ order.order_number }}</td>
|
|
<td class="text-muted">{{ order.customer_name }}</td>
|
|
<td class="text-right pr-4">${{ Number(order.total_amount).toFixed(2) }}</td>
|
|
<td class="pl-4">
|
|
<span class="inline-flex items-center gap-1">
|
|
<span class="w-1 h-1 rounded-full" :class="orderStatusDot(order.displayStatus || order.status)"></span>
|
|
{{ order.displayStatus || order.status }}
|
|
</span>
|
|
</td>
|
|
<td class="text-right">
|
|
<div class="inline-flex gap-1">
|
|
<button @click="processOrder(order)"
|
|
class="px-1.5 py-0.5 text-[10px] text-accent border border-accent/20 hover:bg-accent/10 transition-all rounded-sm cursor-pointer">
|
|
process
|
|
</button>
|
|
<button @click="shipOrder(order)"
|
|
class="px-1.5 py-0.5 text-[10px] text-value border border-border hover:bg-border/30 transition-all rounded-sm cursor-pointer">
|
|
ship
|
|
</button>
|
|
<button @click="checkStatus(order)"
|
|
class="px-1.5 py-0.5 text-[10px] text-muted border border-border/50 hover:bg-border/20 transition-all rounded-sm cursor-pointer">
|
|
status
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
<p v-else class="text-[11px] text-muted">
|
|
No orders found. Run <code class="bg-deep px-1 py-0.5 rounded-sm text-label">TemporalDemoSeeder</code> first.
|
|
</p>
|
|
|
|
<div v-if="resultJson" class="bg-deep border border-border rounded-sm overflow-hidden">
|
|
<div class="px-2 py-1 border-b border-border/50 text-[10px] text-muted uppercase tracking-wider">Response</div>
|
|
<pre class="p-2 text-[10px] text-label leading-relaxed overflow-x-auto">{{ resultJson }}</pre>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive } from 'vue';
|
|
import axios from 'axios';
|
|
import { usePolling } from '../composables/usePolling.js';
|
|
import SimulationControls from './SimulationControls.vue';
|
|
|
|
const props = defineProps({
|
|
orders: { type: Array, default: () => [] },
|
|
});
|
|
|
|
const simulation = ref({
|
|
failureRate: 0,
|
|
latencyMs: 0,
|
|
rateLimiting: { enabled: false, hitChance: 25, retryAfterMs: 2000 },
|
|
});
|
|
|
|
const localOrders = reactive([...props.orders]);
|
|
const resultJson = ref('');
|
|
|
|
let currentPollingOrder = null;
|
|
const { start: startPolling, stop: stopPolling } = usePolling(async () => {
|
|
if (!currentPollingOrder) return true;
|
|
const { data } = await axios.get(`/temporal/order/${currentPollingOrder.id}/status`);
|
|
if (data.error && !data.status) {
|
|
currentPollingOrder.displayStatus = 'completed';
|
|
resultJson.value = JSON.stringify(data, null, 2);
|
|
return true;
|
|
}
|
|
currentPollingOrder.displayStatus = data.status || currentPollingOrder.displayStatus;
|
|
resultJson.value = JSON.stringify(data, null, 2);
|
|
return ['completed', 'failed'].includes(data.status);
|
|
});
|
|
|
|
async function processOrder(order) {
|
|
const { data } = await axios.post(`/temporal/order/${order.id}/process`, { simulation: simulation.value });
|
|
resultJson.value = JSON.stringify(data, null, 2);
|
|
stopPolling();
|
|
currentPollingOrder = order;
|
|
startPolling();
|
|
}
|
|
|
|
async function shipOrder(order) {
|
|
const { data } = await axios.post(`/temporal/order/${order.id}/ship`);
|
|
resultJson.value = JSON.stringify(data, null, 2);
|
|
}
|
|
|
|
async function checkStatus(order) {
|
|
const { data } = await axios.get(`/temporal/order/${order.id}/status`);
|
|
order.displayStatus = data.status || order.displayStatus;
|
|
resultJson.value = JSON.stringify(data, null, 2);
|
|
}
|
|
|
|
function orderStatusDot(status) {
|
|
if (status === 'completed' || status === 'delivered') return 'bg-accent';
|
|
if (status === 'failed' || status === 'cancelled') return 'bg-danger';
|
|
if (status === 'processing' || status === 'shipping') return 'bg-warn';
|
|
return 'bg-muted';
|
|
}
|
|
</script>
|