@burdenoff/microfe-bigconsole 2026.812.2 → 2026.827.1

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.
Files changed (40) hide show
  1. package/dist/bigconsole/components/builder/inspector/LayoutSection.js +71 -0
  2. package/dist/bigconsole/components/builder/inspector/LayoutSection.js.map +1 -0
  3. package/dist/bigconsole/components/builder/inspector/index.js +1 -0
  4. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +137 -121
  5. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  6. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +204 -93
  7. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
  8. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +259 -133
  9. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
  10. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +87 -39
  11. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
  12. package/dist/bigconsole/components/datasink/DataSinkCard.js +88 -68
  13. package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
  14. package/dist/bigconsole/components/widgets/PropertiesPanel.js +213 -199
  15. package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
  16. package/dist/bigconsole/components/workflow/WorkflowCard.js +46 -27
  17. package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
  18. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +44 -47
  19. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
  20. package/dist/bigconsole/customWidget/proposalSource.js +31 -24
  21. package/dist/bigconsole/customWidget/proposalSource.js.map +1 -1
  22. package/dist/bigconsole/hooks/useDashboardOperations.js +74 -64
  23. package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
  24. package/dist/bigconsole/hooks/useDataSinkOperations.js +2 -2
  25. package/dist/bigconsole/hooks/useDataSinkOperations.js.map +1 -1
  26. package/dist/bigconsole/pages/DashboardBuilderPage.js +1 -1
  27. package/dist/bigconsole/pages/DashboardsPage.js +386 -316
  28. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  29. package/dist/bigconsole/pages/DataSinksPage.js +251 -206
  30. package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
  31. package/dist/bigconsole/pages/InsightsPage.js +646 -378
  32. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  33. package/dist/bigconsole/pages/WorkflowsPage.js +153 -108
  34. package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
  35. package/dist/bigconsole/utils/format.js +18 -0
  36. package/dist/bigconsole/utils/format.js.map +1 -0
  37. package/dist/bigconsole/utils/widgetPositioning.js +1 -1
  38. package/dist/generated/wspace-operations.js +1 -0
  39. package/dist/generated/wspace-operations.js.map +1 -1
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DataSinksPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinksPage.tsx"],"sourcesContent":["/**\n * DataSinksPage\n *\n * List and manage DataSinks - fetches data from backend.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { type FC, memo, useCallback, useMemo, useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useExportDataSink } from '../hooks/useExportDataSink';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport type { DataSink, DataSinkSetupState } from '../types';\nimport { DataSinkCard } from '../components/datasink/DataSinkCard';\nimport { DataSinkDeleteDialog } from '../components/datasink/DataSinkDeleteDialog';\nimport { DataSinkRefreshDialog } from '../components/datasink/DataSinkRefreshDialog';\nimport { ImportDataSinkDialog } from '../components/datasink/ImportDataSinkDialog';\n\n// fe-libs components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\n// Section-emphasis primitives (BOFF-5632)\nimport { Section, SectionStack, SECTION_FOCUS_RING } from '../components/common';\n\n// Lucide icons\nimport {\n Plus,\n Search,\n AlertCircle,\n Container,\n X,\n Upload,\n LayoutGrid,\n Table2,\n Eye,\n Pencil,\n RefreshCw,\n Download,\n Trash2,\n} from 'lucide-react';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { SetupStateBadge } from '../components/datasink/SetupStateBadge';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype SetupStateFilter = 'all' | 'in_progress' | 'completed';\ntype ListViewMode = 'grid' | 'table';\n\nconst VIEW_MODE_KEY = 'bigconsole-datasinks-view-mode';\n\n/**\n * Row-action buttons in the table view. `--size-controlHeight-md` (40px) replaces the\n * previous `padding: 4` around a 14px icon, which was a ~22px hit target — far\n * under the touch minimum on the one layout that is already a side-scroll.\n */\nconst TABLE_ICON_BUTTON =\n `inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button border-0 bg-transparent text-text-muted hover:bg-bg-surface hover:text-text-primary motion-safe:transition-colors ${SECTION_FOCUS_RING}` as const;\n\n/**\n * Segmented controls clip their children (`overflow: hidden` keeps the group's\n * rounded corners), which would swallow an offset focus ring entirely — leaving\n * the buttons with no visible focus at all, since `SECTION_FOCUS_RING` also\n * suppresses the native outline. Painting the ring inside the button keeps it\n * visible without dropping the group's rounding.\n */\nconst SEGMENT_RING_FIX = 'focus-visible:ring-inset focus-visible:ring-offset-0' as const;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Determine setup state for a DataSink based on its properties.\n */\nconst getSetupState = (dataSink: DataSink): DataSinkSetupState => {\n if (dataSink.setupState === 'completed') {\n return 'completed';\n }\n if (dataSink.ttl !== null && dataSink.ttl !== undefined && dataSink.ttl > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId && dataSink.data && Object.keys(dataSink.data).length > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId) {\n return 'workflow_running';\n }\n return 'workflow_pending';\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DataSinksPage: FC = memo(function DataSinksPage() {\n const navigate = useAppNavigate();\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n\n // Permissions\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_READ);\n const canCreate = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_CREATE);\n const canUpdate = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_UPDATE);\n const canDelete = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_DELETE);\n const canExecute = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_EXECUTE);\n\n // Local state\n const [setupStateFilter, setSetupStateFilter] = useState<SetupStateFilter>('all');\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n const [viewMode, setViewMode] = useState<ListViewMode>(\n () => (localStorage.getItem(getProfileScopedKey(VIEW_MODE_KEY)) as ListViewMode) || 'grid'\n );\n\n const toggleViewMode = useCallback((mode: ListViewMode) => {\n setViewMode(mode);\n localStorage.setItem(getProfileScopedKey(VIEW_MODE_KEY), mode);\n }, []);\n\n // Store state\n const statusFilter = useDataSinkStore((state) => state.statusFilter);\n const activeFilter = useDataSinkStore((state) => state.activeFilter);\n const searchQuery = useDataSinkStore((state) => state.searchQuery);\n const setStatusFilter = useDataSinkStore((state) => state.setStatusFilter);\n const setActiveFilter = useDataSinkStore((state) => state.setActiveFilter);\n const setSearchQuery = useDataSinkStore((state) => state.setSearchQuery);\n const deleteDialogOpen = useDataSinkStore((state) => state.deleteDialogOpen);\n const refreshDialogOpen = useDataSinkStore((state) => state.refreshDialogOpen);\n const currentDataSink = useDataSinkStore((state) => state.currentDataSink);\n const openDeleteDialog = useDataSinkStore((state) => state.openDeleteDialog);\n const closeDeleteDialog = useDataSinkStore((state) => state.closeDeleteDialog);\n const openRefreshDialog = useDataSinkStore((state) => state.openRefreshDialog);\n const closeRefreshDialog = useDataSinkStore((state) => state.closeRefreshDialog);\n\n // Data operations\n const { loading, error, dataSinks, deleteDataSink, refreshDataSink, refetch } = useDataSinkOperations();\n\n // Export hook\n const { downloadDataSink } = useExportDataSink();\n\n // Compute filtered DataSinks with setup states\n const dataSinksWithSetupState = useMemo(() => {\n return dataSinks.map((ds) => ({\n dataSink: ds,\n setupState: getSetupState(ds),\n }));\n }, [dataSinks]);\n\n // Compute filtered DataSinks\n const filteredDataSinks = useMemo(() => {\n let filtered = [...dataSinksWithSetupState];\n\n if (setupStateFilter === 'in_progress') {\n filtered = filtered.filter((item) => item.setupState !== 'completed');\n } else if (setupStateFilter === 'completed') {\n filtered = filtered.filter((item) => item.setupState === 'completed');\n }\n\n if (statusFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.status === statusFilter);\n }\n\n if (activeFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.isActive === activeFilter);\n }\n\n if (searchQuery.trim()) {\n const query = searchQuery.toLowerCase();\n filtered = filtered.filter(\n (item) =>\n item.dataSink.name?.toLowerCase().includes(query) ||\n item.dataSink.key.toLowerCase().includes(query) ||\n item.dataSink.description?.toLowerCase().includes(query)\n );\n }\n\n return filtered.sort((a, b) => new Date(b.dataSink.updatedAt).getTime() - new Date(a.dataSink.updatedAt).getTime());\n }, [dataSinksWithSetupState, setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n // Handlers\n const handleCreateDataSink = useCallback(() => {\n navigate('/datasinks/new');\n }, [navigate]);\n\n const handleViewDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}`);\n },\n [navigate]\n );\n\n const handleEditDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}/edit`);\n },\n [navigate]\n );\n\n const handleRefreshClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openRefreshDialog(dataSink);\n }\n },\n [dataSinks, openRefreshDialog]\n );\n\n const handleRefreshConfirm = useCallback(\n async (forceRefresh: boolean) => {\n if (currentDataSink) {\n await refreshDataSink({\n id: currentDataSink.id,\n forceRefresh,\n });\n closeRefreshDialog();\n }\n },\n [currentDataSink, refreshDataSink, closeRefreshDialog]\n );\n\n const handleDeleteClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openDeleteDialog(dataSink);\n }\n },\n [dataSinks, openDeleteDialog]\n );\n\n const handleDeleteConfirm = useCallback(async () => {\n if (currentDataSink) {\n try {\n await deleteDataSink(currentDataSink.id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n closeDeleteDialog();\n }\n }, [currentDataSink, deleteDataSink, closeDeleteDialog]);\n\n const handleExportDataSink = useCallback(\n async (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n await downloadDataSink(id, dataSink.name || dataSink.key);\n }\n },\n [dataSinks, downloadDataSink]\n );\n\n const handleImportDataSink = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_dataSinkId: string, _dataSinkName: string | null, _wasUpdate: boolean) => {\n // Refetch DataSinks to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = useMemo(() => {\n return setupStateFilter !== 'all' || statusFilter !== null || activeFilter !== null || searchQuery.trim() !== '';\n }, [setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n const handleClearFilters = useCallback(() => {\n setSetupStateFilter('all');\n setStatusFilter(null);\n setActiveFilter(null);\n setSearchQuery('');\n }, [setStatusFilter, setActiveFilter, setSearchQuery]);\n\n // Setup state filter options\n const setupStateOptions: { value: SetupStateFilter; label: string }[] = [\n { value: 'all', label: tr('bigconsole.dataSinks.filters.setupState.all', 'All') },\n { value: 'in_progress', label: tr('bigconsole.dataSinks.filters.setupState.inProgress', 'In Progress') },\n { value: 'completed', label: tr('bigconsole.dataSinks.filters.setupState.completed', 'Completed') },\n ];\n\n // Read gate — once permissions have loaded, block actors without read access.\n if (!permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={tr('bigconsole.dataSinks.accessDenied.title', 'Access Denied')}\n description={tr(\n 'bigconsole.dataSinks.accessDenied.description',\n \"You don't have permission to view data sinks.\"\n )}\n />\n );\n }\n\n // The fetch-failure state is rendered inline below (inside the page body), so\n // the header band and page purpose survive an error instead of the whole\n // route collapsing into a standalone error card.\n return (\n // No `minHeight: 100vh`: the app shell already pads this content area, so a\n // full-viewport child guaranteed ~16-24px of vertical overflow everywhere.\n <div className=\"min-w-0\" style={{ backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Page header strip. `border-border-seam` / `bg-gloss-card` are not\n registered utilities in this shell — they emitted no CSS at all, so\n this band previously rendered with neither seam nor gloss. */}\n <header className=\"relative overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 'var(--space-formGap)',\n flexWrap: 'wrap',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n flex: '1 1 280px',\n minWidth: 0,\n }}\n >\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <Container\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div style={{ minWidth: 0 }}>\n {/* text-2xl matches the builder and detail page titles — those\n used `text-heading-h1`, which emits no CSS in this shell. */}\n <h1 className=\"m-0 text-2xl font-bold text-text-primary\">\n <GradientText>{tr('bigconsole.dataSinks.title', 'Data Sinks')}</GradientText>\n </h1>\n <p className=\"m-0 text-sm text-text-secondary\">\n {tr('bigconsole.dataSinks.subtitle', 'Manage pre-processed data from FluidGrids workflows')}\n </p>\n </div>\n </div>\n\n {/* Create + Import both require DATASINK_CREATE; omit them entirely when not permitted. */}\n <CTAOverflowMenu\n primary={\n canCreate\n ? {\n label: tr('bigconsole.dataSinks.actions.create', 'Create Data Sink'),\n onSelect: handleCreateDataSink,\n icon: (\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n ),\n }\n : undefined\n }\n actions={\n canCreate\n ? [\n {\n label: tr('bigconsole.dataSinks.actions.import', 'Import'),\n onSelect: handleImportDataSink,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </header>\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Data sinks are the structured destinations that hold your processed data — define a schema once and every\n pipeline, parser, and workflow can write into it. Dashboards then query sinks directly, so a sink is where\n your data lives and where your charts read from.\n </PagePurpose>\n\n <SectionStack>\n {error ? (\n /* The header band and page purpose stay on screen, so a fetch\n failure no longer costs the user all page identity. */\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={tr('bigconsole.dataSinks.error.title', 'Failed to load Data Sinks')}\n description={error.message}\n icon={<AlertCircle className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {tr('bigconsole.dataSinks.error.retry', 'Try Again')}\n </Button>\n }\n />\n ) : (\n <>\n {/* Both filter affordances now live in one labelled block, so it\n is obvious that the tab strip and the search row compose. */}\n <Section\n headingLevel={2}\n padding=\"compact\"\n title={tr('bigconsole.dataSinks.filters.heading', 'Filters')}\n description={tr(\n 'bigconsole.dataSinks.filters.description',\n 'Narrow the list by setup progress, status, or name.'\n )}\n bodyClassName=\"space-y-form-gap\"\n >\n <>\n {/* Setup State Filter Tabs */}\n <div\n role=\"group\"\n aria-label={tr('bigconsole.dataSinks.filters.setupState.label', 'Setup state')}\n style={{\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '2px',\n padding: '4px',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-card)',\n maxWidth: '100%',\n width: 'fit-content',\n }}\n >\n {setupStateOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={setupStateFilter === option.value}\n onClick={() => setSetupStateFilter(option.value)}\n className={SECTION_FOCUS_RING}\n style={{\n minHeight: 'var(--size-controlHeight-md)',\n padding: 'var(--space-controlGap) var(--space-formGap)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n cursor: 'pointer',\n backgroundColor:\n setupStateFilter === option.value ? 'var(--color-bg-surface)' : 'transparent',\n color:\n setupStateFilter === option.value\n ? 'var(--color-text-primary)'\n : 'var(--color-text-secondary)',\n // Was a raw `rgba(0,0,0,0.05)` literal.\n boxShadow: setupStateFilter === option.value ? 'var(--shadow-elevation-1)' : 'none',\n }}\n >\n {option.label}\n </button>\n ))}\n </div>\n\n {/* Search and Filters Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Search Bar */}\n <div\n style={{\n position: 'relative',\n maxWidth: '400px',\n minWidth: 'min(100%, 240px)',\n flex: '1 1 240px',\n }}\n >\n <Search\n style={{\n position: 'absolute',\n left: 'var(--space-controlGap)',\n top: '50%',\n transform: 'translateY(-50%)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <Input\n type=\"text\"\n placeholder={tr('bigconsole.dataSinks.search.placeholder', 'Search data sinks...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Status Filter */}\n <Select\n value={statusFilter || 'all'}\n onValueChange={(value) =>\n setStatusFilter(value === 'all' ? null : (value as 'ready' | 'error' | 'stale' | 'refreshing'))\n }\n >\n <SelectTrigger style={{ width: 'min(100%, 150px)' }}>\n <SelectValue\n placeholder={tr('bigconsole.dataSinks.filters.status.placeholder', 'All Statuses')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tr('bigconsole.dataSinks.filters.status.all', 'All Statuses')}\n </SelectItem>\n <SelectItem value=\"ready\">\n {tr('bigconsole.dataSinks.filters.status.ready', 'Ready')}\n </SelectItem>\n <SelectItem value=\"error\">\n {tr('bigconsole.dataSinks.filters.status.error', 'Error')}\n </SelectItem>\n <SelectItem value=\"stale\">\n {tr('bigconsole.dataSinks.filters.status.stale', 'Stale')}\n </SelectItem>\n <SelectItem value=\"refreshing\">\n {tr('bigconsole.dataSinks.filters.status.refreshing', 'Refreshing')}\n </SelectItem>\n </SelectContent>\n </Select>\n\n {/* Active Filter */}\n <Select\n value={activeFilter === null ? 'all' : activeFilter ? 'true' : 'false'}\n onValueChange={(value) => setActiveFilter(value === 'all' ? null : value === 'true')}\n >\n <SelectTrigger style={{ width: 'min(100%, 130px)' }}>\n <SelectValue placeholder={tr('bigconsole.dataSinks.filters.active.placeholder', 'All')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{tr('bigconsole.dataSinks.filters.active.all', 'All')}</SelectItem>\n <SelectItem value=\"true\">\n {tr('bigconsole.dataSinks.filters.active.active', 'Active')}\n </SelectItem>\n <SelectItem value=\"false\">\n {tr('bigconsole.dataSinks.filters.active.inactive', 'Inactive')}\n </SelectItem>\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n <X\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n )}\n </div>\n </>\n </Section>\n\n {/* The point of this route. The result count and the view toggle\n belong to the results, not to the filters — moving them here\n also removes the `margin-left: auto` that jumped to the left\n edge as soon as the filter row wrapped. */}\n <Section\n emphasis=\"primary\"\n headingLevel={2}\n title={tr('bigconsole.dataSinks.list.heading', 'Data sinks')}\n description={tr(\n 'bigconsole.dataSinks.list.description',\n 'Every sink in this workspace, most recently updated first.'\n )}\n meta={\n <Badge variant=\"secondary\">\n {filteredDataSinks.length}{' '}\n {filteredDataSinks.length === 1\n ? tr('bigconsole.dataSinks.count.single', 'Data Sink')\n : tr('bigconsole.dataSinks.count.plural', 'Data Sinks')}\n </Badge>\n }\n actions={\n <div\n role=\"group\"\n aria-label={tr('bigconsole.dataSinks.view.label', 'List layout')}\n style={{\n display: 'flex',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n overflow: 'hidden',\n }}\n >\n <button\n type=\"button\"\n onClick={() => toggleViewMode('grid')}\n aria-pressed={viewMode === 'grid'}\n title={tr('bigconsole.dataSinks.view.grid', 'Grid view')}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center ${SECTION_FOCUS_RING} ${SEGMENT_RING_FIX}`}\n style={{\n border: 'none',\n cursor: 'pointer',\n backgroundColor: viewMode === 'grid' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'grid' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <LayoutGrid size={16} />\n </button>\n <button\n type=\"button\"\n onClick={() => toggleViewMode('table')}\n aria-pressed={viewMode === 'table'}\n title={tr('bigconsole.dataSinks.view.table', 'Table view')}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center ${SECTION_FOCUS_RING} ${SEGMENT_RING_FIX}`}\n style={{\n border: 'none',\n borderLeft: '1px solid var(--color-border)',\n cursor: 'pointer',\n backgroundColor: viewMode === 'table' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'table' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <Table2 size={16} />\n </button>\n </div>\n }\n >\n <>\n {/* Loading state */}\n {loading && dataSinks.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <div style={{ textAlign: 'center' }}>\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <p className=\"text-sm text-text-muted\">\n {tr('bigconsole.dataSinks.loading', 'Loading data sinks...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state — no GlassCard: the zero-data state should not\n be more emphasised than the content it replaces. */}\n {!loading && dataSinks.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('bigconsole.dataSinks.empty.title', 'No Data Sinks yet')}\n description={tr(\n 'bigconsole.dataSinks.empty.description',\n 'Data Sinks store pre-processed data from FluidGrids workflows for fast access in widgets.'\n )}\n action={\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DATASINK_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateDataSink}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dataSinks.actions.create', 'Create Data Sink')}\n </Button>\n </PermissionButton>\n }\n />\n )}\n\n {/* No results state — an info tone, so \"you have none\" and\n \"your filter matched none\" are no longer identical. */}\n {!loading && filteredDataSinks.length === 0 && dataSinks.length > 0 && (\n <Section\n tone=\"info\"\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={3}\n title={tr('bigconsole.dataSinks.empty.filteredTitle', 'No matching data sinks')}\n description={tr('bigconsole.dataSinks.empty.filtered', 'No data sinks match your filters')}\n actions={\n <Button variant=\"secondary\" size=\"sm\" onClick={handleClearFilters}>\n {tr('bigconsole.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n }\n />\n )}\n\n {/* DataSink Grid */}\n {filteredDataSinks.length > 0 && viewMode === 'grid' && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 320px), 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {filteredDataSinks.map((item) => (\n <DataSinkCard\n key={item.dataSink.id}\n dataSink={item.dataSink}\n setupState={item.setupState}\n onView={handleViewDataSink}\n onEdit={handleEditDataSink}\n onRefresh={handleRefreshClick}\n onExport={handleExportDataSink}\n onDelete={handleDeleteClick}\n canEdit={canUpdate}\n canRefresh={canExecute}\n canDelete={canDelete}\n />\n ))}\n </div>\n )}\n\n {/* Table View. The section already draws the boundary, so the\n table no longer nests a second border inside it. */}\n {filteredDataSinks.length > 0 && viewMode === 'table' && (\n <div className=\"min-w-0\" style={{ overflowX: 'auto' }}>\n <table\n style={{ width: '100%', borderCollapse: 'collapse', fontSize: 'var(--font-size-body-sm)' }}\n >\n <thead>\n <tr\n style={{\n backgroundColor: 'var(--color-bg-sunken)',\n borderBottom: '1px solid var(--color-border)',\n }}\n >\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.name', 'Name')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.key', 'Key')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.status', 'Status')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.setup', 'Setup')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'center',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.active', 'Active')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.version', 'Version')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.updated', 'Updated')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'right',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredDataSinks.map((item) => {\n const ds = item.dataSink;\n return (\n <tr\n key={ds.id}\n onClick={() => handleViewDataSink(ds.id)}\n // Hover was JS-driven, so it ignored theme and the OS\n // reduce-motion setting and stuck after a tap on touch.\n className=\"cursor-pointer hover:bg-bg-sunken motion-safe:transition-colors\"\n style={{ borderBottom: '1px solid var(--color-border)' }}\n >\n <td\n style={{\n padding: '10px 12px',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n maxWidth: 200,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.name || ds.key}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n fontFamily: 'monospace',\n fontSize: '12px',\n maxWidth: 160,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.key}\n </td>\n <td style={{ padding: '10px 12px' }}>\n <DataSinkStatusBadge status={ds.status} />\n </td>\n <td style={{ padding: '10px 12px' }}>\n <SetupStateBadge state={item.setupState} />\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'center' }}>\n <span\n style={{\n display: 'inline-block',\n width: 8,\n height: 8,\n borderRadius: '50%',\n // Was a raw `#22c55e` literal.\n backgroundColor: ds.isActive\n ? 'var(--color-status-success-bg)'\n : 'var(--color-text-muted)',\n }}\n title={\n ds.isActive\n ? tr('bigconsole.dataSinks.badges.active', 'Active')\n : tr('bigconsole.dataSinks.badges.inactive', 'Inactive')\n }\n />\n </td>\n <td style={{ padding: '10px 12px', color: 'var(--color-text-muted)' }}>\n v{ds.version ?? 0}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n whiteSpace: 'nowrap',\n }}\n >\n {new Date(ds.updatedAt).toLocaleDateString()}\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'right' }}>\n <div\n style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}\n onClick={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.view', 'View')}\n aria-label={tr('bigconsole.dataSinks.actions.view', 'View')}\n onClick={() => handleViewDataSink(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Eye size={14} />\n </button>\n {canUpdate && (\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.edit', 'Edit')}\n aria-label={tr('bigconsole.dataSinks.actions.edit', 'Edit')}\n onClick={() => handleEditDataSink(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Pencil size={14} />\n </button>\n )}\n {canExecute && (\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.refresh', 'Refresh')}\n aria-label={tr('bigconsole.dataSinks.actions.refresh', 'Refresh')}\n onClick={() => handleRefreshClick(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <RefreshCw size={14} />\n </button>\n )}\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.export', 'Export')}\n aria-label={tr('bigconsole.dataSinks.actions.export', 'Export')}\n onClick={() => handleExportDataSink(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Download size={14} />\n </button>\n {canDelete && (\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.delete', 'Delete')}\n aria-label={tr('bigconsole.dataSinks.actions.delete', 'Delete')}\n onClick={() => handleDeleteClick(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Trash2 size={14} />\n </button>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )}\n </>\n </Section>\n </>\n )}\n </SectionStack>\n </div>\n\n {/* Dialogs */}\n {currentDataSink && (\n <>\n <DataSinkDeleteDialog\n dataSink={currentDataSink}\n isOpen={deleteDialogOpen}\n onClose={closeDeleteDialog}\n onConfirm={handleDeleteConfirm}\n />\n\n <DataSinkRefreshDialog\n dataSink={currentDataSink}\n isOpen={refreshDialogOpen}\n onClose={closeRefreshDialog}\n onConfirm={handleRefreshConfirm}\n />\n </>\n )}\n\n {/* Import DataSink Dialog */}\n <ImportDataSinkDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataSinksPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataSinksPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinksPage.tsx"],"sourcesContent":["/**\n * DataSinksPage\n *\n * List and manage DataSinks - fetches data from backend.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { type FC, memo, useCallback, useMemo, useState } from 'react';\nimport { formatDistanceToNow } from 'date-fns';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useExportDataSink } from '../hooks/useExportDataSink';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport { formatByteSize } from '../utils';\nimport type { DataSink, DataSinkSetupState } from '../types';\nimport { DataSinkCard } from '../components/datasink/DataSinkCard';\nimport { DataSinkDeleteDialog } from '../components/datasink/DataSinkDeleteDialog';\nimport { DataSinkRefreshDialog } from '../components/datasink/DataSinkRefreshDialog';\nimport { ImportDataSinkDialog } from '../components/datasink/ImportDataSinkDialog';\n\n// fe-libs components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\n// Section-emphasis primitives (BOFF-5632)\nimport { Section, SectionStack, SECTION_FOCUS_RING } from '../components/common';\n\n// Lucide icons\nimport {\n Plus,\n Search,\n AlertCircle,\n Container,\n X,\n Upload,\n LayoutGrid,\n Table2,\n Eye,\n Pencil,\n RefreshCw,\n Download,\n Trash2,\n} from 'lucide-react';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { SetupStateBadge } from '../components/datasink/SetupStateBadge';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype SetupStateFilter = 'all' | 'in_progress' | 'completed';\ntype ListViewMode = 'grid' | 'table';\n\nconst VIEW_MODE_KEY = 'bigconsole-datasinks-view-mode';\n\n/**\n * Row-action buttons in the table view. `--size-controlHeight-md` (40px) replaces the\n * previous `padding: 4` around a 14px icon, which was a ~22px hit target — far\n * under the touch minimum on the one layout that is already a side-scroll.\n */\nconst TABLE_ICON_BUTTON =\n `inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button border-0 bg-transparent text-text-muted hover:bg-bg-surface hover:text-text-primary motion-safe:transition-colors ${SECTION_FOCUS_RING}` as const;\n\n/**\n * Segmented controls clip their children (`overflow: hidden` keeps the group's\n * rounded corners), which would swallow an offset focus ring entirely — leaving\n * the buttons with no visible focus at all, since `SECTION_FOCUS_RING` also\n * suppresses the native outline. Painting the ring inside the button keeps it\n * visible without dropping the group's rounding.\n */\nconst SEGMENT_RING_FIX = 'focus-visible:ring-inset focus-visible:ring-offset-0' as const;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Determine setup state for a DataSink based on its properties.\n */\nconst getSetupState = (dataSink: DataSink): DataSinkSetupState => {\n if (dataSink.setupState === 'completed') {\n return 'completed';\n }\n if (dataSink.ttl !== null && dataSink.ttl !== undefined && dataSink.ttl > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId && dataSink.data && Object.keys(dataSink.data).length > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId) {\n return 'workflow_running';\n }\n return 'workflow_pending';\n};\n\n/**\n * Relative \"last refreshed\" text.\n *\n * `lastRefreshedAt` is non-null on the schema, but the store's normaliser\n * substitutes `new Date()` for a missing value and an unparseable string would\n * otherwise render \"Invalid Date\", so both are guarded here.\n */\nconst formatLastRefreshed = (iso: string | null | undefined): string => {\n if (!iso) return '—';\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return '—';\n return formatDistanceToNow(date, { addSuffix: true });\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DataSinksPage: FC = memo(function DataSinksPage() {\n const navigate = useAppNavigate();\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n\n // Permissions\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_READ);\n const canCreate = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_CREATE);\n const canUpdate = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_UPDATE);\n const canDelete = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_DELETE);\n const canExecute = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_EXECUTE);\n\n // Local state\n const [setupStateFilter, setSetupStateFilter] = useState<SetupStateFilter>('all');\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n const [viewMode, setViewMode] = useState<ListViewMode>(\n () => (localStorage.getItem(getProfileScopedKey(VIEW_MODE_KEY)) as ListViewMode) || 'grid'\n );\n\n const toggleViewMode = useCallback((mode: ListViewMode) => {\n setViewMode(mode);\n localStorage.setItem(getProfileScopedKey(VIEW_MODE_KEY), mode);\n }, []);\n\n // Store state\n const statusFilter = useDataSinkStore((state) => state.statusFilter);\n const activeFilter = useDataSinkStore((state) => state.activeFilter);\n const searchQuery = useDataSinkStore((state) => state.searchQuery);\n const setStatusFilter = useDataSinkStore((state) => state.setStatusFilter);\n const setActiveFilter = useDataSinkStore((state) => state.setActiveFilter);\n const setSearchQuery = useDataSinkStore((state) => state.setSearchQuery);\n const deleteDialogOpen = useDataSinkStore((state) => state.deleteDialogOpen);\n const refreshDialogOpen = useDataSinkStore((state) => state.refreshDialogOpen);\n const currentDataSink = useDataSinkStore((state) => state.currentDataSink);\n const openDeleteDialog = useDataSinkStore((state) => state.openDeleteDialog);\n const closeDeleteDialog = useDataSinkStore((state) => state.closeDeleteDialog);\n const openRefreshDialog = useDataSinkStore((state) => state.openRefreshDialog);\n const closeRefreshDialog = useDataSinkStore((state) => state.closeRefreshDialog);\n\n // Data operations\n const { loading, error, dataSinks, deleteDataSink, refreshDataSink, refetch } = useDataSinkOperations();\n\n // Export hook\n const { downloadDataSink } = useExportDataSink();\n\n // Compute filtered DataSinks with setup states\n const dataSinksWithSetupState = useMemo(() => {\n return dataSinks.map((ds) => ({\n dataSink: ds,\n setupState: getSetupState(ds),\n }));\n }, [dataSinks]);\n\n // Compute filtered DataSinks\n const filteredDataSinks = useMemo(() => {\n let filtered = [...dataSinksWithSetupState];\n\n if (setupStateFilter === 'in_progress') {\n filtered = filtered.filter((item) => item.setupState !== 'completed');\n } else if (setupStateFilter === 'completed') {\n filtered = filtered.filter((item) => item.setupState === 'completed');\n }\n\n if (statusFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.status === statusFilter);\n }\n\n if (activeFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.isActive === activeFilter);\n }\n\n if (searchQuery.trim()) {\n const query = searchQuery.toLowerCase();\n filtered = filtered.filter(\n (item) =>\n item.dataSink.name?.toLowerCase().includes(query) ||\n item.dataSink.key.toLowerCase().includes(query) ||\n item.dataSink.description?.toLowerCase().includes(query)\n );\n }\n\n return filtered.sort((a, b) => new Date(b.dataSink.updatedAt).getTime() - new Date(a.dataSink.updatedAt).getTime());\n }, [dataSinksWithSetupState, setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n // Handlers\n const handleCreateDataSink = useCallback(() => {\n navigate('/datasinks/new');\n }, [navigate]);\n\n const handleViewDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}`);\n },\n [navigate]\n );\n\n const handleEditDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}/edit`);\n },\n [navigate]\n );\n\n const handleRefreshClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openRefreshDialog(dataSink);\n }\n },\n [dataSinks, openRefreshDialog]\n );\n\n const handleRefreshConfirm = useCallback(\n async (forceRefresh: boolean) => {\n if (currentDataSink) {\n await refreshDataSink({\n id: currentDataSink.id,\n forceRefresh,\n });\n closeRefreshDialog();\n }\n },\n [currentDataSink, refreshDataSink, closeRefreshDialog]\n );\n\n const handleDeleteClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openDeleteDialog(dataSink);\n }\n },\n [dataSinks, openDeleteDialog]\n );\n\n const handleDeleteConfirm = useCallback(async () => {\n if (currentDataSink) {\n try {\n await deleteDataSink(currentDataSink.id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n closeDeleteDialog();\n }\n }, [currentDataSink, deleteDataSink, closeDeleteDialog]);\n\n const handleExportDataSink = useCallback(\n async (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n await downloadDataSink(id, dataSink.name || dataSink.key);\n }\n },\n [dataSinks, downloadDataSink]\n );\n\n const handleImportDataSink = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_dataSinkId: string, _dataSinkName: string | null, _wasUpdate: boolean) => {\n // Refetch DataSinks to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = useMemo(() => {\n return setupStateFilter !== 'all' || statusFilter !== null || activeFilter !== null || searchQuery.trim() !== '';\n }, [setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n const handleClearFilters = useCallback(() => {\n setSetupStateFilter('all');\n setStatusFilter(null);\n setActiveFilter(null);\n setSearchQuery('');\n }, [setStatusFilter, setActiveFilter, setSearchQuery]);\n\n // Setup state filter options\n const setupStateOptions: { value: SetupStateFilter; label: string }[] = [\n { value: 'all', label: tr('bigconsole.dataSinks.filters.setupState.all', 'All') },\n { value: 'in_progress', label: tr('bigconsole.dataSinks.filters.setupState.inProgress', 'In Progress') },\n { value: 'completed', label: tr('bigconsole.dataSinks.filters.setupState.completed', 'Completed') },\n ];\n\n // Read gate — once permissions have loaded, block actors without read access.\n if (!permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={tr('bigconsole.dataSinks.accessDenied.title', 'Access Denied')}\n description={tr(\n 'bigconsole.dataSinks.accessDenied.description',\n \"You don't have permission to view data sinks.\"\n )}\n />\n );\n }\n\n // The fetch-failure state is rendered inline below (inside the page body), so\n // the header band and page purpose survive an error instead of the whole\n // route collapsing into a standalone error card.\n return (\n // No `minHeight: 100vh`: the app shell already pads this content area, so a\n // full-viewport child guaranteed ~16-24px of vertical overflow everywhere.\n <div className=\"min-w-0\" style={{ backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Page header strip. `border-border-seam` / `bg-gloss-card` are not\n registered utilities in this shell — they emitted no CSS at all, so\n this band previously rendered with neither seam nor gloss. */}\n <header className=\"relative overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 'var(--space-formGap)',\n flexWrap: 'wrap',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n flex: '1 1 280px',\n minWidth: 0,\n }}\n >\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <Container\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div style={{ minWidth: 0 }}>\n {/* text-2xl matches the builder and detail page titles — those\n used `text-heading-h1`, which emits no CSS in this shell. */}\n <h1 className=\"m-0 text-2xl font-bold text-text-primary\">\n <GradientText>{tr('bigconsole.dataSinks.title', 'Data Sinks')}</GradientText>\n </h1>\n <p className=\"m-0 text-sm text-text-secondary\">\n {tr('bigconsole.dataSinks.subtitle', 'Manage pre-processed data from FluidGrids workflows')}\n </p>\n </div>\n </div>\n\n {/* Create + Import both require DATASINK_CREATE; omit them entirely when not permitted. */}\n <CTAOverflowMenu\n primary={\n canCreate\n ? {\n label: tr('bigconsole.dataSinks.actions.create', 'Create Data Sink'),\n onSelect: handleCreateDataSink,\n icon: (\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n ),\n }\n : undefined\n }\n actions={\n canCreate\n ? [\n {\n label: tr('bigconsole.dataSinks.actions.import', 'Import'),\n onSelect: handleImportDataSink,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </header>\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Data sinks are the structured destinations that hold your processed data — define a schema once and every\n pipeline, parser, and workflow can write into it. Dashboards then query sinks directly, so a sink is where\n your data lives and where your charts read from.\n </PagePurpose>\n\n <SectionStack>\n {error ? (\n /* The header band and page purpose stay on screen, so a fetch\n failure no longer costs the user all page identity. */\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={tr('bigconsole.dataSinks.error.title', 'Failed to load Data Sinks')}\n description={error.message}\n icon={<AlertCircle className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {tr('bigconsole.dataSinks.error.retry', 'Try Again')}\n </Button>\n }\n />\n ) : (\n <>\n {/* Both filter affordances now live in one labelled block, so it\n is obvious that the tab strip and the search row compose. */}\n <Section\n headingLevel={2}\n padding=\"compact\"\n title={tr('bigconsole.dataSinks.filters.heading', 'Filters')}\n description={tr(\n 'bigconsole.dataSinks.filters.description',\n 'Narrow the list by setup progress, status, or name.'\n )}\n bodyClassName=\"space-y-form-gap\"\n >\n <>\n {/* Setup State Filter Tabs */}\n <div\n role=\"group\"\n aria-label={tr('bigconsole.dataSinks.filters.setupState.label', 'Setup state')}\n style={{\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '2px',\n padding: '4px',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-card)',\n maxWidth: '100%',\n width: 'fit-content',\n }}\n >\n {setupStateOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={setupStateFilter === option.value}\n onClick={() => setSetupStateFilter(option.value)}\n className={SECTION_FOCUS_RING}\n style={{\n minHeight: 'var(--size-controlHeight-md)',\n padding: 'var(--space-controlGap) var(--space-formGap)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n cursor: 'pointer',\n backgroundColor:\n setupStateFilter === option.value ? 'var(--color-bg-surface)' : 'transparent',\n color:\n setupStateFilter === option.value\n ? 'var(--color-text-primary)'\n : 'var(--color-text-secondary)',\n // Was a raw `rgba(0,0,0,0.05)` literal.\n boxShadow: setupStateFilter === option.value ? 'var(--shadow-elevation-1)' : 'none',\n }}\n >\n {option.label}\n </button>\n ))}\n </div>\n\n {/* Search and Filters Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Search Bar */}\n <div\n style={{\n position: 'relative',\n maxWidth: '400px',\n minWidth: 'min(100%, 240px)',\n flex: '1 1 240px',\n }}\n >\n <Search\n style={{\n position: 'absolute',\n left: 'var(--space-controlGap)',\n top: '50%',\n transform: 'translateY(-50%)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <Input\n type=\"text\"\n placeholder={tr('bigconsole.dataSinks.search.placeholder', 'Search data sinks...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Status Filter */}\n <Select\n value={statusFilter || 'all'}\n onValueChange={(value) =>\n setStatusFilter(value === 'all' ? null : (value as 'ready' | 'error' | 'stale' | 'refreshing'))\n }\n >\n <SelectTrigger style={{ width: 'min(100%, 150px)' }}>\n <SelectValue\n placeholder={tr('bigconsole.dataSinks.filters.status.placeholder', 'All Statuses')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tr('bigconsole.dataSinks.filters.status.all', 'All Statuses')}\n </SelectItem>\n <SelectItem value=\"ready\">\n {tr('bigconsole.dataSinks.filters.status.ready', 'Ready')}\n </SelectItem>\n <SelectItem value=\"error\">\n {tr('bigconsole.dataSinks.filters.status.error', 'Error')}\n </SelectItem>\n <SelectItem value=\"stale\">\n {tr('bigconsole.dataSinks.filters.status.stale', 'Stale')}\n </SelectItem>\n <SelectItem value=\"refreshing\">\n {tr('bigconsole.dataSinks.filters.status.refreshing', 'Refreshing')}\n </SelectItem>\n </SelectContent>\n </Select>\n\n {/* Active Filter */}\n <Select\n value={activeFilter === null ? 'all' : activeFilter ? 'true' : 'false'}\n onValueChange={(value) => setActiveFilter(value === 'all' ? null : value === 'true')}\n >\n <SelectTrigger style={{ width: 'min(100%, 130px)' }}>\n <SelectValue placeholder={tr('bigconsole.dataSinks.filters.active.placeholder', 'All')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{tr('bigconsole.dataSinks.filters.active.all', 'All')}</SelectItem>\n <SelectItem value=\"true\">\n {tr('bigconsole.dataSinks.filters.active.active', 'Active')}\n </SelectItem>\n <SelectItem value=\"false\">\n {tr('bigconsole.dataSinks.filters.active.inactive', 'Inactive')}\n </SelectItem>\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n <X\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n )}\n </div>\n </>\n </Section>\n\n {/* The point of this route. The result count and the view toggle\n belong to the results, not to the filters — moving them here\n also removes the `margin-left: auto` that jumped to the left\n edge as soon as the filter row wrapped. */}\n <Section\n emphasis=\"primary\"\n headingLevel={2}\n title={tr('bigconsole.dataSinks.list.heading', 'Data sinks')}\n description={tr(\n 'bigconsole.dataSinks.list.description',\n 'Every sink in this workspace, most recently updated first.'\n )}\n meta={\n <Badge variant=\"secondary\">\n {filteredDataSinks.length}{' '}\n {filteredDataSinks.length === 1\n ? tr('bigconsole.dataSinks.count.single', 'Data Sink')\n : tr('bigconsole.dataSinks.count.plural', 'Data Sinks')}\n </Badge>\n }\n actions={\n <div\n role=\"group\"\n aria-label={tr('bigconsole.dataSinks.view.label', 'List layout')}\n style={{\n display: 'flex',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n overflow: 'hidden',\n }}\n >\n <button\n type=\"button\"\n onClick={() => toggleViewMode('grid')}\n aria-pressed={viewMode === 'grid'}\n title={tr('bigconsole.dataSinks.view.grid', 'Grid view')}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center ${SECTION_FOCUS_RING} ${SEGMENT_RING_FIX}`}\n style={{\n border: 'none',\n cursor: 'pointer',\n backgroundColor: viewMode === 'grid' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'grid' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <LayoutGrid size={16} />\n </button>\n <button\n type=\"button\"\n onClick={() => toggleViewMode('table')}\n aria-pressed={viewMode === 'table'}\n title={tr('bigconsole.dataSinks.view.table', 'Table view')}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center ${SECTION_FOCUS_RING} ${SEGMENT_RING_FIX}`}\n style={{\n border: 'none',\n borderLeft: '1px solid var(--color-border)',\n cursor: 'pointer',\n backgroundColor: viewMode === 'table' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'table' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <Table2 size={16} />\n </button>\n </div>\n }\n >\n <>\n {/* Loading state */}\n {loading && dataSinks.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <div style={{ textAlign: 'center' }}>\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <p className=\"text-sm text-text-muted\">\n {tr('bigconsole.dataSinks.loading', 'Loading data sinks...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state — no GlassCard: the zero-data state should not\n be more emphasised than the content it replaces. */}\n {!loading && dataSinks.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('bigconsole.dataSinks.empty.title', 'No Data Sinks yet')}\n description={tr(\n 'bigconsole.dataSinks.empty.description',\n 'Data Sinks store pre-processed data from FluidGrids workflows for fast access in widgets.'\n )}\n action={\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DATASINK_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateDataSink}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dataSinks.actions.create', 'Create Data Sink')}\n </Button>\n </PermissionButton>\n }\n />\n )}\n\n {/* No results state — an info tone, so \"you have none\" and\n \"your filter matched none\" are no longer identical. */}\n {!loading && filteredDataSinks.length === 0 && dataSinks.length > 0 && (\n <Section\n tone=\"info\"\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={3}\n title={tr('bigconsole.dataSinks.empty.filteredTitle', 'No matching data sinks')}\n description={tr('bigconsole.dataSinks.empty.filtered', 'No data sinks match your filters')}\n actions={\n <Button variant=\"secondary\" size=\"sm\" onClick={handleClearFilters}>\n {tr('bigconsole.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n }\n />\n )}\n\n {/* DataSink Grid */}\n {filteredDataSinks.length > 0 && viewMode === 'grid' && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 320px), 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {filteredDataSinks.map((item) => (\n <DataSinkCard\n key={item.dataSink.id}\n dataSink={item.dataSink}\n setupState={item.setupState}\n onView={handleViewDataSink}\n onEdit={handleEditDataSink}\n onRefresh={handleRefreshClick}\n onExport={handleExportDataSink}\n onDelete={handleDeleteClick}\n canEdit={canUpdate}\n canRefresh={canExecute}\n canDelete={canDelete}\n />\n ))}\n </div>\n )}\n\n {/* Table View. The section already draws the boundary, so the\n table no longer nests a second border inside it. */}\n {filteredDataSinks.length > 0 && viewMode === 'table' && (\n <div className=\"min-w-0\" style={{ overflowX: 'auto' }}>\n <table\n style={{ width: '100%', borderCollapse: 'collapse', fontSize: 'var(--font-size-body-sm)' }}\n >\n <thead>\n <tr\n style={{\n backgroundColor: 'var(--color-bg-sunken)',\n borderBottom: '1px solid var(--color-border)',\n }}\n >\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.name', 'Name')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.key', 'Key')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.status', 'Status')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.setup', 'Setup')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'center',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.active', 'Active')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.version', 'Version')}\n </th>\n {/* Size and Last refreshed were already fetched by\n DataSinkListItem (`sizeBytes`, `lastRefreshedAt`)\n and thrown away — they are the two facts that say\n whether a sink is worth opening. */}\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'right',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.size', 'Size')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.lastRefreshed', 'Last refreshed')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.updated', 'Updated')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'right',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredDataSinks.map((item) => {\n const ds = item.dataSink;\n return (\n <tr\n key={ds.id}\n onClick={() => handleViewDataSink(ds.id)}\n // Hover was JS-driven, so it ignored theme and the OS\n // reduce-motion setting and stuck after a tap on touch.\n className=\"cursor-pointer hover:bg-bg-sunken motion-safe:transition-colors\"\n style={{ borderBottom: '1px solid var(--color-border)' }}\n >\n <td\n style={{\n padding: '10px 12px',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n maxWidth: 200,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.name || ds.key}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n fontFamily: 'monospace',\n fontSize: '12px',\n maxWidth: 160,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.key}\n </td>\n <td style={{ padding: '10px 12px' }}>\n <DataSinkStatusBadge status={ds.status} />\n </td>\n <td style={{ padding: '10px 12px' }}>\n <SetupStateBadge state={item.setupState} />\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'center' }}>\n <span\n style={{\n display: 'inline-block',\n width: 8,\n height: 8,\n borderRadius: '50%',\n // Was a raw `#22c55e` literal.\n backgroundColor: ds.isActive\n ? 'var(--color-status-success-bg)'\n : 'var(--color-text-muted)',\n }}\n title={\n ds.isActive\n ? tr('bigconsole.dataSinks.badges.active', 'Active')\n : tr('bigconsole.dataSinks.badges.inactive', 'Inactive')\n }\n />\n </td>\n <td style={{ padding: '10px 12px', color: 'var(--color-text-muted)' }}>\n v{ds.version ?? 0}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n textAlign: 'right',\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {formatByteSize(ds.sizeBytes)}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n whiteSpace: 'nowrap',\n }}\n title={ds.lastRefreshedAt ? new Date(ds.lastRefreshedAt).toLocaleString() : undefined}\n >\n {formatLastRefreshed(ds.lastRefreshedAt)}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n whiteSpace: 'nowrap',\n }}\n >\n {new Date(ds.updatedAt).toLocaleDateString()}\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'right' }}>\n <div\n style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}\n onClick={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.view', 'View')}\n aria-label={tr('bigconsole.dataSinks.actions.view', 'View')}\n onClick={() => handleViewDataSink(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Eye size={14} />\n </button>\n {canUpdate && (\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.edit', 'Edit')}\n aria-label={tr('bigconsole.dataSinks.actions.edit', 'Edit')}\n onClick={() => handleEditDataSink(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Pencil size={14} />\n </button>\n )}\n {canExecute && (\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.refresh', 'Refresh')}\n aria-label={tr('bigconsole.dataSinks.actions.refresh', 'Refresh')}\n onClick={() => handleRefreshClick(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <RefreshCw size={14} />\n </button>\n )}\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.export', 'Export')}\n aria-label={tr('bigconsole.dataSinks.actions.export', 'Export')}\n onClick={() => handleExportDataSink(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Download size={14} />\n </button>\n {canDelete && (\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.delete', 'Delete')}\n aria-label={tr('bigconsole.dataSinks.actions.delete', 'Delete')}\n onClick={() => handleDeleteClick(ds.id)}\n className={TABLE_ICON_BUTTON}\n >\n <Trash2 size={14} />\n </button>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )}\n </>\n </Section>\n </>\n )}\n </SectionStack>\n </div>\n\n {/* Dialogs */}\n {currentDataSink && (\n <>\n <DataSinkDeleteDialog\n dataSink={currentDataSink}\n isOpen={deleteDialogOpen}\n onClose={closeDeleteDialog}\n onConfirm={handleDeleteConfirm}\n />\n\n <DataSinkRefreshDialog\n dataSink={currentDataSink}\n isOpen={refreshDialogOpen}\n onClose={closeRefreshDialog}\n onConfirm={handleRefreshConfirm}\n />\n </>\n )}\n\n {/* Import DataSink Dialog */}\n <ImportDataSinkDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataSinksPage;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEA,IAAM,KAAgB,kCAOhB,IACJ,0NAA0N,KAStN,KAAmB,wDASnB,MAAiB,MACjB,EAAS,eAAe,eAGxB,EAAS,QAAQ,QAAQ,EAAS,QAAQ,KAAA,KAAa,EAAS,MAAM,KAGtE,EAAS,wBAAwB,EAAS,QAAQ,OAAO,KAAK,EAAS,KAAK,CAAC,SAAS,IACjF,cAEL,EAAS,uBACJ,qBAEF,oBAUH,MAAuB,MAA2C;AACtE,KAAI,CAAC,EAAK,QAAO;CACjB,IAAM,IAAO,IAAI,KAAK,EAAI;AAE1B,QADI,OAAO,MAAM,EAAK,SAAS,CAAC,GAAS,MAClC,EAAoB,GAAM,EAAE,WAAW,IAAM,CAAC;GAO1C,IAAoB,EAAK,WAAyB;CAC7D,IAAM,IAAW,GAAgB,EAC3B,EAAE,SAAM,IAAS,EACjB,IAAK,GACR,GAAa,MAAqB;EACjC,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEzC,CAAC,EAAE,CACJ,EAGK,EAAE,kBAAe,WAAW,OAAuB,IAAgB,EACnE,KAAU,EAAc,EAAuB,cAAc,EAC7D,IAAY,EAAc,EAAuB,gBAAgB,EACjE,IAAY,EAAc,EAAuB,gBAAgB,EACjE,IAAY,EAAc,EAAuB,gBAAgB,EACjE,IAAa,EAAc,EAAuB,iBAAiB,EAGnE,CAAC,GAAkB,KAAuB,EAA2B,MAAM,EAC3E,CAAC,IAAoB,KAAyB,EAAS,GAAM,EAC7D,CAAC,GAAU,MAAe,QACvB,aAAa,QAAQ,EAAoB,GAAc,CAAC,IAAqB,OACrF,EAEK,IAAiB,GAAa,MAAuB;AAEzD,EADA,GAAY,EAAK,EACjB,aAAa,QAAQ,EAAoB,GAAc,EAAE,EAAK;IAC7D,EAAE,CAAC,EAGA,IAAe,GAAkB,MAAU,EAAM,aAAa,EAC9D,IAAe,GAAkB,MAAU,EAAM,aAAa,EAC9D,IAAc,GAAkB,MAAU,EAAM,YAAY,EAC5D,IAAkB,GAAkB,MAAU,EAAM,gBAAgB,EACpE,IAAkB,GAAkB,MAAU,EAAM,gBAAgB,EACpE,IAAiB,GAAkB,MAAU,EAAM,eAAe,EAClE,KAAmB,GAAkB,MAAU,EAAM,iBAAiB,EACtE,KAAoB,GAAkB,MAAU,EAAM,kBAAkB,EACxE,IAAkB,GAAkB,MAAU,EAAM,gBAAgB,EACpE,IAAmB,GAAkB,MAAU,EAAM,iBAAiB,EACtE,IAAoB,GAAkB,MAAU,EAAM,kBAAkB,EACxE,IAAoB,GAAkB,MAAU,EAAM,kBAAkB,EACxE,IAAqB,GAAkB,MAAU,EAAM,mBAAmB,EAG1E,EAAE,YAAS,UAAO,cAAW,mBAAgB,qBAAiB,eAAY,IAAuB,EAGjG,EAAE,yBAAqB,IAAmB,EAG1C,KAA0B,QACvB,EAAU,KAAK,OAAQ;EAC5B,UAAU;EACV,YAAY,GAAc,EAAG;EAC9B,EAAE,EACF,CAAC,EAAU,CAAC,EAGT,IAAoB,QAAc;EACtC,IAAI,IAAW,CAAC,GAAG,GAAwB;AAgB3C,MAdI,MAAqB,gBACvB,IAAW,EAAS,QAAQ,MAAS,EAAK,eAAe,YAAY,GAC5D,MAAqB,gBAC9B,IAAW,EAAS,QAAQ,MAAS,EAAK,eAAe,YAAY,GAGnE,MAAiB,SACnB,IAAW,EAAS,QAAQ,MAAS,EAAK,SAAS,WAAW,EAAa,GAGzE,MAAiB,SACnB,IAAW,EAAS,QAAQ,MAAS,EAAK,SAAS,aAAa,EAAa,GAG3E,EAAY,MAAM,EAAE;GACtB,IAAM,IAAQ,EAAY,aAAa;AACvC,OAAW,EAAS,QACjB,MACC,EAAK,SAAS,MAAM,aAAa,CAAC,SAAS,EAAM,IACjD,EAAK,SAAS,IAAI,aAAa,CAAC,SAAS,EAAM,IAC/C,EAAK,SAAS,aAAa,aAAa,CAAC,SAAS,EAAM,CAC3D;;AAGH,SAAO,EAAS,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,SAAS,UAAU,CAAC,SAAS,CAAC;IAClH;EAAC;EAAyB;EAAkB;EAAc;EAAc;EAAY,CAAC,EAGlF,KAAuB,QAAkB;AAC7C,IAAS,iBAAiB;IACzB,CAAC,EAAS,CAAC,EAER,IAAqB,GACxB,MAAe;AACd,IAAS,cAAc,IAAK;IAE9B,CAAC,EAAS,CACX,EAEK,KAAqB,GACxB,MAAe;AACd,IAAS,cAAc,EAAG,OAAO;IAEnC,CAAC,EAAS,CACX,EAEK,KAAqB,GACxB,MAAe;EACd,IAAM,IAAW,EAAU,MAAM,MAAO,EAAG,OAAO,EAAG;AACrD,EAAI,KACF,EAAkB,EAAS;IAG/B,CAAC,GAAW,EAAkB,CAC/B,EAEK,KAAuB,EAC3B,OAAO,MAA0B;AAC/B,EAAI,MACF,MAAM,GAAgB;GACpB,IAAI,EAAgB;GACpB;GACD,CAAC,EACF,GAAoB;IAGxB;EAAC;EAAiB;EAAiB;EAAmB,CACvD,EAEK,KAAoB,GACvB,MAAe;EACd,IAAM,IAAW,EAAU,MAAM,MAAO,EAAG,OAAO,EAAG;AACrD,EAAI,KACF,EAAiB,EAAS;IAG9B,CAAC,GAAW,EAAiB,CAC9B,EAEK,KAAsB,EAAY,YAAY;AAClD,MAAI,GAAiB;AACnB,OAAI;AAEF,IADA,MAAM,EAAe,EAAgB,GAAG,EACpC,GAAU,IAAO,EAAa,UAAU;YACrC,GAAK;AAEZ,UADI,GAAU,IAAO,EAAa,QAAQ,EACpC;;AAER,MAAmB;;IAEpB;EAAC;EAAiB;EAAgB;EAAkB,CAAC,EAElD,KAAuB,EAC3B,OAAO,MAAe;EACpB,IAAM,IAAW,EAAU,MAAM,MAAO,EAAG,OAAO,EAAG;AACrD,EAAI,KACF,MAAM,GAAiB,GAAI,EAAS,QAAQ,EAAS,IAAI;IAG7D,CAAC,GAAW,GAAiB,CAC9B,EAEK,KAAuB,QAAkB;AAC7C,IAAsB,GAAK;IAC1B,EAAE,CAAC,EAEA,KAAsB,GACzB,GAAqB,GAA8B,MAAwB;AAE1E,KAAS;IAEX,CAAC,EAAQ,CACV,EAEK,KAAmB,QAChB,MAAqB,SAAS,MAAiB,QAAQ,MAAiB,QAAQ,EAAY,MAAM,KAAK,IAC7G;EAAC;EAAkB;EAAc;EAAc;EAAY,CAAC,EAEzD,IAAqB,QAAkB;AAI3C,EAHA,EAAoB,MAAM,EAC1B,EAAgB,KAAK,EACrB,EAAgB,KAAK,EACrB,EAAe,GAAG;IACjB;EAAC;EAAiB;EAAiB;EAAe,CAAC,EAGhD,KAAkE;EACtE;GAAE,OAAO;GAAO,OAAO,EAAG,+CAA+C,MAAM;GAAE;EACjF;GAAE,OAAO;GAAe,OAAO,EAAG,sDAAsD,cAAc;GAAE;EACxG;GAAE,OAAO;GAAa,OAAO,EAAG,qDAAqD,YAAY;GAAE;EACpG;AAkBD,QAfI,CAAC,MAAsB,CAAC,KAExB,kBAAC,IAAD;EACE,OAAO,EAAG,2CAA2C,gBAAgB;EACrE,aAAa,EACX,iDACA,gDACD;EACD,CAAA,GAUJ,kBAAC,OAAD;EAAK,WAAU;EAAU,OAAO,EAAE,iBAAiB,0BAA0B;YAA7E;GAIE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,IAAD,EAAkB,WAAU,UAAW,CAAA,EACvC,kBAAC,OAAD;KAAK,OAAO,EAAE,SAAS,4BAA4B;eACjD,kBAAC,OAAD;MACE,OAAO;OACL,SAAS;OACT,YAAY;OACZ,gBAAgB;OAChB,KAAK;OACL,UAAU;OACX;gBAPH,CASE,kBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,YAAY;QACZ,KAAK;QACL,MAAM;QACN,UAAU;QACX;iBAPH,CASE,kBAAC,OAAD;QACE,OAAO;SACL,SAAS;SACT,QAAQ;SACR,OAAO;SACP,YAAY;SACZ,gBAAgB;SAChB,cAAc;SACd,iBAAiB;SAClB;kBAED,kBAAC,IAAD,EACE,OAAO;SACL,QAAQ;SACR,OAAO;SACP,OAAO;SACR,EACD,CAAA;QACE,CAAA,EACN,kBAAC,OAAD;QAAK,OAAO,EAAE,UAAU,GAAG;kBAA3B,CAGE,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,IAAD,EAAA,UAAe,EAAG,8BAA8B,aAAa,EAAgB,CAAA;SAC1E,CAAA,EACL,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAG,iCAAiC,sDAAsD;SACzF,CAAA,CACA;UACF;UAGN,kBAAC,IAAD;OACE,SACE,IACI;QACE,OAAO,EAAG,uCAAuC,mBAAmB;QACpE,UAAU;QACV,MACE,kBAAC,GAAD,EACE,OAAO;SACL,aAAa;SACb,QAAQ;SACR,OAAO;SACR,EACD,CAAA;QAEL,GACD,KAAA;OAEN,SACE,IACI,CACE;QACE,OAAO,EAAG,uCAAuC,SAAS;QAC1D,UAAU;QACV,MAAM,kBAAC,IAAD,EAAQ,OAAO;SAAE,QAAQ;SAAuB,OAAO;SAAuB,EAAI,CAAA;QACzF,CACF,GACD,EAAE;OAER,CAAA,CACE;;KACF,CAAA,CACC;;GAGT,kBAAC,OAAD;IAAK,OAAO,EAAE,SAAS,4BAA4B;cAAnD,CACE,kBAAC,IAAD;KAAa,WAAU;eAA+B;KAIxC,CAAA,EAEd,kBAAC,IAAD,EAAA,UACG,IAGC,kBAAC,GAAD;KACE,MAAK;KACL,cAAc;KACd,OAAO,EAAG,oCAAoC,4BAA4B;KAC1E,aAAa,EAAM;KACnB,MAAM,kBAAC,IAAD,EAAa,WAAU,uBAAwB,CAAA;KACrD,SACE,kBAAC,GAAD;MAAQ,MAAK;MAAK,eAAe,GAAS;gBACvC,EAAG,oCAAoC,YAAY;MAC7C,CAAA;KAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CAGE,kBAAC,GAAD;KACE,cAAc;KACd,SAAQ;KACR,OAAO,EAAG,wCAAwC,UAAU;KAC5D,aAAa,EACX,4CACA,sDACD;KACD,eAAc;eAEd,kBAAA,GAAA,EAAA,UAAA,CAEE,kBAAC,OAAD;MACE,MAAK;MACL,cAAY,EAAG,iDAAiD,cAAc;MAC9E,OAAO;OACL,SAAS;OACT,YAAY;OACZ,UAAU;OACV,KAAK;OACL,SAAS;OACT,iBAAiB;OACjB,cAAc;OACd,UAAU;OACV,OAAO;OACR;gBAEA,GAAkB,KAAK,MACtB,kBAAC,UAAD;OAEE,MAAK;OACL,gBAAc,MAAqB,EAAO;OAC1C,eAAe,EAAoB,EAAO,MAAM;OAChD,WAAW;OACX,OAAO;QACL,WAAW;QACX,SAAS;QACT,UAAU;QACV,YAAY;QACZ,cAAc;QACd,QAAQ;QACR,QAAQ;QACR,iBACE,MAAqB,EAAO,QAAQ,4BAA4B;QAClE,OACE,MAAqB,EAAO,QACxB,8BACA;QAEN,WAAW,MAAqB,EAAO,QAAQ,8BAA8B;QAC9E;iBAEA,EAAO;OACD,EAxBF,EAAO,MAwBL,CACT;MACE,CAAA,EAGN,kBAAC,OAAD;MACE,OAAO;OACL,SAAS;OACT,YAAY;OACZ,KAAK;OACL,UAAU;OACX;gBANH;OASE,kBAAC,OAAD;QACE,OAAO;SACL,UAAU;SACV,UAAU;SACV,UAAU;SACV,MAAM;SACP;kBANH,CAQE,kBAAC,IAAD,EACE,OAAO;SACL,UAAU;SACV,MAAM;SACN,KAAK;SACL,WAAW;SACX,QAAQ;SACR,OAAO;SACP,OAAO;SACR,EACD,CAAA,EACF,kBAAC,IAAD;SACE,MAAK;SACL,aAAa,EAAG,2CAA2C,uBAAuB;SAClF,OAAO;SACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;SAC/C,OAAO;UAAE,aAAa;UAAgC,QAAQ;UAAgC;SAC9F,CAAA,CACE;;OAGN,kBAAC,GAAD;QACE,OAAO,KAAgB;QACvB,gBAAgB,MACd,EAAgB,MAAU,QAAQ,OAAQ,EAAqD;kBAHnG,CAME,kBAAC,GAAD;SAAe,OAAO,EAAE,OAAO,oBAAoB;mBACjD,kBAAC,GAAD,EACE,aAAa,EAAG,mDAAmD,eAAe,EAClF,CAAA;SACY,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA;SACE,kBAAC,GAAD;UAAY,OAAM;oBACf,EAAG,2CAA2C,eAAe;UACnD,CAAA;SACb,kBAAC,GAAD;UAAY,OAAM;oBACf,EAAG,6CAA6C,QAAQ;UAC9C,CAAA;SACb,kBAAC,GAAD;UAAY,OAAM;oBACf,EAAG,6CAA6C,QAAQ;UAC9C,CAAA;SACb,kBAAC,GAAD;UAAY,OAAM;oBACf,EAAG,6CAA6C,QAAQ;UAC9C,CAAA;SACb,kBAAC,GAAD;UAAY,OAAM;oBACf,EAAG,kDAAkD,aAAa;UACxD,CAAA;SACC,EAAA,CAAA,CACT;;OAGT,kBAAC,GAAD;QACE,OAAO,MAAiB,OAAO,QAAQ,IAAe,SAAS;QAC/D,gBAAgB,MAAU,EAAgB,MAAU,QAAQ,OAAO,MAAU,OAAO;kBAFtF,CAIE,kBAAC,GAAD;SAAe,OAAO,EAAE,OAAO,oBAAoB;mBACjD,kBAAC,GAAD,EAAa,aAAa,EAAG,mDAAmD,MAAM,EAAI,CAAA;SAC5E,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA;SACE,kBAAC,GAAD;UAAY,OAAM;oBAAO,EAAG,2CAA2C,MAAM;UAAc,CAAA;SAC3F,kBAAC,GAAD;UAAY,OAAM;oBACf,EAAG,8CAA8C,SAAS;UAChD,CAAA;SACb,kBAAC,GAAD;UAAY,OAAM;oBACf,EAAG,gDAAgD,WAAW;UACpD,CAAA;SACC,EAAA,CAAA,CACT;;OAGR,MACC,kBAAC,GAAD;QAAQ,SAAQ;QAAQ,MAAK;QAAK,SAAS;kBAA3C,CACE,kBAAC,IAAD,EACE,OAAO;SACL,aAAa;SACb,QAAQ;SACR,OAAO;SACR,EACD,CAAA,EACD,EAAG,sCAAsC,gBAAgB,CACnD;;OAEP;QACL,EAAA,CAAA;KACK,CAAA,EAMV,kBAAC,GAAD;KACE,UAAS;KACT,cAAc;KACd,OAAO,EAAG,qCAAqC,aAAa;KAC5D,aAAa,EACX,yCACA,6DACD;KACD,MACE,kBAAC,IAAD;MAAO,SAAQ;gBAAf;OACG,EAAkB;OAAQ;OAC1B,EAAkB,WAAW,IAC1B,EAAG,qCAAqC,YAAY,GACpD,EAAG,qCAAqC,aAAa;OACnD;;KAEV,SACE,kBAAC,OAAD;MACE,MAAK;MACL,cAAY,EAAG,mCAAmC,cAAc;MAChE,OAAO;OACL,SAAS;OACT,cAAc;OACd,QAAQ;OACR,UAAU;OACX;gBARH,CAUE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAe,OAAO;OACrC,gBAAc,MAAa;OAC3B,OAAO,EAAG,kCAAkC,YAAY;OACxD,WAAW,kFAAkF,EAAmB,GAAG;OACnH,OAAO;QACL,QAAQ;QACR,QAAQ;QACR,iBAAiB,MAAa,SAAS,2BAA2B;QAClE,OAAO,MAAa,SAAS,8BAA8B;QAC5D;iBAED,kBAAC,IAAD,EAAY,MAAM,IAAM,CAAA;OACjB,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAe,QAAQ;OACtC,gBAAc,MAAa;OAC3B,OAAO,EAAG,mCAAmC,aAAa;OAC1D,WAAW,kFAAkF,EAAmB,GAAG;OACnH,OAAO;QACL,QAAQ;QACR,YAAY;QACZ,QAAQ;QACR,iBAAiB,MAAa,UAAU,2BAA2B;QACnE,OAAO,MAAa,UAAU,8BAA8B;QAC7D;iBAED,kBAAC,IAAD,EAAQ,MAAM,IAAM,CAAA;OACb,CAAA,CACL;;eAGR,kBAAA,GAAA,EAAA,UAAA;MAEG,KAAW,EAAU,WAAW,KAC/B,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,kBAAC,IAAD,EACE,OAAO;SACL,QAAQ;SACR,OAAO;SACP,QAAQ;SACT,EACD,CAAA,EACF,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAG,gCAAgC,wBAAwB;SAC1D,CAAA,CACA;;OACF,CAAA;MAKP,CAAC,KAAW,EAAU,WAAW,KAChC,kBAAC,IAAD;OACE,cAAa;OACb,OAAO,EAAG,oCAAoC,oBAAoB;OAClE,aAAa,EACX,0CACA,4FACD;OACD,QACE,kBAAC,IAAD;QAAkB,YAAY,EAAuB;QAAiB,gBAAe;kBACnF,kBAAC,GAAD;SAAQ,SAAS;mBAAjB,CACE,kBAAC,GAAD,EACE,OAAO;UACL,aAAa;UACb,QAAQ;UACR,OAAO;UACR,EACD,CAAA,EACD,EAAG,uCAAuC,mBAAmB,CACvD;;QACQ,CAAA;OAErB,CAAA;MAKH,CAAC,KAAW,EAAkB,WAAW,KAAK,EAAU,SAAS,KAChE,kBAAC,GAAD;OACE,MAAK;OACL,UAAS;OACT,SAAQ;OACR,cAAc;OACd,OAAO,EAAG,4CAA4C,yBAAyB;OAC/E,aAAa,EAAG,uCAAuC,mCAAmC;OAC1F,SACE,kBAAC,GAAD;QAAQ,SAAQ;QAAY,MAAK;QAAK,SAAS;kBAC5C,EAAG,sCAAsC,gBAAgB;QACnD,CAAA;OAEX,CAAA;MAIH,EAAkB,SAAS,KAAK,MAAa,UAC5C,kBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,qBAAqB;QACrB,KAAK;QACN;iBAEA,EAAkB,KAAK,MACtB,kBAAC,IAAD;QAEE,UAAU,EAAK;QACf,YAAY,EAAK;QACjB,QAAQ;QACR,QAAQ;QACR,WAAW;QACX,UAAU;QACV,UAAU;QACV,SAAS;QACT,YAAY;QACD;QACX,EAXK,EAAK,SAAS,GAWnB,CACF;OACE,CAAA;MAKP,EAAkB,SAAS,KAAK,MAAa,WAC5C,kBAAC,OAAD;OAAK,WAAU;OAAU,OAAO,EAAE,WAAW,QAAQ;iBACnD,kBAAC,SAAD;QACE,OAAO;SAAE,OAAO;SAAQ,gBAAgB;SAAY,UAAU;SAA4B;kBAD5F,CAGE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;SACE,OAAO;UACL,iBAAiB;UACjB,cAAc;UACf;mBAJH;UAME,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,mCAAmC,OAAO;WAC3C,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,kCAAkC,MAAM;WACzC,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,qCAAqC,SAAS;WAC/C,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,oCAAoC,QAAQ;WAC7C,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,qCAAqC,SAAS;WAC/C,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,sCAAsC,UAAU;WACjD,CAAA;UAKL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,mCAAmC,OAAO;WAC3C,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,4CAA4C,iBAAiB;WAC9D,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,sCAAsC,UAAU;WACjD,CAAA;UACL,kBAAC,MAAD;WACE,OAAO;YACL,SAAS;YACT,WAAW;YACX,YAAY;YACZ,OAAO;YACP,YAAY;YACb;qBAEA,EAAG,sCAAsC,UAAU;WACjD,CAAA;UACF;YACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAkB,KAAK,MAAS;SAC/B,IAAM,IAAK,EAAK;AAChB,gBACE,kBAAC,MAAD;UAEE,eAAe,EAAmB,EAAG,GAAG;UAGxC,WAAU;UACV,OAAO,EAAE,cAAc,iCAAiC;oBAN1D;WAQE,kBAAC,MAAD;YACE,OAAO;aACL,SAAS;aACT,YAAY;aACZ,OAAO;aACP,UAAU;aACV,UAAU;aACV,cAAc;aACd,YAAY;aACb;sBAEA,EAAG,QAAQ,EAAG;YACZ,CAAA;WACL,kBAAC,MAAD;YACE,OAAO;aACL,SAAS;aACT,OAAO;aACP,YAAY;aACZ,UAAU;aACV,UAAU;aACV,UAAU;aACV,cAAc;aACd,YAAY;aACb;sBAEA,EAAG;YACD,CAAA;WACL,kBAAC,MAAD;YAAI,OAAO,EAAE,SAAS,aAAa;sBACjC,kBAAC,IAAD,EAAqB,QAAQ,EAAG,QAAU,CAAA;YACvC,CAAA;WACL,kBAAC,MAAD;YAAI,OAAO,EAAE,SAAS,aAAa;sBACjC,kBAAC,IAAD,EAAiB,OAAO,EAAK,YAAc,CAAA;YACxC,CAAA;WACL,kBAAC,MAAD;YAAI,OAAO;aAAE,SAAS;aAAa,WAAW;aAAU;sBACtD,kBAAC,QAAD;aACE,OAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ;cACR,cAAc;cAEd,iBAAiB,EAAG,WAChB,mCACA;cACL;aACD,OACE,EAAG,WACC,EAAG,sCAAsC,SAAS,GAClD,EAAG,wCAAwC,WAAW;aAE5D,CAAA;YACC,CAAA;WACL,kBAAC,MAAD;YAAI,OAAO;aAAE,SAAS;aAAa,OAAO;aAA2B;sBAArE,CAAuE,KACnE,EAAG,WAAW,EACb;;WACL,kBAAC,MAAD;YACE,OAAO;aACL,SAAS;aACT,OAAO;aACP,WAAW;aACX,YAAY;aACZ,oBAAoB;aACrB;sBAEA,GAAe,EAAG,UAAU;YAC1B,CAAA;WACL,kBAAC,MAAD;YACE,OAAO;aACL,SAAS;aACT,OAAO;aACP,YAAY;aACb;YACD,OAAO,EAAG,kBAAkB,IAAI,KAAK,EAAG,gBAAgB,CAAC,gBAAgB,GAAG,KAAA;sBAE3E,GAAoB,EAAG,gBAAgB;YACrC,CAAA;WACL,kBAAC,MAAD;YACE,OAAO;aACL,SAAS;aACT,OAAO;aACP,YAAY;aACb;sBAEA,IAAI,KAAK,EAAG,UAAU,CAAC,oBAAoB;YACzC,CAAA;WACL,kBAAC,MAAD;YAAI,OAAO;aAAE,SAAS;aAAa,WAAW;aAAS;sBACrD,kBAAC,OAAD;aACE,OAAO;cAAE,SAAS;cAAQ,KAAK;cAAG,gBAAgB;cAAY;aAC9D,UAAU,MAAM,EAAE,iBAAiB;uBAFrC;cAIE,kBAAC,UAAD;eACE,MAAK;eACL,OAAO,EAAG,qCAAqC,OAAO;eACtD,cAAY,EAAG,qCAAqC,OAAO;eAC3D,eAAe,EAAmB,EAAG,GAAG;eACxC,WAAW;yBAEX,kBAAC,IAAD,EAAK,MAAM,IAAM,CAAA;eACV,CAAA;cACR,KACC,kBAAC,UAAD;eACE,MAAK;eACL,OAAO,EAAG,qCAAqC,OAAO;eACtD,cAAY,EAAG,qCAAqC,OAAO;eAC3D,eAAe,GAAmB,EAAG,GAAG;eACxC,WAAW;yBAEX,kBAAC,IAAD,EAAQ,MAAM,IAAM,CAAA;eACb,CAAA;cAEV,KACC,kBAAC,UAAD;eACE,MAAK;eACL,OAAO,EAAG,wCAAwC,UAAU;eAC5D,cAAY,EAAG,wCAAwC,UAAU;eACjE,eAAe,GAAmB,EAAG,GAAG;eACxC,WAAW;yBAEX,kBAAC,IAAD,EAAW,MAAM,IAAM,CAAA;eAChB,CAAA;cAEX,kBAAC,UAAD;eACE,MAAK;eACL,OAAO,EAAG,uCAAuC,SAAS;eAC1D,cAAY,EAAG,uCAAuC,SAAS;eAC/D,eAAe,GAAqB,EAAG,GAAG;eAC1C,WAAW;yBAEX,kBAAC,IAAD,EAAU,MAAM,IAAM,CAAA;eACf,CAAA;cACR,KACC,kBAAC,UAAD;eACE,MAAK;eACL,OAAO,EAAG,uCAAuC,SAAS;eAC1D,cAAY,EAAG,uCAAuC,SAAS;eAC/D,eAAe,GAAkB,EAAG,GAAG;eACvC,WAAW;yBAEX,kBAAC,IAAD,EAAQ,MAAM,IAAM,CAAA;eACb,CAAA;cAEP;;YACH,CAAA;WACF;YAtJE,EAAG,GAsJL;UAEP,EACI,CAAA,CACF;;OACJ,CAAA;MAEP,EAAA,CAAA;KACK,CAAA,CACT,EAAA,CAAA,EAEQ,CAAA,CACX;;GAGL,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,IAAD;IACE,UAAU;IACV,QAAQ;IACR,SAAS;IACT,WAAW;IACX,CAAA,EAEF,kBAAC,IAAD;IACE,UAAU;IACV,QAAQ;IACR,SAAS;IACT,WAAW;IACX,CAAA,CACD,EAAA,CAAA;GAIL,kBAAC,IAAD;IACE,QAAQ;IACR,eAAe,EAAsB,GAAM;IAC3C,WAAW;IACX,CAAA;GACE;;EAER"}