150 lines
6.3 KiB
Vue
150 lines
6.3 KiB
Vue
<script setup lang="ts">
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
import { Link } from "@inertiajs/vue3";
|
|
import { GitCommitIcon } from "lucide-vue-next";
|
|
import { ref } from "vue";
|
|
|
|
defineProps<{
|
|
operations: Record<string, any>[];
|
|
showTarget?: boolean;
|
|
}>();
|
|
|
|
const selectedStep = ref<Record<string, any> | null>(null);
|
|
|
|
const label = (value?: string | null): string => value?.replaceAll("_", " ").replaceAll("-", " ") ?? "";
|
|
|
|
const targetLabel = (target?: Record<string, any> | null): string => {
|
|
if (!target) {
|
|
return "Unknown target";
|
|
}
|
|
|
|
return target.name ?? target.hostname ?? `#${target.id}`;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="grid gap-3">
|
|
<div
|
|
v-for="operation in operations"
|
|
:key="operation.id"
|
|
class="rounded-md border p-3 text-sm"
|
|
>
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div class="min-w-0">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<GitCommitIcon class="size-4 text-muted-foreground" />
|
|
<Link
|
|
:href="
|
|
route('operations.show', {
|
|
organisation: $page.props.organisation.id,
|
|
operation: operation.id,
|
|
})
|
|
"
|
|
class="font-medium hover:underline"
|
|
>
|
|
{{ label(operation.kind) }}
|
|
</Link>
|
|
<Badge variant="outline">{{ operation.hash }}</Badge>
|
|
<Badge
|
|
:variant="operation.status === 'completed' ? 'success' : 'secondary'"
|
|
>
|
|
{{ label(operation.status) }}
|
|
</Badge>
|
|
</div>
|
|
<p v-if="showTarget" class="mt-1 text-muted-foreground">
|
|
Target: {{ targetLabel(operation.target) }}
|
|
</p>
|
|
</div>
|
|
<div class="text-xs text-muted-foreground">
|
|
{{ operation.steps_count ?? operation.steps?.length ?? 0 }} steps
|
|
<span v-if="operation.children_count ?? operation.children?.length">
|
|
· {{ operation.children_count ?? operation.children?.length }} child ops
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="operation.steps?.length" class="mt-3 grid gap-2 border-l pl-3">
|
|
<div
|
|
v-for="step in operation.steps"
|
|
:key="step.id"
|
|
class="flex items-start justify-between gap-3"
|
|
>
|
|
<div class="min-w-0">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<div class="font-medium">{{ step.name ?? "Unnamed step" }}</div>
|
|
<Badge
|
|
:variant="step.status === 'completed' ? 'success' : 'secondary'"
|
|
>
|
|
{{ label(step.status) }}
|
|
</Badge>
|
|
</div>
|
|
<pre
|
|
v-if="step.error_logs_excerpt || step.logs_excerpt"
|
|
class="mt-1 max-h-20 overflow-hidden whitespace-pre-wrap text-xs text-muted-foreground"
|
|
>{{ step.error_logs_excerpt ?? step.logs_excerpt }}</pre
|
|
>
|
|
</div>
|
|
<Button
|
|
v-if="step.logs || step.error_logs"
|
|
size="xs"
|
|
variant="link"
|
|
@click="selectedStep = step"
|
|
>
|
|
Logs
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="operation.children?.length" class="mt-3 grid gap-2 border-l pl-3">
|
|
<div
|
|
v-for="child in operation.children"
|
|
:key="child.id"
|
|
class="rounded-md bg-muted/40 p-2"
|
|
>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<Link
|
|
:href="
|
|
route('operations.show', {
|
|
organisation: $page.props.organisation.id,
|
|
operation: child.id,
|
|
})
|
|
"
|
|
class="font-medium hover:underline"
|
|
>
|
|
{{ label(child.kind) }}
|
|
</Link>
|
|
<Badge
|
|
:variant="child.status === 'completed' ? 'success' : 'secondary'"
|
|
>
|
|
{{ label(child.status) }}
|
|
</Badge>
|
|
<span class="text-muted-foreground">{{ targetLabel(child.target) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="operations.length === 0" class="rounded-md border border-dashed p-6 text-sm text-muted-foreground">
|
|
No operations recorded yet.
|
|
</div>
|
|
</div>
|
|
|
|
<Dialog :open="!!selectedStep" @update:open="($event) => (!$event ? (selectedStep = null) : null)">
|
|
<DialogContent class="md:max-w-3xl">
|
|
<DialogHeader>
|
|
<DialogTitle>Logs for {{ selectedStep?.name ?? "step" }}</DialogTitle>
|
|
</DialogHeader>
|
|
<section v-if="selectedStep?.logs">
|
|
<h3 class="text-sm font-medium">Logs</h3>
|
|
<pre class="max-h-80 overflow-auto whitespace-pre-wrap text-xs text-muted-foreground">{{ selectedStep.logs }}</pre>
|
|
</section>
|
|
<section v-if="selectedStep?.error_logs">
|
|
<h3 class="text-sm font-medium">Error Logs</h3>
|
|
<pre class="max-h-80 overflow-auto whitespace-pre-wrap text-xs text-muted-foreground">{{ selectedStep.error_logs }}</pre>
|
|
</section>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template>
|