@burdenoff/microfe-channel 2026.625.2 → 2026.626.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.
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +19 -12
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +407 -353
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +93 -87
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +154 -134
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +279 -244
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +277 -232
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +159 -129
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -22
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +72 -65
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +99 -79
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +154 -148
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +69 -62
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +131 -124
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +174 -154
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionDetailsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ConnectionDetailsPage.tsx"],"sourcesContent":["/**\n * Connection Details Page\n *\n * View and manage a specific connection within a channel.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError } from '../../../utils/shellBridge';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n RefreshCw,\n Trash2,\n Edit,\n Shield,\n Clock,\n} from 'lucide-react';\nimport { CTAOverflowMenu, EmphasisPanel, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { useChannel } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n type GetConnectionQuery,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst HEALTH_CONFIG: Record<\n string,\n { icon: typeof CheckCircle2; color: string; bgColor: string; label: string }\n> = {\n true: {\n icon: CheckCircle2,\n color: 'text-status-success-text',\n bgColor: 'bg-status-success-bg-subtle',\n label: 'Healthy',\n },\n false: {\n icon: XCircle,\n color: 'text-status-error-text',\n bgColor: 'bg-error-500/10',\n label: 'Unhealthy',\n },\n};\n\nexport function ConnectionDetailsPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n const canExecute = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_EXECUTE);\n const params = useParams<{ channelId?: string; connectionId: string }>();\n const { connectionId } = params;\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\n\n // Route may be /channels/connections/:connectionId (no channelId in URL).\n // Resolve channelId from getConnection when absent so downstream channel hooks work.\n const [resolvedChannelId, setResolvedChannelId] = useState<string | undefined>(params.channelId);\n const [resolveError, setResolveError] = useState<string | null>(null);\n\n useEffect(() => {\n if (resolvedChannelId || !connectionId) return;\n let cancelled = false;\n void (async () => {\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection;\n if (conn?.channelId) setResolvedChannelId(conn.channelId);\n else setResolveError('Connection not found');\n } catch (err) {\n if (!cancelled)\n setResolveError(err instanceof Error ? err.message : 'Failed to resolve connection');\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, resolvedChannelId, apiGatewayUrl, authToken, organizationId]);\n\n const channelId = resolvedChannelId;\n const { channel, isLoading, error, runHealthCheck, deleteConnection } = useChannel(channelId);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [isDeleting, setIsDeleting] = useState(false);\n const [isRunningHealth, setIsRunningHealth] = useState(false);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n const connection = channel?.connection;\n\n // Run health check\n const handleRunHealthCheck = async () => {\n setIsRunningHealth(true);\n try {\n await runHealthCheck();\n } finally {\n setIsRunningHealth(false);\n }\n };\n\n // Delete connection\n const handleDelete = async () => {\n if (!connectionId) return;\n void nativeImpact('medium');\n setIsDeleting(true);\n try {\n // deleteConnection throws on failure; success is guaranteed if it returns.\n await deleteConnection(connectionId);\n void nativeNotify('success');\n navigate(`/channels/${channelId}`);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete connection' } },\n '[channel] Failed to delete connection'\n );\n } finally {\n setIsDeleting(false);\n }\n };\n\n if (isLoading || (!resolvedChannelId && !resolveError)) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n if (resolveError || error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-status-error-text\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-text-secondary\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.noConnectionFound', 'No connection found')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.noConnectionConfigured',\n 'This channel does not have a connection configured.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannel', 'Back to channel')}\n </button>\n </div>\n );\n }\n\n const health = HEALTH_CONFIG[String(connection.isHealthy)] || HEALTH_CONFIG.false;\n const HealthIcon = health.icon;\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_READ}>\n <div className=\"min-h-screen bg-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">\n {tr('channel.connectionDetails', 'Connection Details')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {channel.name} • {channel.platform}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={\n canUpdate\n ? {\n label: tr('channel.editChannel', 'Edit Channel'),\n icon: <Edit className=\"size-4\" aria-hidden=\"true\" />,\n onSelect: () => navigate(`/channels/${channelId}/edit`),\n }\n : undefined\n }\n actions={\n canExecute\n ? [\n {\n label: tr('channel.runHealthCheck', 'Run Health Check'),\n icon: isRunningHealth ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n ),\n disabled: isRunningHealth,\n onSelect: () => void handleRunHealthCheck(),\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-5xl space-y-6\">\n {/* Health Status */}\n <EmphasisPanel className=\"p-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div className={health.color}>\n <HealthIcon className=\"size-10 \" />\n </div>\n <div>\n <h2 className=\"text-xl font-semibold\">{health.label}</h2>\n <p className=\"text-sm text-text-secondary\">\n {connection.isHealthy\n ? tr('channel.connectionWorking', 'Connection is working properly')\n : tr('channel.connectionNotWorking', 'Connection is not working properly')}\n </p>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Connection Info */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-2\">\n <h3 className=\"font-semibold\">\n {tr('channel.connectionInformation', 'Connection Information')}\n </h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">Connection ID</p>\n <p className=\"font-mono text-sm\">{connection.id}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">Auth Type</p>\n <p className=\"flex items-center gap-2\">\n <Shield className=\"size-4 text-text-secondary\" />\n {connection.authType}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">Status</p>\n <p\n className={\n connection.isActive ? 'text-status-success-text' : 'text-text-secondary'\n }\n >\n {connection.isActive\n ? tr('channel.activeStatus', 'Active')\n : tr('channel.inactiveStatus', 'Inactive')}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">Token Refresh</p>\n <p\n className={\n connection.needsTokenRefresh\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n }\n >\n {connection.needsTokenRefresh\n ? tr('channel.tokenRefreshRequired', 'Required soon')\n : tr('channel.tokenRefreshNotNeeded', 'Not needed')}\n </p>\n </div>\n\n {connection.lastTestedAt && (\n <div>\n <p className=\"text-sm text-text-secondary\">Last Tested</p>\n <p className=\"flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n {new Date(connection.lastTestedAt).toLocaleString()}\n </p>\n </div>\n )}\n\n {connection.daysUntilExpiry !== null && connection.daysUntilExpiry !== undefined && (\n <div>\n <p className=\"text-sm text-text-secondary\">Days Until Token Expiry</p>\n <p\n className={\n connection.daysUntilExpiry < 30\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n }\n >\n {connection.daysUntilExpiry} days\n </p>\n </div>\n )}\n </div>\n\n {connection.testStatus && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-text-secondary mb-2\">Last Test Status</p>\n <div className=\"p-3 rounded-lg bg-bg-sunken\">\n <p className=\"font-mono text-sm\">{connection.testStatus}</p>\n </div>\n </div>\n )}\n\n {connection.tagAssignments && connection.tagAssignments.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-text-secondary mb-2\">{tr('channel.tags', 'Tags')}</p>\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Connection tags\">\n {connection.tagAssignments.map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"md\" />\n ))}\n </div>\n </div>\n )}\n </GlassCard>\n\n {/* Actions */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-1\">\n <h3 className=\"font-semibold\">{tr('channel.actions', 'Actions')}</h3>\n\n <div className=\"flex flex-col gap-3\">\n <div>\n <p className=\"font-medium\">{tr('channel.deleteConnection', 'Delete Connection')}</p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.deleteConnectionWarning',\n 'Permanently remove this connection. The channel will become inactive.'\n )}\n </p>\n </div>\n\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 rounded-lg text-status-error-text border border-status-error-border hover:bg-status-error-bg/10 transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.delete', 'Delete')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 rounded-lg border hover:bg-bg-sunken transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n </GlassCard>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConnectionDetailsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ConnectionDetailsPage.tsx"],"sourcesContent":["/**\n * Connection Details Page\n *\n * View and manage a specific connection within a channel.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError } from '../../../utils/shellBridge';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n RefreshCw,\n Trash2,\n Edit,\n Shield,\n Clock,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n GlassCard,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useChannel } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n type GetConnectionQuery,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst HEALTH_CONFIG: Record<\n string,\n { icon: typeof CheckCircle2; color: string; bgColor: string; label: string }\n> = {\n true: {\n icon: CheckCircle2,\n color: 'text-status-success-text',\n bgColor: 'bg-status-success-bg-subtle',\n label: 'Healthy',\n },\n false: {\n icon: XCircle,\n color: 'text-status-error-text',\n bgColor: 'bg-error-500/10',\n label: 'Unhealthy',\n },\n};\n\nexport function ConnectionDetailsPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n const canExecute = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_EXECUTE);\n const params = useParams<{ channelId?: string; connectionId: string }>();\n const { connectionId } = params;\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\n\n // Route may be /channels/connections/:connectionId (no channelId in URL).\n // Resolve channelId from getConnection when absent so downstream channel hooks work.\n const [resolvedChannelId, setResolvedChannelId] = useState<string | undefined>(params.channelId);\n const [resolveError, setResolveError] = useState<string | null>(null);\n\n useEffect(() => {\n if (resolvedChannelId || !connectionId) return;\n let cancelled = false;\n void (async () => {\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection;\n if (conn?.channelId) setResolvedChannelId(conn.channelId);\n else setResolveError('Connection not found');\n } catch (err) {\n if (!cancelled)\n setResolveError(err instanceof Error ? err.message : 'Failed to resolve connection');\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, resolvedChannelId, apiGatewayUrl, authToken, organizationId]);\n\n const channelId = resolvedChannelId;\n const { channel, isLoading, error, runHealthCheck, deleteConnection } = useChannel(channelId);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [isDeleting, setIsDeleting] = useState(false);\n const [isRunningHealth, setIsRunningHealth] = useState(false);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n const connection = channel?.connection;\n\n // Run health check\n const handleRunHealthCheck = async () => {\n setIsRunningHealth(true);\n try {\n await runHealthCheck();\n } finally {\n setIsRunningHealth(false);\n }\n };\n\n // Delete connection\n const handleDelete = async () => {\n if (!connectionId) return;\n void nativeImpact('medium');\n setIsDeleting(true);\n try {\n // deleteConnection throws on failure; success is guaranteed if it returns.\n await deleteConnection(connectionId);\n void nativeNotify('success');\n navigate(`/channels/${channelId}`);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete connection' } },\n '[channel] Failed to delete connection'\n );\n } finally {\n setIsDeleting(false);\n }\n };\n\n if (isLoading || (!resolvedChannelId && !resolveError)) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n if (resolveError || error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-status-error-text\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-text-secondary\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.noConnectionFound', 'No connection found')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.noConnectionConfigured',\n 'This channel does not have a connection configured.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannel', 'Back to channel')}\n </button>\n </div>\n );\n }\n\n const health = HEALTH_CONFIG[String(connection.isHealthy)] || HEALTH_CONFIG.false;\n const HealthIcon = health.icon;\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_READ}>\n <div className=\"min-h-screen bg-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">\n {tr('channel.connectionDetails', 'Connection Details')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {channel.name} • {channel.platform}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={\n canUpdate\n ? {\n label: tr('channel.editChannel', 'Edit Channel'),\n icon: <Edit className=\"size-4\" aria-hidden=\"true\" />,\n onSelect: () => navigate(`/channels/${channelId}/edit`),\n }\n : undefined\n }\n actions={\n canExecute\n ? [\n {\n label: tr('channel.runHealthCheck', 'Run Health Check'),\n icon: isRunningHealth ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n ),\n disabled: isRunningHealth,\n onSelect: () => void handleRunHealthCheck(),\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-5xl space-y-6\">\n {/* Page purpose */}\n <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.connectionDetailsPurpose',\n 'The authenticated link between this channel and its provider — its auth type, whether it is active, when it was last tested, and how soon its token expires. Use it to verify the channel can actually send, to spot a token that needs refreshing before it lapses, or to remove a connection you no longer need.'\n )}\n </PagePurpose>\n\n {/* Next-step guidance */}\n <NextSteps\n storageKey=\"channel-connection-detail\"\n steps={[\n ...(canExecute\n ? [\n {\n id: 'health',\n label: tr('channel.nextRunHealthCheck', 'Run a health check'),\n description: tr(\n 'channel.nextConnectionHealthDesc',\n 'Confirm the credentials still authenticate against the provider.'\n ),\n icon: <RefreshCw size={16} aria-hidden=\"true\" />,\n onClick: () => void handleRunHealthCheck(),\n },\n ]\n : []),\n ...(canUpdate\n ? [\n {\n id: 'edit',\n label: tr('channel.nextEditChannel', 'Edit the channel'),\n description: tr(\n 'channel.nextEditChannelDesc',\n 'Update credentials, status or platform metadata for this channel.'\n ),\n icon: <Edit size={16} aria-hidden=\"true\" />,\n onClick: () => navigate(`/channels/${channelId}/edit`),\n },\n ]\n : []),\n {\n id: 'back',\n label: tr('channel.nextBackToChannel', 'Back to the channel'),\n description: tr(\n 'channel.nextBackToChannelDesc',\n 'Return to the channel overview to send a test or review deliveries.'\n ),\n icon: <ArrowLeft size={16} aria-hidden=\"true\" />,\n onClick: () => navigate(`/channels/${channelId}`),\n },\n ]}\n />\n\n {/* Health Status */}\n <EmphasisPanel className=\"p-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div className={health.color}>\n <HealthIcon className=\"size-10 \" />\n </div>\n <div>\n <h2 className=\"text-xl font-semibold\">{health.label}</h2>\n <p className=\"text-sm text-text-secondary\">\n {connection.isHealthy\n ? tr('channel.connectionWorking', 'Connection is working properly')\n : tr('channel.connectionNotWorking', 'Connection is not working properly')}\n </p>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Connection Info */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-2\">\n <h3 className=\"font-semibold\">\n {tr('channel.connectionInformation', 'Connection Information')}\n </h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">Connection ID</p>\n <p className=\"font-mono text-sm\">{connection.id}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">Auth Type</p>\n <p className=\"flex items-center gap-2\">\n <Shield className=\"size-4 text-text-secondary\" />\n {connection.authType}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">Status</p>\n <p\n className={\n connection.isActive ? 'text-status-success-text' : 'text-text-secondary'\n }\n >\n {connection.isActive\n ? tr('channel.activeStatus', 'Active')\n : tr('channel.inactiveStatus', 'Inactive')}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">Token Refresh</p>\n <p\n className={\n connection.needsTokenRefresh\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n }\n >\n {connection.needsTokenRefresh\n ? tr('channel.tokenRefreshRequired', 'Required soon')\n : tr('channel.tokenRefreshNotNeeded', 'Not needed')}\n </p>\n </div>\n\n {connection.lastTestedAt && (\n <div>\n <p className=\"text-sm text-text-secondary\">Last Tested</p>\n <p className=\"flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n {new Date(connection.lastTestedAt).toLocaleString()}\n </p>\n </div>\n )}\n\n {connection.daysUntilExpiry !== null && connection.daysUntilExpiry !== undefined && (\n <div>\n <p className=\"text-sm text-text-secondary\">Days Until Token Expiry</p>\n <p\n className={\n connection.daysUntilExpiry < 30\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n }\n >\n {connection.daysUntilExpiry} days\n </p>\n </div>\n )}\n </div>\n\n {connection.testStatus && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-text-secondary mb-2\">Last Test Status</p>\n <div className=\"p-3 rounded-lg bg-bg-sunken\">\n <p className=\"font-mono text-sm\">{connection.testStatus}</p>\n </div>\n </div>\n )}\n\n {connection.tagAssignments && connection.tagAssignments.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-text-secondary mb-2\">{tr('channel.tags', 'Tags')}</p>\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Connection tags\">\n {connection.tagAssignments.map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"md\" />\n ))}\n </div>\n </div>\n )}\n </GlassCard>\n\n {/* Actions */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-1\">\n <h3 className=\"font-semibold\">{tr('channel.actions', 'Actions')}</h3>\n\n <div className=\"flex flex-col gap-3\">\n <div>\n <p className=\"font-medium\">{tr('channel.deleteConnection', 'Delete Connection')}</p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.deleteConnectionWarning',\n 'Permanently remove this connection. The channel will become inactive.'\n )}\n </p>\n </div>\n\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 rounded-lg text-status-error-text border border-status-error-border hover:bg-status-error-bg/10 transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.delete', 'Delete')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 rounded-lg border hover:bg-bg-sunken transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n </GlassCard>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,37 +3,37 @@ import { PermissionGuard as t } from "../../../components/PermissionGuard.js";
|
|
|
3
3
|
import { CHANNEL_PERMISSIONS as n } from "../../../constants/permissions.js";
|
|
4
4
|
import { useEffect as r, useState as i } from "react";
|
|
5
5
|
import { useI18n as a } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
|
-
import { AlertCircle as o, ArrowLeft as s, CheckCircle2 as c, Loader2 as l, Plus as u, RefreshCw as d, Settings as f,
|
|
7
|
-
import { useNavigate as
|
|
8
|
-
import { usePermissions as
|
|
9
|
-
import { Fragment as
|
|
10
|
-
import { GlassCard as
|
|
11
|
-
import { TagBadge as
|
|
6
|
+
import { AlertCircle as o, ArrowLeft as s, CheckCircle2 as c, Loader2 as l, Plus as u, RefreshCw as d, Settings as f, Shield as p, XCircle as m } from "lucide-react";
|
|
7
|
+
import { useNavigate as h } from "react-router-dom";
|
|
8
|
+
import { usePermissions as g } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
9
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
10
|
+
import { EmphasisPanel as b, GlassCard as x, IllustratedEmptyState as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
import { TagBadge as w } from "@burdenoff/fe-libs/tag";
|
|
12
12
|
//#region src/channel/modules/channels/pages/ConnectionsListPage.tsx
|
|
13
|
-
var
|
|
13
|
+
var T = {
|
|
14
14
|
true: {
|
|
15
15
|
icon: c,
|
|
16
16
|
color: "text-status-success-text",
|
|
17
17
|
label: "Healthy"
|
|
18
18
|
},
|
|
19
19
|
false: {
|
|
20
|
-
icon:
|
|
20
|
+
icon: m,
|
|
21
21
|
color: "text-status-error-text",
|
|
22
22
|
label: "Unhealthy"
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
-
function
|
|
26
|
-
let c =
|
|
25
|
+
function E() {
|
|
26
|
+
let c = h(), { hasPermission: m } = g(), E = m(n.CHANNEL_CONNECTION_CREATE), { channels: D, isLoading: O, error: k, refetch: A } = e();
|
|
27
27
|
r(() => {
|
|
28
28
|
let e = setInterval(() => {
|
|
29
|
-
|
|
29
|
+
A();
|
|
30
30
|
}, 6e4);
|
|
31
31
|
return () => clearInterval(e);
|
|
32
|
-
}, [
|
|
33
|
-
let { t:
|
|
34
|
-
let n =
|
|
32
|
+
}, [A]);
|
|
33
|
+
let { t: j } = a(), M = (e, t) => {
|
|
34
|
+
let n = j(e);
|
|
35
35
|
return n === e ? t : n;
|
|
36
|
-
}, [
|
|
36
|
+
}, [N, P] = i("all"), [F, I] = i("all"), [L, R] = i(0), z = D.reduce((e, t) => {
|
|
37
37
|
let n = t.connection;
|
|
38
38
|
return n && e.push({
|
|
39
39
|
channelId: t.id,
|
|
@@ -49,112 +49,142 @@ function C() {
|
|
|
49
49
|
authType: n.authType,
|
|
50
50
|
tagAssignments: n.tagAssignments ?? []
|
|
51
51
|
}), e;
|
|
52
|
-
}, []),
|
|
53
|
-
return
|
|
52
|
+
}, []), B = [...new Set(z.map((e) => e.platform))], V = z.filter((e) => !e.isHealthy || e.needsTokenRefresh).length, H = z.filter((e) => !(N === "healthy" && !e.isHealthy || N === "unhealthy" && e.isHealthy || F !== "all" && e.platform !== F)), U = Math.max(1, Math.ceil(H.length / 10)), W = H.slice(L * 10, (L + 1) * 10);
|
|
53
|
+
return O ? /* @__PURE__ */ v("div", {
|
|
54
54
|
className: "flex items-center justify-center min-h-screen",
|
|
55
|
-
children: /* @__PURE__ */
|
|
56
|
-
}) :
|
|
55
|
+
children: /* @__PURE__ */ v(l, { className: "size-8 animate-spin text-text-secondary" })
|
|
56
|
+
}) : k ? /* @__PURE__ */ y("div", {
|
|
57
57
|
className: "flex flex-col items-center justify-center min-h-screen gap-4",
|
|
58
58
|
children: [
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
-
/* @__PURE__ */
|
|
59
|
+
/* @__PURE__ */ v(o, { className: "size-12 text-status-error-text" }),
|
|
60
|
+
/* @__PURE__ */ v("h1", {
|
|
61
61
|
className: "text-xl font-semibold",
|
|
62
|
-
children:
|
|
62
|
+
children: M("channel.loadConnectionsFailed", "Failed to load connections")
|
|
63
63
|
}),
|
|
64
|
-
/* @__PURE__ */
|
|
64
|
+
/* @__PURE__ */ v("p", {
|
|
65
65
|
className: "text-sm text-text-secondary",
|
|
66
|
-
children:
|
|
66
|
+
children: k
|
|
67
67
|
}),
|
|
68
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ v("button", {
|
|
69
69
|
type: "button",
|
|
70
|
-
onClick: () =>
|
|
70
|
+
onClick: () => A(),
|
|
71
71
|
className: "px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90",
|
|
72
|
-
children:
|
|
72
|
+
children: M("channel.retry", "Retry")
|
|
73
73
|
})
|
|
74
74
|
]
|
|
75
|
-
}) : /* @__PURE__ */
|
|
75
|
+
}) : /* @__PURE__ */ v(t, {
|
|
76
76
|
permission: n.CHANNEL_CONNECTION_READ,
|
|
77
|
-
children: /* @__PURE__ */
|
|
77
|
+
children: /* @__PURE__ */ y("div", {
|
|
78
78
|
className: "min-h-screen bg-bg-surface",
|
|
79
79
|
children: [
|
|
80
|
-
/* @__PURE__ */
|
|
80
|
+
/* @__PURE__ */ v("div", {
|
|
81
81
|
className: "border-b bg-bg-surface",
|
|
82
82
|
"data-tour": "channels-connections-header",
|
|
83
|
-
children: /* @__PURE__ */
|
|
83
|
+
children: /* @__PURE__ */ v("div", {
|
|
84
84
|
className: "container mx-auto px-6 py-4",
|
|
85
|
-
children: /* @__PURE__ */
|
|
85
|
+
children: /* @__PURE__ */ y("div", {
|
|
86
86
|
className: "flex items-center justify-between",
|
|
87
|
-
children: [/* @__PURE__ */
|
|
87
|
+
children: [/* @__PURE__ */ y("div", {
|
|
88
88
|
className: "flex items-center gap-4",
|
|
89
|
-
children: [/* @__PURE__ */
|
|
89
|
+
children: [/* @__PURE__ */ v("button", {
|
|
90
90
|
type: "button",
|
|
91
91
|
onClick: () => c("./list"),
|
|
92
|
-
"aria-label":
|
|
92
|
+
"aria-label": M("channel.backToChannels", "Back to channels"),
|
|
93
93
|
className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
|
|
94
|
-
children: /* @__PURE__ */
|
|
95
|
-
}), /* @__PURE__ */
|
|
94
|
+
children: /* @__PURE__ */ v(s, { className: "size-5 " })
|
|
95
|
+
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
|
|
96
96
|
className: "text-2xl font-bold",
|
|
97
|
-
children:
|
|
98
|
-
}), /* @__PURE__ */
|
|
97
|
+
children: M("channel.connections", "Connections")
|
|
98
|
+
}), /* @__PURE__ */ y("p", {
|
|
99
99
|
className: "text-sm text-text-secondary",
|
|
100
100
|
children: [
|
|
101
|
-
|
|
101
|
+
H.length,
|
|
102
102
|
" connection",
|
|
103
|
-
|
|
103
|
+
H.length === 1 ? "" : "s"
|
|
104
104
|
]
|
|
105
105
|
})] })]
|
|
106
|
-
}),
|
|
106
|
+
}), E && /* @__PURE__ */ y("button", {
|
|
107
107
|
type: "button",
|
|
108
108
|
onClick: () => c("./create"),
|
|
109
109
|
className: "px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors flex items-center gap-2",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ v(u, { className: "size-4 " }), M("channel.newConnection", "New Connection")]
|
|
111
111
|
})]
|
|
112
112
|
})
|
|
113
113
|
})
|
|
114
114
|
}),
|
|
115
|
-
/* @__PURE__ */
|
|
115
|
+
/* @__PURE__ */ y("div", {
|
|
116
|
+
className: "container mx-auto space-y-4 px-6 pt-4",
|
|
117
|
+
children: [/* @__PURE__ */ v(C, {
|
|
118
|
+
icon: /* @__PURE__ */ v(p, {
|
|
119
|
+
size: 15,
|
|
120
|
+
"aria-hidden": "true"
|
|
121
|
+
}),
|
|
122
|
+
children: M("channel.connectionsPurpose", "A connection is the authenticated link — the OAuth grant, API key or credentials — that lets a channel actually reach its provider. This page lists every channel's connection with its live health and token status. Use it to confirm channels are authenticated and to catch tokens that are about to expire before deliveries start failing.")
|
|
123
|
+
}), V > 0 && /* @__PURE__ */ y(b, {
|
|
124
|
+
className: "flex items-center gap-3 p-4 shadow-[var(--shadow-glow)]",
|
|
125
|
+
children: [/* @__PURE__ */ v("span", {
|
|
126
|
+
className: "rounded-lg bg-status-warning-bg-subtle p-2",
|
|
127
|
+
children: /* @__PURE__ */ v(o, {
|
|
128
|
+
size: 18,
|
|
129
|
+
className: "text-status-warning-text",
|
|
130
|
+
"aria-hidden": "true"
|
|
131
|
+
})
|
|
132
|
+
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ y("p", {
|
|
133
|
+
className: "text-sm font-semibold text-text-primary",
|
|
134
|
+
children: [
|
|
135
|
+
V,
|
|
136
|
+
" ",
|
|
137
|
+
V === 1 ? M("channel.connectionNeedsAttention", "connection needs attention") : M("channel.connectionsNeedAttention", "connections need attention")
|
|
138
|
+
]
|
|
139
|
+
}), /* @__PURE__ */ v("p", {
|
|
140
|
+
className: "text-sm text-text-secondary",
|
|
141
|
+
children: M("channel.connectionsNeedAttentionHint", "These connections are unhealthy or have tokens expiring soon. Refresh or re-authenticate them to keep deliveries flowing.")
|
|
142
|
+
})] })]
|
|
143
|
+
})]
|
|
144
|
+
}),
|
|
145
|
+
/* @__PURE__ */ v("div", {
|
|
116
146
|
className: "container mx-auto px-6 py-4",
|
|
117
147
|
"data-tour": "channels-connections-filters",
|
|
118
|
-
children: /* @__PURE__ */
|
|
148
|
+
children: /* @__PURE__ */ y("div", {
|
|
119
149
|
className: "flex items-center gap-4",
|
|
120
150
|
children: [
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
value:
|
|
123
|
-
onChange: (e) =>
|
|
151
|
+
/* @__PURE__ */ y("select", {
|
|
152
|
+
value: F,
|
|
153
|
+
onChange: (e) => I(e.target.value),
|
|
124
154
|
className: "px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
125
|
-
children: [/* @__PURE__ */
|
|
155
|
+
children: [/* @__PURE__ */ v("option", {
|
|
126
156
|
value: "all",
|
|
127
|
-
children:
|
|
128
|
-
}),
|
|
157
|
+
children: M("channel.allPlatforms", "All Platforms")
|
|
158
|
+
}), B.map((e) => /* @__PURE__ */ v("option", {
|
|
129
159
|
value: e,
|
|
130
160
|
children: e
|
|
131
161
|
}, e))]
|
|
132
162
|
}),
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
value:
|
|
135
|
-
onChange: (e) =>
|
|
163
|
+
/* @__PURE__ */ y("select", {
|
|
164
|
+
value: N,
|
|
165
|
+
onChange: (e) => P(e.target.value),
|
|
136
166
|
className: "px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
137
167
|
children: [
|
|
138
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ v("option", {
|
|
139
169
|
value: "all",
|
|
140
|
-
children:
|
|
170
|
+
children: M("channel.allStatus", "All Status")
|
|
141
171
|
}),
|
|
142
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ v("option", {
|
|
143
173
|
value: "healthy",
|
|
144
|
-
children:
|
|
174
|
+
children: M("channel.healthy", "Healthy")
|
|
145
175
|
}),
|
|
146
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ v("option", {
|
|
147
177
|
value: "unhealthy",
|
|
148
|
-
children:
|
|
178
|
+
children: M("channel.unhealthy", "Unhealthy")
|
|
149
179
|
})
|
|
150
180
|
]
|
|
151
181
|
}),
|
|
152
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ v("button", {
|
|
153
183
|
type: "button",
|
|
154
|
-
onClick: () =>
|
|
155
|
-
"aria-label":
|
|
184
|
+
onClick: () => A(),
|
|
185
|
+
"aria-label": M("channel.refresh", "Refresh"),
|
|
156
186
|
className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
|
|
157
|
-
children: /* @__PURE__ */
|
|
187
|
+
children: /* @__PURE__ */ v(d, {
|
|
158
188
|
className: "size-5 ",
|
|
159
189
|
"aria-hidden": "true"
|
|
160
190
|
})
|
|
@@ -162,31 +192,31 @@ function C() {
|
|
|
162
192
|
]
|
|
163
193
|
})
|
|
164
194
|
}),
|
|
165
|
-
/* @__PURE__ */
|
|
195
|
+
/* @__PURE__ */ v("div", {
|
|
166
196
|
className: "container mx-auto px-6 pb-8",
|
|
167
|
-
children:
|
|
168
|
-
illustration:
|
|
169
|
-
title:
|
|
170
|
-
description:
|
|
171
|
-
action:
|
|
197
|
+
children: H.length === 0 ? /* @__PURE__ */ v(S, {
|
|
198
|
+
illustration: D.length === 0 ? "empty-data" : "empty-search",
|
|
199
|
+
title: M("channel.noConnectionsFound", "No connections found"),
|
|
200
|
+
description: D.length === 0 ? M("channel.createChannelFirst", "Create a channel first to add connections") : M("channel.noConnectionsMatchFilter", "No connections match your filters"),
|
|
201
|
+
action: D.length === 0 ? E ? /* @__PURE__ */ v("button", {
|
|
172
202
|
type: "button",
|
|
173
203
|
onClick: () => c("./create"),
|
|
174
204
|
className: "px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90",
|
|
175
|
-
children:
|
|
176
|
-
}) : void 0 : /* @__PURE__ */
|
|
205
|
+
children: M("channel.createConnection", "Create Connection")
|
|
206
|
+
}) : void 0 : /* @__PURE__ */ v("button", {
|
|
177
207
|
type: "button",
|
|
178
208
|
onClick: () => {
|
|
179
|
-
|
|
209
|
+
P("all"), I("all");
|
|
180
210
|
},
|
|
181
211
|
className: "px-4 py-2 rounded-lg border hover:bg-bg-sunken",
|
|
182
|
-
children:
|
|
212
|
+
children: M("channel.clearFilters", "Clear Filters")
|
|
183
213
|
})
|
|
184
|
-
}) : /* @__PURE__ */
|
|
214
|
+
}) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(x, {
|
|
185
215
|
treatment: "plain",
|
|
186
216
|
className: "border-border-seam rounded-lg p-0 divide-y divide-[var(--color-border-seam)] overflow-hidden",
|
|
187
|
-
children:
|
|
188
|
-
let t =
|
|
189
|
-
return /* @__PURE__ */
|
|
217
|
+
children: W.map((e) => {
|
|
218
|
+
let t = T[String(e.isHealthy)] || T.false, n = t.icon;
|
|
219
|
+
return /* @__PURE__ */ v("div", {
|
|
190
220
|
role: "button",
|
|
191
221
|
tabIndex: 0,
|
|
192
222
|
className: "p-4 hover:bg-bg-sunken/50 transition-colors cursor-pointer",
|
|
@@ -194,21 +224,21 @@ function C() {
|
|
|
194
224
|
onKeyDown: (t) => {
|
|
195
225
|
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), c(`./${e.channelId}`));
|
|
196
226
|
},
|
|
197
|
-
children: /* @__PURE__ */
|
|
227
|
+
children: /* @__PURE__ */ y("div", {
|
|
198
228
|
className: "flex items-center justify-between",
|
|
199
|
-
children: [/* @__PURE__ */
|
|
229
|
+
children: [/* @__PURE__ */ y("div", {
|
|
200
230
|
className: "flex items-center gap-4",
|
|
201
|
-
children: [/* @__PURE__ */
|
|
231
|
+
children: [/* @__PURE__ */ v("div", {
|
|
202
232
|
className: `${t.color} ${e.isHealthy ? "motion-safe:animate-vc-glow-pulse" : ""}`,
|
|
203
233
|
"aria-label": t.label,
|
|
204
|
-
children: /* @__PURE__ */
|
|
234
|
+
children: /* @__PURE__ */ v(n, {
|
|
205
235
|
className: "size-5 ",
|
|
206
236
|
"aria-hidden": "true"
|
|
207
237
|
})
|
|
208
|
-
}), /* @__PURE__ */
|
|
238
|
+
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
209
239
|
className: "font-medium",
|
|
210
240
|
children: e.channelName
|
|
211
|
-
}), /* @__PURE__ */
|
|
241
|
+
}), /* @__PURE__ */ y("p", {
|
|
212
242
|
className: "text-sm text-text-secondary",
|
|
213
243
|
children: [
|
|
214
244
|
e.platform,
|
|
@@ -218,114 +248,114 @@ function C() {
|
|
|
218
248
|
e.authType
|
|
219
249
|
]
|
|
220
250
|
})] })]
|
|
221
|
-
}), /* @__PURE__ */
|
|
251
|
+
}), /* @__PURE__ */ y("div", {
|
|
222
252
|
className: "flex items-center gap-4",
|
|
223
253
|
children: [
|
|
224
|
-
/* @__PURE__ */
|
|
254
|
+
/* @__PURE__ */ y("div", {
|
|
225
255
|
className: "text-right",
|
|
226
|
-
children: [/* @__PURE__ */
|
|
256
|
+
children: [/* @__PURE__ */ v("p", {
|
|
227
257
|
className: "text-sm",
|
|
228
|
-
children: e.channelStatus === "ACTIVE" ? /* @__PURE__ */
|
|
258
|
+
children: e.channelStatus === "ACTIVE" ? /* @__PURE__ */ v("span", {
|
|
229
259
|
className: "text-status-success-text",
|
|
230
|
-
children:
|
|
231
|
-
}) : e.channelStatus === "INACTIVE" ? /* @__PURE__ */
|
|
260
|
+
children: M("channel.activeStatus", "Active")
|
|
261
|
+
}) : e.channelStatus === "INACTIVE" ? /* @__PURE__ */ v("span", {
|
|
232
262
|
className: "text-text-secondary",
|
|
233
|
-
children:
|
|
234
|
-
}) : e.channelStatus === "ERROR" ? /* @__PURE__ */
|
|
263
|
+
children: M("channel.inactiveStatus", "Inactive")
|
|
264
|
+
}) : e.channelStatus === "ERROR" ? /* @__PURE__ */ v("span", {
|
|
235
265
|
className: "text-status-error-text",
|
|
236
|
-
children:
|
|
237
|
-
}) : e.channelStatus === "SUSPENDED" ? /* @__PURE__ */
|
|
266
|
+
children: M("channel.errorStatus", "Error")
|
|
267
|
+
}) : e.channelStatus === "SUSPENDED" ? /* @__PURE__ */ v("span", {
|
|
238
268
|
className: "text-status-warning-text",
|
|
239
|
-
children:
|
|
240
|
-
}) : e.channelStatus === "ARCHIVED" ? /* @__PURE__ */
|
|
269
|
+
children: M("channel.suspendedStatus", "Suspended")
|
|
270
|
+
}) : e.channelStatus === "ARCHIVED" ? /* @__PURE__ */ v("span", {
|
|
241
271
|
className: "text-text-secondary",
|
|
242
|
-
children:
|
|
243
|
-
}) : /* @__PURE__ */
|
|
272
|
+
children: M("channel.archivedStatus", "Archived")
|
|
273
|
+
}) : /* @__PURE__ */ v("span", {
|
|
244
274
|
className: "text-text-secondary",
|
|
245
|
-
children:
|
|
275
|
+
children: M("channel.inactiveStatus", "Inactive")
|
|
246
276
|
})
|
|
247
|
-
}), e.lastTestedAt && /* @__PURE__ */
|
|
277
|
+
}), e.lastTestedAt && /* @__PURE__ */ y("p", {
|
|
248
278
|
className: "text-xs text-text-secondary",
|
|
249
279
|
children: ["Last tested: ", new Date(e.lastTestedAt).toLocaleDateString()]
|
|
250
280
|
})]
|
|
251
281
|
}),
|
|
252
|
-
e.needsTokenRefresh && /* @__PURE__ */
|
|
282
|
+
e.needsTokenRefresh && /* @__PURE__ */ v("span", {
|
|
253
283
|
className: "px-2 py-1 text-xs bg-status-warning-bg-subtle text-status-warning-text rounded",
|
|
254
284
|
children: "Token Expires"
|
|
255
285
|
}),
|
|
256
|
-
e.tagAssignments.length > 0 && /* @__PURE__ */
|
|
286
|
+
e.tagAssignments.length > 0 && /* @__PURE__ */ y("div", {
|
|
257
287
|
className: "flex flex-wrap gap-1",
|
|
258
288
|
"aria-label": "Connection tags",
|
|
259
|
-
children: [e.tagAssignments.slice(0, 3).map((e) => /* @__PURE__ */
|
|
289
|
+
children: [e.tagAssignments.slice(0, 3).map((e) => /* @__PURE__ */ v(w, {
|
|
260
290
|
id: e.tagId,
|
|
261
291
|
label: e.tagId,
|
|
262
292
|
size: "sm"
|
|
263
|
-
}, e.id)), e.tagAssignments.length > 3 && /* @__PURE__ */
|
|
293
|
+
}, e.id)), e.tagAssignments.length > 3 && /* @__PURE__ */ y("span", {
|
|
264
294
|
className: "text-xs text-text-secondary",
|
|
265
295
|
children: ["+", e.tagAssignments.length - 3]
|
|
266
296
|
})]
|
|
267
297
|
}),
|
|
268
|
-
/* @__PURE__ */
|
|
298
|
+
/* @__PURE__ */ v("button", {
|
|
269
299
|
type: "button",
|
|
270
300
|
onClick: (t) => {
|
|
271
301
|
t.stopPropagation(), c(`/channels/${e.channelId}`);
|
|
272
302
|
},
|
|
273
|
-
"aria-label":
|
|
303
|
+
"aria-label": M("channel.viewChannelSettings", "View channel settings"),
|
|
274
304
|
className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
|
|
275
|
-
children: /* @__PURE__ */
|
|
305
|
+
children: /* @__PURE__ */ v(f, { className: "size-4 " })
|
|
276
306
|
})
|
|
277
307
|
]
|
|
278
308
|
})]
|
|
279
309
|
})
|
|
280
310
|
}, e.connectionId);
|
|
281
311
|
})
|
|
282
|
-
}),
|
|
312
|
+
}), H.length > 10 && /* @__PURE__ */ y("div", {
|
|
283
313
|
className: "flex items-center justify-between px-4 py-3 border border-border-seam rounded-lg mt-3",
|
|
284
|
-
children: [/* @__PURE__ */
|
|
314
|
+
children: [/* @__PURE__ */ y("p", {
|
|
285
315
|
className: "text-sm text-text-secondary",
|
|
286
316
|
children: [
|
|
287
|
-
|
|
317
|
+
M("channel.showing", "Showing"),
|
|
288
318
|
" ",
|
|
289
|
-
/* @__PURE__ */
|
|
319
|
+
/* @__PURE__ */ y("span", {
|
|
290
320
|
className: "font-medium",
|
|
291
321
|
children: [
|
|
292
|
-
|
|
322
|
+
L * 10 + 1,
|
|
293
323
|
"–",
|
|
294
|
-
Math.min((
|
|
324
|
+
Math.min((L + 1) * 10, H.length)
|
|
295
325
|
]
|
|
296
326
|
}),
|
|
297
327
|
" ",
|
|
298
|
-
|
|
328
|
+
M("channel.of", "of"),
|
|
299
329
|
" ",
|
|
300
|
-
/* @__PURE__ */
|
|
330
|
+
/* @__PURE__ */ v("span", {
|
|
301
331
|
className: "font-medium",
|
|
302
|
-
children:
|
|
332
|
+
children: H.length
|
|
303
333
|
})
|
|
304
334
|
]
|
|
305
|
-
}), /* @__PURE__ */
|
|
335
|
+
}), /* @__PURE__ */ y("div", {
|
|
306
336
|
className: "flex items-center gap-2",
|
|
307
337
|
children: [
|
|
308
|
-
/* @__PURE__ */
|
|
338
|
+
/* @__PURE__ */ v("button", {
|
|
309
339
|
type: "button",
|
|
310
|
-
onClick: () =>
|
|
311
|
-
disabled:
|
|
340
|
+
onClick: () => R((e) => Math.max(0, e - 1)),
|
|
341
|
+
disabled: L === 0,
|
|
312
342
|
className: "px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
313
|
-
children:
|
|
343
|
+
children: M("channel.previous", "Previous")
|
|
314
344
|
}),
|
|
315
|
-
/* @__PURE__ */
|
|
345
|
+
/* @__PURE__ */ y("span", {
|
|
316
346
|
className: "text-sm text-text-secondary",
|
|
317
347
|
children: [
|
|
318
|
-
|
|
348
|
+
L + 1,
|
|
319
349
|
" / ",
|
|
320
|
-
|
|
350
|
+
U
|
|
321
351
|
]
|
|
322
352
|
}),
|
|
323
|
-
/* @__PURE__ */
|
|
353
|
+
/* @__PURE__ */ v("button", {
|
|
324
354
|
type: "button",
|
|
325
|
-
onClick: () =>
|
|
326
|
-
disabled:
|
|
355
|
+
onClick: () => R((e) => Math.min(U - 1, e + 1)),
|
|
356
|
+
disabled: L >= U - 1,
|
|
327
357
|
className: "px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
328
|
-
children:
|
|
358
|
+
children: M("channel.next", "Next")
|
|
329
359
|
})
|
|
330
360
|
]
|
|
331
361
|
})]
|
|
@@ -336,6 +366,6 @@ function C() {
|
|
|
336
366
|
});
|
|
337
367
|
}
|
|
338
368
|
//#endregion
|
|
339
|
-
export {
|
|
369
|
+
export { E as ConnectionsListPage };
|
|
340
370
|
|
|
341
371
|
//# sourceMappingURL=ConnectionsListPage.js.map
|