@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2
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/components/actions/ActionCard.js +2 -2
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/actions/ExecutionHistory.js +1 -1
- package/dist/components/actions/ExecutionHistory.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
- package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +23 -23
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +5 -5
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
- package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
- package/dist/components/agents/AgentCard.js +14 -11
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +4 -4
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPlanPanel.js +4 -4
- package/dist/components/ai/AIPlanPanel.js.map +1 -1
- package/dist/components/ai/AIPlaygroundTab.js +1 -1
- package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +2 -2
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
- package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +4 -4
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +4 -4
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/ai/AITasksTab.js +1 -1
- package/dist/components/ai/AITasksTab.js.map +1 -1
- package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
- package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +3 -3
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
- package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
- package/dist/components/audit/AuditDeleteDialog.js +2 -2
- package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
- package/dist/components/audit/AuditExportButton.js +5 -5
- package/dist/components/audit/AuditExportButton.js.map +1 -1
- package/dist/components/audit/AuditLogDetail.js +9 -9
- package/dist/components/audit/AuditLogDetail.js.map +1 -1
- package/dist/components/audit/AuditLogFilters.js +1 -1
- package/dist/components/audit/AuditLogFilters.js.map +1 -1
- package/dist/components/audit/AuditLogStats.js +1 -1
- package/dist/components/audit/AuditLogStats.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +28 -28
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/catalog/CatalogCard.js +3 -3
- package/dist/components/catalog/CatalogCard.js.map +1 -1
- package/dist/components/catalog/CatalogForm.js +1 -1
- package/dist/components/catalog/CatalogForm.js.map +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js +6 -6
- package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
- package/dist/components/common/CommandEditor.js +1 -1
- package/dist/components/common/CommandEditor.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/docs/DocsPreviewPanel.js +1 -1
- package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
- package/dist/components/docs/DocsRevisionHistory.js +4 -4
- package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
- package/dist/components/docs/DocsSettingsPanel.js +7 -7
- package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +1 -1
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/idp-components/ComponentCard.js +5 -5
- package/dist/components/idp-components/ComponentCard.js.map +1 -1
- package/dist/components/idp-components/ComponentForm.js +1 -1
- package/dist/components/idp-components/ComponentForm.js.map +1 -1
- package/dist/components/idp-components/DependencyGraphTab.js +7 -7
- package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
- package/dist/components/logs/LogViewer.js +14 -14
- package/dist/components/logs/LogViewer.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +2 -2
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +1 -1
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
- package/dist/components/scorecard/ScorecardPanel.js +3 -3
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/security/SecurityScanList.js +11 -11
- package/dist/components/security/SecurityScanList.js.map +1 -1
- package/dist/components/sessions/SessionCard.js +26 -19
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionStatusBadge.js +7 -7
- package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
- package/dist/components/shared/AgentSelector.js +4 -4
- package/dist/components/shared/AgentSelector.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +3 -3
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/Card.js +4 -4
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/ErrorState.js +15 -9
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/shared/FilterDropdown.js +2 -2
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/PageSkeleton.js +6 -6
- package/dist/components/shared/PageSkeleton.js.map +1 -1
- package/dist/components/shared/SearchInput.js +1 -1
- package/dist/components/shared/SearchInput.js.map +1 -1
- package/dist/components/shared/StatCard.js +7 -7
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/templates/PublishTemplateForm.js +2 -2
- package/dist/components/templates/PublishTemplateForm.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +5 -5
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/templates/TemplateWizard.js +2 -2
- package/dist/components/templates/TemplateWizard.js.map +1 -1
- package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
- package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
- package/dist/components/tunnels/ProviderBadge.js +1 -1
- package/dist/components/tunnels/ProviderBadge.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +16 -16
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
- package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
- package/dist/components/vibecalendar/LogsDrawer.js +2 -2
- package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
- package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
- package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
- package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
- package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
- package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
- package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
- package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
- package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
- package/dist/components/vibecalendar/types.js +6 -6
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
- package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +8 -8
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
- package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
- package/dist/components/vibes/VibeCard.js +22 -18
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +16 -12
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/pages/ActionsPage.js +4 -3
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +9 -9
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsLandingPage.js +4 -4
- package/dist/pages/AgentsLandingPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +14 -14
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +8 -8
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +11 -11
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +12 -9
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +7 -7
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +12 -9
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +2 -2
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +12 -12
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/HealthPage.js +12 -12
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +4 -4
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +5 -5
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +74 -41
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +6 -3
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js +7 -7
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +21 -21
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +20 -20
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +64 -52
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +21 -21
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/SharedVibeDeckPage.js +5 -5
- package/dist/pages/SharedVibeDeckPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +56 -65
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +4 -4
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +10 -10
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +6 -5
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +3 -3
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +3 -3
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +3 -3
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +15 -15
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeTaskDetailPage.js +5 -5
- package/dist/pages/VibeTaskDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +18 -18
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +11 -11
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +1 -1
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +6 -6
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/tunnels/AuditTab.js +1 -1
- package/dist/pages/tunnels/AuditTab.js.map +1 -1
- package/dist/pages/tunnels/DomainsTab.js +1 -1
- package/dist/pages/tunnels/DomainsTab.js.map +1 -1
- package/dist/pages/tunnels/ProvidersTab.js +1 -1
- package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
- package/dist/pages/tunnels/SessionsTab.js +1 -1
- package/dist/pages/tunnels/SessionsTab.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/UsageTab.js +1 -1
- package/dist/pages/tunnels/UsageTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunnelCreateWizard.js","names":[],"sources":["../../../src/components/tunnels/TunnelCreateWizard.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Stepper } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { QuotaExhaustedDialog } from '@/components/shared/QuotaExhaustedDialog';\nimport {\n TunnelEnvironment,\n TunnelProtocol,\n TunnelProviderKey,\n type CreateTunnelInput,\n} from '@/generated/wspace-types';\nimport {\n useCreateTunnelMutation,\n useListTunnelProvidersQuery,\n useListAgentsQuery,\n} from '@/generated/wspace-operations';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n LinkToVibePanel,\n LINK_TO_VIBE_DEFAULT,\n linkToVibeInput,\n type LinkToVibeValue,\n} from '@/components/shared';\n\nconst SUBDOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;\n\ntype HostnameKind = 'managed' | 'custom' | 'auto';\n\ninterface WizardState {\n providerKey: TunnelProviderKey | '';\n agentId: string;\n name: string;\n description: string;\n protocol: TunnelProtocol;\n localHost: string;\n localPort: number;\n hostnameKind: HostnameKind;\n subdomain: string;\n environment: TunnelEnvironment;\n customDomain: string;\n basicAuthUser: string;\n basicAuthPassword: string;\n}\n\nconst INITIAL_STATE: WizardState = {\n // Default to Vibe Tunnels as the preferred provider; users can still pick\n // Cloudflare Tunnels from the provider list.\n providerKey: TunnelProviderKey.Vibetunnels,\n agentId: '',\n name: '',\n description: '',\n protocol: TunnelProtocol.Http,\n localHost: '127.0.0.1',\n localPort: 3000,\n hostnameKind: 'managed',\n subdomain: '',\n environment: TunnelEnvironment.Prod,\n customDomain: '',\n basicAuthUser: '',\n basicAuthPassword: '',\n};\n\nconst STEPS = [\n { id: 'provider', label: 'Provider' },\n { id: 'target', label: 'Protocol & target' },\n { id: 'hostname', label: 'Hostname' },\n { id: 'policies', label: 'Policies' },\n { id: 'review', label: 'Review' },\n] as const;\n\nexport function TunnelCreateWizard() {\n const tr = useTr();\n const navigate = useNavigate();\n const bus = useEventBus();\n const { workspaceId } = useVibeControls();\n const [step, setStep] = useState(0);\n const [state, setState] = useState<WizardState>(INITIAL_STATE);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [isFlushingTags, setIsFlushingTags] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n // G7: Optional link to a Vibe + visibility, threaded into CreateTunnelInput.\n const [vibeLink, setVibeLink] = useState<LinkToVibeValue>(LINK_TO_VIBE_DEFAULT);\n // Tags selected before the tunnel exists — persisted right after create.\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_TUNNEL');\n\n const {\n data: providersData,\n loading: providersLoading,\n error: providersError,\n } = useListTunnelProvidersQuery();\n const { data: agentsData } = useListAgentsQuery({ variables: { pagination: { limit: 100 } } });\n\n const [createTunnel, { loading: creating }] = useCreateTunnelMutation();\n\n const update = useCallback(<K extends keyof WizardState>(key: K, value: WizardState[K]) => {\n setState((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const selectProvider = useCallback((providerKey: TunnelProviderKey) => {\n setState((prev) => ({\n ...prev,\n providerKey,\n hostnameKind: providerKey === TunnelProviderKey.Cloudflare ? 'auto' : 'managed',\n }));\n }, []);\n\n // VibeTunnels first, Cloudflare second, everything else alphabetically.\n const orderedProviders = useMemo(\n () =>\n [...(providersData?.tunnelProviders ?? [])].sort((a: { key: string }, b: { key: string }) => {\n const rank = (key: string) => (key === 'VIBETUNNELS' ? 0 : key === 'CLOUDFLARE' ? 1 : 2);\n const diff = rank(a.key) - rank(b.key);\n if (diff !== 0) return diff;\n return a.key.localeCompare(b.key);\n }),\n [providersData]\n );\n\n const selectedProvider = useMemo(\n () => orderedProviders.find((p: { key: string }) => p.key === state.providerKey),\n [orderedProviders, state.providerKey]\n );\n\n const caps = selectedProvider?.capabilities;\n const supportsAutoHostname = state.providerKey === TunnelProviderKey.Cloudflare;\n const protocolOptions = useMemo(() => {\n if (!caps) return [TunnelProtocol.Http];\n const list: TunnelProtocol[] = [];\n if (caps.supportsHttp) list.push(TunnelProtocol.Http);\n if (caps.supportsHttps) list.push(TunnelProtocol.Https);\n if (caps.supportsTcp) list.push(TunnelProtocol.Tcp);\n return list;\n }, [caps]);\n\n const canAdvance = useMemo(() => {\n if (step === 0) return Boolean(state.providerKey && state.agentId && state.name.trim());\n if (step === 1) return state.localPort > 0 && state.localPort < 65536;\n if (step === 2) {\n if (state.hostnameKind === 'managed')\n return Boolean(state.subdomain && SUBDOMAIN_RE.test(state.subdomain));\n if (state.hostnameKind === 'custom') return state.customDomain.trim().includes('.');\n return supportsAutoHostname;\n }\n return true;\n }, [step, state, supportsAutoHostname]);\n\n const next = () => setStep((s) => Math.min(s + 1, STEPS.length - 1));\n const back = () => setStep((s) => Math.max(s - 1, 0));\n\n const submit = async () => {\n setSubmitError(null);\n try {\n const input: CreateTunnelInput = {\n agentId: state.agentId,\n name: state.name,\n description: state.description || undefined,\n providerKey: state.providerKey as TunnelProviderKey,\n protocol: state.protocol,\n localHost: state.localHost,\n localPort: state.localPort,\n environment: state.environment,\n basicAuthUser: state.basicAuthUser || undefined,\n basicAuthPassword: state.basicAuthPassword || undefined,\n managedSubdomain: state.hostnameKind === 'managed' ? state.subdomain : undefined,\n ...linkToVibeInput(vibeLink),\n };\n const result = await createTunnel({ variables: { input } });\n\n if (result.error) {\n if (isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n setSubmitError(result.error.message);\n return;\n }\n\n const id = result.data?.createTunnel.id;\n if (id) {\n bus.emitCustom('vibecontrols.tunnel.created', {\n workspaceId: workspaceId ?? undefined,\n tunnelId: id,\n });\n setIsFlushingTags(true);\n try {\n await flushTags(id);\n } finally {\n setIsFlushingTags(false);\n }\n // Land on the detail page and immediately prompt to start — a freshly\n // created tunnel is a DRAFT, so offer to bring it online instead of\n // making the user hunt for the Start button.\n navigate(`/vibecontrols/tunnels/${id}`, { state: { autoPromptStart: true } });\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setSubmitError(null);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n };\n\n if (providersLoading) return <LoadingState message=\"Loading providers\" />;\n if (providersError) {\n return (\n <ErrorState\n title=\"Failed to load providers\"\n message={providersError.message}\n onRetry={() => window.location.reload()}\n />\n );\n }\n\n return (\n <div className=\"p-6 max-w-3xl mx-auto\">\n <PageHeader\n breadcrumb={[{ label: 'Tunnels', path: '/vibecontrols/tunnels' }, { label: 'New tunnel' }]}\n title={tr('vibecontrols.tunnels.createTitle', 'Create tunnel')}\n description={tr(\n 'vibecontrols.tunnels.createDescription',\n 'Expose a local service through a managed tunnel.'\n )}\n />\n\n <Stepper\n className=\"mb-6\"\n active={step}\n onStepClick={(i) => i <= step && setStep(i)}\n steps={STEPS.map((s) => ({ key: s.id, label: s.label }))}\n />\n\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={step}\n initial={{ opacity: 0, x: 6 }}\n animate={{ opacity: 1, x: 0 }}\n exit={{ opacity: 0, x: -6 }}\n transition={{ duration: 0.15 }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-6\"\n >\n {step === 0 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Provider</h3>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {orderedProviders.map(\n (p: {\n id: string;\n key: TunnelProviderKey;\n displayName: string;\n description?: string | null;\n }) => (\n <button\n key={p.id}\n type=\"button\"\n onClick={() => selectProvider(p.key)}\n className={`rounded-lg border p-4 text-left transition-colors ${\n state.providerKey === p.key\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n }`}\n >\n <div className=\"font-semibold text-text-primary\">{p.displayName}</div>\n <div className=\"text-xs text-text-muted mt-1\">{p.key}</div>\n {p.description && (\n <div className=\"text-xs text-text-secondary mt-2\">{p.description}</div>\n )}\n </button>\n )\n )}\n </div>\n <LabeledField label=\"Agent\">\n <select\n value={state.agentId}\n onChange={(e) => update('agentId', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">Select agent</option>\n {(agentsData?.vibecontrolsAgents ?? []).map((a: { id: string; name: string }) => (\n <option key={a.id} value={a.id}>\n {a.name}\n </option>\n ))}\n </select>\n </LabeledField>\n <LabeledField label=\"Name\">\n <input\n aria-label=\"My dev tunnel\"\n type=\"text\"\n value={state.name}\n onChange={(e) => update('name', e.target.value)}\n placeholder=\"My dev tunnel\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n <LabeledField label=\"Description (optional)\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={state.description}\n onChange={(e) => update('description', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 1 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Protocol & target</h3>\n <LabeledField label=\"Protocol\">\n <div className=\"flex gap-2\">\n {protocolOptions.map((proto) => (\n <button\n key={proto}\n type=\"button\"\n onClick={() => update('protocol', proto)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.protocol === proto\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n {proto}\n </button>\n ))}\n </div>\n </LabeledField>\n <LabeledField label=\"Local host\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={state.localHost}\n onChange={(e) => update('localHost', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Local port\">\n <input\n aria-label=\"control\"\n type=\"number\"\n value={state.localPort}\n onChange={(e) => update('localPort', parseInt(e.target.value, 10) || 0)}\n min={1}\n max={65535}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 2 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Hostname</h3>\n <div className=\"flex gap-2\">\n {caps?.supportsManagedSubdomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'managed'}\n onClick={() => update('hostnameKind', 'managed')}\n label=\"Managed subdomain\"\n hint=\"*.vibetunnels.com\"\n />\n )}\n {caps?.supportsCustomDomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'custom'}\n onClick={() => update('hostnameKind', 'custom')}\n label=\"Custom domain\"\n hint=\"TXT verification\"\n />\n )}\n {supportsAutoHostname && (\n <HostnameKindButton\n active={state.hostnameKind === 'auto'}\n onClick={() => update('hostnameKind', 'auto')}\n label=\"Auto\"\n hint=\"assigned by Cloudflare\"\n />\n )}\n </div>\n\n {state.hostnameKind === 'managed' && (\n <>\n <LabeledField label=\"Environment\">\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Prod)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Prod\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Prod (vibetunnels.com)\n </button>\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Alpha)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Alpha\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Alpha\n </button>\n </div>\n </LabeledField>\n <LabeledField label=\"Subdomain label\">\n <input\n aria-label=\"demo\"\n type=\"text\"\n value={state.subdomain}\n onChange={(e) => update('subdomain', e.target.value.toLowerCase())}\n placeholder=\"demo\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n {state.subdomain && (\n <p className=\"text-xs text-text-secondary\">\n Will be reserved as{' '}\n <span className=\"font-mono text-text-primary\">\n {state.subdomain}\n {state.environment === TunnelEnvironment.Alpha\n ? '.alpha.vibetunnels.com'\n : '.vibetunnels.com'}\n </span>\n </p>\n )}\n </>\n )}\n\n {state.hostnameKind === 'custom' && (\n <LabeledField label=\"Custom domain\">\n <input\n aria-label=\"preview.example.com\"\n type=\"text\"\n value={state.customDomain}\n onChange={(e) => update('customDomain', e.target.value.toLowerCase())}\n placeholder=\"preview.example.com\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"text-xs text-text-muted mt-2\">\n Custom domains are registered on create but must be verified via a TXT record\n before they can be attached. You'll see the verification challenge in the\n Domains tab after creation.\n </p>\n </LabeledField>\n )}\n </div>\n )}\n\n {step === 3 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Policies (optional)</h3>\n <LabeledField label=\"Basic auth user\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={state.basicAuthUser}\n onChange={(e) => update('basicAuthUser', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Basic auth password\">\n <input\n aria-label=\"control\"\n type=\"password\"\n value={state.basicAuthPassword}\n onChange={(e) => update('basicAuthPassword', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LinkToVibePanel value={vibeLink} onChange={setVibeLink} disabled={creating} />\n </div>\n )}\n\n {step === 4 && (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.tunnels.review', 'Review')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Provider</dt>\n <dd className=\"text-text-primary\">{state.providerKey}</dd>\n <dt className=\"text-text-muted\">Name</dt>\n <dd className=\"text-text-primary\">{state.name}</dd>\n <dt className=\"text-text-muted\">Agent</dt>\n <dd className=\"text-text-primary font-mono\">{state.agentId}</dd>\n <dt className=\"text-text-muted\">Protocol</dt>\n <dd className=\"text-text-primary\">{state.protocol}</dd>\n <dt className=\"text-text-muted\">Target</dt>\n <dd className=\"text-text-primary font-mono\">\n {state.localHost}:{state.localPort}\n </dd>\n <dt className=\"text-text-muted\">Hostname</dt>\n <dd className=\"text-text-primary\">\n {state.hostnameKind === 'managed'\n ? `${state.subdomain}.${state.environment === 'ALPHA' ? 'alpha.' : ''}vibetunnels.com`\n : state.hostnameKind === 'custom'\n ? state.customDomain\n : 'auto-assigned'}\n </dd>\n <dt className=\"text-text-muted\">Basic auth</dt>\n <dd className=\"text-text-primary\">{state.basicAuthUser ? 'yes' : 'no'}</dd>\n </dl>\n <div className=\"border-t border-border-subtle pt-3\">\n <h4 className=\"mb-2 text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.tunnels.tags', 'Tags')}\n </h4>\n <EntityTagPicker entityType=\"VIBECONTROLS_TUNNEL\" onDraftChange={setDraftTagIds} />\n </div>\n {submitError && (\n <div className=\"rounded-md bg-status-error-bg p-3 text-sm text-status-error-fg\">\n {submitError}\n </div>\n )}\n </div>\n )}\n </motion.div>\n </AnimatePresence>\n\n <div className=\"flex items-center justify-between mt-4\">\n <button\n type=\"button\"\n disabled={step === 0}\n onClick={back}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.back', 'Back')}\n </button>\n {step < STEPS.length - 1 ? (\n <button\n type=\"button\"\n disabled={!canAdvance}\n onClick={next}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.next', 'Next')}\n </button>\n ) : (\n <button\n type=\"button\"\n disabled={creating || isFlushingTags}\n onClick={() => void submit()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.tunnels.creating', 'Creating...')\n : tr('vibecontrols.tunnels.createTunnel', 'Create tunnel')}\n </button>\n )}\n </div>\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n {...getQuotaDialogContent('tunnel-subdomain')}\n />\n </div>\n );\n}\n\nfunction LabeledField({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <label className=\"block\">\n <span className=\"text-xs font-medium text-text-secondary\">{label}</span>\n <div className=\"mt-1\">{children}</div>\n </label>\n );\n}\n\nfunction HostnameKindButton({\n active,\n onClick,\n label,\n hint,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n hint: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={`flex-1 rounded-md border p-3 text-left transition-colors ${\n active\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n }`}\n >\n <div className=\"text-sm font-medium text-text-primary\">{label}</div>\n <div className=\"text-xs text-text-muted mt-0.5\">{hint}</div>\n </button>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TunnelCreateWizard.js","names":[],"sources":["../../../src/components/tunnels/TunnelCreateWizard.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Stepper } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { QuotaExhaustedDialog } from '@/components/shared/QuotaExhaustedDialog';\nimport {\n TunnelEnvironment,\n TunnelProtocol,\n TunnelProviderKey,\n type CreateTunnelInput,\n} from '@/generated/wspace-types';\nimport {\n useCreateTunnelMutation,\n useListTunnelProvidersQuery,\n useListAgentsQuery,\n} from '@/generated/wspace-operations';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n LinkToVibePanel,\n LINK_TO_VIBE_DEFAULT,\n linkToVibeInput,\n type LinkToVibeValue,\n} from '@/components/shared';\n\nconst SUBDOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;\n\ntype HostnameKind = 'managed' | 'custom' | 'auto';\n\ninterface WizardState {\n providerKey: TunnelProviderKey | '';\n agentId: string;\n name: string;\n description: string;\n protocol: TunnelProtocol;\n localHost: string;\n localPort: number;\n hostnameKind: HostnameKind;\n subdomain: string;\n environment: TunnelEnvironment;\n customDomain: string;\n basicAuthUser: string;\n basicAuthPassword: string;\n}\n\nconst INITIAL_STATE: WizardState = {\n // Default to Vibe Tunnels as the preferred provider; users can still pick\n // Cloudflare Tunnels from the provider list.\n providerKey: TunnelProviderKey.Vibetunnels,\n agentId: '',\n name: '',\n description: '',\n protocol: TunnelProtocol.Http,\n localHost: '127.0.0.1',\n localPort: 3000,\n hostnameKind: 'managed',\n subdomain: '',\n environment: TunnelEnvironment.Prod,\n customDomain: '',\n basicAuthUser: '',\n basicAuthPassword: '',\n};\n\nconst STEPS = [\n { id: 'provider', label: 'Provider' },\n { id: 'target', label: 'Protocol & target' },\n { id: 'hostname', label: 'Hostname' },\n { id: 'policies', label: 'Policies' },\n { id: 'review', label: 'Review' },\n] as const;\n\nexport function TunnelCreateWizard() {\n const tr = useTr();\n const navigate = useNavigate();\n const bus = useEventBus();\n const { workspaceId } = useVibeControls();\n const [step, setStep] = useState(0);\n const [state, setState] = useState<WizardState>(INITIAL_STATE);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [isFlushingTags, setIsFlushingTags] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n // G7: Optional link to a Vibe + visibility, threaded into CreateTunnelInput.\n const [vibeLink, setVibeLink] = useState<LinkToVibeValue>(LINK_TO_VIBE_DEFAULT);\n // Tags selected before the tunnel exists — persisted right after create.\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_TUNNEL');\n\n const {\n data: providersData,\n loading: providersLoading,\n error: providersError,\n } = useListTunnelProvidersQuery();\n const { data: agentsData } = useListAgentsQuery({ variables: { pagination: { limit: 100 } } });\n\n const [createTunnel, { loading: creating }] = useCreateTunnelMutation();\n\n const update = useCallback(<K extends keyof WizardState>(key: K, value: WizardState[K]) => {\n setState((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const selectProvider = useCallback((providerKey: TunnelProviderKey) => {\n setState((prev) => ({\n ...prev,\n providerKey,\n hostnameKind: providerKey === TunnelProviderKey.Cloudflare ? 'auto' : 'managed',\n }));\n }, []);\n\n // VibeTunnels first, Cloudflare second, everything else alphabetically.\n const orderedProviders = useMemo(\n () =>\n [...(providersData?.tunnelProviders ?? [])].sort((a: { key: string }, b: { key: string }) => {\n const rank = (key: string) => (key === 'VIBETUNNELS' ? 0 : key === 'CLOUDFLARE' ? 1 : 2);\n const diff = rank(a.key) - rank(b.key);\n if (diff !== 0) return diff;\n return a.key.localeCompare(b.key);\n }),\n [providersData]\n );\n\n const selectedProvider = useMemo(\n () => orderedProviders.find((p: { key: string }) => p.key === state.providerKey),\n [orderedProviders, state.providerKey]\n );\n\n const caps = selectedProvider?.capabilities;\n const supportsAutoHostname = state.providerKey === TunnelProviderKey.Cloudflare;\n const protocolOptions = useMemo(() => {\n if (!caps) return [TunnelProtocol.Http];\n const list: TunnelProtocol[] = [];\n if (caps.supportsHttp) list.push(TunnelProtocol.Http);\n if (caps.supportsHttps) list.push(TunnelProtocol.Https);\n if (caps.supportsTcp) list.push(TunnelProtocol.Tcp);\n return list;\n }, [caps]);\n\n const canAdvance = useMemo(() => {\n if (step === 0) return Boolean(state.providerKey && state.agentId && state.name.trim());\n if (step === 1) return state.localPort > 0 && state.localPort < 65536;\n if (step === 2) {\n if (state.hostnameKind === 'managed')\n return Boolean(state.subdomain && SUBDOMAIN_RE.test(state.subdomain));\n if (state.hostnameKind === 'custom') return state.customDomain.trim().includes('.');\n return supportsAutoHostname;\n }\n return true;\n }, [step, state, supportsAutoHostname]);\n\n const next = () => setStep((s) => Math.min(s + 1, STEPS.length - 1));\n const back = () => setStep((s) => Math.max(s - 1, 0));\n\n const submit = async () => {\n setSubmitError(null);\n try {\n const input: CreateTunnelInput = {\n agentId: state.agentId,\n name: state.name,\n description: state.description || undefined,\n providerKey: state.providerKey as TunnelProviderKey,\n protocol: state.protocol,\n localHost: state.localHost,\n localPort: state.localPort,\n environment: state.environment,\n basicAuthUser: state.basicAuthUser || undefined,\n basicAuthPassword: state.basicAuthPassword || undefined,\n managedSubdomain: state.hostnameKind === 'managed' ? state.subdomain : undefined,\n ...linkToVibeInput(vibeLink),\n };\n const result = await createTunnel({ variables: { input } });\n\n if (result.error) {\n if (isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n setSubmitError(result.error.message);\n return;\n }\n\n const id = result.data?.createTunnel.id;\n if (id) {\n bus.emitCustom('vibecontrols.tunnel.created', {\n workspaceId: workspaceId ?? undefined,\n tunnelId: id,\n });\n setIsFlushingTags(true);\n try {\n await flushTags(id);\n } finally {\n setIsFlushingTags(false);\n }\n // Land on the detail page and immediately prompt to start — a freshly\n // created tunnel is a DRAFT, so offer to bring it online instead of\n // making the user hunt for the Start button.\n navigate(`/vibecontrols/tunnels/${id}`, { state: { autoPromptStart: true } });\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setSubmitError(null);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n };\n\n if (providersLoading) return <LoadingState message=\"Loading providers\" />;\n if (providersError) {\n return (\n <ErrorState\n title=\"Failed to load providers\"\n message={providersError.message}\n onRetry={() => window.location.reload()}\n />\n );\n }\n\n return (\n <div className=\"p-6 max-w-3xl mx-auto\">\n <PageHeader\n breadcrumb={[{ label: 'Tunnels', path: '/vibecontrols/tunnels' }, { label: 'New tunnel' }]}\n title={tr('vibecontrols.tunnels.createTitle', 'Create tunnel')}\n description={tr(\n 'vibecontrols.tunnels.createDescription',\n 'Expose a local service through a managed tunnel.'\n )}\n />\n\n <Stepper\n className=\"mb-6\"\n active={step}\n onStepClick={(i) => i <= step && setStep(i)}\n steps={STEPS.map((s) => ({ key: s.id, label: s.label }))}\n />\n\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={step}\n initial={{ opacity: 0, x: 6 }}\n animate={{ opacity: 1, x: 0 }}\n exit={{ opacity: 0, x: -6 }}\n transition={{ duration: 0.15 }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-6\"\n >\n {step === 0 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Provider</h3>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {orderedProviders.map(\n (p: {\n id: string;\n key: TunnelProviderKey;\n displayName: string;\n description?: string | null;\n }) => (\n <button\n key={p.id}\n type=\"button\"\n onClick={() => selectProvider(p.key)}\n className={`rounded-lg border p-4 text-left transition-colors ${\n state.providerKey === p.key\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n }`}\n >\n <div className=\"font-semibold text-text-primary\">{p.displayName}</div>\n <div className=\"text-xs text-text-muted mt-1\">{p.key}</div>\n {p.description && (\n <div className=\"text-xs text-text-secondary mt-2\">{p.description}</div>\n )}\n </button>\n )\n )}\n </div>\n <LabeledField label=\"Agent\">\n <select\n value={state.agentId}\n onChange={(e) => update('agentId', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">Select agent</option>\n {(agentsData?.vibecontrolsAgents ?? []).map((a: { id: string; name: string }) => (\n <option key={a.id} value={a.id}>\n {a.name}\n </option>\n ))}\n </select>\n </LabeledField>\n <LabeledField label=\"Name\">\n <input\n aria-label=\"My dev tunnel\"\n type=\"text\"\n value={state.name}\n onChange={(e) => update('name', e.target.value)}\n placeholder=\"My dev tunnel\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n <LabeledField label=\"Description (optional)\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={state.description}\n onChange={(e) => update('description', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 1 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Protocol & target</h3>\n <LabeledField label=\"Protocol\">\n <div className=\"flex gap-2\">\n {protocolOptions.map((proto) => (\n <button\n key={proto}\n type=\"button\"\n onClick={() => update('protocol', proto)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.protocol === proto\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n {proto}\n </button>\n ))}\n </div>\n </LabeledField>\n <LabeledField label=\"Local host\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={state.localHost}\n onChange={(e) => update('localHost', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Local port\">\n <input\n aria-label=\"control\"\n type=\"number\"\n value={state.localPort}\n onChange={(e) => update('localPort', parseInt(e.target.value, 10) || 0)}\n min={1}\n max={65535}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 2 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Hostname</h3>\n <div className=\"flex gap-2\">\n {caps?.supportsManagedSubdomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'managed'}\n onClick={() => update('hostnameKind', 'managed')}\n label=\"Managed subdomain\"\n hint=\"*.vibetunnels.com\"\n />\n )}\n {caps?.supportsCustomDomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'custom'}\n onClick={() => update('hostnameKind', 'custom')}\n label=\"Custom domain\"\n hint=\"TXT verification\"\n />\n )}\n {supportsAutoHostname && (\n <HostnameKindButton\n active={state.hostnameKind === 'auto'}\n onClick={() => update('hostnameKind', 'auto')}\n label=\"Auto\"\n hint=\"assigned by Cloudflare\"\n />\n )}\n </div>\n\n {state.hostnameKind === 'managed' && (\n <>\n <LabeledField label=\"Environment\">\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Prod)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Prod\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Prod (vibetunnels.com)\n </button>\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Alpha)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Alpha\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Alpha\n </button>\n </div>\n </LabeledField>\n <LabeledField label=\"Subdomain label\">\n <input\n aria-label=\"demo\"\n type=\"text\"\n value={state.subdomain}\n onChange={(e) => update('subdomain', e.target.value.toLowerCase())}\n placeholder=\"demo\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n {state.subdomain && (\n <p className=\"text-xs text-text-secondary\">\n Will be reserved as{' '}\n <span className=\"font-mono text-text-primary\">\n {state.subdomain}\n {state.environment === TunnelEnvironment.Alpha\n ? '.alpha.vibetunnels.com'\n : '.vibetunnels.com'}\n </span>\n </p>\n )}\n </>\n )}\n\n {state.hostnameKind === 'custom' && (\n <LabeledField label=\"Custom domain\">\n <input\n aria-label=\"preview.example.com\"\n type=\"text\"\n value={state.customDomain}\n onChange={(e) => update('customDomain', e.target.value.toLowerCase())}\n placeholder=\"preview.example.com\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"text-xs text-text-muted mt-2\">\n Custom domains are registered on create but must be verified via a TXT record\n before they can be attached. You'll see the verification challenge in the\n Domains tab after creation.\n </p>\n </LabeledField>\n )}\n </div>\n )}\n\n {step === 3 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Policies (optional)</h3>\n <LabeledField label=\"Basic auth user\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={state.basicAuthUser}\n onChange={(e) => update('basicAuthUser', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Basic auth password\">\n <input\n aria-label=\"control\"\n type=\"password\"\n value={state.basicAuthPassword}\n onChange={(e) => update('basicAuthPassword', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LinkToVibePanel value={vibeLink} onChange={setVibeLink} disabled={creating} />\n </div>\n )}\n\n {step === 4 && (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.tunnels.review', 'Review')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Provider</dt>\n <dd className=\"text-text-primary\">{state.providerKey}</dd>\n <dt className=\"text-text-muted\">Name</dt>\n <dd className=\"text-text-primary\">{state.name}</dd>\n <dt className=\"text-text-muted\">Agent</dt>\n <dd className=\"text-text-primary font-mono\">{state.agentId}</dd>\n <dt className=\"text-text-muted\">Protocol</dt>\n <dd className=\"text-text-primary\">{state.protocol}</dd>\n <dt className=\"text-text-muted\">Target</dt>\n <dd className=\"text-text-primary font-mono\">\n {state.localHost}:{state.localPort}\n </dd>\n <dt className=\"text-text-muted\">Hostname</dt>\n <dd className=\"text-text-primary\">\n {state.hostnameKind === 'managed'\n ? `${state.subdomain}.${state.environment === 'ALPHA' ? 'alpha.' : ''}vibetunnels.com`\n : state.hostnameKind === 'custom'\n ? state.customDomain\n : 'auto-assigned'}\n </dd>\n <dt className=\"text-text-muted\">Basic auth</dt>\n <dd className=\"text-text-primary\">{state.basicAuthUser ? 'yes' : 'no'}</dd>\n </dl>\n <div className=\"border-t border-border-subtle pt-3\">\n <h4 className=\"mb-2 text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.tunnels.tags', 'Tags')}\n </h4>\n <EntityTagPicker entityType=\"VIBECONTROLS_TUNNEL\" onDraftChange={setDraftTagIds} />\n </div>\n {submitError && (\n <div className=\"rounded-card border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\">\n {submitError}\n </div>\n )}\n </div>\n )}\n </motion.div>\n </AnimatePresence>\n\n <div className=\"flex items-center justify-between mt-4\">\n <button\n type=\"button\"\n disabled={step === 0}\n onClick={back}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.back', 'Back')}\n </button>\n {step < STEPS.length - 1 ? (\n <button\n type=\"button\"\n disabled={!canAdvance}\n onClick={next}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.next', 'Next')}\n </button>\n ) : (\n <button\n type=\"button\"\n disabled={creating || isFlushingTags}\n onClick={() => void submit()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.tunnels.creating', 'Creating...')\n : tr('vibecontrols.tunnels.createTunnel', 'Create tunnel')}\n </button>\n )}\n </div>\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n {...getQuotaDialogContent('tunnel-subdomain')}\n />\n </div>\n );\n}\n\nfunction LabeledField({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <label className=\"block\">\n <span className=\"text-xs font-medium text-text-secondary\">{label}</span>\n <div className=\"mt-1\">{children}</div>\n </label>\n );\n}\n\nfunction HostnameKindButton({\n active,\n onClick,\n label,\n hint,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n hint: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={`flex-1 rounded-md border p-3 text-left transition-colors ${\n active\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n }`}\n >\n <div className=\"text-sm font-medium text-text-primary\">{label}</div>\n <div className=\"text-xs text-text-muted mt-0.5\">{hint}</div>\n </button>\n );\n}\n"],"mappings":"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"}
|
|
@@ -62,7 +62,7 @@ var f = [
|
|
|
62
62
|
className: "space-y-3",
|
|
63
63
|
children: [
|
|
64
64
|
/* @__PURE__ */ d("div", {
|
|
65
|
-
className: "flex items-center gap-2 rounded-
|
|
65
|
+
className: "flex items-center gap-2 rounded-card border border-status-success-border bg-status-success-bg-subtle px-3 py-2 text-sm text-status-success-text",
|
|
66
66
|
children: [/* @__PURE__ */ u(o, { className: "size-4 shrink-0" }), /* @__PURE__ */ d("span", { children: [
|
|
67
67
|
b("tunPublic.liveUntil", "Public until"),
|
|
68
68
|
" ",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunnelPublicAccessPanel.js","names":[],"sources":["../../../src/components/tunnels/TunnelPublicAccessPanel.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Globe, Copy, Check, Clock, ShieldOff } from 'lucide-react';\n\nimport { notify } from '@/lib/toast';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useSetTunnelPublicAccessMutation,\n useRevokeTunnelPublicAccessMutation,\n} from '@/generated/wspace-operations';\n\ninterface TunnelPublicAccessPanelProps {\n tunnelId: string;\n tunnelStatus: string;\n publicAccessExpiresAt?: string | null;\n publicUrl?: string | null;\n canUpdate: boolean;\n onChanged: () => void;\n}\n\n// TTL choices in seconds, inside the svc's 300 (5m) … 2592000 (30d) bounds.\nconst TTL_OPTIONS = [\n { key: '1h', seconds: 3600 },\n { key: '4h', seconds: 14_400 },\n { key: '24h', seconds: 86_400 },\n { key: '7d', seconds: 604_800 },\n];\n\n/**\n * TunnelPublicAccessPanel — grant a time-boxed public link to a tunnel and have it tear itself down.\n * Picking a window stamps `publicAccessExpiresAt = now + ttl`; a backend sweep stops the tunnel (the\n * public URL goes dead) the moment it lapses, so a \"share my localhost for 4 hours\" or \"open this DB\n * port to a contractor until tonight\" link can't outlive its purpose. Revoke kills it immediately.\n * This is the UI half of the beta-tester-tunnel / local-port-share / database-port-forward stories.\n */\nexport const TunnelPublicAccessPanel: FC<TunnelPublicAccessPanelProps> = ({\n tunnelId,\n tunnelStatus,\n publicAccessExpiresAt,\n publicUrl,\n canUpdate,\n onChanged,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const [ttlSeconds, setTtlSeconds] = useState(TTL_OPTIONS[1].seconds);\n const [copied, setCopied] = useState(false);\n const [setAccess, { loading: granting }] = useSetTunnelPublicAccessMutation();\n const [revokeAccess, { loading: revoking }] = useRevokeTunnelPublicAccessMutation();\n\n const expiresAt = publicAccessExpiresAt ? new Date(publicAccessExpiresAt) : null;\n const isActive = Boolean(expiresAt && expiresAt.getTime() > Date.now());\n const ttlLabel = (key: string): string =>\n ({ '1h': '1 hour', '4h': '4 hours', '24h': '24 hours', '7d': '7 days' })[key] ?? key;\n\n const handleGrant = async (): Promise<void> => {\n try {\n await setAccess({ variables: { tunnelId, ttlSeconds } });\n onChanged();\n notify.success(\n tr('tunPublic.granted', 'Public access granted'),\n tr('tunPublic.grantedDesc', 'The link auto-expires when the window ends.')\n );\n } catch (err) {\n notify.error(tr('tunPublic.grantFailed', 'Could not grant access'), (err as Error).message);\n }\n };\n\n const handleRevoke = async (): Promise<void> => {\n try {\n await revokeAccess({ variables: { tunnelId } });\n onChanged();\n notify.success(tr('tunPublic.revoked', 'Public access revoked — the link is dead'));\n } catch (err) {\n notify.error(tr('tunPublic.revokeFailed', 'Could not revoke'), (err as Error).message);\n }\n };\n\n const copyUrl = async (): Promise<void> => {\n if (!publicUrl) return;\n try {\n await navigator.clipboard.writeText(publicUrl);\n setCopied(true);\n notify.success(tr('tunPublic.copied', 'Public URL copied'));\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n notify.error(tr('tunPublic.copyFailed', 'Could not copy — copy it manually'), publicUrl);\n }\n };\n\n return (\n <section className=\"md:col-span-2 rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"mb-3 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Globe className=\"size-4\" />\n {tr('tunPublic.title', 'Time-limited public access')}\n </h3>\n\n {isActive && expiresAt ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2 rounded-md bg-status-success-bg px-3 py-2 text-sm text-status-success-text\">\n <Clock className=\"size-4 shrink-0\" />\n <span>\n {tr('tunPublic.liveUntil', 'Public until')}{' '}\n <strong>{expiresAt.toLocaleString()}</strong>\n {tunnelStatus !== 'ACTIVE' && (\n <span className=\"ml-1 text-text-tertiary\">\n {tr('tunPublic.notRunning', '(start the tunnel for the URL to serve)')}\n </span>\n )}\n </span>\n </div>\n {publicUrl && (\n <div className=\"flex items-center gap-2 rounded-md border border-border-default px-3 py-2\">\n <span className=\"flex-1 truncate font-mono text-xs text-text-secondary\">\n {publicUrl}\n </span>\n <button\n type=\"button\"\n onClick={() => void copyUrl()}\n className=\"p-1 text-text-secondary hover:text-text-primary\"\n aria-label={tr('common.copy', 'Copy')}\n >\n {copied ? (\n <Check className=\"size-4 text-status-success-text\" />\n ) : (\n <Copy className=\"size-4\" />\n )}\n </button>\n </div>\n )}\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() => void handleRevoke()}\n disabled={revoking}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken disabled:opacity-50\"\n >\n <ShieldOff className=\"size-4\" />\n {tr('tunPublic.revoke', 'Revoke now')}\n </button>\n )}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'tunPublic.off',\n 'Public access is off. Grant a time-boxed window and the link tears itself down when it ends.'\n )}\n </p>\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"flex-1 min-w-[10rem]\">\n <span className=\"text-sm text-text-secondary\">\n {tr('tunPublic.window', 'Access window')}\n </span>\n <select\n value={ttlSeconds}\n onChange={(e) => setTtlSeconds(Number(e.target.value))}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {TTL_OPTIONS.map((o) => (\n <option key={o.key} value={o.seconds}>\n {tr(`tunPublic.ttl.${o.key}`, ttlLabel(o.key))}\n </option>\n ))}\n </select>\n </label>\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() => void handleGrant()}\n disabled={granting}\n className=\"flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n <Globe className=\"size-4\" />\n {tr('tunPublic.grant', 'Grant public access')}\n </button>\n )}\n </div>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TunnelPublicAccessPanel.js","names":[],"sources":["../../../src/components/tunnels/TunnelPublicAccessPanel.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Globe, Copy, Check, Clock, ShieldOff } from 'lucide-react';\n\nimport { notify } from '@/lib/toast';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useSetTunnelPublicAccessMutation,\n useRevokeTunnelPublicAccessMutation,\n} from '@/generated/wspace-operations';\n\ninterface TunnelPublicAccessPanelProps {\n tunnelId: string;\n tunnelStatus: string;\n publicAccessExpiresAt?: string | null;\n publicUrl?: string | null;\n canUpdate: boolean;\n onChanged: () => void;\n}\n\n// TTL choices in seconds, inside the svc's 300 (5m) … 2592000 (30d) bounds.\nconst TTL_OPTIONS = [\n { key: '1h', seconds: 3600 },\n { key: '4h', seconds: 14_400 },\n { key: '24h', seconds: 86_400 },\n { key: '7d', seconds: 604_800 },\n];\n\n/**\n * TunnelPublicAccessPanel — grant a time-boxed public link to a tunnel and have it tear itself down.\n * Picking a window stamps `publicAccessExpiresAt = now + ttl`; a backend sweep stops the tunnel (the\n * public URL goes dead) the moment it lapses, so a \"share my localhost for 4 hours\" or \"open this DB\n * port to a contractor until tonight\" link can't outlive its purpose. Revoke kills it immediately.\n * This is the UI half of the beta-tester-tunnel / local-port-share / database-port-forward stories.\n */\nexport const TunnelPublicAccessPanel: FC<TunnelPublicAccessPanelProps> = ({\n tunnelId,\n tunnelStatus,\n publicAccessExpiresAt,\n publicUrl,\n canUpdate,\n onChanged,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const [ttlSeconds, setTtlSeconds] = useState(TTL_OPTIONS[1].seconds);\n const [copied, setCopied] = useState(false);\n const [setAccess, { loading: granting }] = useSetTunnelPublicAccessMutation();\n const [revokeAccess, { loading: revoking }] = useRevokeTunnelPublicAccessMutation();\n\n const expiresAt = publicAccessExpiresAt ? new Date(publicAccessExpiresAt) : null;\n const isActive = Boolean(expiresAt && expiresAt.getTime() > Date.now());\n const ttlLabel = (key: string): string =>\n ({ '1h': '1 hour', '4h': '4 hours', '24h': '24 hours', '7d': '7 days' })[key] ?? key;\n\n const handleGrant = async (): Promise<void> => {\n try {\n await setAccess({ variables: { tunnelId, ttlSeconds } });\n onChanged();\n notify.success(\n tr('tunPublic.granted', 'Public access granted'),\n tr('tunPublic.grantedDesc', 'The link auto-expires when the window ends.')\n );\n } catch (err) {\n notify.error(tr('tunPublic.grantFailed', 'Could not grant access'), (err as Error).message);\n }\n };\n\n const handleRevoke = async (): Promise<void> => {\n try {\n await revokeAccess({ variables: { tunnelId } });\n onChanged();\n notify.success(tr('tunPublic.revoked', 'Public access revoked — the link is dead'));\n } catch (err) {\n notify.error(tr('tunPublic.revokeFailed', 'Could not revoke'), (err as Error).message);\n }\n };\n\n const copyUrl = async (): Promise<void> => {\n if (!publicUrl) return;\n try {\n await navigator.clipboard.writeText(publicUrl);\n setCopied(true);\n notify.success(tr('tunPublic.copied', 'Public URL copied'));\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n notify.error(tr('tunPublic.copyFailed', 'Could not copy — copy it manually'), publicUrl);\n }\n };\n\n return (\n <section className=\"md:col-span-2 rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"mb-3 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Globe className=\"size-4\" />\n {tr('tunPublic.title', 'Time-limited public access')}\n </h3>\n\n {isActive && expiresAt ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2 rounded-card border border-status-success-border bg-status-success-bg-subtle px-3 py-2 text-sm text-status-success-text\">\n <Clock className=\"size-4 shrink-0\" />\n <span>\n {tr('tunPublic.liveUntil', 'Public until')}{' '}\n <strong>{expiresAt.toLocaleString()}</strong>\n {tunnelStatus !== 'ACTIVE' && (\n <span className=\"ml-1 text-text-tertiary\">\n {tr('tunPublic.notRunning', '(start the tunnel for the URL to serve)')}\n </span>\n )}\n </span>\n </div>\n {publicUrl && (\n <div className=\"flex items-center gap-2 rounded-md border border-border-default px-3 py-2\">\n <span className=\"flex-1 truncate font-mono text-xs text-text-secondary\">\n {publicUrl}\n </span>\n <button\n type=\"button\"\n onClick={() => void copyUrl()}\n className=\"p-1 text-text-secondary hover:text-text-primary\"\n aria-label={tr('common.copy', 'Copy')}\n >\n {copied ? (\n <Check className=\"size-4 text-status-success-text\" />\n ) : (\n <Copy className=\"size-4\" />\n )}\n </button>\n </div>\n )}\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() => void handleRevoke()}\n disabled={revoking}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken disabled:opacity-50\"\n >\n <ShieldOff className=\"size-4\" />\n {tr('tunPublic.revoke', 'Revoke now')}\n </button>\n )}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'tunPublic.off',\n 'Public access is off. Grant a time-boxed window and the link tears itself down when it ends.'\n )}\n </p>\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"flex-1 min-w-[10rem]\">\n <span className=\"text-sm text-text-secondary\">\n {tr('tunPublic.window', 'Access window')}\n </span>\n <select\n value={ttlSeconds}\n onChange={(e) => setTtlSeconds(Number(e.target.value))}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {TTL_OPTIONS.map((o) => (\n <option key={o.key} value={o.seconds}>\n {tr(`tunPublic.ttl.${o.key}`, ttlLabel(o.key))}\n </option>\n ))}\n </select>\n </label>\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() => void handleGrant()}\n disabled={granting}\n className=\"flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n <Globe className=\"size-4\" />\n {tr('tunPublic.grant', 'Grant public access')}\n </button>\n )}\n </div>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as e } from "react/jsx-runtime";
|
|
2
2
|
//#region src/components/tunnels/TunnelStatusBadge.tsx
|
|
3
3
|
var t = {
|
|
4
|
-
ACTIVE: "bg-status-success-bg text-status-success-
|
|
4
|
+
ACTIVE: "bg-status-success-bg-subtle text-status-success-text",
|
|
5
5
|
DRAFT: "bg-bg-sunken text-text-secondary",
|
|
6
|
-
PROVISIONING: "bg-status-warning-bg text-status-warning-
|
|
7
|
-
DEGRADED: "bg-status-warning-bg text-status-warning-
|
|
6
|
+
PROVISIONING: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
7
|
+
DEGRADED: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
8
8
|
STOPPED: "bg-bg-sunken text-text-muted",
|
|
9
|
-
ERROR: "bg-status-error-bg text-status-error-
|
|
9
|
+
ERROR: "bg-status-error-bg-subtle text-status-error-text",
|
|
10
10
|
DELETING: "bg-bg-sunken text-text-muted"
|
|
11
11
|
};
|
|
12
12
|
function n({ status: n }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunnelStatusBadge.js","names":[],"sources":["../../../src/components/tunnels/TunnelStatusBadge.tsx"],"sourcesContent":["import type { TunnelStatus } from '@/generated/wspace-types';\n\nconst STATUS_STYLES: Record<string, string> = {\n ACTIVE: 'bg-status-success-bg text-status-success-
|
|
1
|
+
{"version":3,"file":"TunnelStatusBadge.js","names":[],"sources":["../../../src/components/tunnels/TunnelStatusBadge.tsx"],"sourcesContent":["import type { TunnelStatus } from '@/generated/wspace-types';\n\nconst STATUS_STYLES: Record<string, string> = {\n ACTIVE: 'bg-status-success-bg-subtle text-status-success-text',\n DRAFT: 'bg-bg-sunken text-text-secondary',\n PROVISIONING: 'bg-status-warning-bg-subtle text-status-warning-text',\n DEGRADED: 'bg-status-warning-bg-subtle text-status-warning-text',\n STOPPED: 'bg-bg-sunken text-text-muted',\n ERROR: 'bg-status-error-bg-subtle text-status-error-text',\n DELETING: 'bg-bg-sunken text-text-muted',\n};\n\nexport function TunnelStatusBadge({ status }: { status: TunnelStatus | string }) {\n const style = STATUS_STYLES[status] ?? 'bg-bg-sunken text-text-secondary';\n return (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${style}`}\n >\n {status}\n </span>\n );\n}\n"],"mappings":";;AAEA,IAAM,IAAwC;CAC5C,QAAQ;CACR,OAAO;CACP,cAAc;CACd,UAAU;CACV,SAAS;CACT,OAAO;CACP,UAAU;CACX;AAED,SAAgB,EAAkB,EAAE,aAA6C;AAE/E,QACE,kBAAC,QAAD;EACE,WAAW,yEAHD,EAAc,MAAW;YAKlC;EACI,CAAA"}
|
|
@@ -29,7 +29,7 @@ function i({ isOpen: i, onClose: a, execution: o, taskTitle: s, agentId: c }) {
|
|
|
29
29
|
})] }), /* @__PURE__ */ n("button", {
|
|
30
30
|
type: "button",
|
|
31
31
|
onClick: a,
|
|
32
|
-
className: "p-1 rounded hover:bg-bg-
|
|
32
|
+
className: "p-1 rounded hover:bg-bg-surface",
|
|
33
33
|
children: /* @__PURE__ */ n(t, { size: 18 })
|
|
34
34
|
})]
|
|
35
35
|
}), /* @__PURE__ */ n("div", {
|
|
@@ -82,7 +82,7 @@ function i({ isOpen: i, onClose: a, execution: o, taskTitle: s, agentId: c }) {
|
|
|
82
82
|
className: "text-sm font-medium text-status-error-text mb-1",
|
|
83
83
|
children: "Error"
|
|
84
84
|
}), /* @__PURE__ */ n("pre", {
|
|
85
|
-
className: "bg-status-error-bg
|
|
85
|
+
className: "bg-status-error-bg-subtle border border-status-error-border/20 rounded-lg p-3 text-xs font-mono text-status-error-text overflow-auto max-h-40 whitespace-pre-wrap",
|
|
86
86
|
children: f
|
|
87
87
|
})] })
|
|
88
88
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogsDrawer.js","names":[],"sources":["../../../src/components/vibecalendar/LogsDrawer.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { X, Loader2 } from 'lucide-react';\nimport type { VibeCalendarExecution } from './types';\n\ninterface LogsDrawerProps {\n isOpen: boolean;\n onClose: () => void;\n execution: VibeCalendarExecution | null;\n taskTitle: string;\n agentId: string;\n}\n\nexport function LogsDrawer({\n isOpen,\n onClose,\n execution,\n taskTitle,\n agentId: _agentId,\n}: LogsDrawerProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n // Agent task logs are no longer fetched directly from the calendar —\n // tasks reference a VibeControls Action and execution details come\n // from the scheduler's execution records. Show what the execution\n // record carries (output/error) and defer richer logs to a future\n // Action-aware logs query.\n const logsLoading = false;\n\n if (!isOpen) return null;\n\n // Execution records from the new simplified schema carry a single\n // `output` field (stdout + stderr combined) and an `error` field.\n const stdout = execution?.output ?? null;\n const stderr: string | null = null;\n const exitCode = execution?.exitCode ?? null;\n const error = execution?.error ?? null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex justify-end\">\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (onClose as (...a: unknown[]) => unknown)();\n }\n }}\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={onClose}\n />\n <div className=\"relative w-full max-w-full sm:max-w-2xl bg-bg-surface border-l border-border-default shadow-xl flex flex-col\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Task Logs</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">{taskTitle}</p>\n </div>\n <button type=\"button\" onClick={onClose} className=\"p-1 rounded hover:bg-bg-
|
|
1
|
+
{"version":3,"file":"LogsDrawer.js","names":[],"sources":["../../../src/components/vibecalendar/LogsDrawer.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { X, Loader2 } from 'lucide-react';\nimport type { VibeCalendarExecution } from './types';\n\ninterface LogsDrawerProps {\n isOpen: boolean;\n onClose: () => void;\n execution: VibeCalendarExecution | null;\n taskTitle: string;\n agentId: string;\n}\n\nexport function LogsDrawer({\n isOpen,\n onClose,\n execution,\n taskTitle,\n agentId: _agentId,\n}: LogsDrawerProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n // Agent task logs are no longer fetched directly from the calendar —\n // tasks reference a VibeControls Action and execution details come\n // from the scheduler's execution records. Show what the execution\n // record carries (output/error) and defer richer logs to a future\n // Action-aware logs query.\n const logsLoading = false;\n\n if (!isOpen) return null;\n\n // Execution records from the new simplified schema carry a single\n // `output` field (stdout + stderr combined) and an `error` field.\n const stdout = execution?.output ?? null;\n const stderr: string | null = null;\n const exitCode = execution?.exitCode ?? null;\n const error = execution?.error ?? null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex justify-end\">\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (onClose as (...a: unknown[]) => unknown)();\n }\n }}\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={onClose}\n />\n <div className=\"relative w-full max-w-full sm:max-w-2xl bg-bg-surface border-l border-border-default shadow-xl flex flex-col\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Task Logs</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">{taskTitle}</p>\n </div>\n <button type=\"button\" onClick={onClose} className=\"p-1 rounded hover:bg-bg-surface\">\n <X size={18} />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-auto p-6\">\n {logsLoading ? (\n <div className=\"flex items-center justify-center h-full gap-2\">\n <Loader2 size={16} className=\"animate-spin text-text-tertiary\" />\n <p className=\"text-sm text-text-secondary\">Fetching logs…</p>\n </div>\n ) : execution ? (\n <div className=\"space-y-4\">\n {/* Execution Info */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <span className=\"text-xs text-text-tertiary\">Status</span>\n <p className=\"text-sm font-medium text-text-primary\">{execution.status}</p>\n </div>\n <div>\n <span className=\"text-xs text-text-tertiary\">Started At</span>\n <p className=\"text-sm text-text-primary\">\n {execution.startedAt\n ? new Date(execution.startedAt).toLocaleString()\n : 'Unavailable'}\n </p>\n </div>\n {execution.completedAt && (\n <div>\n <span className=\"text-xs text-text-tertiary\">Completed At</span>\n <p className=\"text-sm text-text-primary\">\n {new Date(execution.completedAt).toLocaleString()}\n </p>\n </div>\n )}\n {exitCode !== null && exitCode !== undefined && (\n <div>\n <span className=\"text-xs text-text-tertiary\">Exit Code</span>\n <p\n className={`text-sm font-mono ${exitCode === 0 ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {exitCode}\n </p>\n </div>\n )}\n </div>\n\n {/* stdout */}\n {stdout && (\n <div>\n <h3 className=\"text-sm font-medium text-text-secondary mb-1\">stdout</h3>\n <pre className=\"bg-bg-sunken border border-border-default rounded-lg p-3 text-xs font-mono text-text-primary overflow-auto max-h-60 whitespace-pre-wrap\">\n {stdout}\n </pre>\n </div>\n )}\n\n {/* stderr */}\n {stderr && (\n <div>\n <h3 className=\"text-sm font-medium text-status-error-text mb-1\">stderr</h3>\n <pre className=\"bg-status-error-bg-subtle border border-status-error-border/20 rounded-lg p-3 text-xs font-mono text-status-error-text overflow-auto max-h-60 whitespace-pre-wrap\">\n {stderr}\n </pre>\n </div>\n )}\n\n {/* Error */}\n {error && (\n <div>\n <h3 className=\"text-sm font-medium text-status-error-text mb-1\">Error</h3>\n <pre className=\"bg-status-error-bg-subtle border border-status-error-border/20 rounded-lg p-3 text-xs font-mono text-status-error-text overflow-auto max-h-40 whitespace-pre-wrap\">\n {error}\n </pre>\n </div>\n )}\n </div>\n ) : (\n <div className=\"flex items-center justify-center h-full\">\n <p className=\"text-sm text-text-secondary\">No execution data available</p>\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;AAYA,SAAgB,EAAW,EACzB,WACA,YACA,cACA,cACA,SAAS,KACS;CAClB,IAAM,EAAE,SAAM,GAAS;AAYvB,KAAI,CAAC,EAAQ,QAAO;CAIpB,IAAM,IAAS,GAAW,UAAU,MAE9B,IAAW,GAAW,YAAY,MAClC,IAAQ,GAAW,SAAS;AAElC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GACE,MAAK;GACL,UAAU;GACV,YAAY,MAAM;AAChB,KAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EACjB,GAA0C;;GAG/C,WAAU;GACV,SAAS;GACT,CAAA,EACF,kBAAC,OAAD;GAAK,WAAU;aAAf,CAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;KAAc,CAAA,EACtE,kBAAC,KAAD;KAAG,WAAU;eAAsC;KAAc,CAAA,CAC7D,EAAA,CAAA,EACN,kBAAC,UAAD;KAAQ,MAAK;KAAS,SAAS;KAAS,WAAU;eAChD,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA;KACR,CAAA,CACL;OAGN,kBAAC,OAAD;IAAK,WAAU;cAMT,IACF,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA6B;SAAa,CAAA,EAC1D,kBAAC,KAAD;SAAG,WAAU;mBAAyC,EAAU;SAAW,CAAA,CACvE,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA6B;SAAiB,CAAA,EAC9D,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAU,YACP,IAAI,KAAK,EAAU,UAAU,CAAC,gBAAgB,GAC9C;SACF,CAAA,CACA,EAAA,CAAA;QACL,EAAU,eACT,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA6B;SAAmB,CAAA,EAChE,kBAAC,KAAD;SAAG,WAAU;mBACV,IAAI,KAAK,EAAU,YAAY,CAAC,gBAAgB;SAC/C,CAAA,CACA,EAAA,CAAA;QAEP,KAAa,QACZ,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA6B;SAAgB,CAAA,EAC7D,kBAAC,KAAD;SACE,WAAW,qBAAqB,MAAa,IAAI,6BAA6B;mBAE7E;SACC,CAAA,CACA,EAAA,CAAA;QAEJ;;MAGL,KACC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA+C;OAAW,CAAA,EACxE,kBAAC,OAAD;OAAK,WAAU;iBACZ;OACG,CAAA,CACF,EAAA,CAAA;MAIP;MAUA,KACC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAkD;OAAU,CAAA,EAC1E,kBAAC,OAAD;OAAK,WAAU;iBACZ;OACG,CAAA,CACF,EAAA,CAAA;MAEJ;SAEN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAA+B,CAAA;KACtE,CAAA;IAEJ,CAAA,CACF;KACF"}
|
|
@@ -5,7 +5,7 @@ import { useMemo as r, useState as i } from "react";
|
|
|
5
5
|
import { Search as a, X as o, Zap as s } from "lucide-react";
|
|
6
6
|
import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/vibecalendar/ScheduleTaskModal.tsx
|
|
8
|
-
var d = "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-lg text-sm text-text-primary placeholder-text-placeholder focus:outline-none focus:border-border-
|
|
8
|
+
var d = "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-lg text-sm text-text-primary placeholder-text-placeholder focus:outline-none focus:border-border-default", f = `${d} font-mono`, p = "block text-sm font-medium text-text-secondary mb-1";
|
|
9
9
|
function m({ isOpen: m, onClose: h, onSubmit: g, loading: _, prefillActionId: v }) {
|
|
10
10
|
let y = t(), [b, x] = i(v ?? ""), [S, C] = i(""), [w, T] = i(""), [E, D] = i(""), [O, k] = i("ONE_TIME"), [A, j] = i(""), [M, N] = i(""), [P, F] = i(60), [I, L] = i("minutes"), [R, z] = i(Intl.DateTimeFormat().resolvedOptions().timeZone), { data: B, loading: V } = e({
|
|
11
11
|
variables: { pagination: { limit: 100 } },
|
|
@@ -38,7 +38,7 @@ function m({ isOpen: m, onClose: h, onSubmit: g, loading: _, prefillActionId: v
|
|
|
38
38
|
}), /* @__PURE__ */ l("button", {
|
|
39
39
|
type: "button",
|
|
40
40
|
onClick: h,
|
|
41
|
-
className: "p-1 rounded hover:bg-bg-
|
|
41
|
+
className: "p-1 rounded hover:bg-bg-surface",
|
|
42
42
|
children: /* @__PURE__ */ l(o, { size: 18 })
|
|
43
43
|
})]
|
|
44
44
|
}), /* @__PURE__ */ u("form", {
|
|
@@ -70,7 +70,7 @@ function m({ isOpen: m, onClose: h, onSubmit: g, loading: _, prefillActionId: v
|
|
|
70
70
|
children: "Calendar entries trigger VibeControls Actions. Agent, command, and webhook details come from the Action definition."
|
|
71
71
|
}),
|
|
72
72
|
G && W && /* @__PURE__ */ u("div", {
|
|
73
|
-
className: "flex items-start gap-3 p-3 rounded-lg border border-border-default bg-bg-
|
|
73
|
+
className: "flex items-start gap-3 p-3 rounded-lg border border-border-default bg-bg-surface",
|
|
74
74
|
children: [/* @__PURE__ */ l("div", {
|
|
75
75
|
className: "p-1.5 rounded-md bg-primary/10 text-primary flex-shrink-0",
|
|
76
76
|
children: /* @__PURE__ */ l(s, { size: 16 })
|
|
@@ -97,7 +97,7 @@ function m({ isOpen: m, onClose: h, onSubmit: g, loading: _, prefillActionId: v
|
|
|
97
97
|
})]
|
|
98
98
|
}),
|
|
99
99
|
G && !W && V && /* @__PURE__ */ l("div", {
|
|
100
|
-
className: "p-3 rounded-lg border border-border-default bg-bg-
|
|
100
|
+
className: "p-3 rounded-lg border border-border-default bg-bg-surface text-xs text-text-secondary",
|
|
101
101
|
children: "Loading action…"
|
|
102
102
|
}),
|
|
103
103
|
G && !W && !V && /* @__PURE__ */ l("div", {
|
|
@@ -141,7 +141,7 @@ function m({ isOpen: m, onClose: h, onSubmit: g, loading: _, prefillActionId: v
|
|
|
141
141
|
return /* @__PURE__ */ l("button", {
|
|
142
142
|
type: "button",
|
|
143
143
|
onClick: () => x(e.id),
|
|
144
|
-
className: `w-full text-left px-3 py-2.5 hover:bg-bg-
|
|
144
|
+
className: `w-full text-left px-3 py-2.5 hover:bg-bg-surface transition-colors ${t ? "bg-primary/5" : ""}`,
|
|
145
145
|
children: /* @__PURE__ */ u("div", {
|
|
146
146
|
className: "flex items-start gap-2.5",
|
|
147
147
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScheduleTaskModal.js","names":[],"sources":["../../../src/components/vibecalendar/ScheduleTaskModal.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { X, Zap, Search } from 'lucide-react';\nimport { useListActionsQuery } from '@/generated/wspace-operations';\nimport type { Action } from '@/generated/wspace-types';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ActionAgentTargetNotice } from '../common/ActionAgentTargetNotice';\n\ntype ActionPickItem = Pick<\n Action,\n | 'id'\n | 'name'\n | 'displayName'\n | 'description'\n | 'actionType'\n | 'triggerType'\n | 'targetAgentId'\n | 'targetPlatformFilter'\n>;\n\ninterface ScheduleTaskModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (input: ScheduleTaskInput) => void;\n // Legacy prop kept for compatibility; unused in Action-only mode.\n agents?: Array<{ id: string; name: string }>;\n loading?: boolean;\n // When provided, the picker is locked to this action (user navigated\n // in from \"Add to calendar\" on the Actions page). Otherwise the user\n // picks from a searchable dropdown of their actions.\n prefillActionId?: string;\n}\n\n/**\n * Calendar entries always delegate execution to a VibeControls Action.\n * Agent, command, script, and webhook fields come from the Action\n * definition at execution time — this modal only collects the calendar\n * metadata and the schedule.\n */\nexport interface ScheduleTaskInput {\n calendarId: string;\n title: string;\n description?: string;\n scheduleType: 'ONE_TIME' | 'CRON' | 'RRULE' | 'FIXED_INTERVAL';\n scheduledAt?: string;\n cronExpression?: string;\n rrule?: string;\n interval?: number;\n intervalUnit?: string;\n timezone: string;\n actionId: string;\n}\n\nconst INPUT_CLASS =\n 'w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-lg text-sm text-text-primary placeholder-text-placeholder focus:outline-none focus:border-border-primary';\nconst MONO_INPUT_CLASS = `${INPUT_CLASS} font-mono`;\nconst LABEL_CLASS = 'block text-sm font-medium text-text-secondary mb-1';\n\nexport function ScheduleTaskModal({\n isOpen,\n onClose,\n onSubmit,\n loading,\n prefillActionId,\n}: ScheduleTaskModalProps) {\n const tr = useTr();\n const [selectedActionId, setSelectedActionId] = useState<string>(prefillActionId ?? '');\n const [actionSearch, setActionSearch] = useState('');\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [scheduleType, setScheduleType] = useState<\n 'ONE_TIME' | 'CRON' | 'RRULE' | 'FIXED_INTERVAL'\n >('ONE_TIME');\n const [scheduledAt, setScheduledAt] = useState('');\n const [cronExpression, setCronExpression] = useState('');\n const [interval, setInterval] = useState(60);\n const [intervalUnit, setIntervalUnit] = useState('minutes');\n const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone);\n\n const { data: actionsData, loading: actionsLoading } = useListActionsQuery({\n variables: { pagination: { limit: 100 } },\n skip: !isOpen,\n fetchPolicy: 'cache-and-network',\n });\n\n const allActions = useMemo<ActionPickItem[]>(\n () => (actionsData?.actions?.items ?? []) as ActionPickItem[],\n [actionsData?.actions?.items]\n );\n\n const filteredActions = useMemo(() => {\n if (!actionSearch.trim()) return allActions;\n const q = actionSearch.trim().toLowerCase();\n return allActions.filter(\n (a: ActionPickItem) =>\n a.name?.toLowerCase().includes(q) ||\n a.displayName?.toLowerCase().includes(q) ||\n a.description?.toLowerCase().includes(q)\n );\n }, [allActions, actionSearch]);\n\n const selectedAction = useMemo(\n () => allActions.find((a: ActionPickItem) => a.id === selectedActionId),\n [allActions, selectedActionId]\n );\n\n if (!isOpen) return null;\n\n const isActionLocked = Boolean(prefillActionId);\n const canSubmit = Boolean(selectedActionId) && !loading;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!selectedActionId) return;\n onSubmit({\n calendarId: '',\n title,\n description: description || undefined,\n scheduleType,\n scheduledAt: scheduleType === 'ONE_TIME' ? new Date(scheduledAt).toISOString() : undefined,\n cronExpression: scheduleType === 'CRON' ? cronExpression : undefined,\n interval: scheduleType === 'FIXED_INTERVAL' ? interval : undefined,\n intervalUnit: scheduleType === 'FIXED_INTERVAL' ? intervalUnit : undefined,\n timezone,\n actionId: selectedActionId,\n });\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (onClose as (...a: unknown[]) => unknown)();\n }\n }}\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={onClose}\n />\n <div className=\"relative bg-bg-surface border border-border-default rounded-xl shadow-xl w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {isActionLocked\n ? tr('vibecontrols.calendar.schedule.addActionToCalendar', 'Add Action to Calendar')\n : tr(\n 'vibecontrols.calendar.schedule.scheduleActionOnCalendar',\n 'Schedule Action on Calendar'\n )}\n </h2>\n <button type=\"button\" onClick={onClose} className=\"p-1 rounded hover:bg-bg-secondary\">\n <X size={18} />\n </button>\n </div>\n\n {/* Form */}\n <form onSubmit={handleSubmit} className=\"px-6 py-4 space-y-4\">\n {/* Action Picker — always required */}\n <div className=\"space-y-2\">\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.actionToSchedule', 'Action to Schedule')} *\n </label>\n <p className=\"text-xs text-text-tertiary -mt-1\">\n Calendar entries trigger VibeControls Actions. Agent, command, and webhook details\n come from the Action definition.\n </p>\n\n {isActionLocked && selectedAction && (\n <div className=\"flex items-start gap-3 p-3 rounded-lg border border-border-default bg-bg-secondary\">\n <div className=\"p-1.5 rounded-md bg-primary/10 text-primary flex-shrink-0\">\n <Zap size={16} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedAction.displayName || selectedAction.name}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {selectedAction.actionType} · {selectedAction.triggerType}\n </p>\n {selectedAction.description && (\n <p className=\"text-xs text-text-tertiary mt-1 line-clamp-2\">\n {selectedAction.description}\n </p>\n )}\n </div>\n </div>\n )}\n\n {isActionLocked && !selectedAction && actionsLoading && (\n <div className=\"p-3 rounded-lg border border-border-default bg-bg-secondary text-xs text-text-secondary\">\n Loading action…\n </div>\n )}\n\n {isActionLocked && !selectedAction && !actionsLoading && (\n <div className=\"p-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle text-xs text-status-error-text\">\n Action not found. It may have been deleted.\n </div>\n )}\n\n {!isActionLocked && (\n <>\n <div className=\"relative\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-tertiary\"\n />\n <input\n aria-label=\"Search actions by name…\"\n type=\"text\"\n value={actionSearch}\n onChange={(e) => setActionSearch(e.target.value)}\n placeholder=\"Search actions by name…\"\n className={`${INPUT_CLASS} pl-9`}\n />\n </div>\n\n <div className=\"max-h-40 overflow-y-auto border border-border-default rounded-lg divide-y divide-border-default\">\n {actionsLoading && allActions.length === 0 && (\n <div className=\"px-3 py-4 text-xs text-text-secondary text-center\">\n Loading actions…\n </div>\n )}\n {!actionsLoading && allActions.length === 0 && (\n <div className=\"px-3 py-4 text-xs text-text-secondary text-center\">\n No actions yet. Create an Action in VibeControls first, then come back to\n schedule it.\n </div>\n )}\n {!actionsLoading && allActions.length > 0 && filteredActions.length === 0 && (\n <div className=\"px-3 py-4 text-xs text-text-secondary text-center\">\n No actions match \"{actionSearch}\".\n </div>\n )}\n {filteredActions.map((action: ActionPickItem) => {\n const isSelected = action.id === selectedActionId;\n return (\n <button\n key={action.id}\n type=\"button\"\n onClick={() => setSelectedActionId(action.id)}\n className={`w-full text-left px-3 py-2.5 hover:bg-bg-secondary transition-colors ${\n isSelected ? 'bg-primary/5' : ''\n }`}\n >\n <div className=\"flex items-start gap-2.5\">\n <div\n className={`p-1 rounded-md flex-shrink-0 ${\n isSelected\n ? 'bg-primary/10 text-primary'\n : 'bg-bg-sunken text-text-tertiary'\n }`}\n >\n <Zap size={14} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {action.displayName || action.name}\n </p>\n <p className=\"text-xs text-text-tertiary mt-0.5\">\n {action.actionType} · {action.triggerType}\n </p>\n </div>\n {isSelected && (\n <span className=\"text-xs font-medium text-primary flex-shrink-0\">\n Selected\n </span>\n )}\n </div>\n </button>\n );\n })}\n </div>\n </>\n )}\n\n {/* Tell the user which agent this action runs on, and warn if that\n agent is unhealthy or gone — before the schedule is created. */}\n {selectedAction && <ActionAgentTargetNotice action={selectedAction} />}\n </div>\n\n {/* Title */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.title', 'Title')}\n </label>\n <input\n aria-label=\"e.g. Run test suite\"\n type=\"text\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Run test suite\"\n className={INPUT_CLASS}\n required\n />\n </div>\n\n {/* Description */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.description', 'Description')}\n </label>\n <input\n aria-label=\"Optional description\"\n type=\"text\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"Optional description\"\n className={INPUT_CLASS}\n />\n </div>\n\n {/* Schedule Type */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.schedule', 'Schedule')}\n </label>\n <select\n value={scheduleType}\n onChange={(e) => setScheduleType(e.target.value as typeof scheduleType)}\n className={INPUT_CLASS}\n >\n <option value=\"ONE_TIME\">One Time</option>\n <option value=\"CRON\">Cron Expression</option>\n <option value=\"FIXED_INTERVAL\">Fixed Interval</option>\n </select>\n </div>\n\n {/* Schedule-specific fields */}\n {scheduleType === 'ONE_TIME' && (\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.runAt', 'Run At')}\n </label>\n <input\n aria-label=\"control\"\n type=\"datetime-local\"\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n className={INPUT_CLASS}\n required\n />\n </div>\n )}\n\n {scheduleType === 'CRON' && (\n <div>\n <label htmlFor=\"scheduletaskmodal-40\" className={LABEL_CLASS}>\n Cron Expression\n </label>\n <input\n id=\"scheduletaskmodal-40\"\n aria-label=\"*/5 * * * * (every 5 minutes)\"\n type=\"text\"\n value={cronExpression}\n onChange={(e) => setCronExpression(e.target.value)}\n placeholder=\"*/5 * * * * (every 5 minutes)\"\n className={MONO_INPUT_CLASS}\n required\n />\n </div>\n )}\n\n {scheduleType === 'FIXED_INTERVAL' && (\n <div className=\"flex gap-2\">\n <div className=\"flex-1\">\n <label htmlFor=\"scheduletaskmodal-39\" className={LABEL_CLASS}>\n Every\n </label>\n <input\n id=\"scheduletaskmodal-39\"\n aria-label=\"control\"\n type=\"number\"\n value={interval}\n onChange={(e) => setInterval(Number(e.target.value))}\n min={1}\n className={INPUT_CLASS}\n required\n />\n </div>\n <div className=\"flex-1\">\n <label htmlFor=\"scheduletaskmodal-38\" className={LABEL_CLASS}>\n Unit\n </label>\n <select\n id=\"scheduletaskmodal-38\"\n value={intervalUnit}\n onChange={(e) => setIntervalUnit(e.target.value)}\n className={INPUT_CLASS}\n >\n <option value=\"seconds\">Seconds</option>\n <option value=\"minutes\">Minutes</option>\n <option value=\"hours\">Hours</option>\n <option value=\"days\">Days</option>\n </select>\n </div>\n </div>\n )}\n\n {/* Timezone */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.timezone', 'Timezone')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={timezone}\n onChange={(e) => setTimezone(e.target.value)}\n className={INPUT_CLASS}\n />\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-default\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-4 py-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr('vibecontrols.calendar.schedule.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={!canSubmit}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed\"\n title={\n !selectedActionId\n ? tr('vibecontrols.calendar.schedule.selectActionFirst', 'Select an Action first')\n : undefined\n }\n >\n {loading\n ? tr('vibecontrols.calendar.schedule.creating', 'Creating...')\n : tr('vibecontrols.calendar.schedule.scheduleBtn', 'Schedule')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ScheduleTaskModal.js","names":[],"sources":["../../../src/components/vibecalendar/ScheduleTaskModal.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { X, Zap, Search } from 'lucide-react';\nimport { useListActionsQuery } from '@/generated/wspace-operations';\nimport type { Action } from '@/generated/wspace-types';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ActionAgentTargetNotice } from '../common/ActionAgentTargetNotice';\n\ntype ActionPickItem = Pick<\n Action,\n | 'id'\n | 'name'\n | 'displayName'\n | 'description'\n | 'actionType'\n | 'triggerType'\n | 'targetAgentId'\n | 'targetPlatformFilter'\n>;\n\ninterface ScheduleTaskModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (input: ScheduleTaskInput) => void;\n // Legacy prop kept for compatibility; unused in Action-only mode.\n agents?: Array<{ id: string; name: string }>;\n loading?: boolean;\n // When provided, the picker is locked to this action (user navigated\n // in from \"Add to calendar\" on the Actions page). Otherwise the user\n // picks from a searchable dropdown of their actions.\n prefillActionId?: string;\n}\n\n/**\n * Calendar entries always delegate execution to a VibeControls Action.\n * Agent, command, script, and webhook fields come from the Action\n * definition at execution time — this modal only collects the calendar\n * metadata and the schedule.\n */\nexport interface ScheduleTaskInput {\n calendarId: string;\n title: string;\n description?: string;\n scheduleType: 'ONE_TIME' | 'CRON' | 'RRULE' | 'FIXED_INTERVAL';\n scheduledAt?: string;\n cronExpression?: string;\n rrule?: string;\n interval?: number;\n intervalUnit?: string;\n timezone: string;\n actionId: string;\n}\n\nconst INPUT_CLASS =\n 'w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-lg text-sm text-text-primary placeholder-text-placeholder focus:outline-none focus:border-border-default';\nconst MONO_INPUT_CLASS = `${INPUT_CLASS} font-mono`;\nconst LABEL_CLASS = 'block text-sm font-medium text-text-secondary mb-1';\n\nexport function ScheduleTaskModal({\n isOpen,\n onClose,\n onSubmit,\n loading,\n prefillActionId,\n}: ScheduleTaskModalProps) {\n const tr = useTr();\n const [selectedActionId, setSelectedActionId] = useState<string>(prefillActionId ?? '');\n const [actionSearch, setActionSearch] = useState('');\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [scheduleType, setScheduleType] = useState<\n 'ONE_TIME' | 'CRON' | 'RRULE' | 'FIXED_INTERVAL'\n >('ONE_TIME');\n const [scheduledAt, setScheduledAt] = useState('');\n const [cronExpression, setCronExpression] = useState('');\n const [interval, setInterval] = useState(60);\n const [intervalUnit, setIntervalUnit] = useState('minutes');\n const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone);\n\n const { data: actionsData, loading: actionsLoading } = useListActionsQuery({\n variables: { pagination: { limit: 100 } },\n skip: !isOpen,\n fetchPolicy: 'cache-and-network',\n });\n\n const allActions = useMemo<ActionPickItem[]>(\n () => (actionsData?.actions?.items ?? []) as ActionPickItem[],\n [actionsData?.actions?.items]\n );\n\n const filteredActions = useMemo(() => {\n if (!actionSearch.trim()) return allActions;\n const q = actionSearch.trim().toLowerCase();\n return allActions.filter(\n (a: ActionPickItem) =>\n a.name?.toLowerCase().includes(q) ||\n a.displayName?.toLowerCase().includes(q) ||\n a.description?.toLowerCase().includes(q)\n );\n }, [allActions, actionSearch]);\n\n const selectedAction = useMemo(\n () => allActions.find((a: ActionPickItem) => a.id === selectedActionId),\n [allActions, selectedActionId]\n );\n\n if (!isOpen) return null;\n\n const isActionLocked = Boolean(prefillActionId);\n const canSubmit = Boolean(selectedActionId) && !loading;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!selectedActionId) return;\n onSubmit({\n calendarId: '',\n title,\n description: description || undefined,\n scheduleType,\n scheduledAt: scheduleType === 'ONE_TIME' ? new Date(scheduledAt).toISOString() : undefined,\n cronExpression: scheduleType === 'CRON' ? cronExpression : undefined,\n interval: scheduleType === 'FIXED_INTERVAL' ? interval : undefined,\n intervalUnit: scheduleType === 'FIXED_INTERVAL' ? intervalUnit : undefined,\n timezone,\n actionId: selectedActionId,\n });\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (onClose as (...a: unknown[]) => unknown)();\n }\n }}\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={onClose}\n />\n <div className=\"relative bg-bg-surface border border-border-default rounded-xl shadow-xl w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {isActionLocked\n ? tr('vibecontrols.calendar.schedule.addActionToCalendar', 'Add Action to Calendar')\n : tr(\n 'vibecontrols.calendar.schedule.scheduleActionOnCalendar',\n 'Schedule Action on Calendar'\n )}\n </h2>\n <button type=\"button\" onClick={onClose} className=\"p-1 rounded hover:bg-bg-surface\">\n <X size={18} />\n </button>\n </div>\n\n {/* Form */}\n <form onSubmit={handleSubmit} className=\"px-6 py-4 space-y-4\">\n {/* Action Picker — always required */}\n <div className=\"space-y-2\">\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.actionToSchedule', 'Action to Schedule')} *\n </label>\n <p className=\"text-xs text-text-tertiary -mt-1\">\n Calendar entries trigger VibeControls Actions. Agent, command, and webhook details\n come from the Action definition.\n </p>\n\n {isActionLocked && selectedAction && (\n <div className=\"flex items-start gap-3 p-3 rounded-lg border border-border-default bg-bg-surface\">\n <div className=\"p-1.5 rounded-md bg-primary/10 text-primary flex-shrink-0\">\n <Zap size={16} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedAction.displayName || selectedAction.name}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {selectedAction.actionType} · {selectedAction.triggerType}\n </p>\n {selectedAction.description && (\n <p className=\"text-xs text-text-tertiary mt-1 line-clamp-2\">\n {selectedAction.description}\n </p>\n )}\n </div>\n </div>\n )}\n\n {isActionLocked && !selectedAction && actionsLoading && (\n <div className=\"p-3 rounded-lg border border-border-default bg-bg-surface text-xs text-text-secondary\">\n Loading action…\n </div>\n )}\n\n {isActionLocked && !selectedAction && !actionsLoading && (\n <div className=\"p-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle text-xs text-status-error-text\">\n Action not found. It may have been deleted.\n </div>\n )}\n\n {!isActionLocked && (\n <>\n <div className=\"relative\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-tertiary\"\n />\n <input\n aria-label=\"Search actions by name…\"\n type=\"text\"\n value={actionSearch}\n onChange={(e) => setActionSearch(e.target.value)}\n placeholder=\"Search actions by name…\"\n className={`${INPUT_CLASS} pl-9`}\n />\n </div>\n\n <div className=\"max-h-40 overflow-y-auto border border-border-default rounded-lg divide-y divide-border-default\">\n {actionsLoading && allActions.length === 0 && (\n <div className=\"px-3 py-4 text-xs text-text-secondary text-center\">\n Loading actions…\n </div>\n )}\n {!actionsLoading && allActions.length === 0 && (\n <div className=\"px-3 py-4 text-xs text-text-secondary text-center\">\n No actions yet. Create an Action in VibeControls first, then come back to\n schedule it.\n </div>\n )}\n {!actionsLoading && allActions.length > 0 && filteredActions.length === 0 && (\n <div className=\"px-3 py-4 text-xs text-text-secondary text-center\">\n No actions match \"{actionSearch}\".\n </div>\n )}\n {filteredActions.map((action: ActionPickItem) => {\n const isSelected = action.id === selectedActionId;\n return (\n <button\n key={action.id}\n type=\"button\"\n onClick={() => setSelectedActionId(action.id)}\n className={`w-full text-left px-3 py-2.5 hover:bg-bg-surface transition-colors ${\n isSelected ? 'bg-primary/5' : ''\n }`}\n >\n <div className=\"flex items-start gap-2.5\">\n <div\n className={`p-1 rounded-md flex-shrink-0 ${\n isSelected\n ? 'bg-primary/10 text-primary'\n : 'bg-bg-sunken text-text-tertiary'\n }`}\n >\n <Zap size={14} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {action.displayName || action.name}\n </p>\n <p className=\"text-xs text-text-tertiary mt-0.5\">\n {action.actionType} · {action.triggerType}\n </p>\n </div>\n {isSelected && (\n <span className=\"text-xs font-medium text-primary flex-shrink-0\">\n Selected\n </span>\n )}\n </div>\n </button>\n );\n })}\n </div>\n </>\n )}\n\n {/* Tell the user which agent this action runs on, and warn if that\n agent is unhealthy or gone — before the schedule is created. */}\n {selectedAction && <ActionAgentTargetNotice action={selectedAction} />}\n </div>\n\n {/* Title */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.title', 'Title')}\n </label>\n <input\n aria-label=\"e.g. Run test suite\"\n type=\"text\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Run test suite\"\n className={INPUT_CLASS}\n required\n />\n </div>\n\n {/* Description */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.description', 'Description')}\n </label>\n <input\n aria-label=\"Optional description\"\n type=\"text\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"Optional description\"\n className={INPUT_CLASS}\n />\n </div>\n\n {/* Schedule Type */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.schedule', 'Schedule')}\n </label>\n <select\n value={scheduleType}\n onChange={(e) => setScheduleType(e.target.value as typeof scheduleType)}\n className={INPUT_CLASS}\n >\n <option value=\"ONE_TIME\">One Time</option>\n <option value=\"CRON\">Cron Expression</option>\n <option value=\"FIXED_INTERVAL\">Fixed Interval</option>\n </select>\n </div>\n\n {/* Schedule-specific fields */}\n {scheduleType === 'ONE_TIME' && (\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.runAt', 'Run At')}\n </label>\n <input\n aria-label=\"control\"\n type=\"datetime-local\"\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n className={INPUT_CLASS}\n required\n />\n </div>\n )}\n\n {scheduleType === 'CRON' && (\n <div>\n <label htmlFor=\"scheduletaskmodal-40\" className={LABEL_CLASS}>\n Cron Expression\n </label>\n <input\n id=\"scheduletaskmodal-40\"\n aria-label=\"*/5 * * * * (every 5 minutes)\"\n type=\"text\"\n value={cronExpression}\n onChange={(e) => setCronExpression(e.target.value)}\n placeholder=\"*/5 * * * * (every 5 minutes)\"\n className={MONO_INPUT_CLASS}\n required\n />\n </div>\n )}\n\n {scheduleType === 'FIXED_INTERVAL' && (\n <div className=\"flex gap-2\">\n <div className=\"flex-1\">\n <label htmlFor=\"scheduletaskmodal-39\" className={LABEL_CLASS}>\n Every\n </label>\n <input\n id=\"scheduletaskmodal-39\"\n aria-label=\"control\"\n type=\"number\"\n value={interval}\n onChange={(e) => setInterval(Number(e.target.value))}\n min={1}\n className={INPUT_CLASS}\n required\n />\n </div>\n <div className=\"flex-1\">\n <label htmlFor=\"scheduletaskmodal-38\" className={LABEL_CLASS}>\n Unit\n </label>\n <select\n id=\"scheduletaskmodal-38\"\n value={intervalUnit}\n onChange={(e) => setIntervalUnit(e.target.value)}\n className={INPUT_CLASS}\n >\n <option value=\"seconds\">Seconds</option>\n <option value=\"minutes\">Minutes</option>\n <option value=\"hours\">Hours</option>\n <option value=\"days\">Days</option>\n </select>\n </div>\n </div>\n )}\n\n {/* Timezone */}\n <div>\n <label className={LABEL_CLASS}>\n {tr('vibecontrols.calendar.schedule.timezone', 'Timezone')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={timezone}\n onChange={(e) => setTimezone(e.target.value)}\n className={INPUT_CLASS}\n />\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-default\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-4 py-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr('vibecontrols.calendar.schedule.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={!canSubmit}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed\"\n title={\n !selectedActionId\n ? tr('vibecontrols.calendar.schedule.selectActionFirst', 'Select an Action first')\n : undefined\n }\n >\n {loading\n ? tr('vibecontrols.calendar.schedule.creating', 'Creating...')\n : tr('vibecontrols.calendar.schedule.scheduleBtn', 'Schedule')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAoDA,IAAM,IACJ,+KACI,IAAmB,GAAG,EAAY,aAClC,IAAc;AAEpB,SAAgB,EAAkB,EAChC,WACA,YACA,aACA,YACA,sBACyB;CACzB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAkB,KAAuB,EAAiB,KAAmB,GAAG,EACjF,CAAC,GAAc,KAAmB,EAAS,GAAG,EAC9C,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAc,KAAmB,EAEtC,WAAW,EACP,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAgB,KAAqB,EAAS,GAAG,EAClD,CAAC,GAAU,KAAe,EAAS,GAAG,EACtC,CAAC,GAAc,KAAmB,EAAS,UAAU,EACrD,CAAC,GAAU,KAAe,EAAS,KAAK,gBAAgB,CAAC,iBAAiB,CAAC,SAAS,EAEpF,EAAE,MAAM,GAAa,SAAS,MAAmB,EAAoB;EACzE,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE;EACzC,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAEI,IAAa,QACV,GAAa,SAAS,SAAS,EAAE,EACxC,CAAC,GAAa,SAAS,MAAM,CAC9B,EAEK,IAAkB,QAAc;AACpC,MAAI,CAAC,EAAa,MAAM,CAAE,QAAO;EACjC,IAAM,IAAI,EAAa,MAAM,CAAC,aAAa;AAC3C,SAAO,EAAW,QACf,MACC,EAAE,MAAM,aAAa,CAAC,SAAS,EAAE,IACjC,EAAE,aAAa,aAAa,CAAC,SAAS,EAAE,IACxC,EAAE,aAAa,aAAa,CAAC,SAAS,EAAE,CAC3C;IACA,CAAC,GAAY,EAAa,CAAC,EAExB,IAAiB,QACf,EAAW,MAAM,MAAsB,EAAE,OAAO,EAAiB,EACvE,CAAC,GAAY,EAAiB,CAC/B;AAED,KAAI,CAAC,EAAQ,QAAO;CAEpB,IAAM,IAAiB,EAAQ,GACzB,IAAY,EAAQ,KAAqB,CAAC;AAmBhD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GACE,MAAK;GACL,UAAU;GACV,YAAY,MAAM;AAChB,KAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EACjB,GAA0C;;GAG/C,WAAU;GACV,SAAS;GACT,CAAA,EACF,kBAAC,OAAD;GAAK,WAAU;aAAf,CAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,IACG,EAAG,sDAAsD,yBAAyB,GAClF,EACE,2DACA,8BACD;KACF,CAAA,EACL,kBAAC,UAAD;KAAQ,MAAK;KAAS,SAAS;KAAS,WAAU;eAChD,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA;KACR,CAAA,CACL;OAGN,kBAAC,QAAD;IAAM,WAhDU,MAAuB;AAC3C,OAAE,gBAAgB,EACb,KACL,EAAS;MACP,YAAY;MACZ;MACA,aAAa,KAAe,KAAA;MAC5B;MACA,aAAa,MAAiB,aAAa,IAAI,KAAK,EAAY,CAAC,aAAa,GAAG,KAAA;MACjF,gBAAgB,MAAiB,SAAS,IAAiB,KAAA;MAC3D,UAAU,MAAiB,mBAAmB,IAAW,KAAA;MACzD,cAAc,MAAiB,mBAAmB,IAAe,KAAA;MACjE;MACA,UAAU;MACX,CAAC;;IAkCgC,WAAU;cAAxC;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,SAAD;QAAO,WAAW;kBAAlB,CACG,EAAG,mDAAmD,qBAAqB,EAAC,KACvE;;OACR,kBAAC,KAAD;QAAG,WAAU;kBAAmC;QAG5C,CAAA;OAEH,KAAkB,KACjB,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAK,MAAM,IAAM,CAAA;SACb,CAAA,EACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAe,eAAe,EAAe;WAC5C,CAAA;UACJ,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAe;YAAW;YAAI,EAAe;YAC5C;;UACH,EAAe,eACd,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAe;WACd,CAAA;UAEF;WACF;;OAGP,KAAkB,CAAC,KAAkB,KACpC,kBAAC,OAAD;QAAK,WAAU;kBAAwF;QAEjG,CAAA;OAGP,KAAkB,CAAC,KAAkB,CAAC,KACrC,kBAAC,OAAD;QAAK,WAAU;kBAA4G;QAErH,CAAA;OAGP,CAAC,KACA,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SACE,MAAM;SACN,WAAU;SACV,CAAA,EACF,kBAAC,SAAD;SACE,cAAW;SACX,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;SAChD,aAAY;SACZ,WAAW,GAAG,EAAY;SAC1B,CAAA,CACE;WAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACG,KAAkB,EAAW,WAAW,KACvC,kBAAC,OAAD;UAAK,WAAU;oBAAoD;UAE7D,CAAA;SAEP,CAAC,KAAkB,EAAW,WAAW,KACxC,kBAAC,OAAD;UAAK,WAAU;oBAAoD;UAG7D,CAAA;SAEP,CAAC,KAAkB,EAAW,SAAS,KAAK,EAAgB,WAAW,KACtE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WAAmE;WAC9C;WAAa;WAC5B;;SAEP,EAAgB,KAAK,MAA2B;UAC/C,IAAM,IAAa,EAAO,OAAO;AACjC,iBACE,kBAAC,UAAD;WAEE,MAAK;WACL,eAAe,EAAoB,EAAO,GAAG;WAC7C,WAAW,sEACT,IAAa,iBAAiB;qBAGhC,kBAAC,OAAD;YAAK,WAAU;sBAAf;aACE,kBAAC,OAAD;cACE,WAAW,gCACT,IACI,+BACA;wBAGN,kBAAC,GAAD,EAAK,MAAM,IAAM,CAAA;cACb,CAAA;aACN,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,KAAD;eAAG,WAAU;yBACV,EAAO,eAAe,EAAO;eAC5B,CAAA,EACJ,kBAAC,KAAD;eAAG,WAAU;yBAAb;gBACG,EAAO;gBAAW;gBAAI,EAAO;gBAC5B;iBACA;;aACL,KACC,kBAAC,QAAD;cAAM,WAAU;wBAAiD;cAE1D,CAAA;aAEL;;WACC,EA/BF,EAAO,GA+BL;WAEX;SACE;UACL,EAAA,CAAA;OAKJ,KAAkB,kBAAC,GAAD,EAAyB,QAAQ,GAAkB,CAAA;OAClE;;KAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAW;gBACf,EAAG,wCAAwC,QAAQ;MAC9C,CAAA,EACR,kBAAC,SAAD;MACE,cAAW;MACX,MAAK;MACL,OAAO;MACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;MACzC,aAAY;MACZ,WAAW;MACX,UAAA;MACA,CAAA,CACE,EAAA,CAAA;KAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAW;gBACf,EAAG,8CAA8C,cAAc;MAC1D,CAAA,EACR,kBAAC,SAAD;MACE,cAAW;MACX,MAAK;MACL,OAAO;MACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;MAC/C,aAAY;MACZ,WAAW;MACX,CAAA,CACE,EAAA,CAAA;KAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAW;gBACf,EAAG,2CAA2C,WAAW;MACpD,CAAA,EACR,kBAAC,UAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAA6B;MACvE,WAAW;gBAHb;OAKE,kBAAC,UAAD;QAAQ,OAAM;kBAAW;QAAiB,CAAA;OAC1C,kBAAC,UAAD;QAAQ,OAAM;kBAAO;QAAwB,CAAA;OAC7C,kBAAC,UAAD;QAAQ,OAAM;kBAAiB;QAAuB,CAAA;OAC/C;QACL,EAAA,CAAA;KAGL,MAAiB,cAChB,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAW;gBACf,EAAG,wCAAwC,SAAS;MAC/C,CAAA,EACR,kBAAC,SAAD;MACE,cAAW;MACX,MAAK;MACL,OAAO;MACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;MAC/C,WAAW;MACX,UAAA;MACA,CAAA,CACE,EAAA,CAAA;KAGP,MAAiB,UAChB,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,SAAQ;MAAuB,WAAW;gBAAa;MAEtD,CAAA,EACR,kBAAC,SAAD;MACE,IAAG;MACH,cAAW;MACX,MAAK;MACL,OAAO;MACP,WAAW,MAAM,EAAkB,EAAE,OAAO,MAAM;MAClD,aAAY;MACZ,WAAW;MACX,UAAA;MACA,CAAA,CACE,EAAA,CAAA;KAGP,MAAiB,oBAChB,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,SAAQ;QAAuB,WAAW;kBAAa;QAEtD,CAAA,EACR,kBAAC,SAAD;QACE,IAAG;QACH,cAAW;QACX,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAY,OAAO,EAAE,OAAO,MAAM,CAAC;QACpD,KAAK;QACL,WAAW;QACX,UAAA;QACA,CAAA,CACE;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,SAAQ;QAAuB,WAAW;kBAAa;QAEtD,CAAA,EACR,kBAAC,UAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;QAChD,WAAW;kBAJb;SAME,kBAAC,UAAD;UAAQ,OAAM;oBAAU;UAAgB,CAAA;SACxC,kBAAC,UAAD;UAAQ,OAAM;oBAAU;UAAgB,CAAA;SACxC,kBAAC,UAAD;UAAQ,OAAM;oBAAQ;UAAc,CAAA;SACpC,kBAAC,UAAD;UAAQ,OAAM;oBAAO;UAAa,CAAA;SAC3B;UACL;SACF;;KAIR,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAW;gBACf,EAAG,2CAA2C,WAAW;MACpD,CAAA,EACR,kBAAC,SAAD;MACE,cAAW;MACX,MAAK;MACL,OAAO;MACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;MAC5C,WAAW;MACX,CAAA,CACE,EAAA,CAAA;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;iBAET,EAAG,yCAAyC,SAAS;OAC/C,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,CAAC;OACX,WAAU;OACV,OACG,IAEG,KAAA,IADA,EAAG,oDAAoD,yBAAyB;iBAIrF,IACG,EAAG,2CAA2C,cAAc,GAC5D,EAAG,8CAA8C,WAAW;OACzD,CAAA,CACL;;KACD;MACH;KACF"}
|
|
@@ -74,14 +74,14 @@ function s({ occurrences: s, onTriggerNow: c, onViewLogs: l, onViewDetail: u })
|
|
|
74
74
|
c && /* @__PURE__ */ a("button", {
|
|
75
75
|
type: "button",
|
|
76
76
|
onClick: () => c(d.id),
|
|
77
|
-
className: "p-1.5 rounded hover:bg-bg-
|
|
77
|
+
className: "p-1.5 rounded hover:bg-bg-surface transition-colors",
|
|
78
78
|
title: "Trigger Now",
|
|
79
79
|
children: /* @__PURE__ */ a(i, { size: 14 })
|
|
80
80
|
}),
|
|
81
81
|
/* @__PURE__ */ a("button", {
|
|
82
82
|
type: "button",
|
|
83
83
|
onClick: () => l(d),
|
|
84
|
-
className: "p-1.5 rounded hover:bg-bg-
|
|
84
|
+
className: "p-1.5 rounded hover:bg-bg-surface transition-colors",
|
|
85
85
|
title: "View Logs",
|
|
86
86
|
children: /* @__PURE__ */ a(r, { size: 14 })
|
|
87
87
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeCalAgendaView.js","names":[],"sources":["../../../src/components/vibecalendar/VibeCalAgendaView.tsx"],"sourcesContent":["import { Play, Eye, ChevronRight } from 'lucide-react';\nimport { STATUS_COLORS } from './types';\nimport type { CalendarOccurrence, VibeCalendarTask } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface VibeCalAgendaViewProps {\n occurrences: CalendarOccurrence[];\n onTriggerNow?: (taskId: string) => void;\n onViewLogs: (task: VibeCalendarTask) => void;\n onViewDetail: (taskId: string) => void;\n}\n\nexport function VibeCalAgendaView({\n occurrences,\n onTriggerNow,\n onViewLogs,\n onViewDetail,\n}: VibeCalAgendaViewProps) {\n const tr = useTr();\n // Group by date\n const grouped = occurrences.reduce(\n (acc, occ) => {\n const dateKey = new Date(occ.occurrenceAt).toLocaleDateString('en-US', {\n weekday: 'long',\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n });\n if (!acc[dateKey]) acc[dateKey] = [];\n acc[dateKey].push(occ);\n return acc;\n },\n {} as Record<string, CalendarOccurrence[]>\n );\n\n if (occurrences.length === 0) {\n return (\n <div className=\"flex-1 flex items-center justify-center\">\n <div className=\"text-center\">\n <p className=\"text-text-secondary text-sm\">\n {tr('vibecontrols.calendar.agenda.noTasks', 'No scheduled tasks in this range')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex-1 overflow-auto space-y-6\">\n {Object.entries(grouped).map(([dateLabel, dayOccurrences]) => (\n <div key={dateLabel}>\n <h3 className=\"text-sm font-semibold text-text-secondary mb-2 sticky top-0 bg-bg-surface py-1\">\n {dateLabel}\n </h3>\n <div className=\"space-y-2\">\n {dayOccurrences.map((occ, i) => {\n const task = occ.task;\n const statusColor =\n STATUS_COLORS[occ.execution?.status || task.status] || STATUS_COLORS.PENDING;\n const time = new Date(occ.occurrenceAt).toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n });\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onViewDetail(task.id))();\n }\n }}\n key={`${task.id}-${i}`}\n className={`flex items-center gap-4 p-3 rounded-lg border ${statusColor} hover:opacity-90 transition-opacity cursor-pointer`}\n onClick={() => onViewDetail(task.id)}\n >\n {/* Time */}\n <div className=\"w-16 text-sm font-mono text-text-secondary\">{time}</div>\n\n {/* Task Info */}\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm text-text-primary truncate\">{task.title}</h4>\n <div className=\"flex items-center gap-2 mt-0.5\">\n <span className=\"text-xs text-text-tertiary font-mono truncate\">\n {task.taskConfig.command || 'Script'}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {task.cronExpression ? 'Cron' : task.rrule ? 'Recurrence' : 'One Time'}\n </span>\n </div>\n </div>\n\n {/* Status */}\n <span className=\"text-xs px-2 py-0.5 rounded-full border\">\n {occ.execution?.status || task.status}\n </span>\n\n {/* Actions */}\n <div className=\"flex items-center gap-1\" onClick={(e) => e.stopPropagation()}>\n {onTriggerNow && (\n <button\n type=\"button\"\n onClick={() => onTriggerNow(task.id)}\n className=\"p-1.5 rounded hover:bg-bg-
|
|
1
|
+
{"version":3,"file":"VibeCalAgendaView.js","names":[],"sources":["../../../src/components/vibecalendar/VibeCalAgendaView.tsx"],"sourcesContent":["import { Play, Eye, ChevronRight } from 'lucide-react';\nimport { STATUS_COLORS } from './types';\nimport type { CalendarOccurrence, VibeCalendarTask } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface VibeCalAgendaViewProps {\n occurrences: CalendarOccurrence[];\n onTriggerNow?: (taskId: string) => void;\n onViewLogs: (task: VibeCalendarTask) => void;\n onViewDetail: (taskId: string) => void;\n}\n\nexport function VibeCalAgendaView({\n occurrences,\n onTriggerNow,\n onViewLogs,\n onViewDetail,\n}: VibeCalAgendaViewProps) {\n const tr = useTr();\n // Group by date\n const grouped = occurrences.reduce(\n (acc, occ) => {\n const dateKey = new Date(occ.occurrenceAt).toLocaleDateString('en-US', {\n weekday: 'long',\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n });\n if (!acc[dateKey]) acc[dateKey] = [];\n acc[dateKey].push(occ);\n return acc;\n },\n {} as Record<string, CalendarOccurrence[]>\n );\n\n if (occurrences.length === 0) {\n return (\n <div className=\"flex-1 flex items-center justify-center\">\n <div className=\"text-center\">\n <p className=\"text-text-secondary text-sm\">\n {tr('vibecontrols.calendar.agenda.noTasks', 'No scheduled tasks in this range')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex-1 overflow-auto space-y-6\">\n {Object.entries(grouped).map(([dateLabel, dayOccurrences]) => (\n <div key={dateLabel}>\n <h3 className=\"text-sm font-semibold text-text-secondary mb-2 sticky top-0 bg-bg-surface py-1\">\n {dateLabel}\n </h3>\n <div className=\"space-y-2\">\n {dayOccurrences.map((occ, i) => {\n const task = occ.task;\n const statusColor =\n STATUS_COLORS[occ.execution?.status || task.status] || STATUS_COLORS.PENDING;\n const time = new Date(occ.occurrenceAt).toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n });\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onViewDetail(task.id))();\n }\n }}\n key={`${task.id}-${i}`}\n className={`flex items-center gap-4 p-3 rounded-lg border ${statusColor} hover:opacity-90 transition-opacity cursor-pointer`}\n onClick={() => onViewDetail(task.id)}\n >\n {/* Time */}\n <div className=\"w-16 text-sm font-mono text-text-secondary\">{time}</div>\n\n {/* Task Info */}\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm text-text-primary truncate\">{task.title}</h4>\n <div className=\"flex items-center gap-2 mt-0.5\">\n <span className=\"text-xs text-text-tertiary font-mono truncate\">\n {task.taskConfig.command || 'Script'}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {task.cronExpression ? 'Cron' : task.rrule ? 'Recurrence' : 'One Time'}\n </span>\n </div>\n </div>\n\n {/* Status */}\n <span className=\"text-xs px-2 py-0.5 rounded-full border\">\n {occ.execution?.status || task.status}\n </span>\n\n {/* Actions */}\n <div className=\"flex items-center gap-1\" onClick={(e) => e.stopPropagation()}>\n {onTriggerNow && (\n <button\n type=\"button\"\n onClick={() => onTriggerNow(task.id)}\n className=\"p-1.5 rounded hover:bg-bg-surface transition-colors\"\n title=\"Trigger Now\"\n >\n <Play size={14} />\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onViewLogs(task)}\n className=\"p-1.5 rounded hover:bg-bg-surface transition-colors\"\n title=\"View Logs\"\n >\n <Eye size={14} />\n </button>\n <ChevronRight size={14} className=\"text-text-tertiary\" />\n </div>\n </div>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;AAYA,SAAgB,EAAkB,EAChC,gBACA,iBACA,eACA,mBACyB;CACzB,IAAM,IAAK,GAAO,EAEZ,IAAU,EAAY,QACzB,GAAK,MAAQ;EACZ,IAAM,IAAU,IAAI,KAAK,EAAI,aAAa,CAAC,mBAAmB,SAAS;GACrE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACP,CAAC;AAGF,SAFK,EAAI,OAAU,EAAI,KAAW,EAAE,GACpC,EAAI,GAAS,KAAK,EAAI,EACf;IAET,EAAE,CACH;AAcD,QAZI,EAAY,WAAW,IAEvB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,wCAAwC,mCAAmC;IAC7E,CAAA;GACA,CAAA;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YACZ,OAAO,QAAQ,EAAQ,CAAC,KAAK,CAAC,GAAW,OACxC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aACX;GACE,CAAA,EACL,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAe,KAAK,GAAK,MAAM;IAC9B,IAAM,IAAO,EAAI,MACX,IACJ,EAAc,EAAI,WAAW,UAAU,EAAK,WAAW,EAAc,SACjE,IAAO,IAAI,KAAK,EAAI,aAAa,CAAC,mBAAmB,SAAS;KAClE,MAAM;KACN,QAAQ;KACT,CAAC;AAEF,WACE,kBAAC,OAAD;KACE,MAAK;KACL,UAAU;KACV,YAAY,MAAM;AAChB,OAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EACX,EAAa,EAAK,GAAG;;KAIhC,WAAW,iDAAiD,EAAY;KACxE,eAAe,EAAa,EAAK,GAAG;eAXtC;MAcE,kBAAC,OAAD;OAAK,WAAU;iBAA8C;OAAW,CAAA;MAGxE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAAkD,EAAK;QAAW,CAAA,EAChF,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAK,WAAW,WAAW;SACvB,CAAA,EACP,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAK,iBAAiB,SAAS,EAAK,QAAQ,eAAe;SACvD,CAAA,CACH;UACF;;MAGN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAI,WAAW,UAAU,EAAK;OAC1B,CAAA;MAGP,kBAAC,OAAD;OAAK,WAAU;OAA0B,UAAU,MAAM,EAAE,iBAAiB;iBAA5E;QACG,KACC,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAa,EAAK,GAAG;SACpC,WAAU;SACV,OAAM;mBAEN,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;SACX,CAAA;QAEX,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAW,EAAK;SAC/B,WAAU;SACV,OAAM;mBAEN,kBAAC,GAAD,EAAK,MAAM,IAAM,CAAA;SACV,CAAA;QACT,kBAAC,GAAD;SAAc,MAAM;SAAI,WAAU;SAAuB,CAAA;QACrD;;MACF;OA/CC,GAAG,EAAK,GAAG,GAAG,IA+Cf;KAER;GACE,CAAA,CACF,EAAA,EA3EI,EA2EJ,CACN;EACE,CAAA"}
|