@burdenoff/microfe-channel 2026.625.1 → 2026.625.3
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 +73 -76
- 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 +119 -96
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +219 -196
- 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 +112 -91
- 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 +101 -93
- 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 +152 -129
- 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,14 +3,14 @@ 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,
|
|
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
7
|
import { useNavigate as h } from "react-router-dom";
|
|
8
8
|
import { usePermissions as g } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
9
9
|
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
10
|
-
import {
|
|
11
|
-
import { TagBadge as
|
|
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",
|
|
@@ -22,18 +22,18 @@ var S = {
|
|
|
22
22
|
label: "Unhealthy"
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
-
function
|
|
26
|
-
let c = h(), { hasPermission: m } = g(),
|
|
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,27 +49,27 @@ 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__ */ v(
|
|
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
59
|
/* @__PURE__ */ v(o, { className: "size-12 text-status-error-text" }),
|
|
60
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
64
|
/* @__PURE__ */ v("p", {
|
|
65
65
|
className: "text-sm text-text-secondary",
|
|
66
|
-
children:
|
|
66
|
+
children: k
|
|
67
67
|
}),
|
|
68
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
75
|
}) : /* @__PURE__ */ v(t, {
|
|
@@ -89,29 +89,59 @@ function C() {
|
|
|
89
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
94
|
children: /* @__PURE__ */ v(s, { className: "size-5 " })
|
|
95
95
|
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
|
|
96
96
|
className: "text-2xl font-bold",
|
|
97
|
-
children:
|
|
97
|
+
children: M("channel.connections", "Connections")
|
|
98
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__ */ v(
|
|
110
|
+
children: [/* @__PURE__ */ v(u, { className: "size-4 " }), M("channel.newConnection", "New Connection")]
|
|
111
111
|
})]
|
|
112
112
|
})
|
|
113
113
|
})
|
|
114
114
|
}),
|
|
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
|
+
}),
|
|
115
145
|
/* @__PURE__ */ v("div", {
|
|
116
146
|
className: "container mx-auto px-6 py-4",
|
|
117
147
|
"data-tour": "channels-connections-filters",
|
|
@@ -119,42 +149,42 @@ function C() {
|
|
|
119
149
|
className: "flex items-center gap-4",
|
|
120
150
|
children: [
|
|
121
151
|
/* @__PURE__ */ y("select", {
|
|
122
|
-
value:
|
|
123
|
-
onChange: (e) =>
|
|
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
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
163
|
/* @__PURE__ */ y("select", {
|
|
134
|
-
value:
|
|
135
|
-
onChange: (e) =>
|
|
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
168
|
/* @__PURE__ */ v("option", {
|
|
139
169
|
value: "all",
|
|
140
|
-
children:
|
|
170
|
+
children: M("channel.allStatus", "All Status")
|
|
141
171
|
}),
|
|
142
172
|
/* @__PURE__ */ v("option", {
|
|
143
173
|
value: "healthy",
|
|
144
|
-
children:
|
|
174
|
+
children: M("channel.healthy", "Healthy")
|
|
145
175
|
}),
|
|
146
176
|
/* @__PURE__ */ v("option", {
|
|
147
177
|
value: "unhealthy",
|
|
148
|
-
children:
|
|
178
|
+
children: M("channel.unhealthy", "Unhealthy")
|
|
149
179
|
})
|
|
150
180
|
]
|
|
151
181
|
}),
|
|
152
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__ */ v(
|
|
187
|
+
children: /* @__PURE__ */ v(d, {
|
|
158
188
|
className: "size-5 ",
|
|
159
189
|
"aria-hidden": "true"
|
|
160
190
|
})
|
|
@@ -164,37 +194,28 @@ function C() {
|
|
|
164
194
|
}),
|
|
165
195
|
/* @__PURE__ */ v("div", {
|
|
166
196
|
className: "container mx-auto px-6 pb-8",
|
|
167
|
-
children:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
type: "button",
|
|
186
|
-
onClick: () => {
|
|
187
|
-
j("all"), N("all");
|
|
188
|
-
},
|
|
189
|
-
className: "px-4 py-2 rounded-lg border hover:bg-bg-sunken",
|
|
190
|
-
children: k("channel.clearFilters", "Clear Filters")
|
|
191
|
-
})
|
|
192
|
-
]
|
|
193
|
-
}) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(b, {
|
|
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", {
|
|
202
|
+
type: "button",
|
|
203
|
+
onClick: () => c("./create"),
|
|
204
|
+
className: "px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90",
|
|
205
|
+
children: M("channel.createConnection", "Create Connection")
|
|
206
|
+
}) : void 0 : /* @__PURE__ */ v("button", {
|
|
207
|
+
type: "button",
|
|
208
|
+
onClick: () => {
|
|
209
|
+
P("all"), I("all");
|
|
210
|
+
},
|
|
211
|
+
className: "px-4 py-2 rounded-lg border hover:bg-bg-sunken",
|
|
212
|
+
children: M("channel.clearFilters", "Clear Filters")
|
|
213
|
+
})
|
|
214
|
+
}) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(x, {
|
|
194
215
|
treatment: "plain",
|
|
195
216
|
className: "border-border-seam rounded-lg p-0 divide-y divide-[var(--color-border-seam)] overflow-hidden",
|
|
196
|
-
children:
|
|
197
|
-
let t =
|
|
217
|
+
children: W.map((e) => {
|
|
218
|
+
let t = T[String(e.isHealthy)] || T.false, n = t.icon;
|
|
198
219
|
return /* @__PURE__ */ v("div", {
|
|
199
220
|
role: "button",
|
|
200
221
|
tabIndex: 0,
|
|
@@ -208,7 +229,7 @@ function C() {
|
|
|
208
229
|
children: [/* @__PURE__ */ y("div", {
|
|
209
230
|
className: "flex items-center gap-4",
|
|
210
231
|
children: [/* @__PURE__ */ v("div", {
|
|
211
|
-
className: t.color
|
|
232
|
+
className: `${t.color} ${e.isHealthy ? "motion-safe:animate-vc-glow-pulse" : ""}`,
|
|
212
233
|
"aria-label": t.label,
|
|
213
234
|
children: /* @__PURE__ */ v(n, {
|
|
214
235
|
className: "size-5 ",
|
|
@@ -236,22 +257,22 @@ function C() {
|
|
|
236
257
|
className: "text-sm",
|
|
237
258
|
children: e.channelStatus === "ACTIVE" ? /* @__PURE__ */ v("span", {
|
|
238
259
|
className: "text-status-success-text",
|
|
239
|
-
children:
|
|
260
|
+
children: M("channel.activeStatus", "Active")
|
|
240
261
|
}) : e.channelStatus === "INACTIVE" ? /* @__PURE__ */ v("span", {
|
|
241
262
|
className: "text-text-secondary",
|
|
242
|
-
children:
|
|
263
|
+
children: M("channel.inactiveStatus", "Inactive")
|
|
243
264
|
}) : e.channelStatus === "ERROR" ? /* @__PURE__ */ v("span", {
|
|
244
265
|
className: "text-status-error-text",
|
|
245
|
-
children:
|
|
266
|
+
children: M("channel.errorStatus", "Error")
|
|
246
267
|
}) : e.channelStatus === "SUSPENDED" ? /* @__PURE__ */ v("span", {
|
|
247
268
|
className: "text-status-warning-text",
|
|
248
|
-
children:
|
|
269
|
+
children: M("channel.suspendedStatus", "Suspended")
|
|
249
270
|
}) : e.channelStatus === "ARCHIVED" ? /* @__PURE__ */ v("span", {
|
|
250
271
|
className: "text-text-secondary",
|
|
251
|
-
children:
|
|
272
|
+
children: M("channel.archivedStatus", "Archived")
|
|
252
273
|
}) : /* @__PURE__ */ v("span", {
|
|
253
274
|
className: "text-text-secondary",
|
|
254
|
-
children:
|
|
275
|
+
children: M("channel.inactiveStatus", "Inactive")
|
|
255
276
|
})
|
|
256
277
|
}), e.lastTestedAt && /* @__PURE__ */ y("p", {
|
|
257
278
|
className: "text-xs text-text-secondary",
|
|
@@ -265,7 +286,7 @@ function C() {
|
|
|
265
286
|
e.tagAssignments.length > 0 && /* @__PURE__ */ y("div", {
|
|
266
287
|
className: "flex flex-wrap gap-1",
|
|
267
288
|
"aria-label": "Connection tags",
|
|
268
|
-
children: [e.tagAssignments.slice(0, 3).map((e) => /* @__PURE__ */ v(
|
|
289
|
+
children: [e.tagAssignments.slice(0, 3).map((e) => /* @__PURE__ */ v(w, {
|
|
269
290
|
id: e.tagId,
|
|
270
291
|
label: e.tagId,
|
|
271
292
|
size: "sm"
|
|
@@ -279,36 +300,36 @@ function C() {
|
|
|
279
300
|
onClick: (t) => {
|
|
280
301
|
t.stopPropagation(), c(`/channels/${e.channelId}`);
|
|
281
302
|
},
|
|
282
|
-
"aria-label":
|
|
303
|
+
"aria-label": M("channel.viewChannelSettings", "View channel settings"),
|
|
283
304
|
className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
|
|
284
|
-
children: /* @__PURE__ */ v(
|
|
305
|
+
children: /* @__PURE__ */ v(f, { className: "size-4 " })
|
|
285
306
|
})
|
|
286
307
|
]
|
|
287
308
|
})]
|
|
288
309
|
})
|
|
289
310
|
}, e.connectionId);
|
|
290
311
|
})
|
|
291
|
-
}),
|
|
312
|
+
}), H.length > 10 && /* @__PURE__ */ y("div", {
|
|
292
313
|
className: "flex items-center justify-between px-4 py-3 border border-border-seam rounded-lg mt-3",
|
|
293
314
|
children: [/* @__PURE__ */ y("p", {
|
|
294
315
|
className: "text-sm text-text-secondary",
|
|
295
316
|
children: [
|
|
296
|
-
|
|
317
|
+
M("channel.showing", "Showing"),
|
|
297
318
|
" ",
|
|
298
319
|
/* @__PURE__ */ y("span", {
|
|
299
320
|
className: "font-medium",
|
|
300
321
|
children: [
|
|
301
|
-
|
|
322
|
+
L * 10 + 1,
|
|
302
323
|
"–",
|
|
303
|
-
Math.min((
|
|
324
|
+
Math.min((L + 1) * 10, H.length)
|
|
304
325
|
]
|
|
305
326
|
}),
|
|
306
327
|
" ",
|
|
307
|
-
|
|
328
|
+
M("channel.of", "of"),
|
|
308
329
|
" ",
|
|
309
330
|
/* @__PURE__ */ v("span", {
|
|
310
331
|
className: "font-medium",
|
|
311
|
-
children:
|
|
332
|
+
children: H.length
|
|
312
333
|
})
|
|
313
334
|
]
|
|
314
335
|
}), /* @__PURE__ */ y("div", {
|
|
@@ -316,25 +337,25 @@ function C() {
|
|
|
316
337
|
children: [
|
|
317
338
|
/* @__PURE__ */ v("button", {
|
|
318
339
|
type: "button",
|
|
319
|
-
onClick: () =>
|
|
320
|
-
disabled:
|
|
340
|
+
onClick: () => R((e) => Math.max(0, e - 1)),
|
|
341
|
+
disabled: L === 0,
|
|
321
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",
|
|
322
|
-
children:
|
|
343
|
+
children: M("channel.previous", "Previous")
|
|
323
344
|
}),
|
|
324
345
|
/* @__PURE__ */ y("span", {
|
|
325
346
|
className: "text-sm text-text-secondary",
|
|
326
347
|
children: [
|
|
327
|
-
|
|
348
|
+
L + 1,
|
|
328
349
|
" / ",
|
|
329
|
-
|
|
350
|
+
U
|
|
330
351
|
]
|
|
331
352
|
}),
|
|
332
353
|
/* @__PURE__ */ v("button", {
|
|
333
354
|
type: "button",
|
|
334
|
-
onClick: () =>
|
|
335
|
-
disabled:
|
|
355
|
+
onClick: () => R((e) => Math.min(U - 1, e + 1)),
|
|
356
|
+
disabled: L >= U - 1,
|
|
336
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",
|
|
337
|
-
children:
|
|
358
|
+
children: M("channel.next", "Next")
|
|
338
359
|
})
|
|
339
360
|
]
|
|
340
361
|
})]
|
|
@@ -345,6 +366,6 @@ function C() {
|
|
|
345
366
|
});
|
|
346
367
|
}
|
|
347
368
|
//#endregion
|
|
348
|
-
export {
|
|
369
|
+
export { E as ConnectionsListPage };
|
|
349
370
|
|
|
350
371
|
//# sourceMappingURL=ConnectionsListPage.js.map
|