{@render children()}
-
+
+ {#if actions}
+ {@render actions()}
+ {/if}
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-actions.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-actions.svelte
index 63c43aae0..cf9431e7f 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-actions.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-actions.svelte
@@ -2,24 +2,18 @@
import * as Command from '$comp/ui/command';
interface Props {
- apply: () => void;
clear: () => void;
close: () => void;
remove: () => void;
- showApply: boolean;
showClear: boolean;
}
- let { apply, clear, close, remove, showApply, showClear }: Props = $props();
+ let { clear, close, remove, showClear }: Props = $props();
- {#if showApply}
- Apply filter
-
- {/if}
{#if showClear}
Clear filter
{/if}
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-boolean.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-boolean.svelte
index 65d97255b..481a5943f 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-boolean.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-boolean.svelte
@@ -21,7 +21,7 @@
updatedValue = value;
});
- function onApplyFilter() {
+ function onClose() {
if (updatedValue !== value) {
changed(updatedValue);
}
@@ -29,12 +29,18 @@
open = false;
}
+ function onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ onClose();
+ }
+ }
+
export function onClearFilter() {
updatedValue = undefined;
}
-
+
{title}
@@ -50,13 +56,6 @@
- (open = false)}
- {remove}
- showApply={updatedValue !== value}
- showClear={updatedValue !== undefined}
- >
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte
index b82d00706..98ccc72b5 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte
@@ -91,9 +91,15 @@
function onClose() {
open = false;
}
+
+ function onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ onClose();
+ }
+ }
-
+
Filter
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-drop-down.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-drop-down.svelte
index de0be289e..24fb416eb 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-drop-down.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-drop-down.svelte
@@ -32,7 +32,7 @@
updatedValue = value;
});
- function onApplyFilter() {
+ function onClose() {
if (updatedValue !== value) {
changed(updatedValue);
}
@@ -40,6 +40,12 @@
open = false;
}
+ function onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ onClose();
+ }
+ }
+
export function onValueSelected(currentValue: string) {
if (updatedValue === currentValue) {
updatedValue = undefined;
@@ -70,7 +76,7 @@
}
-
+
{title}
@@ -114,13 +120,6 @@
{/if}
-
(open = false)}
- {remove}
- showApply={updatedValue !== value}
- showClear={!!updatedValue?.trim()}
- >
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-keyword.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-keyword.svelte
index 4beb7d188..382a4adb8 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-keyword.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-keyword.svelte
@@ -21,7 +21,7 @@
updatedValue = value;
});
- function onApplyFilter() {
+ function onClose() {
if (updatedValue !== value) {
changed(updatedValue);
}
@@ -29,12 +29,18 @@
open = false;
}
+ function onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ onClose();
+ }
+ }
+
export function onClearFilter() {
updatedValue = undefined;
}
-
+
{title}
@@ -50,13 +56,6 @@
- (open = false)}
- {remove}
- showApply={updatedValue !== value}
- showClear={!!updatedValue?.trim()}
- >
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte
index b2efd1828..a409e62e8 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte
@@ -39,7 +39,7 @@
const hasChanged = $derived(updatedValues.length !== values.length || updatedValues.some((value) => !values.includes(value)));
- function onApplyFilter() {
+ function onClose() {
if (hasChanged) {
changed(updatedValues);
}
@@ -47,6 +47,12 @@
open = false;
}
+ function onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ onClose();
+ }
+ }
+
export function onValueSelected(currentValue: string) {
updatedValues = updatedValues.includes(currentValue) ? updatedValues.filter((v) => v !== currentValue) : [...updatedValues, currentValue];
}
@@ -69,7 +75,7 @@
}
-
+
{title}
@@ -118,13 +124,6 @@
{/if}
- (open = false)}
- {remove}
- showApply={hasChanged}
- showClear={updatedValues.length > 0}
- >
+ 0} />
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-number.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-number.svelte
index c2e50077f..75e2a7e79 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-number.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-number.svelte
@@ -21,7 +21,7 @@
updatedValue = value;
});
- function onApplyFilter() {
+ function onClose() {
if (updatedValue !== value) {
changed(updatedValue);
}
@@ -29,12 +29,18 @@
open = false;
}
+ function onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ onClose();
+ }
+ }
+
export function onClearFilter() {
updatedValue = undefined;
}
-
+
{title}
@@ -50,13 +56,6 @@
- (open = false)}
- {remove}
- showApply={updatedValue !== value}
- showClear={updatedValue !== undefined}
- >
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-string.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-string.svelte
index c1d5df2f1..f2f15b754 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-string.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-string.svelte
@@ -21,7 +21,7 @@
updatedValue = value;
});
- function onApplyFilter() {
+ function onClose() {
if (updatedValue !== value) {
changed(updatedValue);
}
@@ -29,12 +29,18 @@
open = false;
}
+ function onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ onClose();
+ }
+ }
+
export function onClearFilter() {
updatedValue = undefined;
}
-
+
{title}
@@ -50,13 +56,6 @@
- (open = false)}
- {remove}
- showApply={updatedValue !== value}
- showClear={!!updatedValue?.trim()}
- >
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/streaming-indicator-button.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/streaming-indicator-button.svelte
new file mode 100644
index 000000000..db53eb4cf
--- /dev/null
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/streaming-indicator-button.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {#if paused}
+
+ {:else}
+
+ {/if}
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-card.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-card.svelte
index 0ec9f0e53..99a4da53f 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-card.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-card.svelte
@@ -160,7 +160,7 @@
{#each stack.tags as tag (tag)}
{tag}
{/each}
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/token-actions-cell.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/token-actions-cell.svelte
index 397af9230..3bcf92ddc 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/token-actions-cell.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/token-actions-cell.svelte
@@ -6,8 +6,8 @@
import { UseClipboard } from '$lib/hooks/use-clipboard.svelte';
import Disable from '@lucide/svelte/icons/ban';
import Enable from '@lucide/svelte/icons/check';
- import ChevronDown from '@lucide/svelte/icons/chevron-down';
import Copy from '@lucide/svelte/icons/copy';
+ import EllipsisIcon from '@lucide/svelte/icons/ellipsis';
import Edit from '@lucide/svelte/icons/pen';
import X from '@lucide/svelte/icons/x';
import { toast } from 'svelte-sonner';
@@ -80,9 +80,12 @@
-
-
-
+ {#snippet child({ props })}
+
+ Open menu
+
+
+ {/snippet}
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/webhook-actions-cell.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/webhook-actions-cell.svelte
index c1a4209a0..d2d0d0b03 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/webhook-actions-cell.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/webhook-actions-cell.svelte
@@ -3,7 +3,7 @@
import * as DropdownMenu from '$comp/ui/dropdown-menu';
import { deleteWebhook } from '$features/webhooks/api.svelte';
import { Webhook } from '$features/webhooks/models';
- import ChevronDown from '@lucide/svelte/icons/chevron-down';
+ import EllipsisIcon from '@lucide/svelte/icons/ellipsis';
import X from '@lucide/svelte/icons/x';
import { toast } from 'svelte-sonner';
@@ -32,9 +32,12 @@
-
-
-
+ {#snippet child({ props })}
+
+ Open menu
+
+
+ {/snippet}
(showRemoveWebhookDialog = true)} disabled={removeWebhook.isPending}>
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte
index 802633860..90935c613 100644
--- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte
@@ -3,11 +3,10 @@
import type { EventSummaryModel, SummaryTemplateKeys } from '$features/events/components/summary/index';
import { page } from '$app/state';
- import AutomaticRefreshIndicatorButton from '$comp/automatic-refresh-indicator-button.svelte';
import * as DataTable from '$comp/data-table';
import * as FacetedFilter from '$comp/faceted-filter';
+ import StreamingIndicatorButton from '$comp/streaming-indicator-button.svelte';
import { Button } from '$comp/ui/button';
- import * as Card from '$comp/ui/card';
import * as Sheet from '$comp/ui/sheet';
import EventsOverview from '$features/events/components/events-overview.svelte';
import { type DateFilter, StatusFilter } from '$features/events/components/filters';
@@ -72,6 +71,7 @@
updateFilterCache(filterCacheKey(DEFAULT_PARAMS.filter), DEFAULT_FILTERS);
//params.$reset(); // Work around for https://github.com/beynar/kit-query-params/issues/7
Object.assign(queryParams, DEFAULT_PARAMS);
+ reset();
},
{ lazy: true }
);
@@ -147,9 +147,30 @@
})
);
- const canRefresh = $derived(!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected() && !table.getCanPreviousPage());
+ const canRefresh = $derived(!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected() && table.getState().pagination.pageIndex === 0);
+
+ let manualPause = $state(false);
+ let paused = $derived(manualPause || !canRefresh);
+
+ function reset() {
+ manualPause = false;
+ table.resetRowSelection();
+ table.setPageIndex(0);
+ }
+
+ function handleToggle() {
+ if (!canRefresh) {
+ reset();
+ } else {
+ manualPause = !manualPause;
+ }
+ }
async function loadData() {
+ if (paused) {
+ return;
+ }
+
if (client.isLoading || !organization.current) {
return;
}
@@ -166,13 +187,17 @@
if (removeTableData(table, (doc) => doc.id === message.id)) {
// If the grid data is empty from all events being removed, we should refresh the data.
- if (isTableEmpty(table)) {
+ if (isTableEmpty(table) && !paused) {
await throttledLoadData();
return;
}
}
}
+ if (paused) {
+ return;
+ }
+
// Do not refresh if the filter criteria doesn't match the web socket message.
if (
!shouldRefreshPersistentEventChanged(filters ?? [], queryParams.filter, message.organization_id, message.project_id, message.stack_id, message.id)
@@ -180,11 +205,6 @@
return;
}
- // Do not refresh if the grid has selections or grid is currently paged.
- if (!canRefresh) {
- return;
- }
-
await throttledLoadData();
}
@@ -197,36 +217,30 @@
-
-
- Events
-
-
-
- {#snippet toolbarChildren()}
-
-
-
- {/snippet}
- {#snippet footerChildren()}
-
- {#if table.getSelectedRowModel().flatRows.length}
-
- {/if}
-
-
-
-
-
-
-
- {/snippet}
-
-
-
+
+ {#snippet toolbarChildren()}
+ Events
+
+
+
+ {/snippet}
+ {#snippet toolbarActions()}
+
+ {/snippet}
+ {#snippet footerChildren()}
+
+ {#if table.getSelectedRowModel().flatRows.length}
+
+ {/if}
+
+
+
+
+
+
+
+ {/snippet}
+
(selectedEventId = null)} open={!!selectedEventId}>
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte
index b7038926d..8b5662dd0 100644
--- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte
@@ -2,11 +2,10 @@
import type { EventSummaryModel, SummaryTemplateKeys } from '$features/events/components/summary/index';
import { page } from '$app/state';
- import AutomaticRefreshIndicatorButton from '$comp/automatic-refresh-indicator-button.svelte';
import * as DataTable from '$comp/data-table';
import * as FacetedFilter from '$comp/faceted-filter';
+ import StreamingIndicatorButton from '$comp/streaming-indicator-button.svelte';
import { Button } from '$comp/ui/button';
- import * as Card from '$comp/ui/card';
import * as Sheet from '$comp/ui/sheet';
import { type GetEventsParams, getStackEventsQuery } from '$features/events/api.svelte';
import EventsOverview from '$features/events/components/events-overview.svelte';
@@ -85,6 +84,7 @@
updateFilterCache(filterCacheKey(DEFAULT_PARAMS.filter), DEFAULT_FILTERS);
//params.$reset(); // Work around for https://github.com/beynar/kit-query-params/issues/7
Object.assign(queryParams, DEFAULT_PARAMS);
+ reset();
},
{ lazy: true }
);
@@ -159,9 +159,30 @@
})
);
- const canRefresh = $derived(!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected() && !table.getCanPreviousPage());
+ const canRefresh = $derived(!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected() && table.getState().pagination.pageIndex === 0);
+
+ let manualPause = $state(false);
+ let paused = $derived(manualPause || !canRefresh);
+
+ function reset() {
+ manualPause = false;
+ table.resetRowSelection();
+ table.setPageIndex(0);
+ }
+
+ function handleToggle() {
+ if (!canRefresh) {
+ reset();
+ } else {
+ manualPause = !manualPause;
+ }
+ }
async function loadData() {
+ if (paused) {
+ return;
+ }
+
if (client.isLoading || !organization.current) {
return;
}
@@ -179,20 +200,19 @@
if (removeTableData(table, (doc) => doc.id === message.id)) {
// If the grid data is empty from all events being removed, we should refresh the data.
- if (isTableEmpty(table)) {
+ if (isTableEmpty(table) && !paused) {
await throttledLoadData();
return;
}
}
}
- // Do not refresh if the filter criteria doesn't match the web socket message.
- if (!shouldRefreshPersistentEventChanged(filters, queryParams.filter, message.organization_id, message.project_id, message.id)) {
+ if (paused) {
return;
}
- // Do not refresh if the grid has selections or grid is currently paged.
- if (!canRefresh) {
+ // Do not refresh if the filter criteria doesn't match the web socket message.
+ if (!shouldRefreshPersistentEventChanged(filters, queryParams.filter, message.organization_id, message.project_id, message.id)) {
return;
}
@@ -208,32 +228,28 @@
-
- Issues
-
-
- {#snippet toolbarChildren()}
-
-
-
- {/snippet}
- {#snippet footerChildren()}
-
- {#if table.getSelectedRowModel().flatRows.length}
-
- {/if}
-
-
-
-
-
-
-
- {/snippet}
-
-
-
+
+ {#snippet toolbarChildren()}
+ Issues
+
+
+
+ {/snippet}
+ {#snippet toolbarActions()}
+
+ {/snippet}
+ {#snippet footerChildren()}
+
+
+
+
+
+
+
+ {/snippet}
+
(selectedStackId = undefined)} open={eventsQuery.isSuccess}>
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte
index 8319a69f3..fd0f2b616 100644
--- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte
@@ -7,8 +7,8 @@
import DelayedRender from '$comp/delayed-render.svelte';
import ErrorMessage from '$comp/error-message.svelte';
import * as FacetedFilter from '$comp/faceted-filter';
+ import StreamingIndicatorButton from '$comp/streaming-indicator-button.svelte';
import { Button } from '$comp/ui/button';
- import * as Card from '$comp/ui/card';
import * as Sheet from '$comp/ui/sheet';
import EventsOverview from '$features/events/components/events-overview.svelte';
import { StatusFilter } from '$features/events/components/filters';
@@ -67,6 +67,7 @@
updateFilterCache(filterCacheKey(DEFAULT_PARAMS.filter), DEFAULT_FILTERS);
//params.$reset(); // Work around for https://github.com/beynar/kit-query-params/issues/7
Object.assign(queryParams, DEFAULT_PARAMS);
+ paused = false;
},
{ lazy: true }
);
@@ -146,7 +147,16 @@
})
);
+ let paused = $state(false);
+ function handleToggle() {
+ paused = !paused;
+ }
+
async function loadData(filterChanged: boolean = false) {
+ if (paused) {
+ return;
+ }
+
if (!organization.current) {
return;
}
@@ -185,13 +195,17 @@
if (message.id && message.change_type === ChangeType.Removed) {
if (removeTableData(table, (doc) => doc.id === message.id)) {
// If the grid data is empty from all events being removed, we should refresh the data.
- if (isTableEmpty(table)) {
+ if (isTableEmpty(table) && !paused) {
await debouncedLoadData();
return;
}
}
}
+ if (paused) {
+ return;
+ }
+
// Do not refresh if the filter criteria doesn't match the web socket message.
if (!shouldRefreshPersistentEventChanged(filters, queryParams.filter, message.organization_id, message.project_id, message.stack_id, message.id)) {
return;
@@ -213,37 +227,35 @@
});
-
- Event Stream
-
-
-
-
-
-
-
-
-
- {#if clientStatus.isLoading}
-
-
-
- {:else}
-
- {/if}
-
-
-
-
-
-
+
+
+ Event Stream
+
+
+
+
+ {#snippet actions()}
+
+ {/snippet}
+
+
+ {#if clientStatus.isLoading}
+
+
+
+ {:else}
+
+ {/if}
+
+
+
+
+
(selectedEventId = null)} open={!!selectedEventId}>