Agregar dropdown de eventos Frigate y corregir endpoint de creacion
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 59s

- Agregar toggle Live/Eventos en StreamViewer
- Agregar dropdown para seleccionar eventos recientes (max 10)
- Reproducir clips de eventos en el mismo reproductor
- Crear endpoint proxy /api/frigate/events para listar eventos
- Corregir URL de creacion de eventos: /api/events/:camera/:label/create
- Actualizar useFrigateEvents con fetchEvents, formatEventTime, getEventClipUrl
This commit is contained in:
2025-12-30 04:02:36 -06:00
parent 6ddd339c3d
commit 8e555b543d
4 changed files with 262 additions and 15 deletions

View File

@@ -64,10 +64,52 @@
:items="streamTypeOptions"
value-key="value"
class="w-full"
:disabled="viewMode === 'event'"
/>
</div>
</div>
<!-- Toggle Live/Eventos -->
<div v-if="selectedStream" class="mb-4">
<div class="flex items-center gap-2 mb-2">
<UButton
:color="viewMode === 'live' ? 'primary' : 'neutral'"
:variant="viewMode === 'live' ? 'solid' : 'ghost'"
size="sm"
icon="i-heroicons-signal"
@click="viewMode = 'live'; selectedEventId = null"
>
En Vivo
</UButton>
<UButton
:color="viewMode === 'event' ? 'primary' : 'neutral'"
:variant="viewMode === 'event' ? 'solid' : 'ghost'"
size="sm"
icon="i-heroicons-film"
:loading="isLoadingEvents"
@click="viewMode = 'event'"
>
Eventos ({{ events.length }})
</UButton>
</div>
<!-- Selector de Eventos -->
<div v-if="viewMode === 'event'" class="mt-2">
<USelect
v-model="selectedEventId"
:items="eventOptions"
placeholder="Seleccionar evento..."
:loading="isLoadingEvents"
:disabled="events.length === 0"
value-key="value"
class="w-full"
/>
<p v-if="events.length === 0 && !isLoadingEvents" class="text-sm text-gray-500 mt-1">
No hay eventos recientes para esta camara
</p>
</div>
</div>
<!-- Banner de conexión -->
<div
v-if="!isStreamSessionActive"
@@ -95,15 +137,40 @@
</div>
<!-- Reproductor (solo si hay sesión activa) -->
<StreamsStreamPlayer
v-if="isStreamSessionActive"
:key="streamPlayerKey"
:stream-url="getStreamUrl"
:use-iframe="useIframe"
:stream-type="selectedType"
:is-loading="isLoading"
@error="handlePlayerError"
/>
<template v-if="isStreamSessionActive">
<!-- Reproductor Live -->
<StreamsStreamPlayer
v-if="viewMode === 'live'"
:key="streamPlayerKey"
:stream-url="getStreamUrl"
:use-iframe="useIframe"
:stream-type="selectedType"
:is-loading="isLoading"
@error="handlePlayerError"
/>
<!-- Reproductor de Evento -->
<div v-else-if="viewMode === 'event'" class="relative aspect-video bg-black rounded-lg overflow-hidden">
<video
v-if="selectedEventUrl"
:key="selectedEventId"
:src="selectedEventUrl"
controls
autoplay
class="w-full h-full object-contain"
@error="handlePlayerError('Error al cargar el clip del evento')"
/>
<div
v-else
class="absolute inset-0 flex items-center justify-center text-gray-400"
>
<div class="text-center">
<UIcon name="i-heroicons-film" class="w-12 h-12 mb-2" />
<p>Selecciona un evento para reproducir</p>
</div>
</div>
</div>
</template>
<!-- Botones de Eventos -->
<div v-if="selectedStream" class="mt-4 flex items-center gap-2">
@@ -245,7 +312,13 @@ const {
const {
isCreating: isCreatingEvent,
isLoadingEvents,
error: eventError,
events,
fetchEvents,
getEventClipUrl,
getEventSnapshotUrl,
formatEventTime,
createEvent,
createQuickEvent,
clearError: clearEventError
@@ -256,6 +329,10 @@ const toast = useToast()
// Modal state
const showEventModal = ref(false)
// View mode: 'live' or 'event'
const viewMode = ref<'live' | 'event'>('live')
const selectedEventId = ref<string | null>(null)
// Estado de conexión a streams
const isConnecting = ref(false)
const isStreamSessionActive = ref(false)
@@ -338,6 +415,30 @@ const currentTypeDescription = computed(() => {
return type?.description || ''
})
// Opciones para el dropdown de eventos
const eventOptions = computed(() => {
return events.value.map(e => ({
value: e.id,
label: `${formatEventTime(e.start_time)} - ${e.label}${e.sub_label ? ` (${e.sub_label})` : ''}`
}))
})
// URL del evento seleccionado
const selectedEventUrl = computed(() => {
if (!selectedEventId.value) return null
return getEventClipUrl(selectedEventId.value)
})
// Cargar eventos cuando cambia la cámara
watch(() => selectedStream.value, async (newStream) => {
if (newStream) {
await fetchEvents(newStream, 10)
// Reset event selection when camera changes
selectedEventId.value = null
viewMode.value = 'live'
}
})
// Cargar streams al montar el componente
onMounted(() => {
fetchStreams()