63 lines
2.3 KiB
Vue
63 lines
2.3 KiB
Vue
<template>
|
||
<div
|
||
class="border-b border-surface-border px-4 py-3 hover:bg-surface-raised transition-colors"
|
||
:class="{ 'border-l-2 border-l-sev-error': isHighSeverity }"
|
||
>
|
||
<div class="flex items-start gap-3">
|
||
<SeverityBadge :severity="entry.severity" class="mt-0.5 shrink-0 w-16 text-center" />
|
||
<div class="min-w-0 flex-1">
|
||
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||
<span class="text-accent text-xs">{{ entry.source_id }}</span>
|
||
<span v-if="entry.timestamp_iso" class="text-text-dim text-xs">{{ formatTs(entry.timestamp_iso) }}</span>
|
||
<span
|
||
v-for="p in entry.matched_patterns"
|
||
:key="p"
|
||
class="px-1.5 py-0.5 rounded bg-accent-muted text-accent text-xs"
|
||
>{{ p }}</span>
|
||
<span v-if="entry.repeat_count > 1" class="text-text-dim text-xs">×{{ entry.repeat_count }}</span>
|
||
</div>
|
||
<p
|
||
:id="`entry-text-${entry.entry_id}`"
|
||
class="text-text-primary text-sm whitespace-pre-wrap break-words leading-relaxed"
|
||
:class="{ 'line-clamp-3': isLong && !expanded }"
|
||
>{{ entry.text }}</p>
|
||
<button
|
||
v-if="isLong"
|
||
@click="expanded = !expanded"
|
||
:aria-expanded="expanded"
|
||
:aria-controls="`entry-text-${entry.entry_id}`"
|
||
class="mt-1 text-text-dim hover:text-accent text-xs transition-colors"
|
||
>
|
||
<span aria-hidden="true">{{ expanded ? '▲' : '▼' }}</span>
|
||
{{ expanded ? 'Collapse entry' : 'Expand entry' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue'
|
||
import type { LogEntry } from '@/stores/search'
|
||
import SeverityBadge from './SeverityBadge.vue'
|
||
|
||
const props = defineProps<{ entry: LogEntry }>()
|
||
const expanded = ref(false)
|
||
|
||
const isHighSeverity = computed(() =>
|
||
['ERROR', 'CRITICAL'].includes((props.entry.severity ?? '').toUpperCase())
|
||
)
|
||
|
||
const isLong = computed(() => props.entry.text.length > 200 || props.entry.text.includes('\n'))
|
||
|
||
function formatTs(iso: string): string {
|
||
try {
|
||
return new Date(iso).toLocaleString(undefined, {
|
||
month: 'short', day: 'numeric',
|
||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||
})
|
||
} catch {
|
||
return iso
|
||
}
|
||
}
|
||
</script>
|