} />
@@ -1757,13 +1785,24 @@ function WebhookPage({
{summaryLoading ? : Object.keys(summary?.receipts ?? {}).length ? {Object.entries(summary?.receipts ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([state, count]) => )}
: }
+
+
+
+
+
+
+
+
+
+ {exceptions?.length ? | Type | Repository | Reference | Observed |
{exceptions.map((item) => | {item.kind} | {item.repository ?? "unknown"} | {item.event_key ?? item.reference} | {item.created_at} |
)}
: }
+
> : null}
{section === "hooks" ?
- {sectionLoading ? : hooks?.length ? <>| Target | State | Configuration | Last delivery | Last ping |
{hooks.map((hook) => onViewHook(hook.id)}>{hook.target_type} · #{hook.id} | {hook.status} | {hook.events.join(", ") || "No event snapshot"} {hook.content_type ?? "content type unknown"} · SSL {hook.ssl_verify === null || hook.ssl_verify === undefined ? "unknown" : hook.ssl_verify ? "verified" : "disabled"} | {hook.last_event_name ?? "Never"}{hook.last_action ? ` · ${hook.last_action}` : ""} {hook.last_repository ?? hook.last_event_at ?? "No delivery observed"} | {hook.last_ping_at ?? "Never"} |
)}
> : }
+ {sectionLoading ? : hooks?.length ? | Target | State | Configuration | Last delivery | Last ping |
{hooks.map((hook) => onViewHook(hook.id)}>{hook.target_type} · #{hook.id} | {hook.status} | {hook.events.join(", ") || "No event snapshot"} {hook.content_type ?? "content type unknown"} · SSL {hook.ssl_verify === null || hook.ssl_verify === undefined ? "unknown" : hook.ssl_verify ? "verified" : "disabled"} | {hook.last_event_name ?? "Never"}{hook.last_action ? ` · ${hook.last_action}` : ""} {hook.last_repository ?? hook.last_event_at ?? "No delivery observed"} | {hook.last_ping_at ?? "Never"} |
)}
: }
: null}
{section === "deliveries" ?
- {sectionLoading ? : deliveries?.length ? <>> : }
+ {sectionLoading ? : deliveries?.length ? : }
: null}
);
@@ -1780,24 +1819,39 @@ function WebhookDeliveryFiltersForm({ filters, onChange }: { filters: WebhookDel
}
function WebhookDeliveriesTable({ deliveries, onViewHook }: { deliveries: WebhookDelivery[]; onViewHook: (hookId: string) => void }) {
- return
| Received | Hook | Event | Repository | Result | Delivery |
{deliveries.map((delivery) => | {delivery.created_at} | {delivery.hook_id ? : Unknown (legacy)} | {delivery.event_name}{delivery.action ? ` · ${delivery.action}` : ""} | {delivery.repository ?? "—"} | {delivery.status}{delivery.duplicate_count ? {delivery.duplicate_count} retries : null} | {delivery.delivery_id} |
)}
;
+ return
| Received | Hook | Event | Repository | Result | Delivery |
{deliveries.map((delivery) => | {delivery.created_at} | {delivery.hook_id ? : Unknown (legacy)} | {delivery.event_name}{delivery.action ? ` · ${delivery.action}` : ""} | {delivery.repository ?? "—"} | {delivery.status}{delivery.duplicate_count ? {delivery.duplicate_count} retries : null} | {delivery.delivery_id} |
)}
;
+}
+
+function LazyScrollFrame({ noun, hasMore, loading, onLoadMore, children, className }: { noun: string; hasMore: boolean; loading: boolean; onLoadMore?: () => void; children: React.ReactNode; className?: string }) {
+ return
{children}
;
}
-function IncrementalLoadControls({ kind, hasMore, loading, onLoadMore }: { kind: "hooks" | "deliveries"; hasMore: boolean; loading: boolean; onLoadMore: () => void }) {
+function LazyLoadSentinel({ noun, hasMore, loading, onLoadMore }: { noun: string; hasMore: boolean; loading: boolean; onLoadMore?: () => void }) {
const ref = React.useRef
(null);
+ const requested = React.useRef(false);
+ React.useEffect(() => {
+ if (!loading) requested.current = false;
+ }, [loading]);
React.useEffect(() => {
const node = ref.current;
- if (!node || !hasMore || loading) return;
- if (!("IntersectionObserver" in window)) return;
+ if (!node || !hasMore || loading || !onLoadMore) return;
+ const requestMore = () => {
+ if (requested.current) return;
+ requested.current = true;
+ onLoadMore();
+ };
+ if (!("IntersectionObserver" in window)) {
+ requestMore();
+ return;
+ }
const observer = new IntersectionObserver((entries) => {
- if (entries.some((entry) => entry.isIntersecting)) onLoadMore();
+ if (entries.some((entry) => entry.isIntersecting)) requestMore();
}, { rootMargin: "240px 0px" });
observer.observe(node);
return () => observer.disconnect();
- }, [hasMore, kind, loading, onLoadMore]);
+ }, [hasMore, loading, noun, onLoadMore]);
if (!hasMore && !loading) return null;
- const noun = kind === "hooks" ? "hooks" : "deliveries";
- return ;
+ return {loading ? `Loading more ${noun}...` : `Scroll for more ${noun}`}
;
}
function WebhookHookDetailPage({ data, loading, error, onBack, onRefresh, onViewHook }: { data?: WebhookHookDetailResponse; loading: boolean; error: Error | null; onBack: () => void; onRefresh: () => void; onViewHook: (hookId: string) => void }) {
@@ -3230,7 +3284,7 @@ function JobsList({
))}
-