@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":"DocsSettingsPanel.js","names":[],"sources":["../../../src/components/docs/DocsSettingsPanel.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport {\n X,\n Globe,\n Lock,\n Plus,\n Check,\n XCircle,\n Loader2,\n AlertTriangle,\n RefreshCw,\n} from 'lucide-react';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport {\n useListDocsBuildImagesQuery,\n useRegisterDocsBuildImageMutation,\n useValidateDocsBuildImageMutation,\n useDisableDocsBuildImageMutation,\n useUpdateDocsSiteRuntimeMutation,\n} from '@/generated/wspace-operations';\nimport type { DocsProvider, DocsSite } from './types';\nimport { DOCS_PROVIDERS } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Build a provider-specific config payload from the shared form fields.\n * Each provider has its own JSON shape — we keep the persisted shape narrow so\n * the backend assembler can rely on it without parsing the union.\n */\nfunction buildProviderConfig(\n provider: DocsProvider,\n prev: Record<string, unknown>,\n themeConfig: Record<string, unknown>,\n fields: { name: string; tagline: string; theme: string }\n): Record<string, unknown> {\n switch (provider) {\n case 'MKDOCS':\n return {\n ...prev,\n title: fields.name,\n tagline: fields.tagline,\n themePalette: (prev.themePalette as Record<string, unknown>) || {\n primary: 'indigo',\n accent: 'indigo',\n },\n };\n case 'VITEPRESS':\n return {\n ...prev,\n title: fields.name,\n tagline: fields.tagline,\n themeConfig: {\n ...((prev.themeConfig as Record<string, unknown>) || {}),\n nav: (themeConfig.nav as unknown[]) || [],\n },\n };\n case 'DOCUSAURUS':\n default:\n return {\n ...prev,\n title: fields.name,\n tagline: fields.tagline,\n themeConfig: {\n ...themeConfig,\n colorMode: {\n defaultMode: fields.theme,\n disableSwitch: false,\n respectPrefersColorScheme: true,\n },\n },\n };\n }\n}\n\ninterface DocsBuildImage {\n id: string;\n name: string;\n imageRef: string;\n imageDigest?: string | null;\n status: 'PENDING' | 'VALIDATING' | 'APPROVED' | 'REJECTED' | 'DISABLED';\n buildCommand?: string | null;\n outputDir?: string | null;\n description?: string | null;\n lastValidationError?: string | null;\n}\n\ninterface DocsSettingsPanelProps {\n site: DocsSite;\n open: boolean;\n onClose: () => void;\n onSave: (data: {\n name?: string;\n visibility?: 'PRIVATE' | 'PUBLIC';\n provider?: DocsProvider;\n providerConfig?: Record<string, unknown>;\n }) => Promise<void>;\n}\n\nexport function DocsSettingsPanel({ site, open, onClose, onSave }: DocsSettingsPanelProps) {\n const tr = useTr();\n const [name, setName] = useState(site.name);\n const [visibility, setVisibility] = useState<'PRIVATE' | 'PUBLIC'>(site.visibility);\n const [provider, setProvider] = useState<DocsProvider>(site.provider || 'DOCUSAURUS');\n const config = (site.providerConfig || site.docusaurusConfig || {}) as Record<string, unknown>;\n const [tagline, setTagline] = useState((config.tagline as string) || '');\n const themeConfig = (config.themeConfig || {}) as Record<string, unknown>;\n const colorMode = (themeConfig.colorMode || {}) as Record<string, unknown>;\n const [theme, setTheme] = useState<string>((colorMode.defaultMode as string) || 'light');\n const [saving, setSaving] = useState(false);\n\n const [runtime, setRuntime] = useState<'MANAGED' | 'CUSTOMER_IMAGE'>(\n (site as any).runtime || 'MANAGED'\n );\n const [selectedImageId, setSelectedImageId] = useState<string | null>(\n (site as any).buildImageId || null\n );\n const [showRegister, setShowRegister] = useState(false);\n const [regName, setRegName] = useState('');\n const [regImageRef, setRegImageRef] = useState('');\n const [regBuildCommand, setRegBuildCommand] = useState('');\n const [regOutputDir, setRegOutputDir] = useState('build');\n const [regDescription, setRegDescription] = useState('');\n const [registering, setRegistering] = useState(false);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const { data: imagesData, refetch: refetchImages } = useListDocsBuildImagesQuery({\n fetchPolicy: 'cache-and-network',\n });\n const [registerImage] = useRegisterDocsBuildImageMutation();\n const [validateImage] = useValidateDocsBuildImageMutation();\n const [disableImage] = useDisableDocsBuildImageMutation();\n const [updateRuntime] = useUpdateDocsSiteRuntimeMutation();\n\n const images = (imagesData?.docsBuildImages?.items || []) as DocsBuildImage[];\n const approvedImages = images.filter((i) => i.status === 'APPROVED');\n\n useEffect(() => {\n setName(site.name);\n setVisibility(site.visibility);\n setProvider(site.provider || 'DOCUSAURUS');\n const cfg = (site.providerConfig || site.docusaurusConfig || {}) as Record<string, unknown>;\n setTagline((cfg.tagline as string) || '');\n const tc = (cfg.themeConfig || {}) as Record<string, unknown>;\n const cm = (tc.colorMode || {}) as Record<string, unknown>;\n setTheme((cm.defaultMode as string) || 'light');\n setRuntime(((site as any).runtime as 'MANAGED' | 'CUSTOMER_IMAGE') || 'MANAGED');\n setSelectedImageId((site as any).buildImageId || null);\n }, [site]);\n\n // Poll during VALIDATING state so the UI reflects status transitions live.\n useEffect(() => {\n if (!open) return;\n const hasValidating = images.some((i) => i.status === 'VALIDATING');\n if (!hasValidating) return;\n const interval = setInterval(() => {\n void refetchImages();\n }, 3000);\n return () => clearInterval(interval);\n }, [open, images, refetchImages]);\n\n const handleSave = async () => {\n setSaving(true);\n setActionError(null);\n try {\n await onSave({\n name,\n visibility,\n provider,\n providerConfig: buildProviderConfig(provider, config, themeConfig, {\n name,\n tagline,\n theme,\n }),\n });\n await updateRuntime({\n variables: {\n id: site.id,\n runtime: runtime as any,\n buildImageId: runtime === 'CUSTOMER_IMAGE' ? selectedImageId : null,\n },\n });\n onClose();\n } catch (err) {\n setActionError((err as Error).message);\n } finally {\n setSaving(false);\n }\n };\n\n const handleRegister = async () => {\n setRegistering(true);\n setActionError(null);\n try {\n const { data } = await registerImage({\n variables: {\n input: {\n name: regName,\n imageRef: regImageRef,\n description: regDescription || null,\n buildCommand: regBuildCommand || null,\n outputDir: regOutputDir || 'build',\n },\n },\n });\n const newId = (data as any)?.registerDocsBuildImage?.id;\n if (newId) {\n await validateImage({ variables: { id: newId } });\n }\n setRegName('');\n setRegImageRef('');\n setRegBuildCommand('');\n setRegOutputDir('build');\n setRegDescription('');\n setShowRegister(false);\n await refetchImages();\n } catch (err) {\n setActionError((err as Error).message);\n } finally {\n setRegistering(false);\n }\n };\n\n const handleValidate = async (id: string) => {\n setActionError(null);\n try {\n await validateImage({ variables: { id } });\n await refetchImages();\n } catch (err) {\n setActionError((err as Error).message);\n }\n };\n\n const handleDisable = async (id: string) => {\n setActionError(null);\n try {\n await disableImage({ variables: { id } });\n await refetchImages();\n } catch (err) {\n setActionError((err as Error).message);\n }\n };\n\n const statusBadge = (status: DocsBuildImage['status']) => {\n const base = 'inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium';\n if (status === 'APPROVED')\n return (\n <span className={`${base} bg-status-success-bg text-status-success-text`}>\n <Check className=\"size-3\" />\n Approved\n </span>\n );\n if (status === 'VALIDATING')\n return (\n <span className={`${base} bg-status-info-bg text-status-info-text`}>\n <Loader2 className=\"size-3 animate-spin\" />\n Validating\n </span>\n );\n if (status === 'REJECTED')\n return (\n <span className={`${base} bg-status-error-bg text-status-error-text`}>\n <XCircle className=\"size-3\" />\n Rejected\n </span>\n );\n if (status === 'DISABLED')\n return <span className={`${base} bg-bg-sunken text-text-muted`}>Disabled</span>;\n return <span className={`${base} bg-bg-sunken text-text-secondary`}>Pending</span>;\n };\n\n return (\n <AnimatePresence>\n {open && (\n <>\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 bg-bg-overlay z-40\"\n onClick={onClose}\n />\n <motion.div\n initial={{ x: '100%' }}\n animate={{ x: 0 }}\n exit={{ x: '100%' }}\n transition={{ type: 'spring', damping: 30, stiffness: 300 }}\n className=\"fixed right-0 top-0 bottom-0 w-[28rem] bg-bg-surface border-l border-border-default z-50 overflow-y-auto\"\n >\n <div className=\"p-4 border-b border-border-default flex items-center justify-between\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.docs.settings.title', 'Site Settings')}\n </h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 text-text-secondary hover:text-text-primary\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <div className=\"p-4 space-y-6\">\n <div>\n <label\n htmlFor=\"docssettingspanel-17\"\n className=\"block text-sm font-medium text-text-secondary mb-1\"\n >\n Site Title\n </label>\n <input\n id=\"docssettingspanel-17\"\n aria-label=\"control\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"docssettingspanel-16\"\n className=\"block text-sm font-medium text-text-secondary mb-1\"\n >\n Tagline\n </label>\n <input\n id=\"docssettingspanel-16\"\n aria-label=\"A brief tagline for your docs\"\n type=\"text\"\n value={tagline}\n onChange={(e) => setTagline(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n placeholder=\"A brief tagline for your docs\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"docssettingspanel-15\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Visibility\n </label>\n <div className=\"space-y-2\">\n <label className=\"flex items-center gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n id=\"docssettingspanel-15\"\n aria-label=\"visibility\"\n type=\"radio\"\n name=\"visibility\"\n value=\"PRIVATE\"\n checked={visibility === 'PRIVATE'}\n onChange={() => setVisibility('PRIVATE')}\n className=\"accent-action-primary-bg\"\n />\n <Lock className=\"size-4 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Private</p>\n <p className=\"text-xs text-text-muted\">\n Signed URLs that expire periodically\n </p>\n </div>\n </label>\n <label className=\"flex items-center gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n aria-label=\"visibility\"\n type=\"radio\"\n name=\"visibility\"\n value=\"PUBLIC\"\n checked={visibility === 'PUBLIC'}\n onChange={() => setVisibility('PUBLIC')}\n className=\"accent-action-primary-bg\"\n />\n <Globe className=\"size-4 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Public</p>\n <p className=\"text-xs text-text-muted\">Static URL accessible to anyone</p>\n </div>\n </label>\n </div>\n </div>\n\n <div>\n <label\n htmlFor=\"docssettingspanel-14\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Static-site generator\n </label>\n <div className=\"space-y-2\">\n {DOCS_PROVIDERS.map((p) => (\n <label\n key={p.value}\n data-testid={`docs-provider-${p.value.toLowerCase()}`}\n className=\"flex items-start gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\"\n >\n <input\n id=\"docssettingspanel-14\"\n aria-label=\"provider\"\n type=\"radio\"\n name=\"provider\"\n value={p.value}\n checked={provider === p.value}\n onChange={() => setProvider(p.value)}\n className=\"mt-1 accent-action-primary-bg\"\n />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{p.label}</p>\n <p className=\"text-xs text-text-muted\">{p.description}</p>\n </div>\n </label>\n ))}\n </div>\n {provider !== (site.provider || 'DOCUSAURUS') && (\n <p className=\"mt-2 text-xs text-text-warning\">\n Switching providers rebuilds the site from scratch. Existing artifacts stay\n until the next compile.\n </p>\n )}\n </div>\n\n {provider === 'DOCUSAURUS' && (\n <div>\n <label\n htmlFor=\"docssettingspanel-13\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Theme\n </label>\n <div className=\"flex gap-2\">\n {(['light', 'dark'] as const).map((t) => (\n <button\n type=\"button\"\n key={t}\n onClick={() => setTheme(t)}\n className={`flex-1 px-3 py-2 text-sm rounded-md border transition-colors ${\n theme === t\n ? 'bg-action-primary-bg text-action-primary-text border-action-primary-bg'\n : 'bg-bg-sunken text-text-secondary border-border-default hover:border-action-primary-bg'\n }`}\n >\n {t.charAt(0).toUpperCase() + t.slice(1)}\n </button>\n ))}\n </div>\n </div>\n )}\n\n <div className=\"pt-4 border-t border-border-default\">\n <label\n htmlFor=\"docssettingspanel-13\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Build runtime\n </label>\n <div className=\"space-y-2\">\n <label className=\"flex items-start gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n id=\"docssettingspanel-13\"\n aria-label=\"runtime\"\n type=\"radio\"\n name=\"runtime\"\n value=\"MANAGED\"\n checked={runtime === 'MANAGED'}\n onChange={() => setRuntime('MANAGED')}\n className=\"accent-action-primary-bg mt-1\"\n />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Managed (default)</p>\n <p className=\"text-xs text-text-muted\">\n Uses the platform's built-in Docusaurus image. Zero setup — just compile and\n go.\n </p>\n </div>\n </label>\n <label className=\"flex items-start gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n aria-label=\"runtime\"\n type=\"radio\"\n name=\"runtime\"\n value=\"CUSTOMER_IMAGE\"\n checked={runtime === 'CUSTOMER_IMAGE'}\n onChange={() => setRuntime('CUSTOMER_IMAGE')}\n className=\"accent-action-primary-bg mt-1\"\n />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Custom image</p>\n <p className=\"text-xs text-text-muted\">\n Bring your own Docker build image — useful for custom Docusaurus plugins,\n themes, or alternative generators (MkDocs, VitePress). The image must be\n registered and approved below before it can be used.\n </p>\n </div>\n </label>\n </div>\n\n {runtime === 'CUSTOMER_IMAGE' && (\n <div className=\"mt-3 space-y-3\">\n <div>\n <label\n htmlFor=\"docssettingspanel-12\"\n className=\"block text-xs font-medium text-text-secondary mb-1\"\n >\n Select approved image\n </label>\n <select\n id=\"docssettingspanel-12\"\n value={selectedImageId ?? ''}\n onChange={(e) => setSelectedImageId(e.target.value || null)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n >\n <option value=\"\"> - Select an image - </option>\n {approvedImages.map((img) => (\n <option key={img.id} value={img.id}>\n {img.name} ({img.imageRef})\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"border border-border-default rounded-md overflow-hidden\">\n <div className=\"p-2 bg-bg-sunken border-b border-border-default flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-secondary\">\n Build images ({images.length})\n </span>\n <button\n type=\"button\"\n onClick={() => setShowRegister((v) => !v)}\n className=\"text-xs text-action-primary-text-alt hover:underline flex items-center gap-1\"\n data-testid=\"docs-register-image-toggle\"\n >\n <Plus className=\"size-3\" />\n {showRegister ? 'Cancel' : 'Register'}\n </button>\n </div>\n\n {showRegister && (\n <div className=\"p-3 space-y-2 bg-bg-base\">\n <input\n aria-label=\"Name (e.g. custom-theme)\"\n type=\"text\"\n value={regName}\n onChange={(e) => setRegName(e.target.value)}\n placeholder=\"Name (e.g. custom-theme)\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n data-testid=\"docs-register-image-name\"\n />\n <input\n aria-label=\"harbor.tooling.internal.burdenoff.com/...\"\n type=\"text\"\n value={regImageRef}\n onChange={(e) => setRegImageRef(e.target.value)}\n placeholder=\"harbor.tooling.internal.burdenoff.com/...\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n data-testid=\"docs-register-image-ref\"\n />\n <input\n aria-label=\"Build command (default: npm install && npm run build)\"\n type=\"text\"\n value={regBuildCommand}\n onChange={(e) => setRegBuildCommand(e.target.value)}\n placeholder=\"Build command (default: npm install && npm run build)\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n data-testid=\"docs-register-image-command\"\n />\n <input\n aria-label=\"Output dir (default: build)\"\n type=\"text\"\n value={regOutputDir}\n onChange={(e) => setRegOutputDir(e.target.value)}\n placeholder=\"Output dir (default: build)\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n />\n <textarea\n aria-label=\"Description (optional)\"\n value={regDescription}\n onChange={(e) => setRegDescription(e.target.value)}\n placeholder=\"Description (optional)\"\n rows={2}\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n />\n <button\n type=\"button\"\n onClick={handleRegister}\n disabled={registering || !regName || !regImageRef}\n className=\"w-full px-2 py-1 bg-action-primary-bg text-action-primary-text rounded text-xs hover:bg-action-primary-bg-hover disabled:opacity-50\"\n data-testid=\"docs-register-image-submit\"\n >\n {registering ? 'Registering…' : 'Register & Validate'}\n </button>\n </div>\n )}\n\n {images.length === 0 ? (\n <p className=\"text-xs text-text-muted p-3\">\n No build images registered yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-default\">\n {images.map((img) => (\n <li\n key={img.id}\n className={`p-2 ${img.status === 'REJECTED' ? 'bg-status-error-bg/30' : ''}`}\n data-testid={`docs-build-image-${img.id}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-xs font-medium text-text-primary truncate\">\n {img.name}\n </p>\n <p\n className=\"text-[10px] text-text-muted truncate\"\n title={img.imageRef}\n >\n {img.imageRef}\n </p>\n {img.lastValidationError && img.status === 'REJECTED' && (\n <div className=\"mt-1.5 flex items-start gap-1 p-1.5 rounded bg-status-error-bg text-status-error-text\">\n <AlertTriangle className=\"size-3 mt-0.5 shrink-0\" />\n <p className=\"text-[10px] leading-snug\">\n {img.lastValidationError}\n </p>\n </div>\n )}\n </div>\n <div className=\"flex flex-col items-end gap-1.5 shrink-0\">\n {statusBadge(img.status)}\n {img.status === 'REJECTED' && (\n <button\n type=\"button\"\n onClick={() => handleValidate(img.id)}\n className=\"flex items-center gap-1 px-2 py-0.5 rounded border border-status-error-text text-[10px] text-status-error-text hover:bg-status-error-bg transition-colors\"\n data-testid={`docs-validate-image-${img.id}`}\n >\n <RefreshCw className=\"size-2.5\" />\n Re-validate\n </button>\n )}\n {(img.status === 'PENDING' || img.status === 'DISABLED') && (\n <button\n type=\"button\"\n onClick={() => handleValidate(img.id)}\n className=\"flex items-center gap-1 px-2 py-0.5 rounded border border-border-default text-[10px] text-text-secondary hover:border-action-primary-bg hover:text-action-primary-text-alt transition-colors\"\n data-testid={`docs-validate-image-${img.id}`}\n >\n Validate\n </button>\n )}\n {img.status === 'APPROVED' && (\n <button\n type=\"button\"\n onClick={() => handleDisable(img.id)}\n className=\"text-[10px] text-text-muted hover:underline\"\n >\n Disable\n </button>\n )}\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n )}\n </div>\n\n {actionError && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text rounded-md text-xs\">\n {actionError}\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={saving || (runtime === 'CUSTOMER_IMAGE' && !selectedImageId)}\n className=\"w-full px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n data-testid=\"docs-settings-save\"\n >\n {saving\n ? tr('vibecontrols.docs.settings.saving', 'Saving...')\n : tr('vibecontrols.docs.settings.save', 'Save Settings')}\n </button>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":";;;;;;;;AA6BA,SAAS,GACP,GACA,GACA,GACA,GACyB;AACzB,SAAQ,GAAR;EACE,KAAK,SACH,QAAO;GACL,GAAG;GACH,OAAO,EAAO;GACd,SAAS,EAAO;GAChB,cAAe,EAAK,gBAA4C;IAC9D,SAAS;IACT,QAAQ;IACT;GACF;EACH,KAAK,YACH,QAAO;GACL,GAAG;GACH,OAAO,EAAO;GACd,SAAS,EAAO;GAChB,aAAa;IACX,GAAK,EAAK,eAA2C,EAAE;IACvD,KAAM,EAAY,OAAqB,EAAE;IAC1C;GACF;EAEH,QACE,QAAO;GACL,GAAG;GACH,OAAO,EAAO;GACd,SAAS,EAAO;GAChB,aAAa;IACX,GAAG;IACH,WAAW;KACT,aAAa,EAAO;KACpB,eAAe;KACf,2BAA2B;KAC5B;IACF;GACF;;;AA4BP,SAAgB,EAAkB,EAAE,SAAM,SAAM,YAAS,cAAkC;CACzF,IAAM,IAAK,IAAO,EACZ,CAAC,GAAM,KAAW,EAAS,EAAK,KAAK,EACrC,CAAC,GAAY,KAAiB,EAA+B,EAAK,WAAW,EAC7E,CAAC,GAAU,KAAe,EAAuB,EAAK,YAAY,aAAa,EAC/E,IAAU,EAAK,kBAAkB,EAAK,oBAAoB,EAAE,EAC5D,CAAC,GAAS,KAAc,EAAU,EAAO,WAAsB,GAAG,EAClE,IAAe,EAAO,eAAe,EAAE,EAEvC,CAAC,GAAO,KAAY,GADP,EAAY,aAAa,EAAE,EACQ,eAA0B,QAAQ,EAClF,CAAC,GAAQ,KAAa,EAAS,GAAM,EAErC,CAAC,GAAS,KAAc,EAC3B,EAAa,WAAW,UAC1B,EACK,CAAC,GAAiB,KAAsB,EAC3C,EAAa,gBAAgB,KAC/B,EACK,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAiB,KAAsB,EAAS,GAAG,EACpD,CAAC,GAAc,KAAmB,EAAS,QAAQ,EACnD,CAAC,GAAgB,KAAqB,EAAS,GAAG,EAClD,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,EAAE,MAAM,IAAY,SAAS,MAAkB,EAA4B,EAC/E,aAAa,qBACd,CAAC,EACI,CAAC,MAAiB,GAAmC,EACrD,CAAC,KAAiB,IAAmC,EACrD,CAAC,MAAgB,GAAkC,EACnD,CAAC,MAAiB,GAAkC,EAEpD,IAAU,IAAY,iBAAiB,SAAS,EAAE,EAClD,KAAiB,EAAO,QAAQ,MAAM,EAAE,WAAW,WAAW;AAgBpE,CAdA,QAAgB;AAGd,EAFA,EAAQ,EAAK,KAAK,EAClB,EAAc,EAAK,WAAW,EAC9B,EAAY,EAAK,YAAY,aAAa;EAC1C,IAAM,IAAO,EAAK,kBAAkB,EAAK,oBAAoB,EAAE;AAM/D,EALA,EAAY,EAAI,WAAsB,GAAG,EAGzC,IAFY,EAAI,eAAe,EAAE,EAClB,aAAa,EAAE,EACjB,eAA0B,QAAQ,EAC/C,EAAa,EAAa,WAA4C,UAAU,EAChF,EAAoB,EAAa,gBAAgB,KAAK;IACrD,CAAC,EAAK,CAAC,EAGV,QAAgB;AAGd,MAFI,CAAC,KAED,CADkB,EAAO,MAAM,MAAM,EAAE,WAAW,aAAa,CAC/C;EACpB,IAAM,IAAW,kBAAkB;AAC5B,MAAe;KACnB,IAAK;AACR,eAAa,cAAc,EAAS;IACnC;EAAC;EAAM;EAAQ;EAAc,CAAC;CAEjC,IAAM,KAAa,YAAY;AAE7B,EADA,EAAU,GAAK,EACf,EAAe,KAAK;AACpB,MAAI;AAkBF,GAjBA,MAAM,GAAO;IACX;IACA;IACA;IACA,gBAAgB,GAAoB,GAAU,GAAQ,GAAa;KACjE;KACA;KACA;KACD,CAAC;IACH,CAAC,EACF,MAAM,GAAc,EAClB,WAAW;IACT,IAAI,EAAK;IACA;IACT,cAAc,MAAY,mBAAmB,IAAkB;IAChE,EACF,CAAC,EACF,GAAS;WACF,GAAK;AACZ,KAAgB,EAAc,QAAQ;YAC9B;AACR,KAAU,GAAM;;IAId,KAAiB,YAAY;AAEjC,EADA,EAAe,GAAK,EACpB,EAAe,KAAK;AACpB,MAAI;GACF,IAAM,EAAE,YAAS,MAAM,GAAc,EACnC,WAAW,EACT,OAAO;IACL,MAAM;IACN,UAAU;IACV,aAAa,KAAkB;IAC/B,cAAc,KAAmB;IACjC,WAAW,KAAgB;IAC5B,EACF,EACF,CAAC,EACI,IAAS,GAAc,wBAAwB;AAUrD,GATI,KACF,MAAM,EAAc,EAAE,WAAW,EAAE,IAAI,GAAO,EAAE,CAAC,EAEnD,EAAW,GAAG,EACd,EAAe,GAAG,EAClB,EAAmB,GAAG,EACtB,EAAgB,QAAQ,EACxB,EAAkB,GAAG,EACrB,EAAgB,GAAM,EACtB,MAAM,GAAe;WACd,GAAK;AACZ,KAAgB,EAAc,QAAQ;YAC9B;AACR,KAAe,GAAM;;IAInB,IAAiB,OAAO,MAAe;AAC3C,IAAe,KAAK;AACpB,MAAI;AAEF,GADA,MAAM,EAAc,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EAC1C,MAAM,GAAe;WACd,GAAK;AACZ,KAAgB,EAAc,QAAQ;;IAIpC,KAAgB,OAAO,MAAe;AAC1C,IAAe,KAAK;AACpB,MAAI;AAEF,GADA,MAAM,GAAa,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EACzC,MAAM,GAAe;WACd,GAAK;AACZ,KAAgB,EAAc,QAAQ;;IAIpC,MAAe,MAAqC;EACxD,IAAM,IAAO;AAwBb,SAvBI,MAAW,aAEX,kBAAC,QAAD;GAAM,WAAW,GAAG,EAAK;aAAzB,CACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,EAAA,WAEvB;OAEP,MAAW,eAEX,kBAAC,QAAD;GAAM,WAAW,GAAG,EAAK;aAAzB,CACE,kBAAC,IAAD,EAAS,WAAU,uBAAwB,CAAA,EAAA,aAEtC;OAEP,MAAW,aAEX,kBAAC,QAAD;GAAM,WAAW,GAAG,EAAK;aAAzB,CACE,kBAAC,IAAD,EAAS,WAAU,UAAW,CAAA,EAAA,WAEzB;OAEP,MAAW,aACN,kBAAC,QAAD;GAAM,WAAW,GAAG,EAAK;aAAgC;GAAe,CAAA,GAC1E,kBAAC,QAAD;GAAM,WAAW,GAAG,EAAK;aAAoC;GAAc,CAAA;;AAGpF,QACE,kBAAC,IAAD,EAAA,UACG,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,EAAO,KAAR;EACE,SAAS,EAAE,SAAS,GAAG;EACvB,SAAS,EAAE,SAAS,GAAG;EACvB,MAAM,EAAE,SAAS,GAAG;EACpB,WAAU;EACV,SAAS;EACT,CAAA,EACF,kBAAC,EAAO,KAAR;EACE,SAAS,EAAE,GAAG,QAAQ;EACtB,SAAS,EAAE,GAAG,GAAG;EACjB,MAAM,EAAE,GAAG,QAAQ;EACnB,YAAY;GAAE,MAAM;GAAU,SAAS;GAAI,WAAW;GAAK;EAC3D,WAAU;YALZ,CAOE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAG,oCAAoC,gBAAgB;IACrD,CAAA,EACL,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;IACjB,CAAA,CACL;MAEN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eACX;KAEO,CAAA,EACR,kBAAC,SAAD;KACE,IAAG;KACH,cAAW;KACX,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;KACxC,WAAU;KACV,CAAA,CACE,EAAA,CAAA;IAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eACX;KAEO,CAAA,EACR,kBAAC,SAAD;KACE,IAAG;KACH,cAAW;KACX,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;KAC3C,WAAU;KACV,aAAY;KACZ,CAAA,CACE,EAAA,CAAA;IAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eACX;KAEO,CAAA,EACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB;OACE,kBAAC,SAAD;QACE,IAAG;QACH,cAAW;QACX,MAAK;QACL,MAAK;QACL,OAAM;QACN,SAAS,MAAe;QACxB,gBAAgB,EAAc,UAAU;QACxC,WAAU;QACV,CAAA;OACF,kBAAC,IAAD,EAAM,WAAU,8BAA+B,CAAA;OAC/C,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAwC;QAAW,CAAA,EAChE,kBAAC,KAAD;QAAG,WAAU;kBAA0B;QAEnC,CAAA,CACA,EAAA,CAAA;OACA;SACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB;OACE,kBAAC,SAAD;QACE,cAAW;QACX,MAAK;QACL,MAAK;QACL,OAAM;QACN,SAAS,MAAe;QACxB,gBAAgB,EAAc,SAAS;QACvC,WAAU;QACV,CAAA;OACF,kBAAC,IAAD,EAAO,WAAU,8BAA+B,CAAA;OAChD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAwC;QAAU,CAAA,EAC/D,kBAAC,KAAD;QAAG,WAAU;kBAA0B;QAAmC,CAAA,CACtE,EAAA,CAAA;OACA;QACJ;OACF,EAAA,CAAA;IAEN,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,SAAD;MACE,SAAQ;MACR,WAAU;gBACX;MAEO,CAAA;KACR,kBAAC,OAAD;MAAK,WAAU;gBACZ,GAAe,KAAK,MACnB,kBAAC,SAAD;OAEE,eAAa,iBAAiB,EAAE,MAAM,aAAa;OACnD,WAAU;iBAHZ,CAKE,kBAAC,SAAD;QACE,IAAG;QACH,cAAW;QACX,MAAK;QACL,MAAK;QACL,OAAO,EAAE;QACT,SAAS,MAAa,EAAE;QACxB,gBAAgB,EAAY,EAAE,MAAM;QACpC,WAAU;QACV,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAyC,EAAE;QAAU,CAAA,EAClE,kBAAC,KAAD;QAAG,WAAU;kBAA2B,EAAE;QAAgB,CAAA,CACtD,EAAA,CAAA,CACA;SAlBD,EAAE,MAkBD,CACR;MACE,CAAA;KACL,OAAc,EAAK,YAAY,iBAC9B,kBAAC,KAAD;MAAG,WAAU;gBAAiC;MAG1C,CAAA;KAEF,EAAA,CAAA;IAEL,MAAa,gBACZ,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eACX;KAEO,CAAA,EACR,kBAAC,OAAD;KAAK,WAAU;eACX,CAAC,SAAS,OAAO,CAAW,KAAK,MACjC,kBAAC,UAAD;MACE,MAAK;MAEL,eAAe,EAAS,EAAE;MAC1B,WAAW,gEACT,MAAU,IACN,2EACA;gBAGL,EAAE,OAAO,EAAE,CAAC,aAAa,GAAG,EAAE,MAAM,EAAE;MAChC,EATF,EASE,CACT;KACE,CAAA,CACF,EAAA,CAAA;IAGR,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,SAAD;OACE,SAAQ;OACR,WAAU;iBACX;OAEO,CAAA;MACR,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACE,kBAAC,SAAD;SACE,IAAG;SACH,cAAW;SACX,MAAK;SACL,MAAK;SACL,OAAM;SACN,SAAS,MAAY;SACrB,gBAAgB,EAAW,UAAU;SACrC,WAAU;SACV,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAwC;SAAqB,CAAA,EAC1E,kBAAC,KAAD;SAAG,WAAU;mBAA0B;SAGnC,CAAA,CACA,EAAA,CAAA,CACA;WACR,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACE,kBAAC,SAAD;SACE,cAAW;SACX,MAAK;SACL,MAAK;SACL,OAAM;SACN,SAAS,MAAY;SACrB,gBAAgB,EAAW,iBAAiB;SAC5C,WAAU;SACV,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAwC;SAAgB,CAAA,EACrE,kBAAC,KAAD;SAAG,WAAU;mBAA0B;SAInC,CAAA,CACA,EAAA,CAAA,CACA;UACJ;;MAEL,MAAY,oBACX,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QACE,SAAQ;QACR,WAAU;kBACX;QAEO,CAAA,EACR,kBAAC,UAAD;QACE,IAAG;QACH,OAAO,KAAmB;QAC1B,WAAW,MAAM,EAAmB,EAAE,OAAO,SAAS,KAAK;QAC3D,WAAU;kBAJZ,CAME,kBAAC,UAAD;SAAQ,OAAM;mBAAG;SAA8B,CAAA,EAC9C,GAAe,KAAK,MACnB,kBAAC,UAAD;SAAqB,OAAO,EAAI;mBAAhC;UACG,EAAI;UAAK;UAAG,EAAI;UAAS;UACnB;WAFI,EAAI,GAER,CACT,CACK;UACL,EAAA,CAAA,EAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YAA0D;YACzC,EAAO;YAAO;YACxB;cACP,kBAAC,UAAD;WACE,MAAK;WACL,eAAe,GAAiB,MAAM,CAAC,EAAE;WACzC,WAAU;WACV,eAAY;qBAJd,CAME,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,IAAe,WAAW,WACpB;aACL;;SAEL,KACC,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,SAAD;YACE,cAAW;YACX,MAAK;YACL,OAAO;YACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;YAC3C,aAAY;YACZ,WAAU;YACV,eAAY;YACZ,CAAA;WACF,kBAAC,SAAD;YACE,cAAW;YACX,MAAK;YACL,OAAO;YACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;YAC/C,aAAY;YACZ,WAAU;YACV,eAAY;YACZ,CAAA;WACF,kBAAC,SAAD;YACE,cAAW;YACX,MAAK;YACL,OAAO;YACP,WAAW,MAAM,EAAmB,EAAE,OAAO,MAAM;YACnD,aAAY;YACZ,WAAU;YACV,eAAY;YACZ,CAAA;WACF,kBAAC,SAAD;YACE,cAAW;YACX,MAAK;YACL,OAAO;YACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;YAChD,aAAY;YACZ,WAAU;YACV,CAAA;WACF,kBAAC,YAAD;YACE,cAAW;YACX,OAAO;YACP,WAAW,MAAM,EAAkB,EAAE,OAAO,MAAM;YAClD,aAAY;YACZ,MAAM;YACN,WAAU;YACV,CAAA;WACF,kBAAC,UAAD;YACE,MAAK;YACL,SAAS;YACT,UAAU,KAAe,CAAC,KAAW,CAAC;YACtC,WAAU;YACV,eAAY;sBAEX,IAAc,iBAAiB;YACzB,CAAA;WACL;;SAGP,EAAO,WAAW,IACjB,kBAAC,KAAD;UAAG,WAAU;oBAA8B;UAEvC,CAAA,GAEJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAO,KAAK,MACX,kBAAC,MAAD;WAEE,WAAW,OAAO,EAAI,WAAW,aAAa,0BAA0B;WACxE,eAAa,oBAAoB,EAAI;qBAErC,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,OAAD;aAAK,WAAU;uBAAf;cACE,kBAAC,KAAD;eAAG,WAAU;yBACV,EAAI;eACH,CAAA;cACJ,kBAAC,KAAD;eACE,WAAU;eACV,OAAO,EAAI;yBAEV,EAAI;eACH,CAAA;cACH,EAAI,uBAAuB,EAAI,WAAW,cACzC,kBAAC,OAAD;eAAK,WAAU;yBAAf,CACE,kBAAC,IAAD,EAAe,WAAU,0BAA2B,CAAA,EACpD,kBAAC,KAAD;gBAAG,WAAU;0BACV,EAAI;gBACH,CAAA,CACA;;cAEJ;gBACN,kBAAC,OAAD;aAAK,WAAU;uBAAf;cACG,GAAY,EAAI,OAAO;cACvB,EAAI,WAAW,cACd,kBAAC,UAAD;eACE,MAAK;eACL,eAAe,EAAe,EAAI,GAAG;eACrC,WAAU;eACV,eAAa,uBAAuB,EAAI;yBAJ1C,CAME,kBAAC,IAAD,EAAW,WAAU,YAAa,CAAA,EAAA,cAE3B;;eAET,EAAI,WAAW,aAAa,EAAI,WAAW,eAC3C,kBAAC,UAAD;eACE,MAAK;eACL,eAAe,EAAe,EAAI,GAAG;eACrC,WAAU;eACV,eAAa,uBAAuB,EAAI;yBACzC;eAEQ,CAAA;cAEV,EAAI,WAAW,cACd,kBAAC,UAAD;eACE,MAAK;eACL,eAAe,GAAc,EAAI,GAAG;eACpC,WAAU;yBACX;eAEQ,CAAA;cAEP;eACF;;WACH,EA1DE,EAAI,GA0DN,CACL;UACC,CAAA;SAEH;UACF;;MAEJ;;IAEL,KACC,kBAAC,OAAD;KAAK,WAAU;eACZ;KACG,CAAA;IAGR,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU,KAAW,MAAY,oBAAoB,CAAC;KACtD,WAAU;KACV,eAAY;eAEX,IACG,EAAG,qCAAqC,YAAY,GACpD,EAAG,mCAAmC,gBAAgB;KACnD,CAAA;IACL;KACK;IACZ,EAAA,CAAA,EAEW,CAAA"}
|
|
1
|
+
{"version":3,"file":"DocsSettingsPanel.js","names":[],"sources":["../../../src/components/docs/DocsSettingsPanel.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport {\n X,\n Globe,\n Lock,\n Plus,\n Check,\n XCircle,\n Loader2,\n AlertTriangle,\n RefreshCw,\n} from 'lucide-react';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport {\n useListDocsBuildImagesQuery,\n useRegisterDocsBuildImageMutation,\n useValidateDocsBuildImageMutation,\n useDisableDocsBuildImageMutation,\n useUpdateDocsSiteRuntimeMutation,\n} from '@/generated/wspace-operations';\nimport type { DocsProvider, DocsSite } from './types';\nimport { DOCS_PROVIDERS } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Build a provider-specific config payload from the shared form fields.\n * Each provider has its own JSON shape — we keep the persisted shape narrow so\n * the backend assembler can rely on it without parsing the union.\n */\nfunction buildProviderConfig(\n provider: DocsProvider,\n prev: Record<string, unknown>,\n themeConfig: Record<string, unknown>,\n fields: { name: string; tagline: string; theme: string }\n): Record<string, unknown> {\n switch (provider) {\n case 'MKDOCS':\n return {\n ...prev,\n title: fields.name,\n tagline: fields.tagline,\n themePalette: (prev.themePalette as Record<string, unknown>) || {\n primary: 'indigo',\n accent: 'indigo',\n },\n };\n case 'VITEPRESS':\n return {\n ...prev,\n title: fields.name,\n tagline: fields.tagline,\n themeConfig: {\n ...((prev.themeConfig as Record<string, unknown>) || {}),\n nav: (themeConfig.nav as unknown[]) || [],\n },\n };\n case 'DOCUSAURUS':\n default:\n return {\n ...prev,\n title: fields.name,\n tagline: fields.tagline,\n themeConfig: {\n ...themeConfig,\n colorMode: {\n defaultMode: fields.theme,\n disableSwitch: false,\n respectPrefersColorScheme: true,\n },\n },\n };\n }\n}\n\ninterface DocsBuildImage {\n id: string;\n name: string;\n imageRef: string;\n imageDigest?: string | null;\n status: 'PENDING' | 'VALIDATING' | 'APPROVED' | 'REJECTED' | 'DISABLED';\n buildCommand?: string | null;\n outputDir?: string | null;\n description?: string | null;\n lastValidationError?: string | null;\n}\n\ninterface DocsSettingsPanelProps {\n site: DocsSite;\n open: boolean;\n onClose: () => void;\n onSave: (data: {\n name?: string;\n visibility?: 'PRIVATE' | 'PUBLIC';\n provider?: DocsProvider;\n providerConfig?: Record<string, unknown>;\n }) => Promise<void>;\n}\n\nexport function DocsSettingsPanel({ site, open, onClose, onSave }: DocsSettingsPanelProps) {\n const tr = useTr();\n const [name, setName] = useState(site.name);\n const [visibility, setVisibility] = useState<'PRIVATE' | 'PUBLIC'>(site.visibility);\n const [provider, setProvider] = useState<DocsProvider>(site.provider || 'DOCUSAURUS');\n const config = (site.providerConfig || site.docusaurusConfig || {}) as Record<string, unknown>;\n const [tagline, setTagline] = useState((config.tagline as string) || '');\n const themeConfig = (config.themeConfig || {}) as Record<string, unknown>;\n const colorMode = (themeConfig.colorMode || {}) as Record<string, unknown>;\n const [theme, setTheme] = useState<string>((colorMode.defaultMode as string) || 'light');\n const [saving, setSaving] = useState(false);\n\n const [runtime, setRuntime] = useState<'MANAGED' | 'CUSTOMER_IMAGE'>(\n (site as any).runtime || 'MANAGED'\n );\n const [selectedImageId, setSelectedImageId] = useState<string | null>(\n (site as any).buildImageId || null\n );\n const [showRegister, setShowRegister] = useState(false);\n const [regName, setRegName] = useState('');\n const [regImageRef, setRegImageRef] = useState('');\n const [regBuildCommand, setRegBuildCommand] = useState('');\n const [regOutputDir, setRegOutputDir] = useState('build');\n const [regDescription, setRegDescription] = useState('');\n const [registering, setRegistering] = useState(false);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const { data: imagesData, refetch: refetchImages } = useListDocsBuildImagesQuery({\n fetchPolicy: 'cache-and-network',\n });\n const [registerImage] = useRegisterDocsBuildImageMutation();\n const [validateImage] = useValidateDocsBuildImageMutation();\n const [disableImage] = useDisableDocsBuildImageMutation();\n const [updateRuntime] = useUpdateDocsSiteRuntimeMutation();\n\n const images = (imagesData?.docsBuildImages?.items || []) as DocsBuildImage[];\n const approvedImages = images.filter((i) => i.status === 'APPROVED');\n\n useEffect(() => {\n setName(site.name);\n setVisibility(site.visibility);\n setProvider(site.provider || 'DOCUSAURUS');\n const cfg = (site.providerConfig || site.docusaurusConfig || {}) as Record<string, unknown>;\n setTagline((cfg.tagline as string) || '');\n const tc = (cfg.themeConfig || {}) as Record<string, unknown>;\n const cm = (tc.colorMode || {}) as Record<string, unknown>;\n setTheme((cm.defaultMode as string) || 'light');\n setRuntime(((site as any).runtime as 'MANAGED' | 'CUSTOMER_IMAGE') || 'MANAGED');\n setSelectedImageId((site as any).buildImageId || null);\n }, [site]);\n\n // Poll during VALIDATING state so the UI reflects status transitions live.\n useEffect(() => {\n if (!open) return;\n const hasValidating = images.some((i) => i.status === 'VALIDATING');\n if (!hasValidating) return;\n const interval = setInterval(() => {\n void refetchImages();\n }, 3000);\n return () => clearInterval(interval);\n }, [open, images, refetchImages]);\n\n const handleSave = async () => {\n setSaving(true);\n setActionError(null);\n try {\n await onSave({\n name,\n visibility,\n provider,\n providerConfig: buildProviderConfig(provider, config, themeConfig, {\n name,\n tagline,\n theme,\n }),\n });\n await updateRuntime({\n variables: {\n id: site.id,\n runtime: runtime as any,\n buildImageId: runtime === 'CUSTOMER_IMAGE' ? selectedImageId : null,\n },\n });\n onClose();\n } catch (err) {\n setActionError((err as Error).message);\n } finally {\n setSaving(false);\n }\n };\n\n const handleRegister = async () => {\n setRegistering(true);\n setActionError(null);\n try {\n const { data } = await registerImage({\n variables: {\n input: {\n name: regName,\n imageRef: regImageRef,\n description: regDescription || null,\n buildCommand: regBuildCommand || null,\n outputDir: regOutputDir || 'build',\n },\n },\n });\n const newId = (data as any)?.registerDocsBuildImage?.id;\n if (newId) {\n await validateImage({ variables: { id: newId } });\n }\n setRegName('');\n setRegImageRef('');\n setRegBuildCommand('');\n setRegOutputDir('build');\n setRegDescription('');\n setShowRegister(false);\n await refetchImages();\n } catch (err) {\n setActionError((err as Error).message);\n } finally {\n setRegistering(false);\n }\n };\n\n const handleValidate = async (id: string) => {\n setActionError(null);\n try {\n await validateImage({ variables: { id } });\n await refetchImages();\n } catch (err) {\n setActionError((err as Error).message);\n }\n };\n\n const handleDisable = async (id: string) => {\n setActionError(null);\n try {\n await disableImage({ variables: { id } });\n await refetchImages();\n } catch (err) {\n setActionError((err as Error).message);\n }\n };\n\n const statusBadge = (status: DocsBuildImage['status']) => {\n const base = 'inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium';\n if (status === 'APPROVED')\n return (\n <span className={`${base} bg-status-success-bg-subtle text-status-success-text`}>\n <Check className=\"size-3\" />\n Approved\n </span>\n );\n if (status === 'VALIDATING')\n return (\n <span className={`${base} bg-status-info-bg-subtle text-status-info-text`}>\n <Loader2 className=\"size-3 animate-spin\" />\n Validating\n </span>\n );\n if (status === 'REJECTED')\n return (\n <span className={`${base} bg-status-error-bg-subtle text-status-error-text`}>\n <XCircle className=\"size-3\" />\n Rejected\n </span>\n );\n if (status === 'DISABLED')\n return <span className={`${base} bg-bg-sunken text-text-muted`}>Disabled</span>;\n return <span className={`${base} bg-bg-sunken text-text-secondary`}>Pending</span>;\n };\n\n return (\n <AnimatePresence>\n {open && (\n <>\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 bg-bg-overlay z-40\"\n onClick={onClose}\n />\n <motion.div\n initial={{ x: '100%' }}\n animate={{ x: 0 }}\n exit={{ x: '100%' }}\n transition={{ type: 'spring', damping: 30, stiffness: 300 }}\n className=\"fixed right-0 top-0 bottom-0 w-[28rem] bg-bg-surface border-l border-border-default z-50 overflow-y-auto\"\n >\n <div className=\"p-4 border-b border-border-default flex items-center justify-between\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.docs.settings.title', 'Site Settings')}\n </h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 text-text-secondary hover:text-text-primary\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <div className=\"p-4 space-y-6\">\n <div>\n <label\n htmlFor=\"docssettingspanel-17\"\n className=\"block text-sm font-medium text-text-secondary mb-1\"\n >\n Site Title\n </label>\n <input\n id=\"docssettingspanel-17\"\n aria-label=\"control\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"docssettingspanel-16\"\n className=\"block text-sm font-medium text-text-secondary mb-1\"\n >\n Tagline\n </label>\n <input\n id=\"docssettingspanel-16\"\n aria-label=\"A brief tagline for your docs\"\n type=\"text\"\n value={tagline}\n onChange={(e) => setTagline(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n placeholder=\"A brief tagline for your docs\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"docssettingspanel-15\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Visibility\n </label>\n <div className=\"space-y-2\">\n <label className=\"flex items-center gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n id=\"docssettingspanel-15\"\n aria-label=\"visibility\"\n type=\"radio\"\n name=\"visibility\"\n value=\"PRIVATE\"\n checked={visibility === 'PRIVATE'}\n onChange={() => setVisibility('PRIVATE')}\n className=\"accent-action-primary-bg\"\n />\n <Lock className=\"size-4 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Private</p>\n <p className=\"text-xs text-text-muted\">\n Signed URLs that expire periodically\n </p>\n </div>\n </label>\n <label className=\"flex items-center gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n aria-label=\"visibility\"\n type=\"radio\"\n name=\"visibility\"\n value=\"PUBLIC\"\n checked={visibility === 'PUBLIC'}\n onChange={() => setVisibility('PUBLIC')}\n className=\"accent-action-primary-bg\"\n />\n <Globe className=\"size-4 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Public</p>\n <p className=\"text-xs text-text-muted\">Static URL accessible to anyone</p>\n </div>\n </label>\n </div>\n </div>\n\n <div>\n <label\n htmlFor=\"docssettingspanel-14\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Static-site generator\n </label>\n <div className=\"space-y-2\">\n {DOCS_PROVIDERS.map((p) => (\n <label\n key={p.value}\n data-testid={`docs-provider-${p.value.toLowerCase()}`}\n className=\"flex items-start gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\"\n >\n <input\n id=\"docssettingspanel-14\"\n aria-label=\"provider\"\n type=\"radio\"\n name=\"provider\"\n value={p.value}\n checked={provider === p.value}\n onChange={() => setProvider(p.value)}\n className=\"mt-1 accent-action-primary-bg\"\n />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{p.label}</p>\n <p className=\"text-xs text-text-muted\">{p.description}</p>\n </div>\n </label>\n ))}\n </div>\n {provider !== (site.provider || 'DOCUSAURUS') && (\n <p className=\"mt-2 text-xs text-text-warning\">\n Switching providers rebuilds the site from scratch. Existing artifacts stay\n until the next compile.\n </p>\n )}\n </div>\n\n {provider === 'DOCUSAURUS' && (\n <div>\n <label\n htmlFor=\"docssettingspanel-13\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Theme\n </label>\n <div className=\"flex gap-2\">\n {(['light', 'dark'] as const).map((t) => (\n <button\n type=\"button\"\n key={t}\n onClick={() => setTheme(t)}\n className={`flex-1 px-3 py-2 text-sm rounded-md border transition-colors ${\n theme === t\n ? 'bg-action-primary-bg text-action-primary-text border-action-primary-bg'\n : 'bg-bg-sunken text-text-secondary border-border-default hover:border-action-primary-bg'\n }`}\n >\n {t.charAt(0).toUpperCase() + t.slice(1)}\n </button>\n ))}\n </div>\n </div>\n )}\n\n <div className=\"pt-4 border-t border-border-default\">\n <label\n htmlFor=\"docssettingspanel-13\"\n className=\"block text-sm font-medium text-text-secondary mb-2\"\n >\n Build runtime\n </label>\n <div className=\"space-y-2\">\n <label className=\"flex items-start gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n id=\"docssettingspanel-13\"\n aria-label=\"runtime\"\n type=\"radio\"\n name=\"runtime\"\n value=\"MANAGED\"\n checked={runtime === 'MANAGED'}\n onChange={() => setRuntime('MANAGED')}\n className=\"accent-action-primary-bg mt-1\"\n />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Managed (default)</p>\n <p className=\"text-xs text-text-muted\">\n Uses the platform's built-in Docusaurus image. Zero setup — just compile and\n go.\n </p>\n </div>\n </label>\n <label className=\"flex items-start gap-3 p-3 bg-bg-sunken rounded-md cursor-pointer border border-border-default hover:border-action-primary-bg transition-colors\">\n <input\n aria-label=\"runtime\"\n type=\"radio\"\n name=\"runtime\"\n value=\"CUSTOMER_IMAGE\"\n checked={runtime === 'CUSTOMER_IMAGE'}\n onChange={() => setRuntime('CUSTOMER_IMAGE')}\n className=\"accent-action-primary-bg mt-1\"\n />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Custom image</p>\n <p className=\"text-xs text-text-muted\">\n Bring your own Docker build image — useful for custom Docusaurus plugins,\n themes, or alternative generators (MkDocs, VitePress). The image must be\n registered and approved below before it can be used.\n </p>\n </div>\n </label>\n </div>\n\n {runtime === 'CUSTOMER_IMAGE' && (\n <div className=\"mt-3 space-y-3\">\n <div>\n <label\n htmlFor=\"docssettingspanel-12\"\n className=\"block text-xs font-medium text-text-secondary mb-1\"\n >\n Select approved image\n </label>\n <select\n id=\"docssettingspanel-12\"\n value={selectedImageId ?? ''}\n onChange={(e) => setSelectedImageId(e.target.value || null)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n >\n <option value=\"\"> - Select an image - </option>\n {approvedImages.map((img) => (\n <option key={img.id} value={img.id}>\n {img.name} ({img.imageRef})\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"border border-border-default rounded-md overflow-hidden\">\n <div className=\"p-2 bg-bg-sunken border-b border-border-default flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-secondary\">\n Build images ({images.length})\n </span>\n <button\n type=\"button\"\n onClick={() => setShowRegister((v) => !v)}\n className=\"text-xs text-action-primary-text-alt hover:underline flex items-center gap-1\"\n data-testid=\"docs-register-image-toggle\"\n >\n <Plus className=\"size-3\" />\n {showRegister ? 'Cancel' : 'Register'}\n </button>\n </div>\n\n {showRegister && (\n <div className=\"p-3 space-y-2 bg-bg-base\">\n <input\n aria-label=\"Name (e.g. custom-theme)\"\n type=\"text\"\n value={regName}\n onChange={(e) => setRegName(e.target.value)}\n placeholder=\"Name (e.g. custom-theme)\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n data-testid=\"docs-register-image-name\"\n />\n <input\n aria-label=\"harbor.tooling.internal.burdenoff.com/...\"\n type=\"text\"\n value={regImageRef}\n onChange={(e) => setRegImageRef(e.target.value)}\n placeholder=\"harbor.tooling.internal.burdenoff.com/...\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n data-testid=\"docs-register-image-ref\"\n />\n <input\n aria-label=\"Build command (default: npm install && npm run build)\"\n type=\"text\"\n value={regBuildCommand}\n onChange={(e) => setRegBuildCommand(e.target.value)}\n placeholder=\"Build command (default: npm install && npm run build)\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n data-testid=\"docs-register-image-command\"\n />\n <input\n aria-label=\"Output dir (default: build)\"\n type=\"text\"\n value={regOutputDir}\n onChange={(e) => setRegOutputDir(e.target.value)}\n placeholder=\"Output dir (default: build)\"\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n />\n <textarea\n aria-label=\"Description (optional)\"\n value={regDescription}\n onChange={(e) => setRegDescription(e.target.value)}\n placeholder=\"Description (optional)\"\n rows={2}\n className=\"w-full px-2 py-1 bg-bg-sunken border border-border-default rounded text-text-primary text-xs\"\n />\n <button\n type=\"button\"\n onClick={handleRegister}\n disabled={registering || !regName || !regImageRef}\n className=\"w-full px-2 py-1 bg-action-primary-bg text-action-primary-text rounded text-xs hover:bg-action-primary-bg-hover disabled:opacity-50\"\n data-testid=\"docs-register-image-submit\"\n >\n {registering ? 'Registering…' : 'Register & Validate'}\n </button>\n </div>\n )}\n\n {images.length === 0 ? (\n <p className=\"text-xs text-text-muted p-3\">\n No build images registered yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-default\">\n {images.map((img) => (\n <li\n key={img.id}\n className={`p-2 ${img.status === 'REJECTED' ? 'bg-status-error-bg-subtle' : ''}`}\n data-testid={`docs-build-image-${img.id}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-xs font-medium text-text-primary truncate\">\n {img.name}\n </p>\n <p\n className=\"text-[10px] text-text-muted truncate\"\n title={img.imageRef}\n >\n {img.imageRef}\n </p>\n {img.lastValidationError && img.status === 'REJECTED' && (\n <div className=\"mt-1.5 flex items-start gap-1 p-1.5 rounded bg-status-error-bg-subtle text-status-error-text\">\n <AlertTriangle className=\"size-3 mt-0.5 shrink-0\" />\n <p className=\"text-[10px] leading-snug\">\n {img.lastValidationError}\n </p>\n </div>\n )}\n </div>\n <div className=\"flex flex-col items-end gap-1.5 shrink-0\">\n {statusBadge(img.status)}\n {img.status === 'REJECTED' && (\n <button\n type=\"button\"\n onClick={() => handleValidate(img.id)}\n className=\"flex items-center gap-1 px-2 py-0.5 rounded border border-status-error-border text-[10px] text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n data-testid={`docs-validate-image-${img.id}`}\n >\n <RefreshCw className=\"size-2.5\" />\n Re-validate\n </button>\n )}\n {(img.status === 'PENDING' || img.status === 'DISABLED') && (\n <button\n type=\"button\"\n onClick={() => handleValidate(img.id)}\n className=\"flex items-center gap-1 px-2 py-0.5 rounded border border-border-default text-[10px] text-text-secondary hover:border-action-primary-bg hover:text-action-primary-text-alt transition-colors\"\n data-testid={`docs-validate-image-${img.id}`}\n >\n Validate\n </button>\n )}\n {img.status === 'APPROVED' && (\n <button\n type=\"button\"\n onClick={() => handleDisable(img.id)}\n className=\"text-[10px] text-text-muted hover:underline\"\n >\n Disable\n </button>\n )}\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n )}\n </div>\n\n {actionError && (\n <div className=\"p-3 bg-status-error-bg-subtle text-status-error-text rounded-md text-xs\">\n {actionError}\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={saving || (runtime === 'CUSTOMER_IMAGE' && !selectedImageId)}\n className=\"w-full px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n data-testid=\"docs-settings-save\"\n >\n {saving\n ? tr('vibecontrols.docs.settings.saving', 'Saving...')\n : tr('vibecontrols.docs.settings.save', 'Save Settings')}\n </button>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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"}
|
|
@@ -167,7 +167,7 @@ function w({ vibeId: w, ownerType: ue, ownerId: de }) {
|
|
|
167
167
|
message: "You have reached the documentation site limit for your current plan. Upgrade to create more docs sites."
|
|
168
168
|
})] });
|
|
169
169
|
let Z = N ? /* @__PURE__ */ C("div", {
|
|
170
|
-
className: "flex items-center gap-2 px-4 py-2 mb-2 bg-status-error-bg text-status-error-text text-sm rounded-md",
|
|
170
|
+
className: "flex items-center gap-2 px-4 py-2 mb-2 bg-status-error-bg-subtle text-status-error-text text-sm rounded-md",
|
|
171
171
|
children: [
|
|
172
172
|
/* @__PURE__ */ S(b, { className: "size-4 shrink-0" }),
|
|
173
173
|
/* @__PURE__ */ S("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocsTabPanel.js","names":[],"sources":["../../../src/components/docs/DocsTabPanel.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { AlertCircle, X } from 'lucide-react';\nimport { LoadingState, ErrorState, QuotaExhaustedDialog } from '@/components/shared';\nimport { EntityShareButton } from '@/components/sharing';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { resolveGatewayUrls } from '@burdenoff/fe-libs/shared/config';\nimport { DocsEmptyState } from './DocsEmptyState';\nimport { DocsPreviewPanel } from './DocsPreviewPanel';\nimport { DocsEditPanel } from './DocsEditPanel';\nimport type { DocsSite, DocsRevision } from './types';\nimport {\n isQuotaExhaustedError,\n markQuotaHandledLocally,\n getQuotaDialogContent,\n} from '@/utils/quotaUtils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useGetDocsSiteQuery,\n useCreateDocsSiteMutation,\n useUpdateDocsSiteMutation,\n useUpdateDocsSiteVisibilityMutation,\n useCreateDocsPageMutation,\n useUpdateDocsPageMutation,\n useDeleteDocsPageMutation,\n useCompileDocsSiteMutation,\n useDocsContentQuery,\n useGetDocsRevisionLazyQuery,\n useGenerateDocsSignedUrlMutation,\n useRollbackDocsRevisionMutation,\n useDeleteDocsSiteMutation,\n} from '@/generated/wspace-operations';\n\ninterface DocsTabPanelProps {\n vibeId?: string;\n ownerType?: string;\n ownerId?: string;\n}\n\nexport function DocsTabPanel({ vibeId, ownerType, ownerId }: DocsTabPanelProps) {\n const tr = useTr();\n const effectiveOwnerType = ownerType || 'VIBE';\n const effectiveOwnerId = ownerId || vibeId || '';\n const [mode, setMode] = useState<'preview' | 'edit'>('preview');\n const [compileRevision, setCompileRevision] = useState<DocsRevision | null>(null);\n const [compiling, setCompiling] = useState(false);\n const [compileError, setCompileError] = useState<string | null>(null);\n const [selectedPageSlug, setSelectedPageSlug] = useState('');\n const [rollingBack, setRollingBack] = useState<string | null>(null);\n const navigate = useVibeNavigate();\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showCompileQuotaExhausted, setShowCompileQuotaExhausted] = useState(false);\n\n // Queries\n const { data, loading, error, refetch } = useGetDocsSiteQuery({\n variables: { ownerType: effectiveOwnerType as any, ownerId: effectiveOwnerId },\n skip: !effectiveOwnerId,\n fetchPolicy: 'cache-and-network',\n });\n\n const site = (data?.vibecontrolsDocsSiteByOwner ?? null) as DocsSite | null;\n const pages = site?.pages || [];\n\n const {\n data: docsContentData,\n loading: docsContentLoading,\n refetch: refetchDocsContent,\n } = useDocsContentQuery({\n variables: { siteId: site?.id || '', path: selectedPageSlug },\n skip: !site?.id || !site.currentRevisionId,\n fetchPolicy: 'network-only',\n });\n const docsHtml = docsContentData?.docsContent?.html ?? null;\n\n // Re-fetch docs content whenever currentRevisionId changes (compile finished or first publish).\n // This runs post-render so skip:false is guaranteed — imperative calls during async handlers\n // are no-ops when skip is still true in the closure.\n const prevRevisionIdRef = useRef<string | null | undefined>(undefined);\n useEffect(() => {\n const currentId = site?.currentRevisionId ?? null;\n if (prevRevisionIdRef.current === undefined) {\n prevRevisionIdRef.current = currentId;\n return;\n }\n if (currentId && currentId !== prevRevisionIdRef.current) {\n prevRevisionIdRef.current = currentId;\n refetchDocsContent();\n }\n }, [site?.currentRevisionId]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Compute gateway docs URL for iframe src (workspaceId prefix for isolation)\n const gatewayUrls = resolveGatewayUrls();\n const docsBaseUrl = site?.workspaceId\n ? `${gatewayUrls.workspaceBaseUrl}/workspaces/static/${site.workspaceId}/docs/${effectiveOwnerId}/`\n : null;\n\n // Mutations\n const [createSiteMutation, { loading: creating }] = useCreateDocsSiteMutation();\n const [updateSiteMutation] = useUpdateDocsSiteMutation();\n const [updateVisibilityMutation] = useUpdateDocsSiteVisibilityMutation();\n const [createPageMutation, { loading: pageAdding }] = useCreateDocsPageMutation();\n const [updatePageMutation] = useUpdateDocsPageMutation();\n const [deletePageMutation] = useDeleteDocsPageMutation();\n const [compileMutation] = useCompileDocsSiteMutation();\n const [generateSignedUrl] = useGenerateDocsSignedUrlMutation();\n const [rollbackRevision] = useRollbackDocsRevisionMutation();\n const [deleteSiteMutation] = useDeleteDocsSiteMutation();\n const [fetchRevision] = useGetDocsRevisionLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n // Handlers\n const handleCreateSite = async (\n provider: 'DOCUSAURUS' | 'MKDOCS' | 'VITEPRESS' = 'DOCUSAURUS'\n ) => {\n try {\n await createSiteMutation({\n variables: {\n input: {\n ownerType: effectiveOwnerType as any,\n ownerId: effectiveOwnerId,\n name: 'Documentation',\n provider: provider as any,\n },\n },\n refetchQueries: ['GetDocsSite'],\n awaitRefetchQueries: true,\n });\n setMode('edit');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n throw err;\n }\n };\n\n const handleSavePage = async (\n id: string,\n data: { title?: string; content?: string; slug?: string }\n ) => {\n await updatePageMutation({\n variables: { id, input: data },\n context: { autoRefreshAfterMutation: false },\n });\n };\n\n const handleAddPage = async (parentId?: string, category?: string) => {\n if (!site) return;\n const pageCount = pages.length;\n const slug = `page-${pageCount + 1}`;\n await createPageMutation({\n variables: {\n input: {\n siteId: site.id,\n parentId: parentId || null,\n slug,\n title: `New Page ${pageCount + 1}`,\n content: '# New Page\\n\\nStart writing here...',\n status: 'DRAFT',\n sidebarPosition: pageCount,\n category: category || null,\n },\n },\n });\n await refetch();\n };\n\n const handleDeletePage = async (pageId: string) => {\n if (!confirm('Delete this page?')) return;\n await deletePageMutation({ variables: { id: pageId } });\n await refetch();\n };\n\n const handleCompile = async () => {\n if (!site) return;\n setCompiling(true);\n setCompileError(null);\n try {\n const result = await compileMutation({\n variables: { siteId: site.id, publish: true },\n });\n if (result.error && isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setCompiling(false);\n setShowCompileQuotaExhausted(true);\n return;\n }\n if (result.data?.compileDocsSite) {\n setCompileRevision(result.data.compileDocsSite as DocsRevision);\n } else {\n setCompiling(false);\n setCompileError(\n 'Build could not be started — the server returned an empty response. Please try again.'\n );\n }\n } catch (err) {\n setCompiling(false);\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowCompileQuotaExhausted(true);\n return;\n }\n setCompileError(\n err instanceof Error ? err.message : 'Failed to start the build. Please try again.'\n );\n }\n };\n\n const handlePollRevision = async (revisionId: string): Promise<DocsRevision | null> => {\n const { data: revData } = await fetchRevision({ variables: { id: revisionId } });\n const rev = revData?.docsRevision as DocsRevision | null;\n if (rev) {\n setCompileRevision(rev);\n const done = ['SUCCEEDED', 'PUBLISHED', 'FAILED'].includes(rev.status);\n if (done) {\n setCompiling(false);\n if (rev.status === 'PUBLISHED' || rev.status === 'SUCCEEDED') {\n setSelectedPageSlug('');\n await refetch();\n }\n }\n }\n return rev;\n };\n\n const handleSaveSettings = async (data: {\n name?: string;\n visibility?: 'PRIVATE' | 'PUBLIC';\n provider?: 'DOCUSAURUS' | 'MKDOCS' | 'VITEPRESS';\n providerConfig?: Record<string, unknown>;\n }) => {\n if (!site) return;\n if (data.visibility && data.visibility !== site.visibility) {\n await updateVisibilityMutation({ variables: { id: site.id, visibility: data.visibility } });\n }\n const rest = {\n name: data.name,\n provider: data.provider,\n providerConfig: data.providerConfig,\n };\n if (Object.keys(rest).length > 0) {\n await updateSiteMutation({ variables: { id: site.id, input: rest } });\n }\n await refetch();\n };\n\n const handleGetShareUrl = async (): Promise<string | null> => {\n if (!site?.currentRevisionId) return null;\n try {\n const { data: urlData } = await generateSignedUrl({ variables: { siteId: site.id } });\n return urlData?.generateDocsSignedUrl?.url ?? null;\n } catch {\n return null;\n }\n };\n\n const handleRollback = async (revisionId: string) => {\n if (!site) return;\n if (!confirm('Roll back to this revision? The current published docs will be replaced.'))\n return;\n setRollingBack(revisionId);\n try {\n await rollbackRevision({ variables: { siteId: site.id, revisionId } });\n await refetch();\n } finally {\n setRollingBack(null);\n }\n };\n\n const handleDeleteSite = async () => {\n if (!site) return;\n if (!confirm('Delete this docs site and all its pages and revisions? This cannot be undone.'))\n return;\n await deleteSiteMutation({ variables: { id: site.id } });\n await refetch();\n };\n\n // Loading / Error states\n if (loading && !site) {\n return (\n <LoadingState message={tr('vibecontrols.docs.tab.loading', 'Loading documentation...')} />\n );\n }\n\n if (error && !site) {\n return (\n <ErrorState\n title={tr('vibecontrols.docs.tab.loadFailed', 'Failed to load docs')}\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n }\n\n // No site yet\n if (!site) {\n return (\n <>\n <DocsEmptyState onCreate={handleCreateSite} loading={creating} />\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Docs Site Quota Reached\"\n message=\"You have reached the documentation site limit for your current plan. Upgrade to create more docs sites.\"\n />\n </>\n );\n }\n\n const compileErrorBanner = compileError ? (\n <div className=\"flex items-center gap-2 px-4 py-2 mb-2 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span className=\"flex-1\">{compileError}</span>\n <button\n type=\"button\"\n onClick={() => setCompileError(null)}\n className=\"p-0.5 hover:opacity-70\"\n >\n <X className=\"size-3.5\" />\n </button>\n </div>\n ) : null;\n\n const compileQuotaDialog = (\n <QuotaExhaustedDialog\n isOpen={showCompileQuotaExhausted}\n onClose={() => setShowCompileQuotaExhausted(false)}\n onConfirm={() => {\n setShowCompileQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title={getQuotaDialogContent('docs-compilation').title}\n message={getQuotaDialogContent('docs-compilation').message}\n />\n );\n\n // Generic grant-based sharing for the docs site. Owner-keyed (no read-by-id\n // query) so we pass the `(ownerId, ownerType)` pair the controller reads by.\n const shareSlot = (\n <EntityShareButton\n entity=\"docs\"\n resource={{\n id: site.id,\n name: site.name,\n vibeId: site.vibeId ?? null,\n owner: { ownerId: effectiveOwnerId, ownerType: effectiveOwnerType },\n }}\n label={tr('vibecontrols.docs.share', 'Share')}\n />\n );\n\n // Edit mode\n if (mode === 'edit') {\n return (\n <>\n {compileErrorBanner}\n {compileQuotaDialog}\n <DocsEditPanel\n site={site}\n pages={pages}\n shareSlot={shareSlot}\n onBack={() => setMode('preview')}\n onSavePage={handleSavePage}\n onAddPage={handleAddPage}\n pageAdding={pageAdding}\n onDeletePage={handleDeletePage}\n onCompile={handleCompile}\n onSaveSettings={handleSaveSettings}\n onRollback={handleRollback}\n onDeleteSite={handleDeleteSite}\n rollingBack={rollingBack}\n compileRevision={compileRevision}\n onPollRevision={handlePollRevision}\n compiling={compiling}\n />\n </>\n );\n }\n\n // Preview mode\n return (\n <>\n {compileErrorBanner}\n {compileQuotaDialog}\n <DocsPreviewPanel\n site={site}\n pages={pages}\n shareSlot={shareSlot}\n selectedPageSlug={selectedPageSlug}\n onSelectPage={setSelectedPageSlug}\n docsBaseUrl={site.currentRevisionId ? (docsBaseUrl ?? null) : null}\n docsHtml={site.currentRevisionId ? docsHtml : null}\n docsContentLoading={docsContentLoading}\n onEdit={() => setMode('edit')}\n onCompile={handleCompile}\n onSettings={() => setMode('edit')}\n onGetShareUrl={handleGetShareUrl}\n onOpenFullScreen={() => {\n const pathMap: Record<string, string> = {\n VIBE: 'vibes',\n CATALOG: 'catalogs',\n COMPONENT: 'components',\n };\n const segment = pathMap[effectiveOwnerType] || 'vibes';\n navigate(`/${segment}/${effectiveOwnerId}/docs`);\n }}\n compileRevision={compileRevision}\n onPollRevision={handlePollRevision}\n compiling={compiling}\n />\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Docs Site Quota Reached\"\n message=\"You have reached the documentation site limit for your current plan. Upgrade to create more docs sites.\"\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DocsTabPanel.js","names":[],"sources":["../../../src/components/docs/DocsTabPanel.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { AlertCircle, X } from 'lucide-react';\nimport { LoadingState, ErrorState, QuotaExhaustedDialog } from '@/components/shared';\nimport { EntityShareButton } from '@/components/sharing';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { resolveGatewayUrls } from '@burdenoff/fe-libs/shared/config';\nimport { DocsEmptyState } from './DocsEmptyState';\nimport { DocsPreviewPanel } from './DocsPreviewPanel';\nimport { DocsEditPanel } from './DocsEditPanel';\nimport type { DocsSite, DocsRevision } from './types';\nimport {\n isQuotaExhaustedError,\n markQuotaHandledLocally,\n getQuotaDialogContent,\n} from '@/utils/quotaUtils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useGetDocsSiteQuery,\n useCreateDocsSiteMutation,\n useUpdateDocsSiteMutation,\n useUpdateDocsSiteVisibilityMutation,\n useCreateDocsPageMutation,\n useUpdateDocsPageMutation,\n useDeleteDocsPageMutation,\n useCompileDocsSiteMutation,\n useDocsContentQuery,\n useGetDocsRevisionLazyQuery,\n useGenerateDocsSignedUrlMutation,\n useRollbackDocsRevisionMutation,\n useDeleteDocsSiteMutation,\n} from '@/generated/wspace-operations';\n\ninterface DocsTabPanelProps {\n vibeId?: string;\n ownerType?: string;\n ownerId?: string;\n}\n\nexport function DocsTabPanel({ vibeId, ownerType, ownerId }: DocsTabPanelProps) {\n const tr = useTr();\n const effectiveOwnerType = ownerType || 'VIBE';\n const effectiveOwnerId = ownerId || vibeId || '';\n const [mode, setMode] = useState<'preview' | 'edit'>('preview');\n const [compileRevision, setCompileRevision] = useState<DocsRevision | null>(null);\n const [compiling, setCompiling] = useState(false);\n const [compileError, setCompileError] = useState<string | null>(null);\n const [selectedPageSlug, setSelectedPageSlug] = useState('');\n const [rollingBack, setRollingBack] = useState<string | null>(null);\n const navigate = useVibeNavigate();\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showCompileQuotaExhausted, setShowCompileQuotaExhausted] = useState(false);\n\n // Queries\n const { data, loading, error, refetch } = useGetDocsSiteQuery({\n variables: { ownerType: effectiveOwnerType as any, ownerId: effectiveOwnerId },\n skip: !effectiveOwnerId,\n fetchPolicy: 'cache-and-network',\n });\n\n const site = (data?.vibecontrolsDocsSiteByOwner ?? null) as DocsSite | null;\n const pages = site?.pages || [];\n\n const {\n data: docsContentData,\n loading: docsContentLoading,\n refetch: refetchDocsContent,\n } = useDocsContentQuery({\n variables: { siteId: site?.id || '', path: selectedPageSlug },\n skip: !site?.id || !site.currentRevisionId,\n fetchPolicy: 'network-only',\n });\n const docsHtml = docsContentData?.docsContent?.html ?? null;\n\n // Re-fetch docs content whenever currentRevisionId changes (compile finished or first publish).\n // This runs post-render so skip:false is guaranteed — imperative calls during async handlers\n // are no-ops when skip is still true in the closure.\n const prevRevisionIdRef = useRef<string | null | undefined>(undefined);\n useEffect(() => {\n const currentId = site?.currentRevisionId ?? null;\n if (prevRevisionIdRef.current === undefined) {\n prevRevisionIdRef.current = currentId;\n return;\n }\n if (currentId && currentId !== prevRevisionIdRef.current) {\n prevRevisionIdRef.current = currentId;\n refetchDocsContent();\n }\n }, [site?.currentRevisionId]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Compute gateway docs URL for iframe src (workspaceId prefix for isolation)\n const gatewayUrls = resolveGatewayUrls();\n const docsBaseUrl = site?.workspaceId\n ? `${gatewayUrls.workspaceBaseUrl}/workspaces/static/${site.workspaceId}/docs/${effectiveOwnerId}/`\n : null;\n\n // Mutations\n const [createSiteMutation, { loading: creating }] = useCreateDocsSiteMutation();\n const [updateSiteMutation] = useUpdateDocsSiteMutation();\n const [updateVisibilityMutation] = useUpdateDocsSiteVisibilityMutation();\n const [createPageMutation, { loading: pageAdding }] = useCreateDocsPageMutation();\n const [updatePageMutation] = useUpdateDocsPageMutation();\n const [deletePageMutation] = useDeleteDocsPageMutation();\n const [compileMutation] = useCompileDocsSiteMutation();\n const [generateSignedUrl] = useGenerateDocsSignedUrlMutation();\n const [rollbackRevision] = useRollbackDocsRevisionMutation();\n const [deleteSiteMutation] = useDeleteDocsSiteMutation();\n const [fetchRevision] = useGetDocsRevisionLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n // Handlers\n const handleCreateSite = async (\n provider: 'DOCUSAURUS' | 'MKDOCS' | 'VITEPRESS' = 'DOCUSAURUS'\n ) => {\n try {\n await createSiteMutation({\n variables: {\n input: {\n ownerType: effectiveOwnerType as any,\n ownerId: effectiveOwnerId,\n name: 'Documentation',\n provider: provider as any,\n },\n },\n refetchQueries: ['GetDocsSite'],\n awaitRefetchQueries: true,\n });\n setMode('edit');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n throw err;\n }\n };\n\n const handleSavePage = async (\n id: string,\n data: { title?: string; content?: string; slug?: string }\n ) => {\n await updatePageMutation({\n variables: { id, input: data },\n context: { autoRefreshAfterMutation: false },\n });\n };\n\n const handleAddPage = async (parentId?: string, category?: string) => {\n if (!site) return;\n const pageCount = pages.length;\n const slug = `page-${pageCount + 1}`;\n await createPageMutation({\n variables: {\n input: {\n siteId: site.id,\n parentId: parentId || null,\n slug,\n title: `New Page ${pageCount + 1}`,\n content: '# New Page\\n\\nStart writing here...',\n status: 'DRAFT',\n sidebarPosition: pageCount,\n category: category || null,\n },\n },\n });\n await refetch();\n };\n\n const handleDeletePage = async (pageId: string) => {\n if (!confirm('Delete this page?')) return;\n await deletePageMutation({ variables: { id: pageId } });\n await refetch();\n };\n\n const handleCompile = async () => {\n if (!site) return;\n setCompiling(true);\n setCompileError(null);\n try {\n const result = await compileMutation({\n variables: { siteId: site.id, publish: true },\n });\n if (result.error && isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setCompiling(false);\n setShowCompileQuotaExhausted(true);\n return;\n }\n if (result.data?.compileDocsSite) {\n setCompileRevision(result.data.compileDocsSite as DocsRevision);\n } else {\n setCompiling(false);\n setCompileError(\n 'Build could not be started — the server returned an empty response. Please try again.'\n );\n }\n } catch (err) {\n setCompiling(false);\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowCompileQuotaExhausted(true);\n return;\n }\n setCompileError(\n err instanceof Error ? err.message : 'Failed to start the build. Please try again.'\n );\n }\n };\n\n const handlePollRevision = async (revisionId: string): Promise<DocsRevision | null> => {\n const { data: revData } = await fetchRevision({ variables: { id: revisionId } });\n const rev = revData?.docsRevision as DocsRevision | null;\n if (rev) {\n setCompileRevision(rev);\n const done = ['SUCCEEDED', 'PUBLISHED', 'FAILED'].includes(rev.status);\n if (done) {\n setCompiling(false);\n if (rev.status === 'PUBLISHED' || rev.status === 'SUCCEEDED') {\n setSelectedPageSlug('');\n await refetch();\n }\n }\n }\n return rev;\n };\n\n const handleSaveSettings = async (data: {\n name?: string;\n visibility?: 'PRIVATE' | 'PUBLIC';\n provider?: 'DOCUSAURUS' | 'MKDOCS' | 'VITEPRESS';\n providerConfig?: Record<string, unknown>;\n }) => {\n if (!site) return;\n if (data.visibility && data.visibility !== site.visibility) {\n await updateVisibilityMutation({ variables: { id: site.id, visibility: data.visibility } });\n }\n const rest = {\n name: data.name,\n provider: data.provider,\n providerConfig: data.providerConfig,\n };\n if (Object.keys(rest).length > 0) {\n await updateSiteMutation({ variables: { id: site.id, input: rest } });\n }\n await refetch();\n };\n\n const handleGetShareUrl = async (): Promise<string | null> => {\n if (!site?.currentRevisionId) return null;\n try {\n const { data: urlData } = await generateSignedUrl({ variables: { siteId: site.id } });\n return urlData?.generateDocsSignedUrl?.url ?? null;\n } catch {\n return null;\n }\n };\n\n const handleRollback = async (revisionId: string) => {\n if (!site) return;\n if (!confirm('Roll back to this revision? The current published docs will be replaced.'))\n return;\n setRollingBack(revisionId);\n try {\n await rollbackRevision({ variables: { siteId: site.id, revisionId } });\n await refetch();\n } finally {\n setRollingBack(null);\n }\n };\n\n const handleDeleteSite = async () => {\n if (!site) return;\n if (!confirm('Delete this docs site and all its pages and revisions? This cannot be undone.'))\n return;\n await deleteSiteMutation({ variables: { id: site.id } });\n await refetch();\n };\n\n // Loading / Error states\n if (loading && !site) {\n return (\n <LoadingState message={tr('vibecontrols.docs.tab.loading', 'Loading documentation...')} />\n );\n }\n\n if (error && !site) {\n return (\n <ErrorState\n title={tr('vibecontrols.docs.tab.loadFailed', 'Failed to load docs')}\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n }\n\n // No site yet\n if (!site) {\n return (\n <>\n <DocsEmptyState onCreate={handleCreateSite} loading={creating} />\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Docs Site Quota Reached\"\n message=\"You have reached the documentation site limit for your current plan. Upgrade to create more docs sites.\"\n />\n </>\n );\n }\n\n const compileErrorBanner = compileError ? (\n <div className=\"flex items-center gap-2 px-4 py-2 mb-2 bg-status-error-bg-subtle text-status-error-text text-sm rounded-md\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span className=\"flex-1\">{compileError}</span>\n <button\n type=\"button\"\n onClick={() => setCompileError(null)}\n className=\"p-0.5 hover:opacity-70\"\n >\n <X className=\"size-3.5\" />\n </button>\n </div>\n ) : null;\n\n const compileQuotaDialog = (\n <QuotaExhaustedDialog\n isOpen={showCompileQuotaExhausted}\n onClose={() => setShowCompileQuotaExhausted(false)}\n onConfirm={() => {\n setShowCompileQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title={getQuotaDialogContent('docs-compilation').title}\n message={getQuotaDialogContent('docs-compilation').message}\n />\n );\n\n // Generic grant-based sharing for the docs site. Owner-keyed (no read-by-id\n // query) so we pass the `(ownerId, ownerType)` pair the controller reads by.\n const shareSlot = (\n <EntityShareButton\n entity=\"docs\"\n resource={{\n id: site.id,\n name: site.name,\n vibeId: site.vibeId ?? null,\n owner: { ownerId: effectiveOwnerId, ownerType: effectiveOwnerType },\n }}\n label={tr('vibecontrols.docs.share', 'Share')}\n />\n );\n\n // Edit mode\n if (mode === 'edit') {\n return (\n <>\n {compileErrorBanner}\n {compileQuotaDialog}\n <DocsEditPanel\n site={site}\n pages={pages}\n shareSlot={shareSlot}\n onBack={() => setMode('preview')}\n onSavePage={handleSavePage}\n onAddPage={handleAddPage}\n pageAdding={pageAdding}\n onDeletePage={handleDeletePage}\n onCompile={handleCompile}\n onSaveSettings={handleSaveSettings}\n onRollback={handleRollback}\n onDeleteSite={handleDeleteSite}\n rollingBack={rollingBack}\n compileRevision={compileRevision}\n onPollRevision={handlePollRevision}\n compiling={compiling}\n />\n </>\n );\n }\n\n // Preview mode\n return (\n <>\n {compileErrorBanner}\n {compileQuotaDialog}\n <DocsPreviewPanel\n site={site}\n pages={pages}\n shareSlot={shareSlot}\n selectedPageSlug={selectedPageSlug}\n onSelectPage={setSelectedPageSlug}\n docsBaseUrl={site.currentRevisionId ? (docsBaseUrl ?? null) : null}\n docsHtml={site.currentRevisionId ? docsHtml : null}\n docsContentLoading={docsContentLoading}\n onEdit={() => setMode('edit')}\n onCompile={handleCompile}\n onSettings={() => setMode('edit')}\n onGetShareUrl={handleGetShareUrl}\n onOpenFullScreen={() => {\n const pathMap: Record<string, string> = {\n VIBE: 'vibes',\n CATALOG: 'catalogs',\n COMPONENT: 'components',\n };\n const segment = pathMap[effectiveOwnerType] || 'vibes';\n navigate(`/${segment}/${effectiveOwnerId}/docs`);\n }}\n compileRevision={compileRevision}\n onPollRevision={handlePollRevision}\n compiling={compiling}\n />\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Docs Site Quota Reached\"\n message=\"You have reached the documentation site limit for your current plan. Upgrade to create more docs sites.\"\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -54,7 +54,7 @@ function p({ connection: p, availablePermissions: m, availableScopes: h, onSave:
|
|
|
54
54
|
type: "button",
|
|
55
55
|
"aria-pressed": x.has(e),
|
|
56
56
|
onClick: () => T(e),
|
|
57
|
-
className: `${f} ${x.has(e) ? "border-action-primary-bg bg-
|
|
57
|
+
className: `${f} ${x.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
58
58
|
children: e
|
|
59
59
|
}, e)), (m ?? []).length === 0 && /* @__PURE__ */ r("span", {
|
|
60
60
|
className: "text-xs text-text-muted",
|
|
@@ -74,7 +74,7 @@ function p({ connection: p, availablePermissions: m, availableScopes: h, onSave:
|
|
|
74
74
|
type: "button",
|
|
75
75
|
"aria-pressed": C.has(e),
|
|
76
76
|
onClick: () => E(e),
|
|
77
|
-
className: `${f} ${C.has(e) ? "border-action-primary-bg bg-
|
|
77
|
+
className: `${f} ${C.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
78
78
|
children: e
|
|
79
79
|
}, e)), (h ?? []).length === 0 && /* @__PURE__ */ r("span", {
|
|
80
80
|
className: "text-xs text-text-muted",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionPermissionsDialog.js","names":[],"sources":["../../../src/components/graph/ConnectionPermissionsDialog.tsx"],"sourcesContent":["/**\n * ConnectionPermissionsDialog — Edit RBAC permissions and scopes for an agent connection edge.\n */\n\nimport { useId, useState } from 'react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface Connection {\n id: string;\n protocol: string;\n permissions?: string[];\n scopes?: string[];\n label?: string | null;\n description?: string | null;\n sourceAgent: { id: string; name: string };\n target: { id: string; name: string };\n}\n\ninterface Props {\n connection: Connection;\n availablePermissions?: string[];\n availableScopes?: string[];\n onSave: (permissions: string[], scopes: string[]) => void;\n onClose: () => void;\n}\n\nconst CHIP_BASE_CLASS =\n 'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function ConnectionPermissionsDialog({\n connection,\n availablePermissions,\n availableScopes,\n onSave,\n onClose,\n}: Props) {\n const tr = useTr();\n const permissionsLabelId = useId();\n const scopesLabelId = useId();\n const [permissions, setPermissions] = useState<Set<string>>(\n new Set(connection.permissions || [])\n );\n const [scopes, setScopes] = useState<Set<string>>(new Set(connection.scopes || []));\n\n const togglePermission = (p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n };\n\n const toggleScope = (s: string) => {\n setScopes((prev) => {\n const next = new Set(prev);\n if (next.has(s)) next.delete(s);\n else next.add(s);\n return next;\n });\n };\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('connectionPermissionsDialog.title', 'Connection Permissions')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n <span className=\"font-medium text-text-primary\">{connection.sourceAgent.name}</span>\n {' → '}\n <span className=\"font-medium text-text-primary\">{connection.target.name}</span>\n <span className=\"ml-2 rounded bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {connection.protocol}\n </span>\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Permissions */}\n <div role=\"group\" aria-labelledby={permissionsLabelId}>\n <span\n id={permissionsLabelId}\n className=\"mb-2 block text-sm font-medium text-text-primary\"\n >\n {tr('connectionPermissionsDialog.permissions', 'Permissions')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(availablePermissions ?? []).map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-
|
|
1
|
+
{"version":3,"file":"ConnectionPermissionsDialog.js","names":[],"sources":["../../../src/components/graph/ConnectionPermissionsDialog.tsx"],"sourcesContent":["/**\n * ConnectionPermissionsDialog — Edit RBAC permissions and scopes for an agent connection edge.\n */\n\nimport { useId, useState } from 'react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface Connection {\n id: string;\n protocol: string;\n permissions?: string[];\n scopes?: string[];\n label?: string | null;\n description?: string | null;\n sourceAgent: { id: string; name: string };\n target: { id: string; name: string };\n}\n\ninterface Props {\n connection: Connection;\n availablePermissions?: string[];\n availableScopes?: string[];\n onSave: (permissions: string[], scopes: string[]) => void;\n onClose: () => void;\n}\n\nconst CHIP_BASE_CLASS =\n 'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function ConnectionPermissionsDialog({\n connection,\n availablePermissions,\n availableScopes,\n onSave,\n onClose,\n}: Props) {\n const tr = useTr();\n const permissionsLabelId = useId();\n const scopesLabelId = useId();\n const [permissions, setPermissions] = useState<Set<string>>(\n new Set(connection.permissions || [])\n );\n const [scopes, setScopes] = useState<Set<string>>(new Set(connection.scopes || []));\n\n const togglePermission = (p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n };\n\n const toggleScope = (s: string) => {\n setScopes((prev) => {\n const next = new Set(prev);\n if (next.has(s)) next.delete(s);\n else next.add(s);\n return next;\n });\n };\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('connectionPermissionsDialog.title', 'Connection Permissions')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n <span className=\"font-medium text-text-primary\">{connection.sourceAgent.name}</span>\n {' → '}\n <span className=\"font-medium text-text-primary\">{connection.target.name}</span>\n <span className=\"ml-2 rounded bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {connection.protocol}\n </span>\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Permissions */}\n <div role=\"group\" aria-labelledby={permissionsLabelId}>\n <span\n id={permissionsLabelId}\n className=\"mb-2 block text-sm font-medium text-text-primary\"\n >\n {tr('connectionPermissionsDialog.permissions', 'Permissions')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(availablePermissions ?? []).map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n ))}\n {(availablePermissions ?? []).length === 0 && (\n <span className=\"text-xs text-text-muted\">\n {tr('connectionPermissionsDialog.noPermissions', 'No permissions configured')}\n </span>\n )}\n </div>\n </div>\n\n {/* Scopes */}\n <div role=\"group\" aria-labelledby={scopesLabelId}>\n <span id={scopesLabelId} className=\"mb-2 block text-sm font-medium text-text-primary\">\n {tr('connectionPermissionsDialog.scopes', 'Scopes')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(availableScopes ?? []).map((s) => (\n <button\n type=\"button\"\n key={s}\n aria-pressed={scopes.has(s)}\n onClick={() => toggleScope(s)}\n className={`${CHIP_BASE_CLASS} ${\n scopes.has(s)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {s}\n </button>\n ))}\n {(availableScopes ?? []).length === 0 && (\n <span className=\"text-xs text-text-muted\">\n {tr('connectionPermissionsDialog.noScopes', 'No scopes configured')}\n </span>\n )}\n </div>\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} className=\"max-md:min-h-11\">\n {tr('connectionPermissionsDialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => onSave(Array.from(permissions), Array.from(scopes))}\n className=\"max-md:min-h-11\"\n >\n {tr('connectionPermissionsDialog.save', 'Save Permissions')}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -251,7 +251,7 @@ function y({ sourceAgents: y, initialSourceAgentId: ve, availablePermissions: b,
|
|
|
251
251
|
type: "button",
|
|
252
252
|
"aria-pressed": T === "ssh",
|
|
253
253
|
onClick: () => E("ssh"),
|
|
254
|
-
className: `${v} ${T === "ssh" ? "border-action-primary-bg bg-
|
|
254
|
+
className: `${v} ${T === "ssh" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
255
255
|
children: [/* @__PURE__ */ s(ae, {
|
|
256
256
|
className: "size-3.5",
|
|
257
257
|
"aria-hidden": "true"
|
|
@@ -260,7 +260,7 @@ function y({ sourceAgents: y, initialSourceAgentId: ve, availablePermissions: b,
|
|
|
260
260
|
type: "button",
|
|
261
261
|
"aria-pressed": T === "same",
|
|
262
262
|
onClick: () => E("same"),
|
|
263
|
-
className: `${v} ${T === "same" ? "border-action-primary-bg bg-
|
|
263
|
+
className: `${v} ${T === "same" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
264
264
|
children: [/* @__PURE__ */ s(ie, {
|
|
265
265
|
className: "size-3.5",
|
|
266
266
|
"aria-hidden": "true"
|
|
@@ -368,7 +368,7 @@ function y({ sourceAgents: y, initialSourceAgentId: ve, availablePermissions: b,
|
|
|
368
368
|
type: "button",
|
|
369
369
|
"aria-pressed": F.has(e),
|
|
370
370
|
onClick: () => De(e),
|
|
371
|
-
className: `${_e} ${F.has(e) ? "border-action-primary-bg bg-
|
|
371
|
+
className: `${_e} ${F.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
372
372
|
children: e
|
|
373
373
|
}, e))
|
|
374
374
|
})] }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeshDeployWizard.js","names":[],"sources":["../../../src/components/graph/MeshDeployWizard.tsx"],"sourcesContent":["/**\n * MeshDeployWizard — deploy a new agent THROUGH the mesh from a chosen source\n * (root) agent. Supports two modes:\n * - \"Remote (SSH)\": deploy onto another machine over SSH (host/user/port/key).\n * - \"Same machine\": deploy a sibling agent on the source agent's own host.\n *\n * Submitting calls `deployAgentViaMesh({ rootAgentId, input })`, reads the\n * `{ jobId }` out of the AgentProxyResponse, then polls\n * `meshDeployJobStatus(rootAgentId, jobId)` on a 2s interval, rendering the\n * job's step list in a progress panel. On completion it calls `onCompleted`\n * so the caller can refetch the mesh graph.\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { Loader2, CheckCircle, XCircle, Check, Server, Laptop } from 'lucide-react';\nimport {\n Button,\n Checkbox,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Stepper,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useField } from '../../shared/hooks/useField';\nimport {\n useDeployAgentViaMeshMutation,\n useMeshDeployJobStatusLazyQuery,\n} from '@/generated/wspace-operations';\n\n// English fallbacks for the mesh-deploy job step labels. Extends the\n// install-job labels with the mesh-specific bootstrap/onboard/peer-probe\n// steps. Translated via `mesh.deploy.steps.{step}` keys.\nconst stepLabels: Record<string, string> = {\n probe: 'Probing target',\n 'install-bun': 'Installing Bun runtime',\n 'install-agent': 'Installing VibeControls Agent',\n autostart: 'Configuring autostart',\n plugins: 'Installing plugins',\n start: 'Starting agent',\n bootstrap: 'Bootstrapping tunnel',\n onboard: 'Onboarding agent',\n 'store-creds': 'Storing credentials',\n 'report-link': 'Linking into mesh',\n 'probe-peer': 'Probing new peer',\n};\n\ninterface DeployJobStep {\n step: string;\n status: string;\n message?: string;\n}\n\ninterface DeployJob {\n status: 'running' | 'completed' | 'failed' | string;\n steps: DeployJobStep[];\n error?: string;\n}\n\ninterface SourceAgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n}\n\ninterface MeshDeployWizardProps {\n /** Agents that can act as the deploying root. */\n sourceAgents: SourceAgentOption[];\n /** Pre-seeded source (root) agent id (e.g. \"deploy from here\" on a node). */\n initialSourceAgentId?: string | null;\n availablePermissions?: string[];\n onClose: () => void;\n onCompleted: () => void;\n}\n\ntype DeployMode = 'ssh' | 'same';\ntype AuthMethod = 'agent' | 'key';\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Record<string, unknown> | null, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\n\nconst LABEL_CLASS = 'mb-1.5 block text-sm font-medium text-text-primary';\nconst CHIP_BASE_CLASS =\n 'rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\nconst MODE_BUTTON_BASE_CLASS =\n 'inline-flex flex-1 items-center justify-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function MeshDeployWizard({\n sourceAgents,\n initialSourceAgentId,\n availablePermissions,\n onClose,\n onCompleted,\n}: MeshDeployWizardProps) {\n const tr = useTr();\n\n const [sourceAgentId, setSourceAgentId] = useState<string>(\n initialSourceAgentId ?? sourceAgents[0]?.id ?? ''\n );\n const [mode, setMode] = useState<DeployMode>('ssh');\n const [host, setHost] = useState('');\n const [port, setPort] = useState(22);\n const [user, setUser] = useState('');\n const [authMethod, setAuthMethod] = useState<AuthMethod>('agent');\n const [keyPath, setKeyPath] = useState('');\n const [label, setLabel] = useState('');\n const [connectExisting, setConnectExisting] = useState(false);\n const [permissions, setPermissions] = useState<Set<string>>(new Set());\n\n const [jobId, setJobId] = useState<string | null>(null);\n const [job, setJob] = useState<DeployJob | null>(null);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n const sourceField = useField();\n const hostField = useField();\n const portField = useField();\n const userField = useField();\n const keyField = useField();\n const labelField = useField();\n\n const [deploy, { loading: deploying }] = useDeployAgentViaMeshMutation();\n const [fetchJobStatus] = useMeshDeployJobStatusLazyQuery({ fetchPolicy: 'network-only' });\n\n const canSubmit = useMemo(() => {\n if (!sourceAgentId) return false;\n if (mode === 'ssh') {\n const targetSpec = host.trim();\n return targetSpec.length > 0 && user.trim().length > 0;\n }\n return true;\n }, [sourceAgentId, mode, host, user]);\n\n const togglePermission = useCallback((p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n }, []);\n\n // Build the SSH `target` string the backend expects (user@host:port).\n const buildTargetString = useCallback((): string => {\n if (mode === 'same') return 'localhost';\n const trimmedHost = host.trim();\n const trimmedUser = user.trim();\n const base = trimmedUser ? `${trimmedUser}@${trimmedHost}` : trimmedHost;\n return port && port !== 22 ? `${base}:${port}` : base;\n }, [mode, host, user, port]);\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return;\n setSubmitError(null);\n setJob(null);\n setJobId(null);\n try {\n const res = await deploy({\n variables: {\n rootAgentId: sourceAgentId,\n input: {\n target: buildTargetString(),\n permissions: permissions.size > 0 ? Array.from(permissions) : undefined,\n identityFile: mode === 'ssh' && authMethod === 'key' ? keyPath || undefined : undefined,\n label: label || undefined,\n connectExisting,\n },\n },\n });\n const proxy = res.data?.deployAgentViaMesh;\n if (res.error || proxy?.error || proxy?.ok === false) {\n setSubmitError(\n proxy?.error ?? res.error?.message ?? tr('mesh.deploy.failed', 'Deploy failed')\n );\n return;\n }\n const data = asRecord(proxy?.data);\n const newJobId = jstr(data, 'jobId');\n if (!newJobId) {\n setSubmitError(\n tr('mesh.deploy.noJob', 'Deploy started but no job id was returned by the agent.')\n );\n return;\n }\n setJobId(newJobId);\n setJob({ status: jstr(data, 'status') ?? 'running', steps: [] });\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n }, [\n canSubmit,\n deploy,\n sourceAgentId,\n buildTargetString,\n permissions,\n mode,\n authMethod,\n keyPath,\n label,\n connectExisting,\n tr,\n ]);\n\n // Poll the mesh-deploy job once a jobId is established.\n useEffect(() => {\n if (!jobId || !sourceAgentId) return;\n let cancelled = false;\n\n const poll = () => {\n void fetchJobStatus({ variables: { rootAgentId: sourceAgentId, jobId } })\n .then((result) => {\n if (cancelled) return;\n const proxy = result.data?.meshDeployJobStatus;\n const data = asRecord(proxy?.data);\n if (!data) return;\n const rawSteps = Array.isArray(data.steps) ? (data.steps as DeployJobStep[]) : [];\n setJob({\n status: jstr(data, 'status') ?? 'running',\n steps: rawSteps,\n error: jstr(data, 'error') ?? proxy?.error ?? undefined,\n });\n const status = jstr(data, 'status');\n if (status === 'completed') {\n onCompleted();\n }\n })\n .catch(() => {\n // transient — retry on next interval\n });\n };\n\n poll();\n const interval = setInterval(poll, 2000);\n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, [jobId, sourceAgentId, fetchJobStatus, onCompleted]);\n\n const jobRunning =\n job?.status === 'running' ||\n (jobId !== null && job?.status !== 'completed' && job?.status !== 'failed');\n\n const wizardSteps = [\n { key: 'configure', label: tr('mesh.deploy.steps.configure', 'Configure') },\n { key: 'deploy', label: tr('mesh.deploy.steps.deploy', 'Deploy') },\n ];\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open && !jobRunning) onClose();\n }}\n >\n <ResponsiveDialogContent\n className=\"max-h-[min(85vh,44rem)] overflow-y-auto sm:max-w-lg\"\n showCloseButton={!jobRunning}\n >\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('mesh.deploy.title', 'Deploy Agent via Mesh')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'mesh.deploy.description',\n 'Deploy a new agent through the mesh from a selected source agent.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <Stepper steps={wizardSteps} active={jobId ? 1 : 0} />\n\n {/* Job progress takes over once a job is running */}\n {jobId ? (\n <div className=\"space-y-3\">\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.deploying', 'Deploying through {{agent}}…', {\n agent: sourceAgents.find((a) => a.id === sourceAgentId)?.name ?? 'agent',\n })}\n </p>\n <div className=\"space-y-2\">\n {job && job.steps.length > 0 ? (\n job.steps.map((step, idx) => (\n <div key={`${step.step}-${idx}`} className=\"flex items-center gap-3\">\n {step.status === 'running' && (\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n )}\n {step.status === 'completed' && (\n <CheckCircle className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'failed' && (\n <XCircle className=\"size-4 text-status-error-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'skipped' && (\n <Check className=\"size-4 text-text-muted\" aria-hidden=\"true\" />\n )}\n {step.status === 'pending' && (\n <div\n className=\"size-4 rounded-full border-2 border-border-default\"\n aria-hidden=\"true\"\n />\n )}\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {tr(`mesh.deploy.steps.${step.step}`, stepLabels[step.step] ?? step.step)}\n </div>\n {step.message ? (\n <div className=\"text-xs break-words text-text-muted\">{step.message}</div>\n ) : null}\n </div>\n </div>\n ))\n ) : (\n <div className=\"flex items-center gap-3\">\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.starting', 'Starting deploy job…')}\n </span>\n </div>\n )}\n </div>\n\n {job?.status === 'completed' && (\n <div\n role=\"status\"\n className=\"rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\"\n >\n {tr('mesh.deploy.success', 'Agent deployed successfully!')}\n </div>\n )}\n {job?.status === 'failed' && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {job.error ?? tr('mesh.deploy.jobFailed', 'Deploy failed.')}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n disabled={jobRunning}\n aria-busy={jobRunning}\n className=\"w-full max-md:min-h-11\"\n >\n {jobRunning\n ? tr('mesh.deploy.deployingShort', 'Deploying…')\n : tr('common.close', 'Close')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Source agent */}\n <div>\n <label className={LABEL_CLASS} htmlFor={sourceField.id}>\n {tr('mesh.deploy.sourceAgent', 'Source agent (deploys from)')}\n </label>\n <Select\n value={sourceAgentId || undefined}\n onValueChange={setSourceAgentId}\n disabled={sourceAgents.length === 0}\n >\n <SelectTrigger id={sourceField.id} className=\"w-full text-base md:text-sm\">\n <SelectValue placeholder={tr('mesh.deploy.noAgents', 'No agents available')} />\n </SelectTrigger>\n <SelectContent>\n {sourceAgents.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name} ({a.hostname}){a.isActive ? '' : ' · offline'}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Mode toggle */}\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.mode', 'Deploy mode')}</span>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n aria-pressed={mode === 'ssh'}\n onClick={() => setMode('ssh')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'ssh'\n ? 'border-action-primary-bg bg-action-primary-bg/10 text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Server className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSsh', 'Remote (SSH)')}\n </button>\n <button\n type=\"button\"\n aria-pressed={mode === 'same'}\n onClick={() => setMode('same')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'same'\n ? 'border-action-primary-bg bg-action-primary-bg/10 text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Laptop className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSame', 'Same machine')}\n </button>\n </div>\n </div>\n\n {/* SSH fields */}\n {mode === 'ssh' && (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-3\">\n <div className=\"sm:col-span-2\">\n <label className={LABEL_CLASS} htmlFor={hostField.id}>\n {tr('mesh.deploy.host', 'Host / alias')}\n </label>\n <Input\n {...hostField.inputProps}\n type=\"text\"\n value={host}\n onChange={(e) => setHost(e.target.value)}\n placeholder=\"prod-web-01 or 10.0.0.5\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={portField.id}>\n {tr('mesh.deploy.port', 'Port')}\n </label>\n <Input\n {...portField.inputProps}\n type=\"number\"\n value={port}\n onChange={(e) => setPort(Number(e.target.value) || 22)}\n className=\"text-base md:text-sm\"\n />\n </div>\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={userField.id}>\n {tr('mesh.deploy.user', 'User')}\n </label>\n <Input\n {...userField.inputProps}\n type=\"text\"\n value={user}\n onChange={(e) => setUser(e.target.value)}\n placeholder=\"root\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.auth', 'Authentication')}</span>\n <div className=\"flex flex-wrap gap-x-4 gap-y-2\">\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'agent'}\n onChange={() => setAuthMethod('agent')}\n />\n {tr('mesh.deploy.authAgent', 'SSH agent / default key')}\n </label>\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'key'}\n onChange={() => setAuthMethod('key')}\n />\n {tr('mesh.deploy.authKey', 'Key path')}\n </label>\n </div>\n </div>\n {authMethod === 'key' && (\n <div>\n <label className={LABEL_CLASS} htmlFor={keyField.id}>\n {tr('mesh.deploy.keyPath', 'Identity file (key path)')}\n </label>\n <Input\n {...keyField.inputProps}\n type=\"text\"\n value={keyPath}\n onChange={(e) => setKeyPath(e.target.value)}\n placeholder=\"~/.ssh/id_ed25519\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n )}\n </div>\n )}\n\n {/* Label */}\n <div>\n <label className={LABEL_CLASS} htmlFor={labelField.id}>\n {tr('mesh.deploy.label', 'Label (optional)')}\n </label>\n <Input\n {...labelField.inputProps}\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder={tr('mesh.deploy.labelPlaceholder', 'e.g. Production web tier')}\n className=\"text-base md:text-sm\"\n />\n </div>\n\n {/* Permission chips */}\n {availablePermissions && availablePermissions.length > 0 && (\n <div>\n <span className={LABEL_CLASS}>\n {tr('mesh.deploy.permissions', 'Grant permissions on the new link')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {availablePermissions.map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-action-primary-bg/10 text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n ))}\n </div>\n </div>\n )}\n\n <label className=\"flex cursor-pointer items-start gap-2 text-sm text-text-primary\">\n <Checkbox\n checked={connectExisting}\n onCheckedChange={(checked) => setConnectExisting(checked === true)}\n className=\"mt-0.5\"\n />\n <span>\n {tr(\n 'mesh.deploy.connectExisting',\n 'Connect to an existing agent on the target instead of installing'\n )}\n </span>\n </label>\n\n {submitError && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {submitError}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} className=\"max-md:min-h-11\">\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={!canSubmit || deploying}\n aria-busy={deploying}\n className=\"max-md:min-h-11\"\n >\n {deploying ? <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" /> : null}\n {tr('mesh.deploy.submit', 'Deploy')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;AA0CA,IAAM,KAAqC;CACzC,OAAO;CACP,eAAe;CACf,iBAAiB;CACjB,WAAW;CACX,SAAS;CACT,OAAO;CACP,WAAW;CACX,SAAS;CACT,eAAe;CACf,eAAe;CACf,cAAc;CACf;AAkCD,SAAS,EAAS,GAA4C;AAC5D,QAAO,KAAK,OAAO,KAAM,YAAY,CAAC,MAAM,QAAQ,EAAE,GAAI,IAAgC;;AAE5F,SAAS,EAAK,GAAqC,GAA4B;CAC7E,IAAM,IAAI,IAAM;AAChB,QAAO,OAAO,KAAM,WAAW,IAAI;;AAGrC,IAAM,IAAc,sDACd,KACJ,mMACI,IACJ;AAEF,SAAgB,EAAiB,EAC/B,iBACA,0BACA,yBACA,YACA,kBACwB;CACxB,IAAM,IAAK,GAAO,EAEZ,CAAC,GAAe,MAAoB,EACxC,MAAwB,EAAa,IAAI,MAAM,GAChD,EACK,CAAC,GAAM,KAAW,EAAqB,MAAM,EAC7C,CAAC,GAAM,MAAW,EAAS,GAAG,EAC9B,CAAC,GAAM,MAAW,EAAS,GAAG,EAC9B,CAAC,GAAM,MAAW,EAAS,GAAG,EAC9B,CAAC,GAAY,KAAiB,EAAqB,QAAQ,EAC3D,CAAC,GAAS,MAAc,EAAS,GAAG,EACpC,CAAC,GAAO,MAAY,EAAS,GAAG,EAChC,CAAC,GAAiB,MAAsB,EAAS,GAAM,EACvD,CAAC,GAAa,MAAkB,kBAAsB,IAAI,KAAK,CAAC,EAEhE,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAK,KAAU,EAA2B,KAAK,EAChD,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,IAAc,GAAU,EACxB,IAAY,GAAU,EACtB,IAAY,GAAU,EACtB,IAAY,GAAU,EACtB,IAAW,GAAU,EACrB,IAAa,GAAU,EAEvB,CAAC,GAAQ,EAAE,SAAS,OAAe,GAA+B,EAClE,CAAC,KAAkB,EAAgC,EAAE,aAAa,gBAAgB,CAAC,EAEnF,IAAY,SACX,IACD,MAAS,QACQ,EAAK,MAAM,CACZ,SAAS,KAAK,EAAK,MAAM,CAAC,SAAS,IAEhD,KALoB,IAM1B;EAAC;EAAe;EAAM;EAAM;EAAK,CAAC,EAE/B,KAAmB,GAAa,MAAc;AAClD,MAAgB,MAAS;GACvB,IAAM,IAAO,IAAI,IAAI,EAAK;AAG1B,UAFI,EAAK,IAAI,EAAE,GAAE,EAAK,OAAO,EAAE,GAC1B,EAAK,IAAI,EAAE,EACT;IACP;IACD,EAAE,CAAC,EAGA,IAAoB,QAA0B;AAClD,MAAI,MAAS,OAAQ,QAAO;EAC5B,IAAM,IAAc,EAAK,MAAM,EACzB,IAAc,EAAK,MAAM,EACzB,IAAO,IAAc,GAAG,EAAY,GAAG,MAAgB;AAC7D,SAAO,KAAQ,MAAS,KAAK,GAAG,EAAK,GAAG,MAAS;IAChD;EAAC;EAAM;EAAM;EAAM;EAAK,CAAC,EAEtB,KAAe,EAAY,YAAY;AACtC,SAGL;GAFA,EAAe,KAAK,EACpB,EAAO,KAAK,EACZ,EAAS,KAAK;AACd,OAAI;IACF,IAAM,IAAM,MAAM,EAAO,EACvB,WAAW;KACT,aAAa;KACb,OAAO;MACL,QAAQ,GAAmB;MAC3B,aAAa,EAAY,OAAO,IAAI,MAAM,KAAK,EAAY,GAAG,KAAA;MAC9D,cAAc,MAAS,SAAS,MAAe,SAAQ,KAAuB,KAAA;MAC9E,OAAO,KAAS,KAAA;MAChB;MACD;KACF,EACF,CAAC,EACI,IAAQ,EAAI,MAAM;AACxB,QAAI,EAAI,SAAS,GAAO,SAAS,GAAO,OAAO,IAAO;AACpD,OACE,GAAO,SAAS,EAAI,OAAO,WAAW,EAAG,sBAAsB,gBAAgB,CAChF;AACD;;IAEF,IAAM,IAAO,EAAS,GAAO,KAAK,EAC5B,IAAW,EAAK,GAAM,QAAQ;AACpC,QAAI,CAAC,GAAU;AACb,OACE,EAAG,qBAAqB,0DAA0D,CACnF;AACD;;AAGF,IADA,EAAS,EAAS,EAClB,EAAO;KAAE,QAAQ,EAAK,GAAM,SAAS,IAAI;KAAW,OAAO,EAAE;KAAE,CAAC;YACzD,GAAK;AACZ,MAAe,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI,CAAC;;;IAEjE;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAGF,UAAgB;AACd,MAAI,CAAC,KAAS,CAAC,EAAe;EAC9B,IAAI,IAAY,IAEV,UAAa;AACZ,KAAe,EAAE,WAAW;IAAE,aAAa;IAAe;IAAO,EAAE,CAAC,CACtE,MAAM,MAAW;AAChB,QAAI,EAAW;IACf,IAAM,IAAQ,EAAO,MAAM,qBACrB,IAAO,EAAS,GAAO,KAAK;AAClC,QAAI,CAAC,EAAM;IACX,IAAM,IAAW,MAAM,QAAQ,EAAK,MAAM,GAAI,EAAK,QAA4B,EAAE;AAOjF,IANA,EAAO;KACL,QAAQ,EAAK,GAAM,SAAS,IAAI;KAChC,OAAO;KACP,OAAO,EAAK,GAAM,QAAQ,IAAI,GAAO,SAAS,KAAA;KAC/C,CAAC,EACa,EAAK,GAAM,SAAS,KACpB,eACb,GAAa;KAEf,CACD,YAAY,GAEX;;AAGN,KAAM;EACN,IAAM,IAAW,YAAY,GAAM,IAAK;AACxC,eAAa;AAEX,GADA,IAAY,IACZ,cAAc,EAAS;;IAExB;EAAC;EAAO;EAAe;EAAgB;EAAY,CAAC;CAEvD,IAAM,IACJ,GAAK,WAAW,aACf,MAAU,QAAQ,GAAK,WAAW,eAAe,GAAK,WAAW,UAE9D,KAAc,CAClB;EAAE,KAAK;EAAa,OAAO,EAAG,+BAA+B,YAAY;EAAE,EAC3E;EAAE,KAAK;EAAU,OAAO,EAAG,4BAA4B,SAAS;EAAE,CACnE;AAED,QACE,kBAAC,GAAD;EACE,MAAA;EACA,eAAe,MAAS;AACtB,GAAI,CAAC,KAAQ,CAAC,KAAY,GAAS;;YAGrC,kBAAC,IAAD;GACE,WAAU;GACV,iBAAiB,CAAC;aAFpB;IAIE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UACG,EAAG,qBAAqB,wBAAwB,EAC3B,CAAA,EACxB,kBAAC,IAAD,EAAA,UACG,EACC,2BACA,oEACD,EAC2B,CAAA,CACP,EAAA,CAAA;IAEzB,kBAAC,IAAD;KAAS,OAAO;KAAa,QAAQ;KAAiB,CAAA;IAGrD,IACC,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,KAAD;OAAG,MAAK;OAAS,WAAU;iBACxB,EAAG,yBAAyB,gCAAgC,EAC3D,OAAO,EAAa,MAAM,MAAM,EAAE,OAAO,EAAc,EAAE,QAAQ,SAClE,CAAC;OACA,CAAA;MACJ,kBAAC,OAAD;OAAK,WAAU;iBACZ,KAAO,EAAI,MAAM,SAAS,IACzB,EAAI,MAAM,KAAK,GAAM,MACnB,kBAAC,OAAD;QAAiC,WAAU;kBAA3C;SACG,EAAK,WAAW,aACf,kBAAC,GAAD;UAAS,WAAU;UAAqC,eAAY;UAAS,CAAA;SAE9E,EAAK,WAAW,eACf,kBAAC,IAAD;UAAa,WAAU;UAAkC,eAAY;UAAS,CAAA;SAE/E,EAAK,WAAW,YACf,kBAAC,IAAD;UAAS,WAAU;UAAgC,eAAY;UAAS,CAAA;SAEzE,EAAK,WAAW,aACf,kBAAC,IAAD;UAAO,WAAU;UAAyB,eAAY;UAAS,CAAA;SAEhE,EAAK,WAAW,aACf,kBAAC,OAAD;UACE,WAAU;UACV,eAAY;UACZ,CAAA;SAEJ,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBACZ,EAAG,qBAAqB,EAAK,QAAQ,GAAW,EAAK,SAAS,EAAK,KAAK;WACrE,CAAA,EACL,EAAK,UACJ,kBAAC,OAAD;WAAK,WAAU;qBAAuC,EAAK;WAAc,CAAA,GACvE,KACA;;SACF;UA3BI,GAAG,EAAK,KAAK,GAAG,IA2BpB,CACN,GAEF,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAS,WAAU;SAAqC,eAAY;SAAS,CAAA,EAC7E,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAG,wBAAwB,uBAAuB;SAC9C,CAAA,CACH;;OAEJ,CAAA;MAEL,GAAK,WAAW,eACf,kBAAC,OAAD;OACE,MAAK;OACL,WAAU;iBAET,EAAG,uBAAuB,+BAA+B;OACtD,CAAA;MAEP,GAAK,WAAW,YACf,kBAAC,OAAD;OACE,MAAK;OACL,WAAU;iBAET,EAAI,SAAS,EAAG,yBAAyB,iBAAiB;OACvD,CAAA;MAGR,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,SAAS;OACT,UAAU;OACV,aAAW;OACX,WAAU;iBAET,IACG,EAAG,8BAA8B,aAAa,GAC9C,EAAG,gBAAgB,QAAQ;OACxB,CAAA,EACc,CAAA;MACrB;SAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;OAAa,SAAS,EAAY;iBACjD,EAAG,2BAA2B,8BAA8B;OACvD,CAAA,EACR,kBAAC,GAAD;OACE,OAAO,KAAiB,KAAA;OACxB,eAAe;OACf,UAAU,EAAa,WAAW;iBAHpC,CAKE,kBAAC,IAAD;QAAe,IAAI,EAAY;QAAI,WAAU;kBAC3C,kBAAC,IAAD,EAAa,aAAa,EAAG,wBAAwB,sBAAsB,EAAI,CAAA;QACjE,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAa,KAAK,MACjB,kBAAC,IAAD;QAAuB,OAAO,EAAE;kBAAhC;SACG,EAAE;SAAK;SAAG,EAAE;SAAS;SAAE,EAAE,WAAW,KAAK;SAC/B;UAFI,EAAE,GAEN,CACb,EACY,CAAA,CACT;SACL,EAAA,CAAA;MAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;OAAM,WAAW;iBAAc,EAAG,oBAAoB,cAAc;OAAQ,CAAA,EAC5E,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,gBAAc,MAAS;QACvB,eAAe,EAAQ,MAAM;QAC7B,WAAW,GAAG,EAAuB,GACnC,MAAS,QACL,4EACA;kBAPR,CAUE,kBAAC,IAAD;SAAQ,WAAU;SAAW,eAAY;SAAS,CAAA,EACjD,EAAG,uBAAuB,eAAe,CACnC;WACT,kBAAC,UAAD;QACE,MAAK;QACL,gBAAc,MAAS;QACvB,eAAe,EAAQ,OAAO;QAC9B,WAAW,GAAG,EAAuB,GACnC,MAAS,SACL,4EACA;kBAPR,CAUE,kBAAC,IAAD;SAAQ,WAAU;SAAW,eAAY;SAAS,CAAA,EACjD,EAAG,wBAAwB,eAAe,CACpC;UACL;SACF,EAAA,CAAA;MAGL,MAAS,SACR,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,SAAD;WAAO,WAAW;WAAa,SAAS,EAAU;qBAC/C,EAAG,oBAAoB,eAAe;WACjC,CAAA,EACR,kBAAC,GAAD;WACE,GAAI,EAAU;WACd,MAAK;WACL,OAAO;WACP,WAAW,MAAM,GAAQ,EAAE,OAAO,MAAM;WACxC,aAAY;WACZ,WAAU;WACV,CAAA,CACE;aACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,WAAW;UAAa,SAAS,EAAU;oBAC/C,EAAG,oBAAoB,OAAO;UACzB,CAAA,EACR,kBAAC,GAAD;UACE,GAAI,EAAU;UACd,MAAK;UACL,OAAO;UACP,WAAW,MAAM,GAAQ,OAAO,EAAE,OAAO,MAAM,IAAI,GAAG;UACtD,WAAU;UACV,CAAA,CACE,EAAA,CAAA,CACF;;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAW;SAAa,SAAS,EAAU;mBAC/C,EAAG,oBAAoB,OAAO;SACzB,CAAA,EACR,kBAAC,GAAD;SACE,GAAI,EAAU;SACd,MAAK;SACL,OAAO;SACP,WAAW,MAAM,GAAQ,EAAE,OAAO,MAAM;SACxC,aAAY;SACZ,WAAU;SACV,CAAA,CACE,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;SAAM,WAAW;mBAAc,EAAG,oBAAoB,iBAAiB;SAAQ,CAAA,EAC/E,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,SAAD;UAAO,WAAU;oBAAjB,CACE,kBAAC,SAAD;WACE,MAAK;WACL,MAAK;WACL,SAAS,MAAe;WACxB,gBAAgB,EAAc,QAAQ;WACtC,CAAA,EACD,EAAG,yBAAyB,0BAA0B,CACjD;aACR,kBAAC,SAAD;UAAO,WAAU;oBAAjB,CACE,kBAAC,SAAD;WACE,MAAK;WACL,MAAK;WACL,SAAS,MAAe;WACxB,gBAAgB,EAAc,MAAM;WACpC,CAAA,EACD,EAAG,uBAAuB,WAAW,CAChC;YACJ;WACF,EAAA,CAAA;QACL,MAAe,SACd,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAW;SAAa,SAAS,EAAS;mBAC9C,EAAG,uBAAuB,2BAA2B;SAChD,CAAA,EACR,kBAAC,GAAD;SACE,GAAI,EAAS;SACb,MAAK;SACL,OAAO;SACP,WAAW,MAAM,GAAW,EAAE,OAAO,MAAM;SAC3C,aAAY;SACZ,WAAU;SACV,CAAA,CACE,EAAA,CAAA;QAEJ;;MAIR,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;OAAa,SAAS,EAAW;iBAChD,EAAG,qBAAqB,mBAAmB;OACtC,CAAA,EACR,kBAAC,GAAD;OACE,GAAI,EAAW;OACf,MAAK;OACL,OAAO;OACP,WAAW,MAAM,GAAS,EAAE,OAAO,MAAM;OACzC,aAAa,EAAG,gCAAgC,2BAA2B;OAC3E,WAAU;OACV,CAAA,CACE,EAAA,CAAA;MAGL,KAAwB,EAAqB,SAAS,KACrD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;OAAM,WAAW;iBACd,EAAG,2BAA2B,oCAAoC;OAC9D,CAAA,EACP,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAqB,KAAK,MACzB,kBAAC,UAAD;QACE,MAAK;QAEL,gBAAc,EAAY,IAAI,EAAE;QAChC,eAAe,GAAiB,EAAE;QAClC,WAAW,GAAG,GAAgB,GAC5B,EAAY,IAAI,EAAE,GACd,4EACA;kBAGL;QACM,EAVF,EAUE,CACT;OACE,CAAA,CACF,EAAA,CAAA;MAGR,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACE,kBAAC,IAAD;QACE,SAAS;QACT,kBAAkB,MAAY,GAAmB,MAAY,GAAK;QAClE,WAAU;QACV,CAAA,EACF,kBAAC,QAAD,EAAA,UACG,EACC,+BACA,mEACD,EACI,CAAA,CACD;;MAEP,KACC,kBAAC,OAAD;OACE,MAAK;OACL,WAAU;iBAET;OACG,CAAA;MAGR,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,SAAS;OAAS,WAAU;iBACjE,EAAG,iBAAiB,SAAS;OACvB,CAAA,EACT,kBAAC,GAAD;OACE,MAAK;OACL,eAAe,KAAK,IAAc;OAClC,UAAU,CAAC,KAAa;OACxB,aAAW;OACX,WAAU;iBALZ,CAOG,IAAY,kBAAC,GAAD;QAAS,WAAU;QAAsB,eAAY;QAAS,CAAA,GAAG,MAC7E,EAAG,sBAAsB,SAAS,CAC5B;SACc,EAAA,CAAA;MACrB;;IAEgB;;EACT,CAAA"}
|
|
1
|
+
{"version":3,"file":"MeshDeployWizard.js","names":[],"sources":["../../../src/components/graph/MeshDeployWizard.tsx"],"sourcesContent":["/**\n * MeshDeployWizard — deploy a new agent THROUGH the mesh from a chosen source\n * (root) agent. Supports two modes:\n * - \"Remote (SSH)\": deploy onto another machine over SSH (host/user/port/key).\n * - \"Same machine\": deploy a sibling agent on the source agent's own host.\n *\n * Submitting calls `deployAgentViaMesh({ rootAgentId, input })`, reads the\n * `{ jobId }` out of the AgentProxyResponse, then polls\n * `meshDeployJobStatus(rootAgentId, jobId)` on a 2s interval, rendering the\n * job's step list in a progress panel. On completion it calls `onCompleted`\n * so the caller can refetch the mesh graph.\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { Loader2, CheckCircle, XCircle, Check, Server, Laptop } from 'lucide-react';\nimport {\n Button,\n Checkbox,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Stepper,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useField } from '../../shared/hooks/useField';\nimport {\n useDeployAgentViaMeshMutation,\n useMeshDeployJobStatusLazyQuery,\n} from '@/generated/wspace-operations';\n\n// English fallbacks for the mesh-deploy job step labels. Extends the\n// install-job labels with the mesh-specific bootstrap/onboard/peer-probe\n// steps. Translated via `mesh.deploy.steps.{step}` keys.\nconst stepLabels: Record<string, string> = {\n probe: 'Probing target',\n 'install-bun': 'Installing Bun runtime',\n 'install-agent': 'Installing VibeControls Agent',\n autostart: 'Configuring autostart',\n plugins: 'Installing plugins',\n start: 'Starting agent',\n bootstrap: 'Bootstrapping tunnel',\n onboard: 'Onboarding agent',\n 'store-creds': 'Storing credentials',\n 'report-link': 'Linking into mesh',\n 'probe-peer': 'Probing new peer',\n};\n\ninterface DeployJobStep {\n step: string;\n status: string;\n message?: string;\n}\n\ninterface DeployJob {\n status: 'running' | 'completed' | 'failed' | string;\n steps: DeployJobStep[];\n error?: string;\n}\n\ninterface SourceAgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n}\n\ninterface MeshDeployWizardProps {\n /** Agents that can act as the deploying root. */\n sourceAgents: SourceAgentOption[];\n /** Pre-seeded source (root) agent id (e.g. \"deploy from here\" on a node). */\n initialSourceAgentId?: string | null;\n availablePermissions?: string[];\n onClose: () => void;\n onCompleted: () => void;\n}\n\ntype DeployMode = 'ssh' | 'same';\ntype AuthMethod = 'agent' | 'key';\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Record<string, unknown> | null, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\n\nconst LABEL_CLASS = 'mb-1.5 block text-sm font-medium text-text-primary';\nconst CHIP_BASE_CLASS =\n 'rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\nconst MODE_BUTTON_BASE_CLASS =\n 'inline-flex flex-1 items-center justify-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function MeshDeployWizard({\n sourceAgents,\n initialSourceAgentId,\n availablePermissions,\n onClose,\n onCompleted,\n}: MeshDeployWizardProps) {\n const tr = useTr();\n\n const [sourceAgentId, setSourceAgentId] = useState<string>(\n initialSourceAgentId ?? sourceAgents[0]?.id ?? ''\n );\n const [mode, setMode] = useState<DeployMode>('ssh');\n const [host, setHost] = useState('');\n const [port, setPort] = useState(22);\n const [user, setUser] = useState('');\n const [authMethod, setAuthMethod] = useState<AuthMethod>('agent');\n const [keyPath, setKeyPath] = useState('');\n const [label, setLabel] = useState('');\n const [connectExisting, setConnectExisting] = useState(false);\n const [permissions, setPermissions] = useState<Set<string>>(new Set());\n\n const [jobId, setJobId] = useState<string | null>(null);\n const [job, setJob] = useState<DeployJob | null>(null);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n const sourceField = useField();\n const hostField = useField();\n const portField = useField();\n const userField = useField();\n const keyField = useField();\n const labelField = useField();\n\n const [deploy, { loading: deploying }] = useDeployAgentViaMeshMutation();\n const [fetchJobStatus] = useMeshDeployJobStatusLazyQuery({ fetchPolicy: 'network-only' });\n\n const canSubmit = useMemo(() => {\n if (!sourceAgentId) return false;\n if (mode === 'ssh') {\n const targetSpec = host.trim();\n return targetSpec.length > 0 && user.trim().length > 0;\n }\n return true;\n }, [sourceAgentId, mode, host, user]);\n\n const togglePermission = useCallback((p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n }, []);\n\n // Build the SSH `target` string the backend expects (user@host:port).\n const buildTargetString = useCallback((): string => {\n if (mode === 'same') return 'localhost';\n const trimmedHost = host.trim();\n const trimmedUser = user.trim();\n const base = trimmedUser ? `${trimmedUser}@${trimmedHost}` : trimmedHost;\n return port && port !== 22 ? `${base}:${port}` : base;\n }, [mode, host, user, port]);\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return;\n setSubmitError(null);\n setJob(null);\n setJobId(null);\n try {\n const res = await deploy({\n variables: {\n rootAgentId: sourceAgentId,\n input: {\n target: buildTargetString(),\n permissions: permissions.size > 0 ? Array.from(permissions) : undefined,\n identityFile: mode === 'ssh' && authMethod === 'key' ? keyPath || undefined : undefined,\n label: label || undefined,\n connectExisting,\n },\n },\n });\n const proxy = res.data?.deployAgentViaMesh;\n if (res.error || proxy?.error || proxy?.ok === false) {\n setSubmitError(\n proxy?.error ?? res.error?.message ?? tr('mesh.deploy.failed', 'Deploy failed')\n );\n return;\n }\n const data = asRecord(proxy?.data);\n const newJobId = jstr(data, 'jobId');\n if (!newJobId) {\n setSubmitError(\n tr('mesh.deploy.noJob', 'Deploy started but no job id was returned by the agent.')\n );\n return;\n }\n setJobId(newJobId);\n setJob({ status: jstr(data, 'status') ?? 'running', steps: [] });\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n }, [\n canSubmit,\n deploy,\n sourceAgentId,\n buildTargetString,\n permissions,\n mode,\n authMethod,\n keyPath,\n label,\n connectExisting,\n tr,\n ]);\n\n // Poll the mesh-deploy job once a jobId is established.\n useEffect(() => {\n if (!jobId || !sourceAgentId) return;\n let cancelled = false;\n\n const poll = () => {\n void fetchJobStatus({ variables: { rootAgentId: sourceAgentId, jobId } })\n .then((result) => {\n if (cancelled) return;\n const proxy = result.data?.meshDeployJobStatus;\n const data = asRecord(proxy?.data);\n if (!data) return;\n const rawSteps = Array.isArray(data.steps) ? (data.steps as DeployJobStep[]) : [];\n setJob({\n status: jstr(data, 'status') ?? 'running',\n steps: rawSteps,\n error: jstr(data, 'error') ?? proxy?.error ?? undefined,\n });\n const status = jstr(data, 'status');\n if (status === 'completed') {\n onCompleted();\n }\n })\n .catch(() => {\n // transient — retry on next interval\n });\n };\n\n poll();\n const interval = setInterval(poll, 2000);\n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, [jobId, sourceAgentId, fetchJobStatus, onCompleted]);\n\n const jobRunning =\n job?.status === 'running' ||\n (jobId !== null && job?.status !== 'completed' && job?.status !== 'failed');\n\n const wizardSteps = [\n { key: 'configure', label: tr('mesh.deploy.steps.configure', 'Configure') },\n { key: 'deploy', label: tr('mesh.deploy.steps.deploy', 'Deploy') },\n ];\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open && !jobRunning) onClose();\n }}\n >\n <ResponsiveDialogContent\n className=\"max-h-[min(85vh,44rem)] overflow-y-auto sm:max-w-lg\"\n showCloseButton={!jobRunning}\n >\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('mesh.deploy.title', 'Deploy Agent via Mesh')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'mesh.deploy.description',\n 'Deploy a new agent through the mesh from a selected source agent.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <Stepper steps={wizardSteps} active={jobId ? 1 : 0} />\n\n {/* Job progress takes over once a job is running */}\n {jobId ? (\n <div className=\"space-y-3\">\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.deploying', 'Deploying through {{agent}}…', {\n agent: sourceAgents.find((a) => a.id === sourceAgentId)?.name ?? 'agent',\n })}\n </p>\n <div className=\"space-y-2\">\n {job && job.steps.length > 0 ? (\n job.steps.map((step, idx) => (\n <div key={`${step.step}-${idx}`} className=\"flex items-center gap-3\">\n {step.status === 'running' && (\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n )}\n {step.status === 'completed' && (\n <CheckCircle className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'failed' && (\n <XCircle className=\"size-4 text-status-error-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'skipped' && (\n <Check className=\"size-4 text-text-muted\" aria-hidden=\"true\" />\n )}\n {step.status === 'pending' && (\n <div\n className=\"size-4 rounded-full border-2 border-border-default\"\n aria-hidden=\"true\"\n />\n )}\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {tr(`mesh.deploy.steps.${step.step}`, stepLabels[step.step] ?? step.step)}\n </div>\n {step.message ? (\n <div className=\"text-xs break-words text-text-muted\">{step.message}</div>\n ) : null}\n </div>\n </div>\n ))\n ) : (\n <div className=\"flex items-center gap-3\">\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.starting', 'Starting deploy job…')}\n </span>\n </div>\n )}\n </div>\n\n {job?.status === 'completed' && (\n <div\n role=\"status\"\n className=\"rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\"\n >\n {tr('mesh.deploy.success', 'Agent deployed successfully!')}\n </div>\n )}\n {job?.status === 'failed' && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {job.error ?? tr('mesh.deploy.jobFailed', 'Deploy failed.')}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n disabled={jobRunning}\n aria-busy={jobRunning}\n className=\"w-full max-md:min-h-11\"\n >\n {jobRunning\n ? tr('mesh.deploy.deployingShort', 'Deploying…')\n : tr('common.close', 'Close')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Source agent */}\n <div>\n <label className={LABEL_CLASS} htmlFor={sourceField.id}>\n {tr('mesh.deploy.sourceAgent', 'Source agent (deploys from)')}\n </label>\n <Select\n value={sourceAgentId || undefined}\n onValueChange={setSourceAgentId}\n disabled={sourceAgents.length === 0}\n >\n <SelectTrigger id={sourceField.id} className=\"w-full text-base md:text-sm\">\n <SelectValue placeholder={tr('mesh.deploy.noAgents', 'No agents available')} />\n </SelectTrigger>\n <SelectContent>\n {sourceAgents.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name} ({a.hostname}){a.isActive ? '' : ' · offline'}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Mode toggle */}\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.mode', 'Deploy mode')}</span>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n aria-pressed={mode === 'ssh'}\n onClick={() => setMode('ssh')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'ssh'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Server className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSsh', 'Remote (SSH)')}\n </button>\n <button\n type=\"button\"\n aria-pressed={mode === 'same'}\n onClick={() => setMode('same')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'same'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Laptop className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSame', 'Same machine')}\n </button>\n </div>\n </div>\n\n {/* SSH fields */}\n {mode === 'ssh' && (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-3\">\n <div className=\"sm:col-span-2\">\n <label className={LABEL_CLASS} htmlFor={hostField.id}>\n {tr('mesh.deploy.host', 'Host / alias')}\n </label>\n <Input\n {...hostField.inputProps}\n type=\"text\"\n value={host}\n onChange={(e) => setHost(e.target.value)}\n placeholder=\"prod-web-01 or 10.0.0.5\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={portField.id}>\n {tr('mesh.deploy.port', 'Port')}\n </label>\n <Input\n {...portField.inputProps}\n type=\"number\"\n value={port}\n onChange={(e) => setPort(Number(e.target.value) || 22)}\n className=\"text-base md:text-sm\"\n />\n </div>\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={userField.id}>\n {tr('mesh.deploy.user', 'User')}\n </label>\n <Input\n {...userField.inputProps}\n type=\"text\"\n value={user}\n onChange={(e) => setUser(e.target.value)}\n placeholder=\"root\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.auth', 'Authentication')}</span>\n <div className=\"flex flex-wrap gap-x-4 gap-y-2\">\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'agent'}\n onChange={() => setAuthMethod('agent')}\n />\n {tr('mesh.deploy.authAgent', 'SSH agent / default key')}\n </label>\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'key'}\n onChange={() => setAuthMethod('key')}\n />\n {tr('mesh.deploy.authKey', 'Key path')}\n </label>\n </div>\n </div>\n {authMethod === 'key' && (\n <div>\n <label className={LABEL_CLASS} htmlFor={keyField.id}>\n {tr('mesh.deploy.keyPath', 'Identity file (key path)')}\n </label>\n <Input\n {...keyField.inputProps}\n type=\"text\"\n value={keyPath}\n onChange={(e) => setKeyPath(e.target.value)}\n placeholder=\"~/.ssh/id_ed25519\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n )}\n </div>\n )}\n\n {/* Label */}\n <div>\n <label className={LABEL_CLASS} htmlFor={labelField.id}>\n {tr('mesh.deploy.label', 'Label (optional)')}\n </label>\n <Input\n {...labelField.inputProps}\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder={tr('mesh.deploy.labelPlaceholder', 'e.g. Production web tier')}\n className=\"text-base md:text-sm\"\n />\n </div>\n\n {/* Permission chips */}\n {availablePermissions && availablePermissions.length > 0 && (\n <div>\n <span className={LABEL_CLASS}>\n {tr('mesh.deploy.permissions', 'Grant permissions on the new link')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {availablePermissions.map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n ))}\n </div>\n </div>\n )}\n\n <label className=\"flex cursor-pointer items-start gap-2 text-sm text-text-primary\">\n <Checkbox\n checked={connectExisting}\n onCheckedChange={(checked) => setConnectExisting(checked === true)}\n className=\"mt-0.5\"\n />\n <span>\n {tr(\n 'mesh.deploy.connectExisting',\n 'Connect to an existing agent on the target instead of installing'\n )}\n </span>\n </label>\n\n {submitError && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {submitError}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} className=\"max-md:min-h-11\">\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={!canSubmit || deploying}\n aria-busy={deploying}\n className=\"max-md:min-h-11\"\n >\n {deploying ? <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" /> : null}\n {tr('mesh.deploy.submit', 'Deploy')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|