diff --git a/web/src/routes/Events.jsx b/web/src/routes/Events.jsx
index b0afff971..912293b69 100644
--- a/web/src/routes/Events.jsx
+++ b/web/src/routes/Events.jsx
@@ -31,6 +31,9 @@ import Timepicker from '../components/TimePicker';
import TimelineSummary from '../components/TimelineSummary';
import TimelineEventOverlay from '../components/TimelineEventOverlay';
import { Score } from '../icons/Score';
+import { About } from '../icons/About';
+import MenuIcon from '../icons/Menu';
+import { MenuOpen } from '../icons/MenuOpen';
const API_LIMIT = 25;
@@ -91,13 +94,15 @@ export default function Events({ path, ...props }) {
showDeleteFavorite: false,
});
+ const [showInProgress, setShowInProgress] = useState(true);
+
const eventsFetcher = useCallback(
(path, params) => {
if (searchParams.event) {
path = `${path}/${searchParams.event}`;
return axios.get(path).then((res) => [res.data]);
}
- params = { ...params, include_thumbnails: 0, limit: API_LIMIT };
+ params = { ...params, in_progress: 0, include_thumbnails: 0, limit: API_LIMIT };
return axios.get(path, { params }).then((res) => res.data);
},
[searchParams]
@@ -116,6 +121,7 @@ export default function Events({ path, ...props }) {
[searchParams]
);
+ const { data: ongoingEvents } = useSWR(['events', { in_progress: 1, include_thumbnails: 0 }]);
const { data: eventPages, mutate, size, setSize, isValidating } = useSWRInfinite(getKey, eventsFetcher);
const { data: allLabels } = useSWR(['labels']);
@@ -604,6 +610,217 @@ export default function Events({ path, ...props }) {
)}
+ {ongoingEvents ? (
+
+
+
+ Ongoing Events
+
+
+
+
+ {showInProgress && ongoingEvents.map((event, _) => {
+ return (
+
+ (viewEvent === event.id ? setViewEvent(null) : setViewEvent(event.id))}
+ >
+
+
onSave(e, event.id, !event.retain_indefinitely)}
+ fill={event.retain_indefinitely ? 'currentColor' : 'none'}
+ />
+ {event.end_time ? null : (
+
+ In progress
+
+ )}
+
+
+
+
+ {event.label.replaceAll('_', ' ')}
+ {event.sub_label ? `: ${event.sub_label.replaceAll('_', ' ')}` : null}
+
+
+
+
+ {formatUnixTimestampToDateTime(event.start_time, { ...config.ui })}
+
+ -
+
+
+
+ ( {getDurationFromTimestamps(event.start_time, event.end_time)} )
+
+
+
+
+ {event.camera.replaceAll('_', ' ')}
+
+ {event.zones.length ? (
+
+
+ {event.zones.join(', ').replaceAll('_', ' ')}
+
+ ) : null}
+
+
+ {(event?.data?.top_score || event.top_score || 0) == 0
+ ? null
+ : `${event.label}: ${((event?.data?.top_score || event.top_score) * 100).toFixed(0)}%`}
+ {(event?.data?.sub_label_score || 0) == 0
+ ? null
+ : `, ${event.sub_label}: ${(event?.data?.sub_label_score * 100).toFixed(0)}%`}
+
+
+
+ {event.end_time && event.has_snapshot && (event?.data?.type || 'object') == 'object' && (
+
+ {event.plus_id ? (
+
+
+ Edit in Frigate+
+
+
+ ) : (
+
+ )}
+
+ )}
+
+
+ onDelete(e, event.id, event.retain_indefinitely)}
+ />
+
+ onDownloadClick(e, event)}
+ />
+
+
+
+ {viewEvent !== event.id ? null : (
+
+
+
+
+
+
+
+
+
+
+ {eventDetailType == 'clip' && event.has_clip ? (
+
+
+ onEventFrameSelected(event, frame, seekSeconds)
+ }
+ />
+
+ {
+ this.player = player;
+ this.player.on('playing', () => {
+ setEventOverlay(undefined);
+ });
+ }}
+ onDispose={() => {
+ this.player = null;
+ }}
+ >
+ {eventOverlay ? (
+
+ ) : null}
+
+
+
+ ) : null}
+
+ {eventDetailType == 'image' || !event.has_clip ? (
+
+

+
+ ) : null}
+
+
+
+ )}
+
+ );
+ })}
+
+ ) : null}
+
+ Past Events
+
{eventPages ? (
eventPages.map((page, i) => {
const lastPage = eventPages.length === i + 1;