@datum-cloud/activity-ui 0.1.0 → 0.3.0-dev.e099a6f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14456,6 +14456,20 @@ function RulePreviewPanel({ rule, ruleType, policyResource, apiClient, className
14456
14456
  }) }) }) }), stats.matched === 0 && stats.errors === 0 && (jsxRuntime.jsxs(Alert, { children: [jsxRuntime.jsx(CircleAlert, { className: "h-4 w-4" }), jsxRuntime.jsxs(AlertDescription, { children: ["This rule did not match any of the ", stats.total, " sample", stats.total !== 1 ? 's' : '', ". Check your match expression."] })] }))] }))] }));
14457
14457
  }
14458
14458
 
14459
+ const defaultTabs = (basePath) => [
14460
+ { label: 'Activity Feed', value: 'feed', href: basePath },
14461
+ { label: 'Events', value: 'events', href: `${basePath}/events` },
14462
+ { label: 'Audit Logs', value: 'audit-logs', href: `${basePath}/audit-logs` },
14463
+ ];
14464
+ /**
14465
+ * Shared activity layout with tab navigation for Activity Feed, Events, and Audit Logs.
14466
+ * Framework-agnostic — pass a linkComponent (e.g., react-router's Link) for navigation.
14467
+ */
14468
+ function ActivityLayout({ basePath, activeTab, tabs, linkComponent: LinkComp, children, className, }) {
14469
+ const resolvedTabs = tabs ?? defaultTabs(basePath);
14470
+ return (jsxRuntime.jsxs("div", { className: cn('flex h-full flex-col overflow-hidden', className), children: [jsxRuntime.jsx("div", { className: "shrink-0 border-b px-4 pt-3", children: jsxRuntime.jsx(Tabs, { value: activeTab, children: jsxRuntime.jsx(TabsList, { children: resolvedTabs.map((tab) => (jsxRuntime.jsx(TabsTrigger, { value: tab.value, asChild: !!LinkComp, children: LinkComp ? (jsxRuntime.jsx(LinkComp, { to: tab.href, children: tab.label })) : (jsxRuntime.jsx("span", { children: tab.label })) }, tab.value))) }) }) }), jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-hidden p-4", children: jsxRuntime.jsx("div", { className: "flex h-full flex-col", children: children }) })] }));
14471
+ }
14472
+
14459
14473
  /**
14460
14474
  * React hook for managing ReindexJobs with optional real-time watching
14461
14475
  */
@@ -14987,6 +15001,205 @@ function MultiCombobox({ options, values, onValuesChange, placeholder = 'Select.
14987
15001
  }, className: "rounded-sm opacity-50 hover:opacity-100 cursor-pointer", children: jsxRuntime.jsx(X, { className: "h-3 w-3" }) })), jsxRuntime.jsx(ChevronsUpDown, { className: "h-4 w-4 shrink-0 opacity-50" })] })] }) }), jsxRuntime.jsx(Popover__namespace.Portal, { children: jsxRuntime.jsx(Popover__namespace.Content, { className: cn('z-50 min-w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-md border shadow-md', 'bg-white dark:bg-slate-900 text-foreground', 'data-[state=open]:animate-in data-[state=closed]:animate-out', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95', 'data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2'), sideOffset: 4, align: "start", children: jsxRuntime.jsxs(cmdk.Command, { filter: filterOptions, className: "w-full", children: [jsxRuntime.jsx("div", { className: "flex items-center border-b px-3", children: jsxRuntime.jsx(cmdk.CommandInput, { placeholder: searchPlaceholder, value: search, onValueChange: setSearch, className: "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50" }) }), values.length > 0 && (jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 p-2 border-b", children: selectedOptions.map((option) => (jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-secondary text-secondary-foreground text-xs", children: [option.label, jsxRuntime.jsx("button", { type: "button", onClick: (e) => handleRemove(e, option.value), className: "rounded-sm hover:bg-secondary-foreground/20", children: jsxRuntime.jsx(X, { className: "h-3 w-3" }) })] }, option.value))) })), jsxRuntime.jsxs(cmdk.CommandList, { className: "max-h-[300px] overflow-y-auto p-1", children: [jsxRuntime.jsx(cmdk.CommandEmpty, { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }), jsxRuntime.jsx(cmdk.CommandGroup, { children: options.map((option) => (jsxRuntime.jsxs(cmdk.CommandItem, { value: option.value, onSelect: () => handleSelect(option.value), className: cn('relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none', 'data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground', 'hover:bg-accent hover:text-accent-foreground'), children: [jsxRuntime.jsx(Check, { className: cn('mr-2 h-4 w-4', values.includes(option.value) ? 'opacity-100' : 'opacity-0') }), jsxRuntime.jsx("span", { className: "flex-1 truncate", children: option.label }), option.count !== undefined && (jsxRuntime.jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: ["(", option.count, ")"] }))] }, option.value))) })] })] }) }) })] }));
14988
15002
  }
14989
15003
 
15004
+ /**
15005
+ * Serialize Activity Feed filters to URL search params.
15006
+ * Used to sync filter changes back to the URL for deep linking.
15007
+ */
15008
+ function serializeActivityFilters(filters, timeRange, streamingEnabled = true) {
15009
+ const params = new URLSearchParams();
15010
+ if (timeRange.start) {
15011
+ params.set('start', timeRange.start);
15012
+ }
15013
+ if (timeRange.end) {
15014
+ params.set('end', timeRange.end);
15015
+ }
15016
+ if (!streamingEnabled) {
15017
+ params.set('streaming', 'false');
15018
+ }
15019
+ if (filters.changeSource && filters.changeSource !== 'human') {
15020
+ params.set('changeSource', filters.changeSource);
15021
+ }
15022
+ if (filters.actorNames && filters.actorNames.length > 0) {
15023
+ params.set('actorNames', filters.actorNames.join(','));
15024
+ }
15025
+ if (filters.resourceKinds && filters.resourceKinds.length > 0) {
15026
+ params.set('resourceKinds', filters.resourceKinds.join(','));
15027
+ }
15028
+ if (filters.apiGroups && filters.apiGroups.length > 0) {
15029
+ params.set('apiGroups', filters.apiGroups.join(','));
15030
+ }
15031
+ if (filters.resourceNamespaces && filters.resourceNamespaces.length > 0) {
15032
+ params.set('resourceNamespaces', filters.resourceNamespaces.join(','));
15033
+ }
15034
+ if (filters.resourceUid) {
15035
+ params.set('resourceUid', filters.resourceUid);
15036
+ }
15037
+ if (filters.resourceName) {
15038
+ params.set('resourceName', filters.resourceName);
15039
+ }
15040
+ if (filters.search) {
15041
+ params.set('search', filters.search);
15042
+ }
15043
+ return params;
15044
+ }
15045
+ /**
15046
+ * Serialize Events Feed filters to URL search params.
15047
+ * Used to sync filter changes back to the URL for deep linking.
15048
+ */
15049
+ function serializeEventFilters(filters, timeRange, streamingEnabled = true) {
15050
+ const params = new URLSearchParams();
15051
+ if (timeRange.start) {
15052
+ params.set('start', timeRange.start);
15053
+ }
15054
+ if (timeRange.end) {
15055
+ params.set('end', timeRange.end);
15056
+ }
15057
+ if (!streamingEnabled) {
15058
+ params.set('streaming', 'false');
15059
+ }
15060
+ if (filters.eventType && filters.eventType !== 'all') {
15061
+ params.set('eventType', filters.eventType);
15062
+ }
15063
+ if (filters.reasons && filters.reasons.length > 0) {
15064
+ params.set('reasons', filters.reasons.join(','));
15065
+ }
15066
+ if (filters.namespaces && filters.namespaces.length > 0) {
15067
+ params.set('namespaces', filters.namespaces.join(','));
15068
+ }
15069
+ if (filters.involvedKinds && filters.involvedKinds.length > 0) {
15070
+ params.set('involvedKinds', filters.involvedKinds.join(','));
15071
+ }
15072
+ if (filters.search) {
15073
+ params.set('search', filters.search);
15074
+ }
15075
+ return params;
15076
+ }
15077
+ /**
15078
+ * Parse Activity Feed filters from URL query params
15079
+ */
15080
+ function parseActivityFilters(searchParams) {
15081
+ const filters = {};
15082
+ const changeSource = searchParams.get('changeSource');
15083
+ if (changeSource === 'human' || changeSource === 'system' || changeSource === 'all') {
15084
+ filters.changeSource = changeSource;
15085
+ }
15086
+ else {
15087
+ filters.changeSource = 'human';
15088
+ }
15089
+ const actorNames = searchParams.get('actorNames');
15090
+ if (actorNames) {
15091
+ filters.actorNames = actorNames.split(',').filter(Boolean);
15092
+ }
15093
+ const resourceKinds = searchParams.get('resourceKinds');
15094
+ if (resourceKinds) {
15095
+ filters.resourceKinds = resourceKinds.split(',').filter(Boolean);
15096
+ }
15097
+ const apiGroups = searchParams.get('apiGroups');
15098
+ if (apiGroups) {
15099
+ filters.apiGroups = apiGroups.split(',').filter(Boolean);
15100
+ }
15101
+ const resourceNamespaces = searchParams.get('resourceNamespaces');
15102
+ if (resourceNamespaces) {
15103
+ filters.resourceNamespaces = resourceNamespaces.split(',').filter(Boolean);
15104
+ }
15105
+ const resourceUid = searchParams.get('resourceUid');
15106
+ if (resourceUid) {
15107
+ filters.resourceUid = resourceUid;
15108
+ }
15109
+ const resourceName = searchParams.get('resourceName');
15110
+ if (resourceName) {
15111
+ filters.resourceName = resourceName;
15112
+ }
15113
+ const search = searchParams.get('search');
15114
+ if (search) {
15115
+ filters.search = search;
15116
+ }
15117
+ return filters;
15118
+ }
15119
+ /**
15120
+ * Parse Events Feed filters from URL query params
15121
+ */
15122
+ function parseEventFilters(searchParams) {
15123
+ const filters = {};
15124
+ const eventType = searchParams.get('eventType');
15125
+ if (eventType === 'Normal' || eventType === 'Warning' || eventType === 'all') {
15126
+ filters.eventType = eventType;
15127
+ }
15128
+ const reasons = searchParams.get('reasons');
15129
+ if (reasons) {
15130
+ filters.reasons = reasons.split(',').filter(Boolean);
15131
+ }
15132
+ const namespaces = searchParams.get('namespaces');
15133
+ if (namespaces) {
15134
+ filters.namespaces = namespaces.split(',').filter(Boolean);
15135
+ }
15136
+ const involvedKinds = searchParams.get('involvedKinds');
15137
+ if (involvedKinds) {
15138
+ filters.involvedKinds = involvedKinds.split(',').filter(Boolean);
15139
+ }
15140
+ const search = searchParams.get('search');
15141
+ if (search) {
15142
+ filters.search = search;
15143
+ }
15144
+ return filters;
15145
+ }
15146
+ /**
15147
+ * Parse time range from URL query params
15148
+ */
15149
+ function parseTimeRange(searchParams) {
15150
+ const start = searchParams.get('start');
15151
+ const end = searchParams.get('end');
15152
+ if (!start) {
15153
+ return undefined;
15154
+ }
15155
+ return { start, end: end || undefined };
15156
+ }
15157
+
15158
+ /**
15159
+ * Generate a shareable URL for the current activity view.
15160
+ *
15161
+ * The shareable URL:
15162
+ * - Uses absolute timestamps (not relative like "now-1h")
15163
+ * - Disables streaming so the view is static
15164
+ * - Preserves all current filters
15165
+ *
15166
+ * This ensures the shared link shows the exact same data regardless of
15167
+ * when someone opens it.
15168
+ *
15169
+ * @param basePath - Current route path (e.g., "/activity/feed")
15170
+ * @param effectiveTimeRange - Server-calculated effective time range
15171
+ * @param filters - Current filter state
15172
+ * @param origin - Window origin for absolute URL (e.g., "https://staff.datum.cloud")
15173
+ */
15174
+ function generateShareableUrl(basePath, effectiveTimeRange, filters, origin = typeof window !== 'undefined' ? window.location.origin : '') {
15175
+ const params = new URLSearchParams();
15176
+ // Use absolute timestamps from server-calculated range
15177
+ params.set('start', effectiveTimeRange.startTime);
15178
+ params.set('end', effectiveTimeRange.endTime);
15179
+ // Disable streaming for shared links (static view)
15180
+ params.set('streaming', 'false');
15181
+ // Preserve all current filters
15182
+ for (const [key, value] of Object.entries(filters)) {
15183
+ if (value && key !== 'start' && key !== 'end' && key !== 'streaming') {
15184
+ params.set(key, value);
15185
+ }
15186
+ }
15187
+ return `${origin}${basePath}?${params.toString()}`;
15188
+ }
15189
+ /**
15190
+ * Copy text to clipboard and return success status
15191
+ */
15192
+ async function copyToClipboard(text) {
15193
+ try {
15194
+ await navigator.clipboard.writeText(text);
15195
+ return true;
15196
+ }
15197
+ catch (err) {
15198
+ console.error('Failed to copy to clipboard:', err);
15199
+ return false;
15200
+ }
15201
+ }
15202
+
14990
15203
  /**
14991
15204
  * Create an empty audit event for preview
14992
15205
  */
@@ -16257,6 +16470,7 @@ exports.ActivityFeed = ActivityFeed;
16257
16470
  exports.ActivityFeedItem = ActivityFeedItem;
16258
16471
  exports.ActivityFeedItemSkeleton = ActivityFeedItemSkeleton;
16259
16472
  exports.ActivityFeedSummary = ActivityFeedSummary;
16473
+ exports.ActivityLayout = ActivityLayout;
16260
16474
  exports.Alert = Alert;
16261
16475
  exports.AlertDescription = AlertDescription;
16262
16476
  exports.AlertTitle = AlertTitle;
@@ -16353,17 +16567,24 @@ exports.badgeVariants = badgeVariants;
16353
16567
  exports.buildAuditLogCEL = buildAuditLogCEL;
16354
16568
  exports.buttonVariants = buttonVariants;
16355
16569
  exports.cn = cn;
16570
+ exports.copyToClipboard = copyToClipboard;
16356
16571
  exports.defaultErrorFormatter = defaultErrorFormatter;
16357
16572
  exports.defaultResourceLinkResolver = defaultResourceLinkResolver;
16358
16573
  exports.extractEvent = extractEvent;
16359
16574
  exports.extractFieldPaths = extractFieldPaths;
16360
16575
  exports.extractFieldPathsFromMany = extractFieldPathsFromMany;
16576
+ exports.generateShareableUrl = generateShareableUrl;
16361
16577
  exports.getReindexJobDuration = getReindexJobDuration;
16362
16578
  exports.getReindexJobStatusMessage = getReindexJobStatusMessage;
16363
16579
  exports.isEventRecord = isEventRecord;
16364
16580
  exports.isReindexJobRunning = isReindexJobRunning;
16365
16581
  exports.isReindexJobTerminal = isReindexJobTerminal;
16582
+ exports.parseActivityFilters = parseActivityFilters;
16366
16583
  exports.parseApiError = parseApiError;
16584
+ exports.parseEventFilters = parseEventFilters;
16585
+ exports.parseTimeRange = parseTimeRange;
16586
+ exports.serializeActivityFilters = serializeActivityFilters;
16587
+ exports.serializeEventFilters = serializeEventFilters;
16367
16588
  exports.useActivityFeed = useActivityFeed;
16368
16589
  exports.useAuditLogFacets = useAuditLogFacets;
16369
16590
  exports.useAuditLogQuery = useAuditLogQuery;